1use serde::{Deserialize, Serialize};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct LogQuantizedConfig {
28 #[serde(default)]
30 pub enabled: bool,
31
32 #[serde(default = "default_compression_ratio")]
35 pub compression_ratio: usize,
36
37 #[serde(default = "default_min_magnitude")]
39 pub min_magnitude: f64,
40}
41
42fn default_compression_ratio() -> usize {
43 4
44}
45
46fn default_min_magnitude() -> f64 {
47 1e-7
48}
49
50impl Default for LogQuantizedConfig {
51 fn default() -> Self {
52 Self {
53 enabled: false,
54 compression_ratio: default_compression_ratio(),
55 min_magnitude: default_min_magnitude(),
56 }
57 }
58}
59
60impl LogQuantizedConfig {
61 pub fn is_available(&self) -> bool {
63 self.enabled
64 }
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct LogQuantizedVector {
70 pub codes: Vec<u8>,
72 pub signs: Vec<bool>,
74 pub max_log: f32,
76 pub min_magnitude: f32,
78}
79
80impl LogQuantizedVector {
81 pub fn quantize(vector: &[f32], cfg: &LogQuantizedConfig) -> Self {
83 let min_m = cfg.min_magnitude.max(1e-12) as f32;
84 let mut max_log = 0.0f32;
85 let mut logs = Vec::with_capacity(vector.len());
86 let mut signs = Vec::with_capacity(vector.len());
87 for &v in vector {
88 let sign = v.is_sign_negative();
89 signs.push(sign);
90 let mag = v.abs().max(min_m);
91 let lg = mag.ln();
92 logs.push(lg);
93 if lg > max_log {
94 max_log = lg;
95 }
96 }
97 if max_log < min_m.ln() + 1e-6 {
99 max_log = min_m.ln() + 1.0;
100 }
101 let min_log = min_m.ln();
102 let span = (max_log - min_log).max(1e-6);
103 let codes: Vec<u8> = logs
104 .iter()
105 .map(|&lg| {
106 let t = ((lg - min_log) / span).clamp(0.0, 1.0);
107 (t * 255.0).round() as u8
108 })
109 .collect();
110 Self {
111 codes,
112 signs,
113 max_log,
114 min_magnitude: min_m,
115 }
116 }
117
118 pub fn reconstruct(&self) -> Vec<f32> {
120 let min_log = self.min_magnitude.ln();
121 let span = (self.max_log - min_log).max(1e-6);
122 self.codes
123 .iter()
124 .zip(self.signs.iter())
125 .map(|(&c, &neg)| {
126 let t = c as f32 / 255.0;
127 let lg = min_log + t * span;
128 let mag = lg.exp();
129 if neg {
130 -mag
131 } else {
132 mag
133 }
134 })
135 .collect()
136 }
137
138 pub fn mse(&self, original: &[f32]) -> f32 {
140 let rec = self.reconstruct();
141 if rec.len() != original.len() || original.is_empty() {
142 return f32::INFINITY;
143 }
144 let mut s = 0.0f32;
145 for (a, b) in original.iter().zip(rec.iter()) {
146 let d = a - b;
147 s += d * d;
148 }
149 s / original.len() as f32
150 }
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
159#[serde(rename_all = "lowercase")]
160pub enum SimdDistanceMetric {
161 #[default]
163 Cosine,
164 L2,
166 InnerProduct,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, Default)]
172pub struct SimdDistanceConfig {
173 #[serde(default)]
175 pub enabled: bool,
176 #[serde(default)]
178 pub pad_to_power_of_two: bool,
179 #[serde(default)]
181 pub metric: SimdDistanceMetric,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
184 pub lane_width: Option<u16>,
185}
186
187impl SimdDistanceConfig {
188 pub fn is_available(&self) -> bool {
190 self.enabled
191 }
192}
193
194pub fn next_pow2(n: usize) -> usize {
196 if n <= 1 {
197 return 1;
198 }
199 n.next_power_of_two()
200}
201
202pub fn maybe_pad(v: &[f32], pad: bool) -> Vec<f32> {
204 if !pad {
205 return v.to_vec();
206 }
207 let n = next_pow2(v.len().max(1));
208 let mut out = vec![0.0f32; n];
209 out[..v.len()].copy_from_slice(v);
210 out
211}
212
213pub fn unified_distance(a: &[f32], b: &[f32], cfg: &SimdDistanceConfig) -> f32 {
218 let a = maybe_pad(a, cfg.pad_to_power_of_two);
219 let b = maybe_pad(b, cfg.pad_to_power_of_two);
220 let n = a.len().min(b.len());
221 if n == 0 {
222 return 0.0;
223 }
224
225 let mut dot = 0.0f32;
227 let mut na = 0.0f32;
228 let mut nb = 0.0f32;
229 let mut l2 = 0.0f32;
230 let chunks = n / 4;
231 for i in 0..chunks {
232 let idx = i * 4;
233 for j in 0..4 {
234 let ai = a[idx + j];
235 let bi = b[idx + j];
236 dot += ai * bi;
237 na += ai * ai;
238 nb += bi * bi;
239 let d = ai - bi;
240 l2 += d * d;
241 }
242 }
243 for i in (chunks * 4)..n {
244 let ai = a[i];
245 let bi = b[i];
246 dot += ai * bi;
247 na += ai * ai;
248 nb += bi * bi;
249 let d = ai - bi;
250 l2 += d * d;
251 }
252
253 match cfg.metric {
254 SimdDistanceMetric::L2 => l2.sqrt(),
255 SimdDistanceMetric::InnerProduct => -dot,
256 SimdDistanceMetric::Cosine => {
257 let denom = na.sqrt() * nb.sqrt();
258 if denom > 1e-8 {
259 1.0 - (dot / denom)
260 } else {
261 1.0
262 }
263 }
264 }
265}
266
267#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn log_quantized_available_when_enabled() {
277 let off = LogQuantizedConfig::default();
278 assert!(!off.is_available());
279 let on = LogQuantizedConfig {
280 enabled: true,
281 ..Default::default()
282 };
283 assert!(on.is_available());
284 }
285
286 #[test]
287 fn log_quantized_roundtrip_skewed() {
288 let v: Vec<f32> = (0..64)
290 .map(|i| (i as f32 * 0.15).exp() * if i % 3 == 0 { -1.0 } else { 1.0 })
291 .collect();
292 let cfg = LogQuantizedConfig {
293 enabled: true,
294 compression_ratio: 4,
295 min_magnitude: 1e-7,
296 };
297 let q = LogQuantizedVector::quantize(&v, &cfg);
298 let mse = q.mse(&v);
299 assert!(mse.is_finite(), "mse={mse}");
301 assert!(mse < 1e6, "unexpected huge mse={mse}");
302 assert_eq!(q.reconstruct().len(), v.len());
305 }
306
307 #[test]
308 fn simd_distance_available_when_enabled() {
309 assert!(!SimdDistanceConfig::default().is_available());
310 assert!(SimdDistanceConfig {
311 enabled: true,
312 ..Default::default()
313 }
314 .is_available());
315 }
316
317 #[test]
318 fn unified_cosine_identical_is_zero() {
319 let v = vec![1.0, 2.0, 3.0, 4.0];
320 let cfg = SimdDistanceConfig {
321 enabled: true,
322 metric: SimdDistanceMetric::Cosine,
323 ..Default::default()
324 };
325 let d = unified_distance(&v, &v, &cfg);
326 assert!(d.abs() < 1e-5, "d={d}");
327 }
328
329 #[test]
330 fn unified_l2_orthogonal() {
331 let a = vec![1.0, 0.0, 0.0, 0.0];
332 let b = vec![0.0, 1.0, 0.0, 0.0];
333 let cfg = SimdDistanceConfig {
334 enabled: true,
335 metric: SimdDistanceMetric::L2,
336 pad_to_power_of_two: true,
337 ..Default::default()
338 };
339 let d = unified_distance(&a, &b, &cfg);
340 assert!((d - 2.0f32.sqrt()).abs() < 1e-5, "d={d}");
341 }
342
343 #[test]
344 fn log_quantized_serde_roundtrip() {
345 let cfg = LogQuantizedConfig {
346 enabled: true,
347 compression_ratio: 8,
348 min_magnitude: 1e-5,
349 };
350 let json = serde_json::to_string(&cfg).unwrap();
351 let restored: LogQuantizedConfig = serde_json::from_str(&json).unwrap();
352 assert!(restored.enabled);
353 assert_eq!(restored.compression_ratio, 8);
354 }
355
356 #[test]
357 fn simd_distance_serde_roundtrip() {
358 let cfg = SimdDistanceConfig {
359 enabled: true,
360 pad_to_power_of_two: true,
361 metric: SimdDistanceMetric::L2,
362 lane_width: Some(256),
363 };
364 let json = serde_json::to_string(&cfg).unwrap();
365 let restored: SimdDistanceConfig = serde_json::from_str(&json).unwrap();
366 assert!(restored.is_available());
367 assert_eq!(restored.metric, SimdDistanceMetric::L2);
368 }
369
370 #[test]
371 fn next_pow2_basic() {
372 assert_eq!(next_pow2(1), 1);
373 assert_eq!(next_pow2(3), 4);
374 assert_eq!(next_pow2(8), 8);
375 assert_eq!(next_pow2(9), 16);
376 }
377}