1use anyhow::{Result, anyhow, bail};
35use rlx_gguf::{GgmlType, quantize};
36
37macro_rules! define_kv_quant {
38 ($( $variant:ident, $dtype:path, $dequant:path, $qk:expr, [$($alias:literal),+]; )+) => {
39 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
41 pub enum KvQuant {
42 F16,
45 $( $variant, )+
46 }
47
48 impl KvQuant {
49 pub fn from_name(name: &str) -> Option<Self> {
51 let n = name.trim().to_ascii_lowercase();
52 if n == "f16" {
53 return Some(Self::F16);
54 }
55 match n.as_str() {
56 $( $( $alias => Some(Self::$variant), )+ )+
57 _ => None,
58 }
59 }
60
61 pub const fn block_elements(self) -> usize {
63 match self {
64 Self::F16 => 1,
65 $( Self::$variant => $qk, )+
66 }
67 }
68
69 pub fn block_bytes(self) -> usize {
71 match self {
72 Self::F16 => 2,
73 _ => {
74 let gg = self.ggml_type().expect("non-F16 scheme must map to ggml type");
75 rlx_gguf::bytes_for_public(gg, self.block_elements())
76 .expect("ggml block size must be valid for kv quant scheme")
77 }
78 }
79 }
80
81 fn ggml_type(self) -> Option<GgmlType> {
82 match self {
83 Self::F16 => None,
84 $( Self::$variant => Some($dtype), )+
85 }
86 }
87
88 fn dequant_dispatch(self, bytes: &[u8], n: usize) -> Result<Vec<f32>> {
89 match self {
90 Self::F16 => unreachable!("F16 dequant is handled in dequant_rows"),
91 $( Self::$variant => {
92 let expected = self.bytes_for(n)?;
93 Ok($dequant(&bytes[..expected], n)?)
94 } )+
95 }
96 }
97
98 pub fn bytes_for(self, n_elements: usize) -> Result<usize> {
100 let blk = self.block_elements();
101 if !n_elements.is_multiple_of(blk) {
102 bail!("{self:?}: element count {n_elements} not aligned to block size {blk}");
103 }
104 Ok((n_elements / blk) * self.block_bytes())
105 }
106 }
107 };
108}
109
110define_kv_quant! {
111 Q8_0, GgmlType::Q8_0, rlx_gguf::dequant_q8_0, 32, ["q8", "q8_0"];
112 Q4_0, GgmlType::Q4_0, rlx_gguf::dequant_q4_0, 32, ["q4", "q4_0"];
113 Q5_0, GgmlType::Q5_0, rlx_gguf::dequant_q5_0, 32, ["q5", "q5_0"];
114 Q4_1, GgmlType::Q4_1, rlx_gguf::dequant_q4_1, 32, ["q4_1"];
115 Q5_1, GgmlType::Q5_1, rlx_gguf::dequant_q5_1, 32, ["q5_1"];
116 Q1_0, GgmlType::Q1_0, rlx_gguf::q1_dequant::dequant_q1_0, 128, ["q1", "q1_0"];
117 Q2_0, GgmlType::Q2_0, rlx_gguf::q2_dequant::dequant_q2_0, 128, ["q2", "q2_0"];
118 Q2K, GgmlType::Q2K, rlx_gguf::dequant_q2_k, 256, ["q2k", "q2_k"];
119 Q3K, GgmlType::Q3K, rlx_gguf::dequant_q3_k, 256, ["q3k", "q3_k"];
120 Q4K, GgmlType::Q4K, rlx_gguf::dequant_q4_k, 256, ["q4k", "q4_k"];
121 Q5K, GgmlType::Q5K, rlx_gguf::dequant_q5_k, 256, ["q5k", "q5_k"];
122 Q6K, GgmlType::Q6K, rlx_gguf::dequant_q6_k, 256, ["q6k", "q6_k"];
123 Q8K, GgmlType::Q8K, rlx_gguf::dequant_q8_k, 256, ["q8k", "q8_k"];
124 IQ4NL, GgmlType::IQ4NL, rlx_gguf::iq_dequant::dequant_iq4_nl, 32, ["iq4_nl", "iq4nl"];
125 IQ4XS, GgmlType::IQ4XS, rlx_gguf::iq_dequant::dequant_iq4_xs, 256, ["iq4_xs", "iq4xs"];
126 IQ2XXS, GgmlType::IQ2XXS, rlx_gguf::iq_dequant::dequant_iq2_xxs, 256, ["iq2_xxs", "iq2xxs"];
127 IQ2XS, GgmlType::IQ2XS, rlx_gguf::iq_dequant::dequant_iq2_xs, 256, ["iq2_xs", "iq2xs"];
128 IQ2S, GgmlType::IQ2S, rlx_gguf::iq_dequant::dequant_iq2_s, 256, ["iq2_s", "iq2s"];
129 IQ3XXS, GgmlType::IQ3XXS, rlx_gguf::iq_dequant::dequant_iq3_xxs, 256, ["iq3_xxs", "iq3xxs"];
130 IQ3S, GgmlType::IQ3S, rlx_gguf::iq_dequant::dequant_iq3_s, 256, ["iq3_s", "iq3s"];
131 IQ1S, GgmlType::IQ1S, rlx_gguf::iq_dequant::dequant_iq1_s, 256, ["iq1_s", "iq1s"];
132 IQ1M, GgmlType::IQ1M, rlx_gguf::iq_dequant::dequant_iq1_m, 256, ["iq1_m", "iq1m"];
133 TQ1_0, GgmlType::TQ1_0, rlx_gguf::tq_dequant::dequant_tq1_0, 256, ["tq1_0", "tq1"];
134 TQ2_0, GgmlType::TQ2_0, rlx_gguf::tq_dequant::dequant_tq2_0, 256, ["tq2_0", "tq2"];
135}
136
137#[derive(Debug, Clone)]
143pub struct QuantizedKvLayer {
144 pub k: Vec<u8>,
145 pub v: Vec<u8>,
146 pub past_len: usize,
147 pub kv_dim: usize,
148 pub scheme: KvQuant,
149 pub bytes_per_row: usize,
150}
151
152impl QuantizedKvLayer {
153 pub fn new(kv_dim: usize, scheme: KvQuant) -> Result<Self> {
154 let blk = scheme.block_elements();
155 if !kv_dim.is_multiple_of(blk) {
156 bail!("kv_dim ({kv_dim}) must be a multiple of {scheme:?} block size ({blk})");
157 }
158 let bytes_per_row = (kv_dim / blk) * scheme.block_bytes();
159 Ok(Self {
160 k: Vec::new(),
161 v: Vec::new(),
162 past_len: 0,
163 kv_dim,
164 scheme,
165 bytes_per_row,
166 })
167 }
168
169 pub fn append_rows(&mut self, k_rows: &[f32], v_rows: &[f32]) -> Result<()> {
173 if k_rows.len() != v_rows.len() {
174 bail!(
175 "append_rows: k len {} != v len {}",
176 k_rows.len(),
177 v_rows.len()
178 );
179 }
180 if !k_rows.len().is_multiple_of(self.kv_dim) {
181 bail!(
182 "append_rows: byte count {} not aligned to kv_dim {}",
183 k_rows.len(),
184 self.kv_dim
185 );
186 }
187 let n_rows = k_rows.len() / self.kv_dim;
188 let k_bytes = quant_rows(k_rows, self.scheme)?;
189 let v_bytes = quant_rows(v_rows, self.scheme)?;
190 self.k.extend_from_slice(&k_bytes);
191 self.v.extend_from_slice(&v_bytes);
192 self.past_len += n_rows;
193 Ok(())
194 }
195
196 pub fn read_all(&self) -> Result<(Vec<f32>, Vec<f32>)> {
198 let k = dequant_rows(&self.k, self.scheme, self.past_len * self.kv_dim)?;
199 let v = dequant_rows(&self.v, self.scheme, self.past_len * self.kv_dim)?;
200 Ok((k, v))
201 }
202
203 pub fn read_rows(&self, start: usize, count: usize) -> Result<(Vec<f32>, Vec<f32>)> {
207 if start + count > self.past_len {
208 bail!(
209 "read_rows: [{start}, {}) out of range (past_len {})",
210 start + count,
211 self.past_len
212 );
213 }
214 let start_byte = start * self.bytes_per_row;
215 let n = count * self.kv_dim;
216 let k = dequant_rows(&self.k[start_byte..], self.scheme, n)?;
217 let v = dequant_rows(&self.v[start_byte..], self.scheme, n)?;
218 Ok((k, v))
219 }
220
221 pub fn read_window(&self, window: usize) -> Result<(Vec<f32>, Vec<f32>)> {
223 if window >= self.past_len {
224 return self.read_all();
225 }
226 self.read_rows(self.past_len - window, window)
227 }
228
229 pub fn drop_front(&mut self, n_rows: usize) -> Result<()> {
231 let n_rows = n_rows.min(self.past_len);
232 if n_rows == 0 {
233 return Ok(());
234 }
235 let drop_bytes = n_rows * self.bytes_per_row;
236 self.k.drain(..drop_bytes);
237 self.v.drain(..drop_bytes);
238 self.past_len -= n_rows;
239 Ok(())
240 }
241
242 pub fn bytes(&self) -> usize {
244 self.k.len() + self.v.len()
245 }
246}
247
248pub fn attend_quantized(
261 q: &[f32],
262 layer: &QuantizedKvLayer,
263 n_heads: usize,
264 kv_heads: usize,
265 head_dim: usize,
266 scale: f32,
267) -> Result<Vec<f32>> {
268 let kv_dim = kv_heads * head_dim;
269 if layer.kv_dim != kv_dim {
270 bail!(
271 "attend_quantized: layer kv_dim {} != {kv_heads}×{head_dim}",
272 layer.kv_dim
273 );
274 }
275 if q.len() != n_heads * head_dim {
276 bail!(
277 "attend_quantized: q len {} != {n_heads}×{head_dim}",
278 q.len()
279 );
280 }
281 let mut out = vec![0f32; n_heads * head_dim];
282 let past = layer.past_len;
283 if past == 0 {
284 return Ok(out);
285 }
286 let group = (n_heads / kv_heads.max(1)).max(1);
287
288 let mut scores = vec![0f32; n_heads * past];
290 for p in 0..past {
291 let (k_row, _) = layer.read_rows(p, 1)?;
292 for h in 0..n_heads {
293 let kvh = h / group;
294 let q_h = &q[h * head_dim..(h + 1) * head_dim];
295 let k_h = &k_row[kvh * head_dim..(kvh + 1) * head_dim];
296 let dot: f32 = q_h.iter().zip(k_h).map(|(a, b)| a * b).sum();
297 scores[h * past + p] = dot * scale;
298 }
299 }
300
301 for h in 0..n_heads {
303 let row = &mut scores[h * past..(h + 1) * past];
304 let max = row.iter().fold(f32::NEG_INFINITY, |m, &v| m.max(v));
305 let mut sum = 0f32;
306 for v in row.iter_mut() {
307 *v = (*v - max).exp();
308 sum += *v;
309 }
310 let inv = 1.0 / sum.max(1e-20);
311 for v in row.iter_mut() {
312 *v *= inv;
313 }
314 }
315
316 for p in 0..past {
318 let (_, v_row) = layer.read_rows(p, 1)?;
319 for h in 0..n_heads {
320 let kvh = h / group;
321 let w = scores[h * past + p];
322 let v_h = &v_row[kvh * head_dim..(kvh + 1) * head_dim];
323 let o_h = &mut out[h * head_dim..(h + 1) * head_dim];
324 for (o, &vv) in o_h.iter_mut().zip(v_h) {
325 *o += w * vv;
326 }
327 }
328 }
329 Ok(out)
330}
331
332#[derive(Debug, Clone)]
334pub struct QuantizedKvCache {
335 pub layers: Vec<QuantizedKvLayer>,
336}
337
338impl QuantizedKvCache {
339 pub fn new(n_layers: usize, kv_dim: usize, scheme: KvQuant) -> Result<Self> {
340 let layers = (0..n_layers)
341 .map(|_| QuantizedKvLayer::new(kv_dim, scheme))
342 .collect::<Result<Vec<_>>>()?;
343 Ok(Self { layers })
344 }
345
346 pub fn n_layers(&self) -> usize {
347 self.layers.len()
348 }
349
350 pub fn past_len(&self) -> usize {
351 self.layers.first().map(|l| l.past_len).unwrap_or(0)
352 }
353
354 pub fn bytes(&self) -> usize {
356 self.layers.iter().map(|l| l.bytes()).sum()
357 }
358}
359
360fn quant_rows(values: &[f32], scheme: KvQuant) -> Result<Vec<u8>> {
363 match scheme {
364 KvQuant::F16 => {
365 let mut out = Vec::with_capacity(values.len() * 2);
366 for &v in values {
367 let h = half::f16::from_f32(v);
368 out.extend_from_slice(&h.to_le_bytes());
369 }
370 Ok(out)
371 }
372 scheme => {
373 let ty = scheme
374 .ggml_type()
375 .ok_or_else(|| anyhow!("internal: missing ggml type for {scheme:?}"))?;
376 Ok(quantize(values, ty)?)
377 }
378 }
379}
380
381fn dequant_rows(bytes: &[u8], scheme: KvQuant, n: usize) -> Result<Vec<f32>> {
382 match scheme {
383 KvQuant::F16 => {
384 if bytes.len() < n * 2 {
385 bail!("F16 dequant: {} bytes < {} expected", bytes.len(), n * 2);
386 }
387 let mut out = Vec::with_capacity(n);
388 for chunk in bytes[..n * 2].chunks_exact(2) {
389 let h = half::f16::from_le_bytes([chunk[0], chunk[1]]);
390 out.push(h.to_f32());
391 }
392 Ok(out)
393 }
394 _ => scheme.dequant_dispatch(bytes, n),
395 }
396}
397
398#[cfg(feature = "mmap-kv")]
419pub mod mmap {
420 use super::*;
421 use memmap2::{MmapMut, MmapOptions};
422 use std::fs::OpenOptions;
423 use std::path::{Path, PathBuf};
424
425 pub struct MmapKvLayer {
428 pub mmap: MmapMut,
429 pub past_len: usize,
430 pub capacity_rows: usize,
431 pub kv_dim: usize,
432 pub scheme: KvQuant,
433 pub bytes_per_row: usize,
434 pub k_offset: usize,
435 pub v_offset: usize,
436 pub path: Option<PathBuf>,
437 file: Option<std::fs::File>,
439 pread: bool,
446 }
447
448 impl MmapKvLayer {
449 pub fn open<P: AsRef<Path>>(
452 path: P,
453 kv_dim: usize,
454 scheme: KvQuant,
455 capacity_rows: usize,
456 ) -> Result<Self> {
457 let blk = scheme.block_elements();
458 if !kv_dim.is_multiple_of(blk) {
459 bail!("kv_dim ({kv_dim}) must be a multiple of {scheme:?} block size ({blk})");
460 }
461 let bytes_per_row = (kv_dim / blk) * scheme.block_bytes();
462 let total = 2 * capacity_rows * bytes_per_row;
463 let file = OpenOptions::new()
464 .read(true)
465 .write(true)
466 .create(true)
467 .truncate(true)
468 .open(&path)?;
469 file.set_len(total as u64)?;
470 let mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
471 Ok(Self {
472 mmap,
473 past_len: 0,
474 capacity_rows,
475 kv_dim,
476 scheme,
477 bytes_per_row,
478 k_offset: 0,
479 v_offset: capacity_rows * bytes_per_row,
480 path: Some(path.as_ref().to_path_buf()),
481 file: Some(file),
482 pread: rlx_ir::env::var_os("RLX_KVSTORE_PREAD").is_some(),
483 })
484 }
485
486 pub fn open_existing<P: AsRef<Path>>(
492 path: P,
493 kv_dim: usize,
494 scheme: KvQuant,
495 capacity_rows: usize,
496 ) -> Result<Self> {
497 let blk = scheme.block_elements();
498 if !kv_dim.is_multiple_of(blk) {
499 bail!("kv_dim ({kv_dim}) must be a multiple of {scheme:?} block size ({blk})");
500 }
501 let bytes_per_row = (kv_dim / blk) * scheme.block_bytes();
502 let total = 2 * capacity_rows * bytes_per_row;
503 let file = OpenOptions::new().read(true).write(true).open(&path)?;
504 let cur_len = file.metadata()?.len() as usize;
505 if cur_len < total {
506 bail!(
507 "open_existing: file too small ({} < expected {}): {}",
508 cur_len,
509 total,
510 path.as_ref().display()
511 );
512 }
513 let mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
514 Ok(Self {
515 mmap,
516 past_len: 0,
517 capacity_rows,
518 kv_dim,
519 scheme,
520 bytes_per_row,
521 k_offset: 0,
522 v_offset: capacity_rows * bytes_per_row,
523 path: Some(path.as_ref().to_path_buf()),
524 file: Some(file),
525 pread: rlx_ir::env::var_os("RLX_KVSTORE_PREAD").is_some(),
526 })
527 }
528
529 pub fn anonymous(kv_dim: usize, scheme: KvQuant, capacity_rows: usize) -> Result<Self> {
533 let blk = scheme.block_elements();
534 if !kv_dim.is_multiple_of(blk) {
535 bail!("kv_dim ({kv_dim}) must be a multiple of {scheme:?} block size ({blk})");
536 }
537 let bytes_per_row = (kv_dim / blk) * scheme.block_bytes();
538 let total = 2 * capacity_rows * bytes_per_row;
539 let mmap = MmapOptions::new().len(total).map_anon()?;
540 Ok(Self {
541 mmap,
542 past_len: 0,
543 capacity_rows,
544 kv_dim,
545 scheme,
546 bytes_per_row,
547 k_offset: 0,
548 v_offset: capacity_rows * bytes_per_row,
549 path: None,
550 file: None,
551 pread: false, })
553 }
554
555 pub fn append_rows(&mut self, k_rows: &[f32], v_rows: &[f32]) -> Result<()> {
558 if k_rows.len() != v_rows.len() {
559 bail!("append_rows: k/v length mismatch");
560 }
561 if !k_rows.len().is_multiple_of(self.kv_dim) {
562 bail!("append_rows: byte count not aligned to kv_dim");
563 }
564 let n_rows = k_rows.len() / self.kv_dim;
565 if self.past_len + n_rows > self.capacity_rows {
566 bail!(
567 "append_rows: would exceed capacity ({} + {} > {})",
568 self.past_len,
569 n_rows,
570 self.capacity_rows
571 );
572 }
573 let kb = quant_rows(k_rows, self.scheme)?;
574 let vb = quant_rows(v_rows, self.scheme)?;
575 let k_start = self.k_offset + self.past_len * self.bytes_per_row;
576 let v_start = self.v_offset + self.past_len * self.bytes_per_row;
577 self.mmap[k_start..k_start + kb.len()].copy_from_slice(&kb);
578 self.mmap[v_start..v_start + vb.len()].copy_from_slice(&vb);
579 self.past_len += n_rows;
580 Ok(())
581 }
582
583 pub fn read_all(&self) -> Result<(Vec<f32>, Vec<f32>)> {
586 let n = self.past_len * self.kv_dim;
587 let k_end = self.k_offset + self.past_len * self.bytes_per_row;
588 let v_end = self.v_offset + self.past_len * self.bytes_per_row;
589 let k = dequant_rows(&self.mmap[self.k_offset..k_end], self.scheme, n)?;
590 let v = dequant_rows(&self.mmap[self.v_offset..v_end], self.scheme, n)?;
591 Ok((k, v))
592 }
593
594 pub fn read_window(&self, window: usize) -> Result<(Vec<f32>, Vec<f32>)> {
596 let window = window.min(self.past_len);
597 let start_row = self.past_len - window;
598 let n = window * self.kv_dim;
599 let k_start = self.k_offset + start_row * self.bytes_per_row;
600 let v_start = self.v_offset + start_row * self.bytes_per_row;
601 let k_end = k_start + window * self.bytes_per_row;
602 let v_end = v_start + window * self.bytes_per_row;
603 let k = dequant_rows(&self.mmap[k_start..k_end], self.scheme, n)?;
604 let v = dequant_rows(&self.mmap[v_start..v_end], self.scheme, n)?;
605 Ok((k, v))
606 }
607
608 pub fn read_rows(&self, start_row: usize, n_rows: usize) -> Result<(Vec<f32>, Vec<f32>)> {
612 let start_row = start_row.min(self.past_len);
613 let n_rows = n_rows.min(self.past_len - start_row);
614 let n = n_rows * self.kv_dim;
615 let nbytes = n_rows * self.bytes_per_row;
616 let k_start = self.k_offset + start_row * self.bytes_per_row;
617 let v_start = self.v_offset + start_row * self.bytes_per_row;
618 #[cfg(unix)]
622 if self.pread {
623 if let Some(f) = self.file.as_ref() {
624 use std::os::unix::fs::FileExt;
625 let mut kb = vec![0u8; nbytes];
626 let mut vb = vec![0u8; nbytes];
627 f.read_exact_at(&mut kb, k_start as u64)?;
628 f.read_exact_at(&mut vb, v_start as u64)?;
629 let k = dequant_rows(&kb, self.scheme, n)?;
630 let v = dequant_rows(&vb, self.scheme, n)?;
631 return Ok((k, v));
632 }
633 }
634 let k_end = k_start + nbytes;
635 let v_end = v_start + nbytes;
636 let k = dequant_rows(&self.mmap[k_start..k_end], self.scheme, n)?;
637 let v = dequant_rows(&self.mmap[v_start..v_end], self.scheme, n)?;
638 Ok((k, v))
639 }
640
641 pub fn prefetch_rows(&self, start_row: usize, n_rows: usize) {
644 let start_row = start_row.min(self.past_len);
645 let n_rows = n_rows.min(self.past_len - start_row);
646 if n_rows == 0 {
647 return;
648 }
649 let k_start = self.k_offset + start_row * self.bytes_per_row;
650 let v_start = self.v_offset + start_row * self.bytes_per_row;
651 #[cfg(unix)]
652 {
653 let len = n_rows * self.bytes_per_row;
654 let _ = self
655 .mmap
656 .advise_range(memmap2::Advice::WillNeed, k_start, len);
657 let _ = self
658 .mmap
659 .advise_range(memmap2::Advice::WillNeed, v_start, len);
660 }
661 #[cfg(not(unix))]
662 {
663 let _ = (k_start, v_start);
664 }
665 }
666
667 pub fn prefetch_window(&self, window: usize) {
672 let window = window.min(self.past_len);
673 if window == 0 {
674 return;
675 }
676 let start_row = self.past_len - window;
677 let k_start = self.k_offset + start_row * self.bytes_per_row;
678 let v_start = self.v_offset + start_row * self.bytes_per_row;
679 #[cfg(unix)]
681 {
682 let _ = self.mmap.advise_range(
683 memmap2::Advice::WillNeed,
684 k_start,
685 window * self.bytes_per_row,
686 );
687 let _ = self.mmap.advise_range(
688 memmap2::Advice::WillNeed,
689 v_start,
690 window * self.bytes_per_row,
691 );
692 }
693 #[cfg(not(unix))]
694 {
695 let _ = (k_start, v_start);
696 }
697 }
698
699 pub fn advise_random(&self) {
705 #[cfg(unix)]
706 {
707 let _ = self.mmap.advise(memmap2::Advice::Random);
708 }
709 }
710
711 pub fn set_pread(&mut self, on: bool) {
714 self.pread = on && self.file.is_some();
715 }
716
717 pub fn flush(&self) -> Result<()> {
720 self.mmap.flush()?;
721 Ok(())
722 }
723
724 pub fn bytes(&self) -> usize {
725 2 * self.past_len * self.bytes_per_row
726 }
727 }
728
729 pub struct MmapKvCache {
731 pub layers: Vec<MmapKvLayer>,
732 }
733
734 impl MmapKvCache {
735 pub fn open_dir<P: AsRef<Path>>(
737 dir: P,
738 n_layers: usize,
739 kv_dim: usize,
740 scheme: KvQuant,
741 capacity_rows: usize,
742 ) -> Result<Self> {
743 let dir = dir.as_ref();
744 std::fs::create_dir_all(dir)?;
745 let layers = (0..n_layers)
746 .map(|i| {
747 MmapKvLayer::open(
748 dir.join(format!("kv_{i}.bin")),
749 kv_dim,
750 scheme,
751 capacity_rows,
752 )
753 })
754 .collect::<Result<Vec<_>>>()?;
755 Ok(Self { layers })
756 }
757
758 pub fn anonymous(
759 n_layers: usize,
760 kv_dim: usize,
761 scheme: KvQuant,
762 capacity_rows: usize,
763 ) -> Result<Self> {
764 let layers = (0..n_layers)
765 .map(|_| MmapKvLayer::anonymous(kv_dim, scheme, capacity_rows))
766 .collect::<Result<Vec<_>>>()?;
767 Ok(Self { layers })
768 }
769
770 pub fn n_layers(&self) -> usize {
771 self.layers.len()
772 }
773
774 pub fn past_len(&self) -> usize {
775 self.layers.first().map(|l| l.past_len).unwrap_or(0)
776 }
777
778 pub fn bytes(&self) -> usize {
780 self.layers.iter().map(|l| l.bytes()).sum()
781 }
782 }
783
784 #[cfg(test)]
785 mod tests {
786 use super::*;
787
788 #[test]
789 fn anonymous_q8_0_roundtrip() {
790 let kv_dim = 64;
791 let mut layer = MmapKvLayer::anonymous(kv_dim, KvQuant::Q8_0, 4).unwrap();
792 let data: Vec<f32> = (0..kv_dim).map(|i| (i as f32).sin()).collect();
793 layer.append_rows(&data, &data).unwrap();
794 let (k, v) = layer.read_all().unwrap();
795 assert_eq!(k.len(), kv_dim);
796 assert_eq!(v.len(), kv_dim);
797 for (a, b) in k.iter().zip(data.iter()) {
799 assert!((a - b).abs() < 0.02);
800 }
801 }
802
803 #[test]
804 fn file_backed_persists_and_reopens() {
805 let dir = tempfile::tempdir().unwrap();
806 let kv_dim = 32;
807 let path = dir.path().join("layer.bin");
808 {
809 let mut layer = MmapKvLayer::open(&path, kv_dim, KvQuant::F16, 8).unwrap();
810 let data: Vec<f32> = (0..kv_dim).map(|i| i as f32 * 0.5).collect();
811 layer.append_rows(&data, &data).unwrap();
812 layer.flush().unwrap();
813 }
814 let bytes = std::fs::read(&path).unwrap();
816 assert!(!bytes.is_empty());
817 assert!(bytes.iter().any(|&b| b != 0));
818 }
819
820 #[test]
821 fn append_past_capacity_errors() {
822 let mut l = MmapKvLayer::anonymous(32, KvQuant::Q8_0, 2).unwrap();
823 let row = vec![0.5f32; 32];
824 l.append_rows(&row, &row).unwrap();
825 l.append_rows(&row, &row).unwrap();
826 assert!(l.append_rows(&row, &row).is_err());
827 }
828
829 #[cfg(unix)]
833 #[test]
834 fn pread_matches_mmap_read() {
835 let dir = tempfile::tempdir().unwrap();
836 let kv_dim = 32;
837 let path = dir.path().join("layer.bin");
838 let mut layer = MmapKvLayer::open(&path, kv_dim, KvQuant::Q8_0, 64).unwrap();
839 let k: Vec<f32> = (0..kv_dim * 4).map(|i| (i as f32) * 0.01 - 1.0).collect();
840 let v: Vec<f32> = (0..kv_dim * 4).map(|i| (i as f32) * -0.02 + 0.5).collect();
841 layer.append_rows(&k, &v).unwrap();
842 layer.set_pread(false);
844 let (km, vm) = layer.read_rows(1, 2).unwrap();
845 layer.set_pread(true);
847 let (kp, vp) = layer.read_rows(1, 2).unwrap();
848 assert_eq!(km.len(), kp.len());
849 assert_eq!(vm.len(), vp.len());
850 assert!(
851 km.iter().zip(&kp).all(|(a, b)| (a - b).abs() < 1e-6),
852 "K mmap != pread"
853 );
854 assert!(
855 vm.iter().zip(&vp).all(|(a, b)| (a - b).abs() < 1e-6),
856 "V mmap != pread"
857 );
858 }
859 }
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865
866 fn cosine(a: &[f32], b: &[f32]) -> f32 {
867 let mut dot = 0.0f32;
868 let mut na = 0.0f32;
869 let mut nb = 0.0f32;
870 for (x, y) in a.iter().zip(b.iter()) {
871 dot += x * y;
872 na += x * x;
873 nb += y * y;
874 }
875 dot / (na.sqrt() * nb.sqrt() + 1e-12)
876 }
877
878 fn mae(a: &[f32], b: &[f32]) -> f32 {
879 let n = a.len().min(b.len()).max(1) as f32;
880 a.iter()
881 .zip(b.iter())
882 .map(|(x, y)| (x - y).abs())
883 .sum::<f32>()
884 / n
885 }
886
887 fn rmse(a: &[f32], b: &[f32]) -> f32 {
888 let n = a.len().min(b.len()).max(1) as f32;
889 (a.iter()
890 .zip(b.iter())
891 .map(|(x, y)| {
892 let d = x - y;
893 d * d
894 })
895 .sum::<f32>()
896 / n)
897 .sqrt()
898 }
899
900 fn attention_reference(
901 q: &[f32],
902 k_all: &[f32],
903 v_all: &[f32],
904 n_heads: usize,
905 kv_heads: usize,
906 head_dim: usize,
907 past: usize,
908 scale: f32,
909 ) -> Vec<f32> {
910 let kv_dim = kv_heads * head_dim;
911 let group = n_heads / kv_heads;
912 let mut out = vec![0f32; n_heads * head_dim];
913 for h in 0..n_heads {
914 let kvh = h / group;
915 let mut sc = vec![0f32; past];
916 for p in 0..past {
917 let qh = &q[h * head_dim..(h + 1) * head_dim];
918 let base = p * kv_dim + kvh * head_dim;
919 let kh = &k_all[base..base + head_dim];
920 let dot: f32 = qh.iter().zip(kh).map(|(a, b)| a * b).sum();
921 sc[p] = dot * scale;
922 }
923 let max = sc.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
924 let mut sum = 0.0;
925 for x in sc.iter_mut() {
926 *x = (*x - max).exp();
927 sum += *x;
928 }
929 for x in sc.iter_mut() {
930 *x /= sum;
931 }
932 for p in 0..past {
933 let base = p * kv_dim + kvh * head_dim;
934 let vh = &v_all[base..base + head_dim];
935 for d in 0..head_dim {
936 out[h * head_dim + d] += sc[p] * vh[d];
937 }
938 }
939 }
940 out
941 }
942
943 #[test]
944 fn block_size_invariants() {
945 assert_eq!(KvQuant::F16.block_bytes(), 2);
946 assert_eq!(KvQuant::Q8_0.block_bytes(), 34);
947 assert_eq!(KvQuant::Q4_0.block_bytes(), 18);
948 assert_eq!(KvQuant::Q5_0.block_bytes(), 22);
949 }
950
951 #[test]
952 fn f16_roundtrip_exact() {
953 let kv_dim = 64;
954 let mut layer = QuantizedKvLayer::new(kv_dim, KvQuant::F16).unwrap();
955 let k_row: Vec<f32> = (0..kv_dim).map(|i| (i as f32) * 0.1).collect();
956 let v_row: Vec<f32> = (0..kv_dim).map(|i| (i as f32) * 0.2).collect();
957 layer.append_rows(&k_row, &v_row).unwrap();
958 let (k, v) = layer.read_all().unwrap();
959 for i in 0..kv_dim {
960 assert!((k[i] - k_row[i]).abs() < 0.01);
962 assert!((v[i] - v_row[i]).abs() < 0.01);
963 }
964 }
965
966 #[test]
967 fn q8_0_roundtrip_high_fidelity() {
968 let kv_dim = 64;
969 let n_rows = 4;
970 let mut layer = QuantizedKvLayer::new(kv_dim, KvQuant::Q8_0).unwrap();
971 let total = n_rows * kv_dim;
972 let k_data: Vec<f32> = (0..total).map(|i| (i as f32).sin()).collect();
973 let v_data: Vec<f32> = (0..total).map(|i| (i as f32).cos()).collect();
974 layer.append_rows(&k_data, &v_data).unwrap();
975 assert_eq!(layer.past_len, n_rows);
976 let (k, v) = layer.read_all().unwrap();
977 assert!(cosine(&k, &k_data) > 0.999, "Q8_0 K cosine too low");
978 assert!(cosine(&v, &v_data) > 0.999, "Q8_0 V cosine too low");
979 }
980
981 #[test]
982 fn q4_0_roundtrip_lossy_but_close() {
983 let kv_dim = 64;
984 let mut layer = QuantizedKvLayer::new(kv_dim, KvQuant::Q4_0).unwrap();
985 let k: Vec<f32> = (0..kv_dim).map(|i| (i as f32 * 0.05).tanh()).collect();
986 let v: Vec<f32> = (0..kv_dim).map(|i| (i as f32 * 0.07).tanh()).collect();
987 layer.append_rows(&k, &v).unwrap();
988 let (kr, vr) = layer.read_all().unwrap();
989 assert!(cosine(&kr, &k) > 0.99);
990 assert!(cosine(&vr, &v) > 0.99);
991 }
992
993 #[test]
994 fn q5_0_roundtrip_better_than_q4() {
995 let kv_dim = 64;
996 let mut q4 = QuantizedKvLayer::new(kv_dim, KvQuant::Q4_0).unwrap();
997 let mut q5 = QuantizedKvLayer::new(kv_dim, KvQuant::Q5_0).unwrap();
998 let k: Vec<f32> = (0..kv_dim).map(|i| (i as f32 * 0.1).sin() * 3.0).collect();
999 let v: Vec<f32> = (0..kv_dim).map(|i| (i as f32 * 0.13).cos() * 3.0).collect();
1000 q4.append_rows(&k, &v).unwrap();
1001 q5.append_rows(&k, &v).unwrap();
1002 let (k4, _) = q4.read_all().unwrap();
1003 let (k5, _) = q5.read_all().unwrap();
1004 let cos4 = cosine(&k4, &k);
1005 let cos5 = cosine(&k5, &k);
1006 assert!(cos5 >= cos4 - 1e-3, "Q5_0 should not be worse than Q4_0");
1007 }
1008
1009 #[test]
1010 fn sliding_window_drops_oldest() {
1011 let kv_dim = 32;
1012 let mut layer = QuantizedKvLayer::new(kv_dim, KvQuant::Q8_0).unwrap();
1013 for r in 0..5 {
1014 let v: Vec<f32> = (0..kv_dim).map(|i| (i + r * 100) as f32).collect();
1015 layer.append_rows(&v, &v).unwrap();
1016 }
1017 assert_eq!(layer.past_len, 5);
1018 layer.drop_front(2).unwrap();
1019 assert_eq!(layer.past_len, 3);
1020 let (k, _v) = layer.read_window(3).unwrap();
1021 assert!((k[0] - 200.0).abs() < 1.0);
1023 }
1024
1025 #[test]
1026 fn kv_dim_must_align_to_block_size() {
1027 assert!(QuantizedKvLayer::new(24, KvQuant::Q8_0).is_err());
1029 assert!(QuantizedKvLayer::new(24, KvQuant::Q4_0).is_err());
1030 assert!(QuantizedKvLayer::new(24, KvQuant::F16).is_ok());
1032 }
1033
1034 #[test]
1035 fn streaming_quantized_attention_matches_full_dequant() {
1036 let (n_heads, kv_heads, head_dim) = (4usize, 2usize, 32usize);
1037 let kv_dim = kv_heads * head_dim; let past = 5usize;
1039 let scale = 1.0 / (head_dim as f32).sqrt();
1040
1041 let mut layer = QuantizedKvLayer::new(kv_dim, KvQuant::Q8_0).unwrap();
1042 for p in 0..past {
1043 let k: Vec<f32> = (0..kv_dim)
1044 .map(|i| ((i + p * 7) as f32 * 0.05).sin())
1045 .collect();
1046 let v: Vec<f32> = (0..kv_dim)
1047 .map(|i| ((i + p * 3) as f32 * 0.04).cos())
1048 .collect();
1049 layer.append_rows(&k, &v).unwrap();
1050 }
1051 let q: Vec<f32> = (0..n_heads * head_dim)
1052 .map(|i| (i as f32 * 0.02).sin())
1053 .collect();
1054
1055 let out = attend_quantized(&q, &layer, n_heads, kv_heads, head_dim, scale).unwrap();
1056
1057 let (k_all, v_all) = layer.read_all().unwrap();
1059 let group = n_heads / kv_heads;
1060 let mut reference = vec![0f32; n_heads * head_dim];
1061 for h in 0..n_heads {
1062 let kvh = h / group;
1063 let mut sc = vec![0f32; past];
1064 for p in 0..past {
1065 let qh = &q[h * head_dim..(h + 1) * head_dim];
1066 let base = p * kv_dim + kvh * head_dim;
1067 let kh = &k_all[base..base + head_dim];
1068 let dot: f32 = qh.iter().zip(kh).map(|(a, b)| a * b).sum();
1069 sc[p] = dot * scale;
1070 }
1071 let max = sc.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
1072 let mut sum = 0.0;
1073 for x in sc.iter_mut() {
1074 *x = (*x - max).exp();
1075 sum += *x;
1076 }
1077 for x in sc.iter_mut() {
1078 *x /= sum;
1079 }
1080 for p in 0..past {
1081 let base = p * kv_dim + kvh * head_dim;
1082 let vh = &v_all[base..base + head_dim];
1083 for d in 0..head_dim {
1084 reference[h * head_dim + d] += sc[p] * vh[d];
1085 }
1086 }
1087 }
1088 for (a, b) in out.iter().zip(&reference) {
1089 assert!((a - b).abs() < 1e-4, "streaming {a} vs full-dequant {b}");
1090 }
1091 }
1092
1093 #[test]
1094 fn cache_memory_decreases_with_quantization() {
1095 let kv_dim = 128;
1096 let n_layers = 4;
1097 let n_rows = 16;
1098 let data: Vec<f32> = (0..kv_dim).map(|i| (i as f32) * 0.01).collect();
1099 let mut f16 = QuantizedKvCache::new(n_layers, kv_dim, KvQuant::F16).unwrap();
1100 let mut q8 = QuantizedKvCache::new(n_layers, kv_dim, KvQuant::Q8_0).unwrap();
1101 let mut q4 = QuantizedKvCache::new(n_layers, kv_dim, KvQuant::Q4_0).unwrap();
1102 for _ in 0..n_rows {
1103 for l in 0..n_layers {
1104 f16.layers[l].append_rows(&data, &data).unwrap();
1105 q8.layers[l].append_rows(&data, &data).unwrap();
1106 q4.layers[l].append_rows(&data, &data).unwrap();
1107 }
1108 }
1109 assert!(q8.bytes() < f16.bytes());
1110 assert!(q4.bytes() < q8.bytes());
1111 }
1112
1113 #[test]
1114 fn q_variant_precision_report() {
1115 let (n_heads, kv_heads, head_dim) = (4usize, 2usize, 128usize);
1117 let kv_dim = kv_heads * head_dim;
1118 let past = 8usize;
1119 let scale = 1.0 / (head_dim as f32).sqrt();
1120
1121 let k_data: Vec<f32> = (0..past * kv_dim)
1122 .map(|i| {
1123 let x = i as f32;
1124 (x * 0.017).sin() * 2.3 + (x * 0.003).cos() * 0.7
1125 })
1126 .collect();
1127 let v_data: Vec<f32> = (0..past * kv_dim)
1128 .map(|i| {
1129 let x = i as f32;
1130 (x * 0.013).cos() * 1.9 + (x * 0.005).sin() * 0.6
1131 })
1132 .collect();
1133 let q: Vec<f32> = (0..n_heads * head_dim)
1134 .map(|i| (i as f32 * 0.021).sin() * 1.1)
1135 .collect();
1136 let ref_attn = attention_reference(
1137 &q, &k_data, &v_data, n_heads, kv_heads, head_dim, past, scale,
1138 );
1139
1140 let schemes = vec![
1141 ("f16", KvQuant::F16),
1142 ("q8_0", KvQuant::Q8_0),
1143 ("q8k", KvQuant::Q8K),
1144 ("q6k", KvQuant::Q6K),
1145 ("q5_1", KvQuant::Q5_1),
1146 ("q5_0", KvQuant::Q5_0),
1147 ("q5k", KvQuant::Q5K),
1148 ("q4_1", KvQuant::Q4_1),
1149 ("q4_0", KvQuant::Q4_0),
1150 ("q4k", KvQuant::Q4K),
1151 ("q3k", KvQuant::Q3K),
1152 ("q2k", KvQuant::Q2K),
1153 ("q2_0", KvQuant::Q2_0),
1154 ("q1_0", KvQuant::Q1_0),
1155 ("iq4_nl", KvQuant::IQ4NL),
1156 ("iq4_xs", KvQuant::IQ4XS),
1157 ("iq3_s", KvQuant::IQ3S),
1158 ("iq2_s", KvQuant::IQ2S),
1159 ("iq1_s", KvQuant::IQ1S),
1160 ("tq2_0", KvQuant::TQ2_0),
1161 ("tq1_0", KvQuant::TQ1_0),
1162 ];
1163
1164 println!("scheme,k_cos,v_cos,k_mae,v_mae,attn_cos,attn_rmse,bytes_per_row");
1165 for (name, scheme) in schemes {
1166 let mut layer = QuantizedKvLayer::new(kv_dim, scheme).unwrap();
1167 layer.append_rows(&k_data, &v_data).unwrap();
1168 let (kr, vr) = layer.read_all().unwrap();
1169 let attn = attend_quantized(&q, &layer, n_heads, kv_heads, head_dim, scale).unwrap();
1170
1171 let k_cos = cosine(&kr, &k_data);
1172 let v_cos = cosine(&vr, &v_data);
1173 let k_mae = mae(&kr, &k_data);
1174 let v_mae = mae(&vr, &v_data);
1175 let a_cos = cosine(&attn, &ref_attn);
1176 let a_rmse = rmse(&attn, &ref_attn);
1177
1178 assert!(k_cos.is_finite() && v_cos.is_finite() && a_cos.is_finite());
1179 println!(
1180 "{name},{k_cos:.6},{v_cos:.6},{k_mae:.6},{v_mae:.6},{a_cos:.6},{a_rmse:.6},{}",
1181 layer.bytes_per_row
1182 );
1183 }
1184 }
1185}