Skip to main content

velesdb_core/
config_quantization.rs

1//! Quantization configuration types (PQ-06).
2//!
3//! Extracted from `config.rs` to keep file NLOC under 500.
4//! Re-exported by both `config` and the crate root so existing import
5//! paths remain unchanged.
6
7use serde::{Deserialize, Serialize};
8
9// ---------------------------------------------------------------------------
10// QuantizationType enum (PQ-06)
11// ---------------------------------------------------------------------------
12
13/// Default value for PQ codebook size (`k`).
14const fn default_k() -> usize {
15    256
16}
17
18/// Default value for PQ oversampling factor.
19#[allow(clippy::unnecessary_wraps)]
20const fn default_oversampling() -> Option<u32> {
21    Some(4)
22}
23
24/// Quantization type for a collection (PQ-06).
25///
26/// Determines which quantization algorithm is applied to stored vectors.
27/// Uses a serde-tagged representation for the new format, with backward
28/// compatibility via [`QuantizationConfig`]'s custom deserializer.
29#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(tag = "type", rename_all = "lowercase")]
31#[non_exhaustive]
32pub enum QuantizationType {
33    /// No quantization -- full-precision vectors.
34    #[default]
35    None,
36    /// Scalar quantization to 8-bit integers (4x compression).
37    #[serde(alias = "sq8")]
38    SQ8,
39    /// Binary quantization (32x compression).
40    Binary,
41    /// Product quantization with configurable subspaces.
42    #[serde(alias = "pq")]
43    PQ {
44        /// Number of subspaces (dimension must be divisible by `m`).
45        m: usize,
46        /// Codebook size per subspace.
47        #[serde(default = "default_k")]
48        k: usize,
49        /// Enable Optimized Product Quantization (OPQ) rotation.
50        #[serde(default)]
51        opq_enabled: bool,
52        /// Oversampling factor for training. `None` disables oversampling.
53        #[serde(default = "default_oversampling")]
54        oversampling: Option<u32>,
55    },
56    /// Randomized Binary Quantization.
57    #[serde(alias = "rabitq")]
58    RaBitQ,
59}
60
61impl QuantizationType {
62    /// Returns `true` if this is Product Quantization.
63    #[must_use]
64    pub const fn is_pq(&self) -> bool {
65        matches!(self, Self::PQ { .. })
66    }
67
68    /// Returns `true` if this is Randomized Binary Quantization.
69    #[must_use]
70    pub const fn is_rabitq(&self) -> bool {
71        matches!(self, Self::RaBitQ)
72    }
73}
74
75// ---------------------------------------------------------------------------
76// QuantizationConfig
77// ---------------------------------------------------------------------------
78
79/// Quantization configuration section (EPIC-073/US-005, PQ-06).
80///
81/// Supports two JSON shapes for backward compatibility:
82/// - **Old format:** `{"default_type": "sq8", "rerank_enabled": true, ...}`
83/// - **New format:** `{"mode": {"type": "pq", "m": 8, ...}, "rerank_enabled": true, ...}`
84#[derive(Debug, Clone, Serialize)]
85pub struct QuantizationConfig {
86    /// Quantization mode (replaces the old `default_type` string).
87    pub mode: QuantizationType,
88    /// Enable reranking after quantized search.
89    pub rerank_enabled: bool,
90    /// Reranking multiplier for candidates.
91    pub rerank_multiplier: usize,
92    /// Auto-enable quantization for large collections (EPIC-073/US-005).
93    pub auto_quantization: bool,
94    /// Threshold for auto-quantization (number of vectors).
95    pub auto_quantization_threshold: usize,
96}
97
98impl Default for QuantizationConfig {
99    fn default() -> Self {
100        Self {
101            mode: QuantizationType::None,
102            rerank_enabled: true,
103            rerank_multiplier: 2,
104            auto_quantization: true,
105            auto_quantization_threshold: 10_000,
106        }
107    }
108}
109
110impl QuantizationConfig {
111    /// Returns a reference to the quantization mode.
112    #[must_use]
113    pub const fn mode(&self) -> &QuantizationType {
114        &self.mode
115    }
116
117    /// Returns whether quantization should be used based on vector count (EPIC-073/US-005).
118    #[must_use]
119    pub fn should_quantize(&self, vector_count: usize) -> bool {
120        self.auto_quantization && vector_count >= self.auto_quantization_threshold
121    }
122}
123
124// ---------------------------------------------------------------------------
125// Custom Deserialize for backward compatibility (PQ-06)
126// ---------------------------------------------------------------------------
127
128impl<'de> Deserialize<'de> for QuantizationConfig {
129    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
130    where
131        D: serde::Deserializer<'de>,
132    {
133        /// Raw intermediate struct that accepts either old or new format.
134        #[derive(Deserialize)]
135        struct RawQuantizationConfig {
136            /// New format: structured mode object.
137            #[serde(default)]
138            mode: Option<QuantizationType>,
139            /// Old format: plain string ("none", "sq8", "binary").
140            #[serde(default)]
141            default_type: Option<String>,
142            #[serde(default = "default_rerank_enabled")]
143            rerank_enabled: bool,
144            #[serde(default = "default_rerank_multiplier")]
145            rerank_multiplier: usize,
146            #[serde(default = "default_auto_quantization")]
147            auto_quantization: bool,
148            #[serde(default = "default_auto_quantization_threshold")]
149            auto_quantization_threshold: usize,
150        }
151
152        fn default_rerank_enabled() -> bool {
153            true
154        }
155        fn default_rerank_multiplier() -> usize {
156            2
157        }
158        fn default_auto_quantization() -> bool {
159            true
160        }
161        fn default_auto_quantization_threshold() -> usize {
162            10_000
163        }
164
165        let raw = RawQuantizationConfig::deserialize(deserializer)?;
166
167        let mode = if let Some(m) = raw.mode {
168            m
169        } else if let Some(ref s) = raw.default_type {
170            match s.as_str() {
171                "none" | "" => QuantizationType::None,
172                "sq8" => QuantizationType::SQ8,
173                "binary" => QuantizationType::Binary,
174                other => {
175                    return Err(serde::de::Error::custom(format!(
176                        "unknown quantization type: '{other}'"
177                    )));
178                }
179            }
180        } else {
181            QuantizationType::None
182        };
183
184        Ok(Self {
185            mode,
186            rerank_enabled: raw.rerank_enabled,
187            rerank_multiplier: raw.rerank_multiplier,
188            auto_quantization: raw.auto_quantization,
189            auto_quantization_threshold: raw.auto_quantization_threshold,
190        })
191    }
192}