1use super::{metric_distance, CodecKind, EncodedQuery, VectorCodec};
9use crate::error::{Result, RuvectorError};
10use crate::types::DistanceMetric;
11use ruvector_turboquant as tq;
12
13fn check_dim(expected: usize, actual: usize) -> Result<()> {
14 if expected != actual {
15 return Err(RuvectorError::DimensionMismatch { expected, actual });
16 }
17 Ok(())
18}
19
20fn check_blob(expected: usize, actual: usize) -> Result<()> {
21 if expected != actual {
22 return Err(RuvectorError::InvalidInput(format!(
23 "encoded blob length {actual} != expected {expected}"
24 )));
25 }
26 Ok(())
27}
28
29struct DecodingQuery {
31 q: Vec<f32>,
32 metric: DistanceMetric,
33 decode: fn(&[u8]) -> Vec<f32>,
34}
35
36impl EncodedQuery for DecodingQuery {
37 fn distance_to(&self, blob: &[u8]) -> f32 {
38 let v = (self.decode)(blob);
39 metric_distance(self.metric, &self.q, &v)
40 }
41}
42
43pub struct Fp32Codec {
49 dim: usize,
50 metric: DistanceMetric,
51}
52
53impl Fp32Codec {
54 pub fn new(dim: usize, metric: DistanceMetric) -> Self {
55 Self { dim, metric }
56 }
57}
58
59fn fp32_decode(blob: &[u8]) -> Vec<f32> {
60 blob.chunks_exact(4)
61 .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
62 .collect()
63}
64
65impl VectorCodec for Fp32Codec {
66 fn kind(&self) -> CodecKind {
67 CodecKind::Fp32
68 }
69 fn dim(&self) -> usize {
70 self.dim
71 }
72 fn encoded_len(&self) -> usize {
73 self.dim * 4
74 }
75 fn encode(&self, v: &[f32]) -> Result<Vec<u8>> {
76 check_dim(self.dim, v.len())?;
77 Ok(v.iter().flat_map(|x| x.to_le_bytes()).collect())
78 }
79 fn decode(&self, blob: &[u8]) -> Result<Vec<f32>> {
80 check_blob(self.encoded_len(), blob.len())?;
81 Ok(fp32_decode(blob))
82 }
83 fn distance(&self, a: &[u8], b: &[u8]) -> Result<f32> {
84 check_blob(self.encoded_len(), a.len())?;
85 check_blob(self.encoded_len(), b.len())?;
86 Ok(metric_distance(
87 self.metric,
88 &fp32_decode(a),
89 &fp32_decode(b),
90 ))
91 }
92 fn make_query(&self, q: &[f32]) -> Result<Box<dyn EncodedQuery>> {
93 check_dim(self.dim, q.len())?;
94 Ok(Box::new(DecodingQuery {
95 q: q.to_vec(),
96 metric: self.metric,
97 decode: fp32_decode,
98 }))
99 }
100}
101
102pub struct Fp16Codec {
110 dim: usize,
111 metric: DistanceMetric,
112}
113
114impl Fp16Codec {
115 pub fn new(dim: usize, metric: DistanceMetric) -> Self {
116 Self { dim, metric }
117 }
118}
119
120pub fn f32_to_f16_bits(x: f32) -> u16 {
122 let bits = x.to_bits();
123 let sign = ((bits >> 16) & 0x8000) as u16;
124 let exp = ((bits >> 23) & 0xFF) as i32;
125 let mant = bits & 0x007F_FFFF;
126 if exp == 255 {
127 return sign | 0x7C00 | if mant != 0 { 0x0200 } else { 0 };
129 }
130 let unbiased = exp - 127;
131 if unbiased > 15 {
132 return sign | 0x7C00; }
134 if unbiased >= -14 {
135 let m16 = (mant >> 13) as u16;
137 let h = sign | (((unbiased + 15) as u16) << 10) | m16;
138 let round = mant & 0x1FFF;
139 if round > 0x1000 || (round == 0x1000 && (m16 & 1) == 1) {
141 h + 1
142 } else {
143 h
144 }
145 } else if unbiased >= -24 {
146 let shift = (13 + (-14 - unbiased)) as u32;
148 let full = mant | 0x0080_0000;
149 let m16 = (full >> shift) as u16;
150 let rem = full & ((1u32 << shift) - 1);
151 let half = 1u32 << (shift - 1);
152 let h = sign | m16;
153 if rem > half || (rem == half && (m16 & 1) == 1) {
154 h + 1
155 } else {
156 h
157 }
158 } else {
159 sign }
161}
162
163pub fn f16_bits_to_f32(h: u16) -> f32 {
165 let sign = ((h & 0x8000) as u32) << 16;
166 let exp = ((h >> 10) & 0x1F) as u32;
167 let mant = (h & 0x03FF) as u32;
168 let bits = if exp == 0 {
169 if mant == 0 {
170 sign
171 } else {
172 let mut e = 113u32; let mut m = mant;
175 while m & 0x0400 == 0 {
176 m <<= 1;
177 e -= 1;
178 }
179 sign | (e << 23) | ((m & 0x03FF) << 13)
180 }
181 } else if exp == 31 {
182 sign | 0x7F80_0000 | (mant << 13)
183 } else {
184 sign | ((exp + 112) << 23) | (mant << 13)
185 };
186 f32::from_bits(bits)
187}
188
189fn fp16_decode(blob: &[u8]) -> Vec<f32> {
190 blob.chunks_exact(2)
191 .map(|c| f16_bits_to_f32(u16::from_le_bytes(c.try_into().unwrap())))
192 .collect()
193}
194
195impl VectorCodec for Fp16Codec {
196 fn kind(&self) -> CodecKind {
197 CodecKind::Fp16
198 }
199 fn dim(&self) -> usize {
200 self.dim
201 }
202 fn encoded_len(&self) -> usize {
203 self.dim * 2
204 }
205 fn encode(&self, v: &[f32]) -> Result<Vec<u8>> {
206 check_dim(self.dim, v.len())?;
207 Ok(v.iter()
208 .flat_map(|x| f32_to_f16_bits(*x).to_le_bytes())
209 .collect())
210 }
211 fn decode(&self, blob: &[u8]) -> Result<Vec<f32>> {
212 check_blob(self.encoded_len(), blob.len())?;
213 Ok(fp16_decode(blob))
214 }
215 fn distance(&self, a: &[u8], b: &[u8]) -> Result<f32> {
216 check_blob(self.encoded_len(), a.len())?;
217 check_blob(self.encoded_len(), b.len())?;
218 Ok(metric_distance(
219 self.metric,
220 &fp16_decode(a),
221 &fp16_decode(b),
222 ))
223 }
224 fn make_query(&self, q: &[f32]) -> Result<Box<dyn EncodedQuery>> {
225 check_dim(self.dim, q.len())?;
226 Ok(Box::new(DecodingQuery {
227 q: q.to_vec(),
228 metric: self.metric,
229 decode: fp16_decode,
230 }))
231 }
232}
233
234pub struct Int8Codec {
242 dim: usize,
243 metric: DistanceMetric,
244}
245
246impl Int8Codec {
247 pub fn new(dim: usize, metric: DistanceMetric) -> Self {
248 Self { dim, metric }
249 }
250}
251
252fn int8_decode(blob: &[u8]) -> Vec<f32> {
253 let min = f32::from_le_bytes(blob[0..4].try_into().unwrap());
254 let scale = f32::from_le_bytes(blob[4..8].try_into().unwrap());
255 blob[8..].iter().map(|&u| min + u as f32 * scale).collect()
256}
257
258impl VectorCodec for Int8Codec {
259 fn kind(&self) -> CodecKind {
260 CodecKind::Int8
261 }
262 fn dim(&self) -> usize {
263 self.dim
264 }
265 fn encoded_len(&self) -> usize {
266 self.dim + 8
267 }
268 fn encode(&self, v: &[f32]) -> Result<Vec<u8>> {
269 check_dim(self.dim, v.len())?;
270 let min = v.iter().copied().fold(f32::INFINITY, f32::min);
271 let max = v.iter().copied().fold(f32::NEG_INFINITY, f32::max);
272 let scale = if (max - min).abs() < f32::EPSILON {
273 1.0
274 } else {
275 (max - min) / 255.0
276 };
277 let mut blob = Vec::with_capacity(self.encoded_len());
278 blob.extend_from_slice(&min.to_le_bytes());
279 blob.extend_from_slice(&scale.to_le_bytes());
280 blob.extend(
281 v.iter()
282 .map(|&x| ((x - min) / scale).round().clamp(0.0, 255.0) as u8),
283 );
284 Ok(blob)
285 }
286 fn decode(&self, blob: &[u8]) -> Result<Vec<f32>> {
287 check_blob(self.encoded_len(), blob.len())?;
288 Ok(int8_decode(blob))
289 }
290 fn distance(&self, a: &[u8], b: &[u8]) -> Result<f32> {
291 check_blob(self.encoded_len(), a.len())?;
292 check_blob(self.encoded_len(), b.len())?;
293 Ok(metric_distance(
294 self.metric,
295 &int8_decode(a),
296 &int8_decode(b),
297 ))
298 }
299 fn make_query(&self, q: &[f32]) -> Result<Box<dyn EncodedQuery>> {
300 check_dim(self.dim, q.len())?;
301 Ok(Box::new(DecodingQuery {
302 q: q.to_vec(),
303 metric: self.metric,
304 decode: int8_decode,
305 }))
306 }
307}
308
309fn to_turbo_metric(metric: DistanceMetric) -> Result<tq::Metric> {
314 match metric {
315 DistanceMetric::Euclidean => Ok(tq::Metric::Euclidean),
316 DistanceMetric::Cosine => Ok(tq::Metric::Cosine),
317 DistanceMetric::DotProduct => Ok(tq::Metric::DotProduct),
318 DistanceMetric::Manhattan => Err(RuvectorError::InvalidParameter(
319 "Turbo4 does not support the Manhattan metric".into(),
320 )),
321 }
322}
323
324pub struct Turbo4PlaneCodec {
327 inner: tq::Turbo4Codec,
328 metric: tq::Metric,
329}
330
331impl Turbo4PlaneCodec {
332 pub fn new(dim: usize, metric: DistanceMetric, rotation_seed: u64) -> Result<Self> {
333 let metric = to_turbo_metric(metric)?;
334 let inner = tq::Turbo4Codec::new(dim, rotation_seed)
335 .map_err(|e| RuvectorError::InvalidParameter(e.to_string()))?;
336 Ok(Self { inner, metric })
337 }
338}
339
340struct Turbo4PlaneQuery {
341 query: tq::Turbo4Query,
342 metric: tq::Metric,
343 dim: usize,
344}
345
346impl EncodedQuery for Turbo4PlaneQuery {
347 fn distance_to(&self, blob: &[u8]) -> f32 {
348 tq::rescore(self.metric, &self.query, blob, self.dim)
349 }
350}
351
352impl VectorCodec for Turbo4PlaneCodec {
353 fn kind(&self) -> CodecKind {
354 CodecKind::Turbo4
355 }
356 fn dim(&self) -> usize {
357 self.inner.dim()
358 }
359 fn encoded_len(&self) -> usize {
360 self.inner.code_len()
361 }
362 fn encode(&self, v: &[f32]) -> Result<Vec<u8>> {
363 self.inner
364 .encode(v)
365 .map_err(|e| RuvectorError::InvalidInput(e.to_string()))
366 }
367 fn decode(&self, blob: &[u8]) -> Result<Vec<f32>> {
368 check_blob(self.encoded_len(), blob.len())?;
369 Ok(self.inner.decode(blob))
370 }
371 fn distance(&self, a: &[u8], b: &[u8]) -> Result<f32> {
372 check_blob(self.encoded_len(), a.len())?;
373 check_blob(self.encoded_len(), b.len())?;
374 Ok(tq::symmetric_distance(self.metric, a, b, self.inner.dim()))
375 }
376 fn make_query(&self, q: &[f32]) -> Result<Box<dyn EncodedQuery>> {
377 let query = self
378 .inner
379 .encode_query(q)
380 .map_err(|e| RuvectorError::InvalidInput(e.to_string()))?;
381 Ok(Box::new(Turbo4PlaneQuery {
382 query,
383 metric: self.metric,
384 dim: self.inner.dim(),
385 }))
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::super::{codec_for, CodecKind};
392 use super::*;
393
394 fn test_vec(dim: usize, seed: u64) -> Vec<f32> {
396 (0..dim as u64)
397 .map(|i| {
398 let mut z = (seed << 32 | i).wrapping_add(0x9E3779B97F4A7C15);
399 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
400 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
401 ((z >> 40) as f32 / (1u64 << 24) as f32) * 2.0 - 1.0
402 })
403 .collect()
404 }
405
406 #[test]
407 fn f16_conversion_known_values() {
408 for (x, bits) in [
409 (0.0f32, 0x0000u16),
410 (1.0, 0x3C00),
411 (-2.0, 0xC000),
412 (65504.0, 0x7BFF), (0.5, 0x3800),
414 ] {
415 assert_eq!(f32_to_f16_bits(x), bits, "encode {x}");
416 assert_eq!(f16_bits_to_f32(bits), x, "decode {x}");
417 }
418 assert_eq!(f32_to_f16_bits(1e30), 0x7C00);
420 assert!(f16_bits_to_f32(f32_to_f16_bits(f32::NAN)).is_nan());
421 let sub = 3.0e-6f32;
422 let rt = f16_bits_to_f32(f32_to_f16_bits(sub));
423 assert!((rt - sub).abs() / sub < 0.05, "subnormal {sub} -> {rt}");
424 }
425
426 #[test]
427 fn f16_roundtrip_relative_error() {
428 for &x in &[1.0f32, -1.5, 0.123, 999.25, -0.0004883, 3.1415926] {
429 let rt = f16_bits_to_f32(f32_to_f16_bits(x));
430 assert!(
431 (rt - x).abs() / x.abs() < 1e-3,
432 "roundtrip {x} -> {rt} exceeds half precision"
433 );
434 }
435 }
436
437 #[test]
438 fn all_codecs_roundtrip_and_score() {
439 let dim = 64;
440 let a = test_vec(dim, 1);
441 let b = test_vec(dim, 7);
442 let exact = metric_distance(DistanceMetric::Euclidean, &a, &b);
443
444 for kind in [
445 CodecKind::Fp32,
446 CodecKind::Fp16,
447 CodecKind::Int8,
448 CodecKind::Turbo4,
449 ] {
450 let codec = codec_for(kind, dim, DistanceMetric::Euclidean, 42).unwrap();
451 let ca = codec.encode(&a).unwrap();
452 let cb = codec.encode(&b).unwrap();
453 assert_eq!(ca.len(), codec.encoded_len(), "{kind:?} blob length");
454
455 let da = codec.decode(&ca).unwrap();
457 let err: f32 = a.iter().zip(&da).map(|(x, y)| (x - y).abs()).sum::<f32>() / dim as f32;
458 let tol = match kind {
459 CodecKind::Fp32 => 1e-9,
460 CodecKind::Fp16 => 1e-3,
461 CodecKind::Int8 => 0.01,
462 _ => 0.2,
463 };
464 assert!(err <= tol, "{kind:?} mean decode error {err} > {tol}");
465
466 let sym = codec.distance(&ca, &cb).unwrap();
468 let asym = codec.make_query(&a).unwrap().distance_to(&cb);
469 let dtol = match kind {
470 CodecKind::Fp32 => 1e-4,
471 CodecKind::Fp16 => 1e-2,
472 CodecKind::Int8 => 0.05,
473 _ => 0.30,
474 };
475 assert!(
476 (sym - exact).abs() <= dtol * exact.max(1.0),
477 "{kind:?} symmetric {sym} vs exact {exact}"
478 );
479 assert!(
480 (asym - exact).abs() <= dtol * exact.max(1.0),
481 "{kind:?} asymmetric {asym} vs exact {exact}"
482 );
483 }
484 }
485
486 #[test]
487 fn turbo4_plane_matches_direct_codec() {
488 let dim = 128;
489 let v = test_vec(dim, 3);
490 let plane = codec_for(CodecKind::Turbo4, dim, DistanceMetric::Cosine, 42).unwrap();
491 let direct = tq::Turbo4Codec::new(dim, 42).unwrap();
492 assert_eq!(plane.encode(&v).unwrap(), direct.encode(&v).unwrap());
493 }
494
495 #[test]
496 fn manhattan_rejected_only_for_turbo4() {
497 assert!(codec_for(CodecKind::Turbo4, 64, DistanceMetric::Manhattan, 42).is_err());
498 assert!(codec_for(CodecKind::Fp16, 64, DistanceMetric::Manhattan, 42).is_ok());
499 }
500}