@@ -18,14 +18,13 @@ use crate::{
1818 default_batch_size, default_center_drift_threshold, default_construction_ef,
1919 default_construction_ef_spann, default_initial_lambda, default_m, default_m_spann,
2020 default_merge_threshold, default_nreplica_count, default_num_centers_to_merge_to,
21- default_num_samples_kmeans, default_num_threads, default_quantize,
22- default_reassign_neighbor_count, default_resize_factor, default_search_ef,
23- default_search_ef_spann, default_search_nprobe, default_search_rng_epsilon,
24- default_search_rng_factor, default_space, default_split_threshold, default_sync_threshold,
25- default_write_nprobe, default_write_rng_epsilon, default_write_rng_factor, ConversionError ,
26- HnswParametersFromSegmentError , InternalHnswConfiguration , InternalSpannConfiguration ,
27- InternalUpdateCollectionConfiguration , KnnIndex , Segment , UpdateCollectionConfiguration ,
28- CHROMA_KEY ,
21+ default_num_samples_kmeans, default_num_threads, default_reassign_neighbor_count,
22+ default_resize_factor, default_search_ef, default_search_ef_spann, default_search_nprobe,
23+ default_search_rng_epsilon, default_search_rng_factor, default_space, default_split_threshold,
24+ default_sync_threshold, default_write_nprobe, default_write_rng_epsilon,
25+ default_write_rng_factor, ConversionError , HnswParametersFromSegmentError ,
26+ InternalHnswConfiguration , InternalSpannConfiguration , InternalUpdateCollectionConfiguration ,
27+ KnnIndex , Segment , UpdateCollectionConfiguration , CHROMA_KEY ,
2928} ;
3029
3130impl ChromaError for SchemaError {
@@ -794,7 +793,7 @@ impl Schema {
794793 ef_search : Some ( default_search_ef_spann ( ) ) ,
795794 max_neighbors : Some ( default_m_spann ( ) ) ,
796795 center_drift_threshold : None ,
797- quantize : default_quantize ( ) ,
796+ quantize : Quantization :: None ,
798797 } ) ,
799798 } ,
800799 } ,
@@ -889,7 +888,7 @@ impl Schema {
889888 ef_search : Some ( default_search_ef_spann ( ) ) ,
890889 max_neighbors : Some ( default_m_spann ( ) ) ,
891890 center_drift_threshold : None ,
892- quantize : default_quantize ( ) ,
891+ quantize : Quantization :: None ,
893892 } ) ,
894893 } ,
895894 } ,
@@ -978,8 +977,7 @@ impl Schema {
978977 . config
979978 . spann
980979 . as_ref ( )
981- . map ( |config| config. quantize )
982- . unwrap_or ( false )
980+ . is_some_and ( |config| !matches ! ( config. quantize, Quantization :: None ) )
983981 } ;
984982
985983 self . keys
@@ -1023,6 +1021,34 @@ impl Schema {
10231021 None
10241022 }
10251023
1024+ /// Set the quantization variant and apply impl-specific SPANN config defaults.
1025+ pub fn quantize ( & mut self , variant : Quantization ) {
1026+ if let Some ( spann_config) = self . get_spann_config_mut ( ) {
1027+ * spann_config = match variant {
1028+ Quantization :: None => SpannIndexConfig {
1029+ quantize : variant,
1030+ ..* spann_config
1031+ } ,
1032+ Quantization :: USearch4BitRabitQ => SpannIndexConfig {
1033+ search_nprobe : Some ( 64 ) ,
1034+ nreplica_count : Some ( 2 ) ,
1035+ write_rng_factor : Some ( 4.0 ) ,
1036+ write_rng_epsilon : Some ( 8.0 ) ,
1037+ split_threshold : Some ( 512 ) ,
1038+ reassign_neighbor_count : Some ( 32 ) ,
1039+ merge_threshold : Some ( 128 ) ,
1040+ write_nprobe : Some ( 64 ) ,
1041+ ef_construction : Some ( 256 ) ,
1042+ ef_search : Some ( 128 ) ,
1043+ max_neighbors : Some ( 24 ) ,
1044+ center_drift_threshold : Some ( 0.125 ) ,
1045+ quantize : variant,
1046+ ..* spann_config
1047+ } ,
1048+ } ;
1049+ }
1050+ }
1051+
10261052 pub fn get_internal_hnsw_config ( & self ) -> Option < InternalHnswConfiguration > {
10271053 let to_internal = |vector_index : & VectorIndexType | {
10281054 if vector_index. config . spann . is_some ( ) {
@@ -1647,10 +1673,12 @@ impl Schema {
16471673 ) -> Result < Option < SpannIndexConfig > , SchemaError > {
16481674 match ( default_spann, user_spann) {
16491675 ( Some ( default) , Some ( user) ) => {
1650- // Validate that quantize is always false (should only be set programmatically by frontend)
1651- if user. quantize != default_quantize ( ) || default. quantize != default_quantize ( ) {
1676+ // Validate that quantize is always None (should only be set programmatically by frontend)
1677+ if !matches ! ( user. quantize, Quantization :: None )
1678+ || !matches ! ( default . quantize, Quantization :: None )
1679+ {
16521680 return Err ( SchemaError :: InvalidUserInput {
1653- reason : "quantize field cannot be set to true in user schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
1681+ reason : "quantize field cannot be set in user schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
16541682 } ) ;
16551683 }
16561684 Ok ( Some ( SpannIndexConfig {
@@ -1677,23 +1705,23 @@ impl Schema {
16771705 center_drift_threshold : user
16781706 . center_drift_threshold
16791707 . or ( default. center_drift_threshold ) ,
1680- quantize : default_quantize ( ) , // Always false - quantization is set programmatically
1708+ quantize : Quantization :: None , // Always None - quantization is set programmatically
16811709 } ) )
16821710 }
16831711 ( Some ( default) , None ) => {
1684- // Validate default is also false
1685- if default. quantize != default_quantize ( ) {
1712+ // Validate default is also None
1713+ if ! matches ! ( default . quantize, Quantization :: None ) {
16861714 return Err ( SchemaError :: InvalidUserInput {
1687- reason : "quantize field cannot be set to true in default schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
1715+ reason : "quantize field cannot be set in default schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
16881716 } ) ;
16891717 }
16901718 Ok ( Some ( default. clone ( ) ) )
16911719 }
16921720 ( None , Some ( user) ) => {
1693- // Validate user is false
1694- if user. quantize != default_quantize ( ) {
1721+ // Validate user is None
1722+ if ! matches ! ( user. quantize, Quantization :: None ) {
16951723 return Err ( SchemaError :: InvalidUserInput {
1696- reason : "quantize field cannot be set to true in user schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
1724+ reason : "quantize field cannot be set in user schema. Quantization can only be enabled via frontend configuration." . to_string ( ) ,
16971725 } ) ;
16981726 }
16991727 Ok ( Some ( user. clone ( ) ) )
@@ -2734,6 +2762,20 @@ impl HnswIndexConfig {
27342762 }
27352763}
27362764
2765+ /// Quantization implementation for SPANN vector index.
2766+ #[ derive( Clone , Debug , Default , PartialEq , Serialize , Deserialize ) ]
2767+ #[ cfg_attr( feature = "utoipa" , derive( utoipa:: ToSchema ) ) ]
2768+ #[ serde( rename_all = "snake_case" ) ]
2769+ pub enum Quantization {
2770+ #[ default]
2771+ None ,
2772+ USearch4BitRabitQ ,
2773+ }
2774+
2775+ fn is_default_quantization ( v : & Quantization ) -> bool {
2776+ matches ! ( v, Quantization :: None )
2777+ }
2778+
27372779/// Configuration for SPANN vector index algorithm parameters
27382780#[ derive( Clone , Debug , PartialEq , Serialize , Deserialize , Validate , Default ) ]
27392781#[ cfg_attr( feature = "utoipa" , derive( utoipa:: ToSchema ) ) ]
@@ -2752,13 +2794,13 @@ pub struct SpannIndexConfig {
27522794 #[ validate( range( max = 8 ) ) ]
27532795 pub nreplica_count : Option < u32 > ,
27542796 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2755- #[ validate( range( min = 1.0 , max = 1 .0) ) ]
2797+ #[ validate( range( min = 1.0 , max = 10 .0) ) ]
27562798 pub write_rng_factor : Option < f32 > ,
27572799 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2758- #[ validate( range( min = 5 .0, max = 10.0 ) ) ]
2800+ #[ validate( range( min = 1 .0, max = 10.0 ) ) ]
27592801 pub write_rng_epsilon : Option < f32 > ,
27602802 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2761- #[ validate( range( min = 50 , max = 200 ) ) ]
2803+ #[ validate( range( min = 50 , max = 1000 ) ) ]
27622804 pub split_threshold : Option < u32 > ,
27632805 #[ serde( skip_serializing_if = "Option::is_none" ) ]
27642806 #[ validate( range( max = 1000 ) ) ]
@@ -2770,7 +2812,7 @@ pub struct SpannIndexConfig {
27702812 #[ validate( range( max = 64 ) ) ]
27712813 pub reassign_neighbor_count : Option < u32 > ,
27722814 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2773- #[ validate( range( min = 25 , max = 100 ) ) ]
2815+ #[ validate( range( min = 25 , max = 500 ) ) ]
27742816 pub merge_threshold : Option < u32 > ,
27752817 #[ serde( skip_serializing_if = "Option::is_none" ) ]
27762818 #[ validate( range( max = 8 ) ) ]
@@ -2779,7 +2821,7 @@ pub struct SpannIndexConfig {
27792821 #[ validate( range( max = 64 ) ) ]
27802822 pub write_nprobe : Option < u32 > ,
27812823 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2782- #[ validate( range( max = 200 ) ) ]
2824+ #[ validate( range( max = 300 ) ) ]
27832825 pub ef_construction : Option < usize > ,
27842826 #[ serde( skip_serializing_if = "Option::is_none" ) ]
27852827 #[ validate( range( max = 200 ) ) ]
@@ -2790,13 +2832,9 @@ pub struct SpannIndexConfig {
27902832 #[ serde( skip_serializing_if = "Option::is_none" ) ]
27912833 #[ validate( range( min = 0.1 , max = 1.0 ) ) ]
27922834 pub center_drift_threshold : Option < f32 > ,
2793- /// Enable quantization for vector search (cloud-only feature)
2794- #[ serde( default = "default_quantize" , skip_serializing_if = "is_false" ) ]
2795- pub quantize : bool ,
2796- }
2797-
2798- fn is_false ( v : & bool ) -> bool {
2799- !* v
2835+ /// Quantization implementation for vector search (cloud-only feature)
2836+ #[ serde( default , skip_serializing_if = "is_default_quantization" ) ]
2837+ pub quantize : Quantization ,
28002838}
28012839
28022840impl SpannIndexConfig {
@@ -2888,7 +2926,7 @@ impl SpannIndexConfig {
28882926 return false ;
28892927 }
28902928 }
2891- if self . quantize != default_quantize ( ) {
2929+ if ! matches ! ( self . quantize, Quantization :: None ) {
28922930 return false ;
28932931 }
28942932 true
@@ -3054,7 +3092,7 @@ impl TryFrom<&InternalCollectionConfiguration> for Schema {
30543092 ef_search : Some ( spann_config. ef_search ) ,
30553093 max_neighbors : Some ( spann_config. max_neighbors ) ,
30563094 center_drift_threshold : None ,
3057- quantize : default_quantize ( ) ,
3095+ quantize : Quantization :: None ,
30583096 } ) ,
30593097 } ,
30603098 } ;
@@ -3425,7 +3463,7 @@ mod tests {
34253463 ef_search : Some ( 40 ) ,
34263464 max_neighbors : Some ( 20 ) ,
34273465 center_drift_threshold : None ,
3428- quantize : false ,
3466+ quantize : Quantization :: None ,
34293467 } ) ;
34303468 }
34313469 }
@@ -3603,7 +3641,7 @@ mod tests {
36033641 ef_search : Some ( 10 ) ,
36043642 max_neighbors : Some ( 16 ) ,
36053643 center_drift_threshold : None ,
3606- quantize : false ,
3644+ quantize : Quantization :: None ,
36073645 } ;
36083646
36093647 let user_spann = SpannIndexConfig {
@@ -3624,7 +3662,7 @@ mod tests {
36243662 ef_search : None ,
36253663 max_neighbors : None ,
36263664 center_drift_threshold : None ,
3627- quantize : false ,
3665+ quantize : Quantization :: None ,
36283666 } ;
36293667
36303668 let result = Schema :: merge_spann_configs ( Some ( & default_spann) , Some ( & user_spann) )
@@ -3663,7 +3701,7 @@ mod tests {
36633701 ef_search : Some ( 10 ) ,
36643702 max_neighbors : Some ( 16 ) ,
36653703 center_drift_threshold : None ,
3666- quantize : false ,
3704+ quantize : Quantization :: None ,
36673705 } ;
36683706
36693707 let user_spann_with_quantize = SpannIndexConfig {
@@ -3684,7 +3722,7 @@ mod tests {
36843722 ef_search : None ,
36853723 max_neighbors : None ,
36863724 center_drift_threshold : None ,
3687- quantize : true , // This should be rejected
3725+ quantize : Quantization :: USearch4BitRabitQ , // This should be rejected
36883726 } ;
36893727
36903728 // Should reject user schema with quantize: true
@@ -3693,7 +3731,7 @@ mod tests {
36933731 assert ! ( result. is_err( ) ) ;
36943732 match result {
36953733 Err ( SchemaError :: InvalidUserInput { reason } ) => {
3696- assert ! ( reason. contains( "quantize field cannot be set to true " ) ) ;
3734+ assert ! ( reason. contains( "quantize field cannot be set" ) ) ;
36973735 }
36983736 _ => panic ! ( "Expected InvalidUserInput error" ) ,
36993737 }
@@ -3717,14 +3755,14 @@ mod tests {
37173755 ef_search : Some ( 10 ) ,
37183756 max_neighbors : Some ( 16 ) ,
37193757 center_drift_threshold : None ,
3720- quantize : true , // This should be rejected
3758+ quantize : Quantization :: USearch4BitRabitQ , // This should be rejected
37213759 } ;
37223760
37233761 let result = Schema :: merge_spann_configs ( Some ( & default_spann_with_quantize) , None ) ;
37243762 assert ! ( result. is_err( ) ) ;
37253763 match result {
37263764 Err ( SchemaError :: InvalidUserInput { reason } ) => {
3727- assert ! ( reason. contains( "quantize field cannot be set to true " ) ) ;
3765+ assert ! ( reason. contains( "quantize field cannot be set" ) ) ;
37283766 }
37293767 _ => panic ! ( "Expected InvalidUserInput error" ) ,
37303768 }
@@ -3734,7 +3772,7 @@ mod tests {
37343772 assert ! ( result. is_err( ) ) ;
37353773 match result {
37363774 Err ( SchemaError :: InvalidUserInput { reason } ) => {
3737- assert ! ( reason. contains( "quantize field cannot be set to true " ) ) ;
3775+ assert ! ( reason. contains( "quantize field cannot be set" ) ) ;
37383776 }
37393777 _ => panic ! ( "Expected InvalidUserInput error" ) ,
37403778 }
@@ -3760,7 +3798,7 @@ mod tests {
37603798 ef_search : Some ( 170 ) ,
37613799 max_neighbors : Some ( 32 ) ,
37623800 center_drift_threshold : None ,
3763- quantize : false ,
3801+ quantize : Quantization :: None ,
37643802 } ;
37653803
37663804 let with_space: InternalSpannConfiguration = ( Some ( & Space :: Cosine ) , & config) . into ( ) ;
@@ -3876,7 +3914,7 @@ mod tests {
38763914 ef_search : None ,
38773915 max_neighbors : None ,
38783916 center_drift_threshold : None ,
3879- quantize : false ,
3917+ quantize : Quantization :: None ,
38803918 } ) , // Add SPANN config
38813919 } ;
38823920
@@ -6257,7 +6295,7 @@ mod tests {
62576295 ef_search,
62586296 max_neighbors,
62596297 center_drift_threshold : None ,
6260- quantize : false ,
6298+ quantize : Quantization :: None ,
62616299 } ,
62626300 )
62636301 }
@@ -6443,7 +6481,7 @@ mod tests {
64436481 ef_search : Some ( spann_config. ef_search ) ,
64446482 max_neighbors : Some ( spann_config. max_neighbors ) ,
64456483 center_drift_threshold : None ,
6446- quantize : false ,
6484+ quantize : Quantization :: None ,
64476485 } ) ,
64486486 } ,
64496487 }
@@ -6609,7 +6647,7 @@ mod tests {
66096647 ef_search : Some ( config. ef_search ) ,
66106648 max_neighbors : Some ( config. max_neighbors ) ,
66116649 center_drift_threshold : None ,
6612- quantize : false ,
6650+ quantize : Quantization :: None ,
66136651 } )
66146652 }
66156653
0 commit comments