1use quant_codec_core::{
12 CodecId, CodecProfile, CodecProfileDigest, EvalReport, KvCacheCodec, KvSliceRequest,
13 KvTensorShape, QuantCodecError, VectorCodec,
14};
15
16use crate::{
17 codec::FibCodeV1,
18 kv::{
19 codec::{encode_kv_tensor, KvEncodedTensorV1},
20 layout::KvCacheLayoutV1,
21 shape::KvTensorShapeV1,
22 },
23 metrics,
24 profile::FibQuantProfileV1,
25 FibQuantizer,
26};
27
28pub const FIB_QUANT_CODEC_ID: &str = "fib_quant";
30
31fn map_err(e: crate::FibQuantError) -> QuantCodecError {
32 QuantCodecError::ShapeMismatch {
33 reason: e.to_string(),
34 }
35}
36
37impl CodecProfile for FibQuantProfileV1 {
40 fn codec_id(&self) -> CodecId {
41 CodecId::new(FIB_QUANT_CODEC_ID).expect("valid codec id")
42 }
43
44 fn codec_version(&self) -> &str {
45 &self.codebook_version
46 }
47
48 fn profile_digest(&self) -> CodecProfileDigest {
49 match self.digest() {
50 Ok(hex) => {
51 let bytes = hex_decode(&hex).unwrap_or_else(|_| vec![0u8; 32]);
52 let mut arr = [0u8; 32];
53 let len = bytes.len().min(32);
54 arr[..len].copy_from_slice(&bytes[..len]);
55 CodecProfileDigest::from_canonical_bytes(&arr)
56 }
57 Err(_) => CodecProfileDigest::from_canonical_bytes(b"fib_quant_error"),
58 }
59 }
60
61 fn fixed_rate_bits(&self) -> Option<u16> {
62 Some(self.wire_index_bits as u16)
63 }
64
65 fn block_dim(&self) -> Option<u16> {
66 Some(self.block_dim as u16)
67 }
68
69 fn is_lossy(&self) -> bool {
70 true
71 }
72}
73
74impl VectorCodec for FibQuantizer {
77 type EncodedBlock = FibCodeV1;
78 type Error = QuantCodecError;
79
80 fn encode_block(&self, input: &[f32]) -> Result<Self::EncodedBlock, Self::Error> {
81 self.encode(input).map_err(map_err)
82 }
83
84 fn decode_block(&self, block: &Self::EncodedBlock, out: &mut [f32]) -> Result<(), Self::Error> {
85 let decoded = self.decode(block).map_err(map_err)?;
86 let len = decoded.len().min(out.len());
87 out[..len].copy_from_slice(&decoded[..len]);
88 Ok(())
89 }
90}
91
92pub struct FibKvCodec {
96 quantizer: FibQuantizer,
97 kv_profile: crate::kv::profile::KvCompressionProfileV1,
98}
99
100impl FibKvCodec {
101 pub fn new(
103 fib_profile: FibQuantProfileV1,
104 kv_profile: crate::kv::profile::KvCompressionProfileV1,
105 ) -> Result<Self, QuantCodecError> {
106 let quantizer = FibQuantizer::new(fib_profile).map_err(map_err)?;
107 Ok(Self {
108 quantizer,
109 kv_profile,
110 })
111 }
112
113 pub fn quantizer(&self) -> &FibQuantizer {
115 &self.quantizer
116 }
117}
118
119impl VectorCodec for FibKvCodec {
120 type EncodedBlock = FibCodeV1;
121 type Error = QuantCodecError;
122
123 fn encode_block(&self, input: &[f32]) -> Result<Self::EncodedBlock, Self::Error> {
124 self.quantizer.encode(input).map_err(map_err)
125 }
126
127 fn decode_block(&self, block: &Self::EncodedBlock, out: &mut [f32]) -> Result<(), Self::Error> {
128 let decoded = self.quantizer.decode(block).map_err(map_err)?;
129 let len = decoded.len().min(out.len());
130 out[..len].copy_from_slice(&decoded[..len]);
131 Ok(())
132 }
133}
134
135impl KvCacheCodec for FibKvCodec {
136 type EncodedCache = KvEncodedTensorV1;
137
138 fn encode_kv_cache(
139 &self,
140 tensors: &[f32],
141 shape: KvTensorShape,
142 ) -> Result<Self::EncodedCache, Self::Error> {
143 let fib_shape = convert_to_fib_shape(&shape)?;
144 let layout = KvCacheLayoutV1::canonical(&fib_shape).map_err(map_err)?;
145 let encoded = encode_kv_tensor(fib_shape, layout, self.kv_profile.clone(), tensors)
146 .map_err(map_err)?;
147 Ok(encoded)
148 }
149
150 fn decode_slice(
151 &self,
152 cache: &Self::EncodedCache,
153 request: KvSliceRequest,
154 out: &mut [f32],
155 ) -> Result<(), Self::Error> {
156 let decoded = crate::kv::codec::decode_kv_slice(
159 cache,
160 request.layer.0,
161 request.head.map(|h| h.0).unwrap_or(0),
162 request.token_span.start as u32,
163 request.token_span.end as u32,
164 )
165 .map_err(map_err)?;
166 let len = decoded.len().min(out.len());
167 out[..len].copy_from_slice(&decoded[..len]);
168 Ok(())
169 }
170}
171
172fn convert_to_fib_shape(shape: &KvTensorShape) -> Result<KvTensorShapeV1, QuantCodecError> {
173 Ok(KvTensorShapeV1::new(
174 crate::kv::shape::KvRole::Key,
175 crate::kv::shape::KvAttentionKind::Mha,
176 1,
177 shape.layers,
178 shape.key_heads,
179 shape.key_heads, shape.seq_len as u32,
181 shape.head_dim,
182 crate::kv::shape::KvDType::F32,
183 crate::kv::shape::KvRopeState::NotApplicable,
184 ))
185}
186
187pub fn eval_compress(
191 quantizer: &FibQuantizer,
192 vectors: &[&[f32]],
193) -> Result<EvalReport, QuantCodecError> {
194 if vectors.is_empty() {
195 return Ok(EvalReport {
196 mse: None,
197 cosine_similarity: None,
198 max_abs_error: None,
199 bytes_exact: 0,
200 bytes_encoded: 0,
201 passed: true,
202 notes: vec!["empty input".to_string()],
203 });
204 }
205 let mut total_mse = 0.0f64;
206 let mut total_cos = 0.0f64;
207 let mut total_bytes_exact = 0u64;
208 let mut total_bytes_encoded = 0u64;
209 let mut max_abs_error = 0.0f64;
210 let count = vectors.len() as f64;
211
212 for v in vectors {
213 let code = quantizer.encode(v).map_err(map_err)?;
214 let decoded = quantizer.decode(&code).map_err(map_err)?;
215 let mse = metrics::mse(v, &decoded).map_err(map_err)?;
216 let cos = metrics::cosine_similarity(v, &decoded).map_err(map_err)?;
217 total_mse += mse;
218 total_cos += cos;
219 total_bytes_exact += (v.len() * 4) as u64;
220 total_bytes_encoded += code.compact_size() as u64;
221 for (a, b) in v.iter().zip(decoded.iter()) {
222 let err = (f64::from(*a) - f64::from(*b)).abs();
223 if err > max_abs_error {
224 max_abs_error = err;
225 }
226 }
227 }
228
229 Ok(EvalReport {
230 mse: Some(total_mse / count),
231 cosine_similarity: Some(total_cos / count),
232 max_abs_error: Some(max_abs_error),
233 bytes_exact: total_bytes_exact,
234 bytes_encoded: total_bytes_encoded,
235 passed: true,
236 notes: vec![format!("{} vectors", vectors.len())],
237 })
238}
239
240fn hex_decode(s: &str) -> Result<Vec<u8>, ()> {
241 if s.len() % 2 != 0 {
242 return Err(());
243 }
244 (0..s.len())
245 .step_by(2)
246 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|_| ()))
247 .collect()
248}