Skip to content

Commit d599ae5

Browse files
committed
Update quantization schema config
1 parent 7843b95 commit d599ae5

File tree

4 files changed

+95
-61
lines changed

4 files changed

+95
-61
lines changed

rust/frontend/src/impls/service_based_frontend.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ use chroma_types::{
3737
GetTenantRequest, GetTenantResponse, HealthCheckResponse, HeartbeatError, Include,
3838
IndexStatusError, IndexStatusResponse, KnnIndex, ListCollectionsRequest,
3939
ListCollectionsResponse, ListDatabasesError, ListDatabasesRequest, ListDatabasesResponse,
40-
Operation, OperationRecord, QueryError, QueryRequest, QueryResponse, ResetError, ResetResponse,
41-
Schema, SchemaError, SearchRequest, SearchResponse, Segment, SegmentScope, SegmentType,
42-
SegmentUuid, UpdateCollectionError, UpdateCollectionRecordsError,
40+
Operation, OperationRecord, Quantization, QueryError, QueryRequest, QueryResponse, ResetError,
41+
ResetResponse, Schema, SchemaError, SearchRequest, SearchResponse, Segment, SegmentScope,
42+
SegmentType, SegmentUuid, UpdateCollectionError, UpdateCollectionRecordsError,
4343
UpdateCollectionRecordsRequest, UpdateCollectionRecordsResponse, UpdateCollectionRequest,
4444
UpdateCollectionResponse, UpdateTenantError, UpdateTenantRequest, UpdateTenantResponse,
4545
UpsertCollectionRecordsError, UpsertCollectionRecordsRequest, UpsertCollectionRecordsResponse,
@@ -548,8 +548,8 @@ impl ServiceBasedFrontend {
548548

549549
// Enable quantization for tenants in the config list (or all tenants if "*" is present)
550550
if let Some(ref mut schema) = reconciled_schema {
551-
if let Some(spann_config) = schema.get_spann_config_mut() {
552-
spann_config.quantize = self.should_enable_quantization_for_tenant(&tenant_id);
551+
if self.should_enable_quantization_for_tenant(&tenant_id) {
552+
schema.quantize(Quantization::USearch4BitRabitQ);
553553
}
554554
}
555555

rust/index/src/spann/quantized_spann.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,7 +1456,7 @@ mod tests {
14561456
use chroma_cache::{new_cache_for_test, new_non_persistent_cache_for_test};
14571457
use chroma_distance::DistanceFunction;
14581458
use chroma_storage::{local::LocalStorage, Storage};
1459-
use chroma_types::{CollectionUuid, DataRecord, SpannIndexConfig};
1459+
use chroma_types::{CollectionUuid, DataRecord, Quantization, SpannIndexConfig};
14601460
use rand::{Rng, SeedableRng};
14611461
use tempfile::TempDir;
14621462

@@ -1491,7 +1491,7 @@ mod tests {
14911491
initial_lambda: None,
14921492
num_centers_to_merge_to: None,
14931493
max_neighbors: Some(8),
1494-
quantize: true,
1494+
quantize: Quantization::USearch4BitRabitQ,
14951495
}
14961496
}
14971497

rust/types/src/collection_schema.rs

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3130
impl 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

28022840
impl 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

Comments
 (0)