1use serde::{Deserialize, Serialize};
2
3use crate::error::Result;
4use crate::policy::CodecId;
5
6pub trait KVecCodec: Send + Sync {
8 fn codec_id(&self) -> CodecId;
10
11 fn encode(&self, vector: &[f32], seed: u64) -> Result<Vec<u8>>;
13
14 fn encode_batch(&self, vectors: &[&[f32]], seed: u64) -> Result<Vec<Vec<u8>>> {
22 vectors.iter().map(|v| self.encode(v, seed)).collect()
23 }
24
25 fn decode(&self, payload: &[u8], seed: u64) -> Result<Vec<f32>>;
27
28 fn decode_batch(&self, payloads: &[&[u8]], seed: u64) -> Result<Vec<Vec<f32>>> {
30 payloads.iter().map(|p| self.decode(p, seed)).collect()
31 }
32
33 fn encode_batch_compact(&self, vectors: &[&[f32]], seed: u64) -> Result<Option<Vec<u8>>> {
39 let _ = (vectors, seed);
40 Ok(None)
41 }
42
43 fn decode_batch_compact(&self, payload: &[u8], seed: u64) -> Result<Option<Vec<Vec<f32>>>> {
45 let _ = (payload, seed);
46 Ok(None)
47 }
48
49 fn dim(&self) -> usize;
51
52 fn compression_ratio(&self) -> f64;
54
55 fn is_gpu_accelerated(&self) -> bool {
65 false
66 }
67
68 fn is_gpu_accelerated_for(&self, n: usize, d: usize) -> bool {
72 let _ = (n, d);
73 self.is_gpu_accelerated()
74 }
75
76 fn codebook_digest(&self, _seed: u64) -> Option<String> {
79 None
80 }
81
82 fn rotation_digest(&self, _seed: u64) -> Option<String> {
85 None
86 }
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct CompressedBlock {
92 pub codec: CodecId,
94 pub encoded_payload: Vec<u8>,
96 pub payload_digest: crate::digest_compat::Digest,
98 pub original_dim: usize,
100 pub compressed_bytes: usize,
102}
103
104impl CompressedBlock {
105 pub fn new(codec: CodecId, encoded_payload: Vec<u8>, original_dim: usize) -> Self {
107 let compressed_bytes = encoded_payload.len();
108 let payload_digest = crate::digest_compat::compute(&encoded_payload);
109 Self {
110 codec,
111 encoded_payload,
112 payload_digest,
113 original_dim,
114 compressed_bytes,
115 }
116 }
117
118 pub fn compression_ratio(&self) -> f64 {
120 let raw_bytes = self.original_dim * 4; if self.compressed_bytes == 0 {
122 return f64::INFINITY;
123 }
124 raw_bytes as f64 / self.compressed_bytes as f64
125 }
126}
127
128pub struct ExactFallbackCodec {
132 dim: usize,
133}
134
135impl ExactFallbackCodec {
136 pub fn new(dim: usize) -> Self {
137 Self { dim }
138 }
139}
140
141impl KVecCodec for ExactFallbackCodec {
142 fn codec_id(&self) -> CodecId {
143 crate::policy::CODEC_EXACT_FALLBACK.into()
144 }
145
146 fn encode(&self, vector: &[f32], _seed: u64) -> Result<Vec<u8>> {
147 if vector.len() != self.dim {
148 return Err(crate::error::PolyKvError::DimensionMismatch {
149 expected: self.dim,
150 got: vector.len(),
151 });
152 }
153 let bytes: Vec<u8> = vector.iter().flat_map(|v| v.to_le_bytes()).collect();
155 Ok(bytes)
156 }
157
158 fn decode(&self, payload: &[u8], _seed: u64) -> Result<Vec<f32>> {
159 let expected_len = self.dim * 4;
160 if payload.len() != expected_len {
161 return Err(crate::error::PolyKvError::CorruptPayload(format!(
162 "exact fallback payload size {} != expected {}",
163 payload.len(),
164 expected_len
165 )));
166 }
167 let mut vec = Vec::with_capacity(self.dim);
168 for chunk in payload.chunks_exact(4) {
169 let arr: [u8; 4] = chunk.try_into().unwrap();
170 vec.push(f32::from_le_bytes(arr));
171 }
172 Ok(vec)
173 }
174
175 fn dim(&self) -> usize {
176 self.dim
177 }
178
179 fn compression_ratio(&self) -> f64 {
180 1.0
181 }
182}
183
184#[cfg(feature = "turbo")]
188pub struct TurboQuantAdapter {
189 dim: usize,
190 bits: u8,
191 projections: usize,
192}
193
194#[cfg(feature = "turbo")]
195impl TurboQuantAdapter {
196 #[allow(clippy::too_many_arguments)]
197 pub fn new(dim: usize, bits: u8, projections: usize) -> Result<Self> {
198 if dim == 0 {
199 return Err(crate::error::PolyKvError::InvalidPolicy(
200 "turbo dim must be > 0".into(),
201 ));
202 }
203 if dim % 2 != 0 {
204 return Err(crate::error::PolyKvError::InvalidPolicy(format!(
206 "turbo requires even dimension, got {}",
207 dim
208 )));
209 }
210 Ok(Self {
211 dim,
212 bits,
213 projections,
214 })
215 }
216}
217
218#[cfg(feature = "turbo")]
219impl KVecCodec for TurboQuantAdapter {
220 fn codec_id(&self) -> CodecId {
221 crate::policy::CODEC_TURBO_8BIT.into()
222 }
223
224 fn encode(&self, vector: &[f32], seed: u64) -> Result<Vec<u8>> {
225 let quantizer =
226 turbo_quant::TurboQuantizer::new(self.dim, self.bits, self.projections, seed).map_err(
227 |e| {
228 crate::error::PolyKvError::CompressionFailed(format!(
229 "turbo quantizer init failed: {}",
230 e
231 ))
232 },
233 )?;
234
235 let code = quantizer.encode(vector).map_err(|e| {
236 crate::error::PolyKvError::CompressionFailed(format!("turbo encode failed: {}", e))
237 })?;
238
239 turbo_quant::TurboCodeWireV1::encode(&code, &quantizer).map_err(|e| {
247 crate::error::PolyKvError::CompressionFailed(format!("turbo wire encode failed: {}", e))
248 })
249 }
250
251 fn decode(&self, payload: &[u8], seed: u64) -> Result<Vec<f32>> {
252 let code: turbo_quant::TurboCode =
256 if payload.len() >= 4 && &payload[0..4] == turbo_quant::TURBO_CODE_WIRE_MAGIC {
257 let quantizer =
258 turbo_quant::TurboQuantizer::new(self.dim, self.bits, self.projections, seed)
259 .map_err(|e| {
260 crate::error::PolyKvError::DecompressionFailed(format!(
261 "turbo quantizer init failed: {}",
262 e
263 ))
264 })?;
265 turbo_quant::TurboCodeWireV1::decode(payload, &quantizer).map_err(|e| {
266 crate::error::PolyKvError::DecompressionFailed(format!(
267 "turbo wire decode failed: {}",
268 e
269 ))
270 })?
271 } else {
272 serde_json::from_slice(payload).map_err(|e| {
273 crate::error::PolyKvError::DecompressionFailed(format!(
274 "turbo code deserialize failed: {}",
275 e
276 ))
277 })?
278 };
279
280 let polar_quant =
284 turbo_quant::PolarQuantizer::new(self.dim, self.bits - 1, seed).map_err(|e| {
285 crate::error::PolyKvError::DecompressionFailed(format!(
286 "turbo polar quantizer init failed: {}",
287 e
288 ))
289 })?;
290
291 let reconstructed = polar_quant.decode(&code.polar_code).map_err(|e| {
292 crate::error::PolyKvError::DecompressionFailed(format!("turbo decode failed: {}", e))
293 })?;
294
295 Ok(reconstructed)
296 }
297
298 fn dim(&self) -> usize {
299 self.dim
300 }
301
302 fn compression_ratio(&self) -> f64 {
303 8.0
304 }
305}
306
307#[cfg(feature = "fib")]
311pub struct FibQuantAdapter {
312 dim: usize,
313 k: u32,
314 n: u32,
315 training_samples: u32,
316 lloyd_restarts: u32,
317 lloyd_iterations: u32,
318}
319
320#[cfg(feature = "fib")]
321impl FibQuantAdapter {
322 #[allow(clippy::too_many_arguments)]
323 pub fn new(
324 dim: usize,
325 k: u32,
326 n: u32,
327 training_samples: u32,
328 lloyd_restarts: u32,
329 lloyd_iterations: u32,
330 ) -> Result<Self> {
331 if dim == 0 {
332 return Err(crate::error::PolyKvError::InvalidPolicy(
333 "fib dim must be > 0".into(),
334 ));
335 }
336 if dim % k as usize != 0 {
337 return Err(crate::error::PolyKvError::InvalidPolicy(format!(
338 "fib ambient dim ({}) must be divisible by k ({})",
339 dim, k
340 )));
341 }
342 Ok(Self {
343 dim,
344 k,
345 n,
346 training_samples,
347 lloyd_restarts,
348 lloyd_iterations,
349 })
350 }
351
352 pub fn build_quantizer(
354 &self,
355 seed: u64,
356 ) -> std::result::Result<fib_quant::FibQuantizer, crate::error::PolyKvError> {
357 let mut profile = fib_quant::FibQuantProfileV1::paper_default(
358 self.dim,
359 self.k as usize,
360 self.n as usize,
361 seed,
362 )
363 .map_err(|e| {
364 crate::error::PolyKvError::CompressionFailed(format!("fib profile build failed: {}", e))
365 })?;
366
367 profile.training_samples = self.training_samples;
369 profile.lloyd_restarts = self.lloyd_restarts;
370 profile.lloyd_iterations = self.lloyd_iterations;
371
372 fib_quant::FibQuantizer::new(profile).map_err(|e| {
373 crate::error::PolyKvError::CompressionFailed(format!(
374 "fib quantizer build failed: {}",
375 e
376 ))
377 })
378 }
379
380 pub fn decode_codes_payload(
384 &self,
385 payload: &[u8],
386 seed: u64,
387 ) -> Result<Vec<fib_quant::FibCodeV1>> {
388 let quantizer = self.build_quantizer(seed)?;
389 let profile = quantizer.profile().clone();
390 let profile_digest = profile.digest().map_err(|e| {
391 crate::error::PolyKvError::DecompressionFailed(format!("fib profile digest: {e}"))
392 })?;
393 let mut codes = if payload.len() >= 4 && payload[0..4] == FIB_WIRE_BATCH_MAGIC {
394 decode_fib_batch_payload_wire(payload)?
395 } else if payload.len() >= 3 && payload[0..3] == FIB_BATCHED_MAGIC {
396 decode_fib_batch_payload(payload, &profile)?
397 } else if payload.len() >= 3 && payload[0..3] == fib_quant::COMPACT_MAGIC {
398 vec![
399 fib_quant::FibCodeV1::from_compact_bytes(payload, &profile).map_err(|e| {
400 crate::error::PolyKvError::DecompressionFailed(format!(
401 "fib compact decode failed: {e}"
402 ))
403 })?,
404 ]
405 } else {
406 vec![serde_json::from_slice(payload).map_err(|e| {
407 crate::error::PolyKvError::DecompressionFailed(format!(
408 "fib code deserialize failed: {e}"
409 ))
410 })?]
411 };
412 for code in &mut codes {
413 code.profile_digest = profile_digest.clone();
414 }
415 Ok(codes)
416 }
417}
418
419#[cfg(feature = "fib")]
421const FIB_WIRE_BATCH_MAGIC: [u8; 4] = [b'F', b'B', b'W', b'B'];
422
423#[cfg(feature = "fib")]
425const FIB_BATCHED_MAGIC: [u8; 3] = [b'F', b'B', b'2'];
426#[cfg(feature = "fib")]
427const FIB_BATCHED_VERSION: u8 = 1;
428
429#[cfg(feature = "fib")]
430fn fib_code_indices_len(block_count: u32, wire_index_bits: u8) -> Result<usize> {
431 (block_count as usize)
432 .checked_mul(wire_index_bits as usize)
433 .map(|bits| bits.div_ceil(8))
434 .ok_or_else(|| {
435 crate::error::PolyKvError::CorruptPayload("FB2 packed index length overflow".into())
436 })
437}
438
439#[cfg(feature = "fib")]
449fn encode_fib_batch_payload(
450 codes: &[fib_quant::FibCodeV1],
451 profile: &fib_quant::FibQuantProfileV1,
452) -> Result<Vec<u8>> {
453 if codes.is_empty() {
454 return Err(crate::error::PolyKvError::CompressionFailed(
455 "cannot encode empty wire batch".into(),
456 ));
457 }
458 let first_wire = fib_quant::FibCodeWireV1::to_wire_bytes(&codes[0], profile).map_err(|e| {
459 crate::error::PolyKvError::CompressionFailed(format!("fib wire encode failed: {e}"))
460 })?;
461 let norm_len = codes[0].norm_payload.len();
462 let indices_len = codes[0].indices.len();
463 let mut out = Vec::with_capacity(
464 4 + 4 + 4 + first_wire.len() + (codes.len() - 1) * (norm_len + indices_len),
465 );
466 out.extend_from_slice(&FIB_WIRE_BATCH_MAGIC);
467 out.extend_from_slice(&(codes.len() as u32).to_le_bytes());
468 out.extend_from_slice(&(first_wire.len() as u32).to_le_bytes());
469 out.extend_from_slice(&first_wire);
470 for code in &codes[1..] {
471 if code.norm_payload.len() != norm_len || code.indices.len() != indices_len {
472 return Err(crate::error::PolyKvError::CompressionFailed(
473 "wire batch contains heterogeneous FibCodeV1 shapes".into(),
474 ));
475 }
476 out.extend_from_slice(&code.norm_payload);
477 out.extend_from_slice(&code.indices);
478 }
479 Ok(out)
480}
481
482#[cfg(feature = "fib")]
485fn decode_fib_batch_payload_wire(payload: &[u8]) -> Result<Vec<fib_quant::FibCodeV1>> {
486 if payload.len() < 12 {
488 return Err(crate::error::PolyKvError::CorruptPayload(format!(
489 "wire batch too short: {} bytes",
490 payload.len()
491 )));
492 }
493 let count = u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]) as usize;
494 if count == 0 {
495 return Ok(Vec::new());
496 }
497 let first_len = u32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]) as usize;
498 let first_end = 12 + first_len;
499 if first_end > payload.len() {
500 return Err(crate::error::PolyKvError::CorruptPayload(format!(
501 "wire batch first code truncated: need {} bytes, have {}",
502 first_end,
503 payload.len()
504 )));
505 }
506 let (first_code, _profile) = fib_quant::FibCodeWireV1::from_wire_bytes(&payload[12..first_end])
507 .map_err(|e| {
508 crate::error::PolyKvError::DecompressionFailed(format!(
509 "fib wire decode failed at code 0: {e}"
510 ))
511 })?;
512 let norm_len = first_code.norm_payload.len();
513 let indices_len = first_code.indices.len();
514 let per_code = norm_len + indices_len;
515 let remaining = count - 1;
516 if payload.len() < first_end + remaining * per_code {
517 return Err(crate::error::PolyKvError::CorruptPayload(format!(
518 "wire batch body truncated: need {} bytes for {} remaining codes, have {}",
519 remaining * per_code,
520 remaining,
521 payload.len() - first_end
522 )));
523 }
524 let mut codes = Vec::with_capacity(count);
525 codes.push(first_code.clone());
526 let mut cursor = first_end;
527 for _ in 1..count {
528 let norm_payload = payload[cursor..cursor + norm_len].to_vec();
529 cursor += norm_len;
530 let indices = payload[cursor..cursor + indices_len].to_vec();
531 cursor += indices_len;
532 codes.push(fib_quant::FibCodeV1 {
533 schema_version: first_code.schema_version.clone(),
534 profile_digest: first_code.profile_digest.clone(),
535 codebook_digest: first_code.codebook_digest.clone(),
536 rotation_digest: first_code.rotation_digest.clone(),
537 ambient_dim: first_code.ambient_dim,
538 block_dim: first_code.block_dim,
539 norm_format: first_code.norm_format.clone(),
540 norm_payload,
541 wire_index_bits: first_code.wire_index_bits,
542 block_count: first_code.block_count,
543 indices,
544 });
545 }
546 Ok(codes)
547}
548
549#[cfg(feature = "fib")]
551fn decode_fib_batch_payload(
552 payload: &[u8],
553 profile: &fib_quant::FibQuantProfileV1,
554) -> Result<Vec<fib_quant::FibCodeV1>> {
555 if payload.len() < 15 {
556 return Err(crate::error::PolyKvError::CorruptPayload(format!(
557 "FB2 payload too short: {} bytes",
558 payload.len()
559 )));
560 }
561 if payload[0..3] != FIB_BATCHED_MAGIC {
562 return Err(crate::error::PolyKvError::CorruptPayload(
563 "FB2 payload bad magic".into(),
564 ));
565 }
566 if payload[3] != FIB_BATCHED_VERSION {
567 return Err(crate::error::PolyKvError::CorruptPayload(format!(
568 "FB2 version {} not supported",
569 payload[3]
570 )));
571 }
572 let count = u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]) as usize;
573 let wire_index_bits = payload[8];
574 let block_count = u32::from_le_bytes([payload[9], payload[10], payload[11], payload[12]]);
575 let norm_len = u16::from_le_bytes([payload[13], payload[14]]) as usize;
576 let indices_len = fib_code_indices_len(block_count, wire_index_bits)?;
577 let per_code = norm_len.checked_add(indices_len).ok_or_else(|| {
578 crate::error::PolyKvError::CorruptPayload("FB2 per-code length overflow".into())
579 })?;
580 let expected = 15usize
581 .checked_add(count.checked_mul(per_code).ok_or_else(|| {
582 crate::error::PolyKvError::CorruptPayload("FB2 payload length overflow".into())
583 })?)
584 .ok_or_else(|| {
585 crate::error::PolyKvError::CorruptPayload("FB2 payload length overflow".into())
586 })?;
587 if payload.len() != expected {
588 return Err(crate::error::PolyKvError::CorruptPayload(format!(
589 "FB2 payload length {} != expected {} (count={count}, per_code={per_code})",
590 payload.len(),
591 expected
592 )));
593 }
594 let profile_digest = profile.digest().map_err(|e| {
595 crate::error::PolyKvError::DecompressionFailed(format!("fib profile digest failed: {e}"))
596 })?;
597 let mut codes = Vec::with_capacity(count);
598 let mut cursor = 15;
599 for _ in 0..count {
600 let norm_payload = payload[cursor..cursor + norm_len].to_vec();
601 cursor += norm_len;
602 let indices = payload[cursor..cursor + indices_len].to_vec();
603 cursor += indices_len;
604 codes.push(fib_quant::FibCodeV1 {
605 schema_version: fib_quant::codec::CODE_SCHEMA.into(),
606 profile_digest: profile_digest.clone(),
607 codebook_digest: String::new(),
608 rotation_digest: String::new(),
609 ambient_dim: profile.ambient_dim,
610 block_dim: profile.block_dim,
611 norm_format: profile.norm_format.clone(),
612 norm_payload,
613 wire_index_bits,
614 block_count,
615 indices,
616 });
617 }
618 Ok(codes)
619}
620
621#[cfg(feature = "fib")]
622impl KVecCodec for FibQuantAdapter {
623 fn codec_id(&self) -> CodecId {
624 crate::policy::CODEC_FIB_K4_N32.into()
625 }
626
627 fn encode(&self, vector: &[f32], seed: u64) -> Result<Vec<u8>> {
628 let quantizer = self.build_quantizer(seed)?;
629 let code = quantizer.encode(vector).map_err(|e| {
630 crate::error::PolyKvError::CompressionFailed(format!("fib encode failed: {}", e))
631 })?;
632
633 Ok(code.to_compact_bytes())
639 }
640
641 fn encode_batch(&self, vectors: &[&[f32]], seed: u64) -> Result<Vec<Vec<u8>>> {
642 let quantizer = self.build_quantizer(seed)?;
646 let codes = quantizer.encode_batch(vectors).map_err(|e| {
647 crate::error::PolyKvError::CompressionFailed(format!("fib encode_batch failed: {}", e))
648 })?;
649 let mut out = Vec::with_capacity(codes.len());
650 for code in codes {
651 out.push(code.to_compact_bytes());
652 }
653 Ok(out)
654 }
655
656 fn encode_batch_compact(&self, vectors: &[&[f32]], seed: u64) -> Result<Option<Vec<u8>>> {
657 let quantizer = self.build_quantizer(seed)?;
658 let codes = quantizer.encode_batch(vectors).map_err(|e| {
659 crate::error::PolyKvError::CompressionFailed(format!("fib encode_batch failed: {}", e))
660 })?;
661 Ok(Some(encode_fib_batch_payload(&codes, quantizer.profile())?))
662 }
663
664 fn decode(&self, payload: &[u8], seed: u64) -> Result<Vec<f32>> {
665 let quantizer = self.build_quantizer(seed)?;
666 let code = if payload.len() >= 3 && payload[0..3] == fib_quant::COMPACT_MAGIC {
670 let profile = quantizer.profile().clone();
671 fib_quant::FibCodeV1::from_compact_bytes(payload, &profile).map_err(|e| {
672 crate::error::PolyKvError::DecompressionFailed(format!(
673 "fib compact decode failed: {}",
674 e
675 ))
676 })?
677 } else {
678 serde_json::from_slice(payload).map_err(|e| {
679 crate::error::PolyKvError::DecompressionFailed(format!(
680 "fib code deserialize failed: {}",
681 e
682 ))
683 })?
684 };
685
686 let decoded = quantizer.decode(&code).map_err(|e| {
687 crate::error::PolyKvError::DecompressionFailed(format!("fib decode failed: {}", e))
688 })?;
689
690 Ok(decoded)
691 }
692
693 fn decode_batch(&self, payloads: &[&[u8]], seed: u64) -> Result<Vec<Vec<f32>>> {
694 let quantizer = self.build_quantizer(seed)?;
695 let profile = quantizer.profile().clone();
696 let mut codes = Vec::with_capacity(payloads.len());
697 for p in payloads {
698 let code = if p.len() >= 3 && p[0..3] == fib_quant::COMPACT_MAGIC {
699 fib_quant::FibCodeV1::from_compact_bytes(p, &profile).map_err(|e| {
700 crate::error::PolyKvError::DecompressionFailed(format!(
701 "fib compact decode failed: {}",
702 e
703 ))
704 })?
705 } else {
706 serde_json::from_slice(p).map_err(|e| {
707 crate::error::PolyKvError::DecompressionFailed(format!(
708 "fib code deserialize failed: {}",
709 e
710 ))
711 })?
712 };
713 codes.push(code);
714 }
715 quantizer.decode_batch_fast(&codes).map_err(|e| {
716 crate::error::PolyKvError::DecompressionFailed(format!(
717 "fib decode_batch_fast failed: {}",
718 e
719 ))
720 })
721 }
722
723 fn decode_batch_compact(&self, payload: &[u8], seed: u64) -> Result<Option<Vec<Vec<f32>>>> {
724 if payload.len() >= 4 && payload[0..4] == FIB_WIRE_BATCH_MAGIC {
725 let mut codes = decode_fib_batch_payload_wire(payload)?;
730 let quantizer = self.build_quantizer(seed)?;
731 let profile_digest = quantizer.profile().digest().map_err(|e| {
732 crate::error::PolyKvError::DecompressionFailed(format!("fib profile digest: {e}"))
733 })?;
734 for code in &mut codes {
735 code.profile_digest = profile_digest.clone();
736 }
737 let decoded = quantizer.decode_batch_fast(&codes).map_err(|e| {
738 crate::error::PolyKvError::DecompressionFailed(format!(
739 "fib wire batch decode_batch_fast failed: {e}"
740 ))
741 })?;
742 return Ok(Some(decoded));
743 }
744 if payload.len() >= 3 && payload[0..3] == FIB_BATCHED_MAGIC {
745 let quantizer = self.build_quantizer(seed)?;
747 let codes = decode_fib_batch_payload(payload, quantizer.profile())?;
748 let decoded = quantizer.decode_batch_fast(&codes).map_err(|e| {
749 crate::error::PolyKvError::DecompressionFailed(format!(
750 "fib FB2 decode_batch_fast failed: {e}"
751 ))
752 })?;
753 return Ok(Some(decoded));
754 }
755 Ok(None)
756 }
757
758 fn dim(&self) -> usize {
759 self.dim
760 }
761
762 fn compression_ratio(&self) -> f64 {
763 50.0
764 }
765
766 fn is_gpu_accelerated(&self) -> bool {
767 match self.build_quantizer(0) {
770 Ok(q) => q.is_gpu_accelerated(),
771 Err(_) => false,
772 }
773 }
774
775 fn is_gpu_accelerated_for(&self, n: usize, d: usize) -> bool {
776 match self.build_quantizer(0) {
777 Ok(q) => q.is_gpu_accelerated_for(n, d),
778 Err(_) => false,
779 }
780 }
781
782 fn codebook_digest(&self, seed: u64) -> Option<String> {
783 self.build_quantizer(seed)
784 .ok()
785 .map(|q| q.codebook_digest().to_string())
786 }
787
788 fn rotation_digest(&self, seed: u64) -> Option<String> {
789 self.build_quantizer(seed)
790 .ok()
791 .map(|q| q.rotation_digest().to_string())
792 }
793}
794
795#[allow(clippy::too_many_arguments)]
800pub fn create_codec(
801 codec_id: &str,
802 dim: usize,
803 fib_config: Option<&crate::policy::FibConfig>,
804 #[cfg_attr(not(feature = "turbo"), allow(unused_variables))] turbo_config: Option<
805 &crate::policy::TurboConfig,
806 >,
807) -> Result<Box<dyn KVecCodec>> {
808 match codec_id {
809 crate::policy::CODEC_FIB_K4_N32 => {
810 #[cfg(feature = "fib")]
811 {
812 let fc = fib_config.ok_or_else(|| {
813 crate::error::PolyKvError::InvalidPolicy("fib codec requires fib_config".into())
814 })?;
815 let adapter = FibQuantAdapter::new(
816 dim,
817 fc.k,
818 fc.n,
819 fc.training_samples,
820 fc.lloyd_restarts,
821 fc.lloyd_iterations,
822 )?;
823 Ok(Box::new(adapter))
824 }
825 #[cfg(not(feature = "fib"))]
826 {
827 Err(crate::error::PolyKvError::CodecUnavailable {
828 codec: crate::policy::CODEC_FIB_K4_N32.into(),
829 feature: "fib".into(),
830 })
831 }
832 }
833 crate::policy::CODEC_TURBO_8BIT => {
834 #[cfg(feature = "turbo")]
835 {
836 let tc = turbo_config.ok_or_else(|| {
837 crate::error::PolyKvError::InvalidPolicy(
838 "turbo codec requires turbo_config".into(),
839 )
840 })?;
841 let adapter = TurboQuantAdapter::new(dim, tc.bits, tc.projections)?;
842 Ok(Box::new(adapter))
843 }
844 #[cfg(not(feature = "turbo"))]
845 {
846 Err(crate::error::PolyKvError::CodecUnavailable {
847 codec: crate::policy::CODEC_TURBO_8BIT.into(),
848 feature: "turbo".into(),
849 })
850 }
851 }
852 crate::policy::CODEC_EXACT_FALLBACK => Ok(Box::new(ExactFallbackCodec::new(dim))),
853 other => Err(crate::error::PolyKvError::InvalidPolicy(format!(
854 "unknown codec id: {}",
855 other
856 ))),
857 }
858}