@@ -56,7 +56,8 @@ def create(self, index_type: str = "hnsw", quantization_config: Optional[Dict[st
5656 'subvectors': 8, # Number of subvectors (must divide dim evenly, default: 8)
5757 'bits': 8, # Bits per subvector (1-8, controls centroids, default: 8)
5858 'training_size': None, # Auto-calculated based on subvectors & bits (or specify manually)
59- 'max_training_vectors': None # Optional limit on training vectors used
59+ 'max_training_vectors': None, # Optional limit on training vectors used
60+ 'storage_mode': 'quantized_only' # Storage mode for quantized vectors (or 'quantized_with_raw')
6061 }
6162
6263 Note: Quantization reduces memory usage (typically 4-32x compression) but may
@@ -88,7 +89,8 @@ def create(self, index_type: str = "hnsw", quantization_config: Optional[Dict[st
8889 'type': 'pq',
8990 'subvectors': 16, # More subvectors = better compression
9091 'bits': 6, # Fewer bits = less memory per centroid
91- 'training_size': 75000 # Override auto-calculation
92+ 'training_size': 75000, # Override auto-calculation
93+ 'storage_mode': 'quantized_only' # Only store quantized vectors
9294 }
9395 index = vdb.create(
9496 index_type="hnsw",
@@ -126,11 +128,12 @@ def create(self, index_type: str = "hnsw", quantization_config: Optional[Dict[st
126128
127129 try :
128130 # Always pass quantization_config parameter
129- clean_config = None
130131 if quantization_config is not None :
131- # Clean quantization_config before passing to Rust (remove internal keys)
132- clean_config = {k : v for k , v in quantization_config .items () if not k .startswith ('_' )}
133-
132+ # Remove keys with None values and internal keys
133+ clean_config = {k : v for k , v in quantization_config .items () if not k .startswith ('_' ) and v is not None }
134+ else :
135+ clean_config = None
136+
134137 return constructor (quantization_config = clean_config , ** kwargs )
135138 except Exception as e :
136139 raise RuntimeError (f"Failed to create { index_type .upper ()} index: { e } " ) from e
@@ -172,7 +175,7 @@ def _validate_quantization_config(self, config: Dict[str, Any], dim: int) -> Dic
172175 if dim % subvectors != 0 :
173176 raise ValueError (
174177 f"subvectors ({ subvectors } ) must divide dimension ({ dim } ) evenly. "
175- f"Consider using subvectors: { self ._suggest_subvector_divisors (dim )} "
178+ f"Consider using subvectors: { ', ' . join ( map ( str , self ._suggest_subvector_divisors (dim )) )} "
176179 )
177180
178181 if subvectors > dim :
@@ -206,9 +209,38 @@ def _validate_quantization_config(self, config: Dict[str, Any], dim: int) -> Dic
206209 )
207210 validated_config ['max_training_vectors' ] = max_training_vectors
208211
212+ # Validate storage mode
213+ storage_mode = str (validated_config .get ('storage_mode' , 'quantized_only' )).lower ()
214+ valid_modes = {'quantized_only' , 'quantized_with_raw' }
215+ if storage_mode not in valid_modes :
216+ raise ValueError (
217+ f"Invalid storage_mode: '{ storage_mode } '. Supported modes: { ', ' .join (sorted (valid_modes ))} "
218+ )
219+
220+ validated_config ['storage_mode' ] = storage_mode
221+
209222 # Calculate and warn about memory usage
210223 self ._check_memory_usage (validated_config , dim )
224+
225+ # Add helpful warnings about storage mode
226+ if storage_mode == 'quantized_with_raw' :
227+ import warnings
228+ compression_ratio = validated_config .get ('__memory_info__' , {}).get ('compression_ratio' , 1.0 )
229+ warnings .warn (
230+ f"storage_mode='quantized_with_raw' will use ~{ compression_ratio :.1f} x more memory "
231+ f"than 'quantized_only' but enables exact vector reconstruction." ,
232+ UserWarning ,
233+ stacklevel = 2
234+ )
211235
236+ # Final safety check: ensure all expected keys are present
237+ # This is a final defensive programming - all the keys should already be set above, but added just in case
238+ validated_config .setdefault ('type' , 'pq' )
239+ validated_config .setdefault ('subvectors' , 8 )
240+ validated_config .setdefault ('bits' , 8 )
241+ validated_config .setdefault ('max_training_vectors' , None )
242+ validated_config .setdefault ('storage_mode' , 'quantized_only' )
243+
212244 return validated_config
213245
214246 def _calculate_smart_training_size (self , subvectors : int , bits : int ) -> int :
@@ -236,13 +268,14 @@ def _calculate_smart_training_size(self, subvectors: int, bits: int) -> int:
236268
237269 return min (max (statistical_minimum , reasonable_minimum ), reasonable_maximum )
238270
239- def _suggest_subvector_divisors (self , dim : int ) -> str :
240- """Suggest valid subvector counts that divide the dimension evenly."""
241- divisors = []
242- for i in range (1 , min (33 , dim + 1 )): # Common subvector counts up to 32
243- if dim % i == 0 :
244- divisors .append (str (i ))
245- return ', ' .join (divisors [:8 ]) # Show first 8 suggestions
271+
272+ def _suggest_subvector_divisors (self , dim : int ) -> list [int ]:
273+ """Return valid subvector counts that divide the dimension evenly (up to 32)."""
274+ return [i for i in range (1 , min (33 , dim + 1 )) if dim % i == 0 ]
275+
276+
277+
278+
246279
247280 def _check_memory_usage (self , config : Dict [str , Any ], dim : int ) -> None :
248281 """
0 commit comments