Skip to main content

cram_codec/
kvcodec.rs

1//! KV-cache codec: append-friendly, error-bounded coding of attention K/V.
2//!
3//! Input is one layer's cache, `[heads][tokens][head_dim]` f32. The design
4//! follows the statistics of real caches (SmolLM2 dumps, see
5//! `bench/kv_ppl.py`) rather than treating the cache as a generic 3-D array:
6//!
7//! - **K is coded pre-RoPE.** The rotation is undone with the stream's
8//!   `theta` and absolute token position, coded, and re-applied on decode.
9//!   Pre-RoPE, most of K's energy is a per-channel constant; post-RoPE that
10//!   constant becomes a position-dependent oscillation. RoPE is a per-pair
11//!   rotation, so a pre-RoPE pair error of `e` is a post-RoPE pair error of
12//!   `e` too, and every per-channel quantity is shared by the two channels of
13//!   a RoPE pair (`c`, `c + hd/2`).
14//! - **Prediction runs along the token axis only**, per (head, channel):
15//!   closed-loop DPCM around the channel mean with a per-channel AR(1)
16//!   coefficient, uniform step. Closed loop means every reconstructed value is
17//!   within `step/2` of the (pre-RoPE) input — the error bound is exact.
18//! - **Attention sinks** (the first `sink` absolute positions) are coded with
19//!   a `sink_div`-times finer step: they carry most of the attention mass, so
20//!   their error is amplified far beyond their share of the values.
21//! - **Context models are per head**: each residual is coded with adaptive
22//!   binary models selected by (head, channel spread class, magnitude of the
23//!   same channel's previous residual). Models start from a discrete-Laplace
24//!   prior of the class's spread, so a fresh block learns nothing from zero.
25//! - **Blocks of `block` tokens are independent** (prediction and models
26//!   reset), so new tokens are coded without touching the past and any token
27//!   range decodes by decoding only the blocks that cover it. The stream
28//!   statistics (channel means, AR coefficients, classes) are fixed from the
29//!   first chunk the encoder sees (the prefill).
30//! - **GPU layout** (`gpu`, header flag bit 3): reconstruction is integer
31//!   (values are `i32` multiples of the unit `u = step / sink_div`, stored as
32//!   `f32`; the AR prediction is `mu + (rho·(prev − mu)) >> 4`), and every
33//!   block is coded as one independent arithmetic-coded substream per head.
34//!   The `(block, head)` substreams are the GPU decoder's lanes
35//!   ([`crate::kvgpu`]), and its output is bit-identical to
36//!   [`KvStream::decode_range`]: the only float operation is the final
37//!   `(q as f32) * u`. The layout codes the coded domain directly (the caller
38//!   passes pre-RoPE K and applies RoPE itself).
39
40use crate::abac::{BitModel, Decoder, Encoder, ONE};
41use crate::bitio::{ByteReader, ByteWriter, DecodeError};
42
43const MAGIC: &[u8; 4] = b"TKV1";
44const NCLASS: usize = 48;
45const NACT: usize = 3;
46const UBITS: usize = 24;
47
48/// Which half of the cache a stream holds.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum Kind {
51    /// Keys (RoPE applied by the model).
52    K,
53    /// Values.
54    V,
55}
56
57/// Encoder settings.
58#[derive(Clone, Debug)]
59pub struct KvParams {
60    /// K or V.
61    pub kind: Kind,
62    /// Number of KV heads.
63    pub heads: usize,
64    /// Head dimension (even).
65    pub hd: usize,
66    /// RoPE base (HF-Llama half-split convention).
67    pub theta: f64,
68    /// Code K before RoPE (undo, code, redo). Ignored for V.
69    pub pre_rope: bool,
70    /// Quantizer step in data units (max abs error is `step / 2`).
71    pub step: f64,
72    /// Number of leading absolute positions treated as attention sinks.
73    pub sink: usize,
74    /// Step divisor for sink positions.
75    pub sink_div: u32,
76    /// Tokens per independent block.
77    pub block: usize,
78    /// Fit a per-channel AR(1) predictor (else predict the channel mean).
79    pub ar: bool,
80    /// GPU layout: integer reconstruction, one substream per (block, head).
81    pub gpu: bool,
82    /// GPU layout: channels per substream (even, divides `hd`; 0 = `hd`).
83    /// Each block then has `heads × hd / lane_ch` substreams; lane `g` of a
84    /// head holds the RoPE pairs `g·lane_ch/2 ..` (channels `c` and
85    /// `c + hd/2` together) so the decoder can rotate on write.
86    pub lane_ch: usize,
87}
88
89impl KvParams {
90    /// Defaults for a cache with `heads` × `hd` per token (SmolLM2 RoPE base).
91    pub fn new(kind: Kind, heads: usize, hd: usize, step: f64) -> Self {
92        Self {
93            kind,
94            heads,
95            hd,
96            theta: 1e5,
97            pre_rope: true,
98            step,
99            sink: 4,
100            sink_div: 16,
101            block: 128,
102            ar: true,
103            gpu: false,
104            lane_ch: 0,
105        }
106    }
107    /// Channels per GPU substream.
108    pub fn lane_width(&self) -> usize {
109        if self.lane_ch == 0 {
110            self.hd
111        } else {
112            self.lane_ch
113        }
114    }
115    /// GPU substreams per (block, head).
116    pub fn lane_groups(&self) -> usize {
117        self.hd / self.lane_width()
118    }
119    fn rotated(&self) -> bool {
120        self.kind == Kind::K && self.pre_rope && !self.gpu
121    }
122    /// GPU-layout unit: every reconstruction is an `i32` multiple of it.
123    pub fn unit(&self) -> f32 {
124        (self.step / self.sink_div as f64) as f32
125    }
126}
127
128/// Per-stream statistics, fixed from the prefill and stored in the header.
129#[derive(Clone, Debug)]
130struct Stats {
131    /// Channel mean as an integer multiple of the channel step.
132    mu: Vec<i64>,
133    /// AR(1) coefficient in sixteenths, 0..=15.
134    rho: Vec<u8>,
135    /// Spread class: round(4 log2(residual sd / step)) + 8, clamped.
136    class: Vec<u8>,
137}
138
139/// Rotate `[heads][hd]` token vectors at absolute position `pos` by RoPE
140/// (`sign = 1`) or its inverse (`sign = -1`).
141fn rope_token(x: &mut [f64], heads: usize, hd: usize, theta: f64, pos: usize, sign: f64) {
142    let half = hd / 2;
143    for i in 0..half {
144        let f = pos as f64 / theta.powf(2.0 * i as f64 / hd as f64);
145        let (s, c) = f.sin_cos();
146        let s = sign * s;
147        for h in 0..heads {
148            let a = x[h * hd + i];
149            let b = x[h * hd + half + i];
150            x[h * hd + i] = a * c - b * s;
151            x[h * hd + half + i] = b * c + a * s;
152        }
153    }
154}
155
156/// Initial adaptive models for one context, from a discrete Laplace prior
157/// with residual sd `sd` (in steps).
158#[derive(Clone)]
159struct Ctx {
160    zero: BitModel,
161    unary: [BitModel; UBITS],
162    mant: [BitModel; UBITS],
163}
164
165impl Ctx {
166    fn prior(sd: f64) -> Self {
167        let b = (sd / std::f64::consts::SQRT_2).max(0.05);
168        let r = (-1.0 / b).exp();
169        let p = |x: f64| ((x * ONE as f64) as u32).clamp(64, ONE - 64);
170        let pz = 1.0 - (-0.5 / b).exp();
171        let mut unary = [BitModel::default(); UBITS];
172        let mut mant = [BitModel::default(); UBITS];
173        for i in 0..UBITS {
174            let ri = r.powf((1u64 << i.min(40)) as f64);
175            unary[i] = BitModel::new(p(ri));
176            let rh = if i == 0 {
177                0.5
178            } else {
179                r.powf((1u64 << (i - 1)) as f64)
180            };
181            mant[i] = BitModel::new(p(rh / (1.0 + rh)));
182        }
183        Self {
184            zero: BitModel::new(p(pz)),
185            unary,
186            mant,
187        }
188    }
189}
190
191fn class_sd(class: u8) -> f64 {
192    2f64.powf((class as f64 - 8.0) / 4.0)
193}
194
195/// Fresh context set for one block.
196fn fresh_models(heads: usize) -> Vec<Ctx> {
197    let mut v = Vec::with_capacity(heads * NCLASS * NACT);
198    for _ in 0..heads {
199        for c in 0..NCLASS {
200            for a in 0..NACT {
201                // previous residual magnitude 0 / 1 / 2+ shifts the prior spread
202                let sd = class_sd(c as u8) * [0.8, 1.0, 1.4][a];
203                v.push(Ctx::prior(sd));
204            }
205        }
206    }
207    v
208}
209
210fn put(e: &mut Encoder, m: &mut Ctx, q: i64) {
211    e.encode(q == 0, &mut m.zero);
212    if q == 0 {
213        return;
214    }
215    e.encode_p(q < 0, ONE / 2);
216    let w = q.unsigned_abs(); // >= 1
217    let k = 63 - w.leading_zeros() as usize;
218    for i in 0..k {
219        e.encode(true, &mut m.unary[i.min(UBITS - 1)]);
220    }
221    e.encode(false, &mut m.unary[k.min(UBITS - 1)]);
222    for i in (0..k).rev() {
223        let bit = (w >> i) & 1 == 1;
224        if i + 1 == k {
225            e.encode(bit, &mut m.mant[k]);
226        } else {
227            e.encode_p(bit, ONE / 2);
228        }
229    }
230}
231
232fn get(d: &mut Decoder, m: &mut Ctx) -> i64 {
233    if d.decode(&mut m.zero) {
234        return 0;
235    }
236    let neg = d.decode_p(ONE / 2);
237    let mut k = 0;
238    while k < 62 && d.decode(&mut m.unary[k.min(UBITS - 1)]) {
239        k += 1;
240    }
241    let mut w = 1u64;
242    for i in (0..k).rev() {
243        let bit = if i + 1 == k {
244            d.decode(&mut m.mant[k])
245        } else {
246            d.decode_p(ONE / 2)
247        };
248        w = (w << 1) | bit as u64;
249    }
250    if neg {
251        -(w as i64)
252    } else {
253        w as i64
254    }
255}
256
257/// Shared closed-loop state of one block (encoder and decoder run the same
258/// arithmetic, so their reconstructions agree bit for bit).
259struct Lane<'a> {
260    p: &'a KvParams,
261    s: &'a Stats,
262    step: &'a [f64],
263    prev: Vec<f64>,
264    act: Vec<u8>,
265    models: Vec<Ctx>,
266    first: bool,
267}
268
269impl<'a> Lane<'a> {
270    fn new(p: &'a KvParams, s: &'a Stats, step: &'a [f64]) -> Self {
271        let n = p.heads * p.hd;
272        Self {
273            p,
274            s,
275            step,
276            prev: vec![0.0; n],
277            act: vec![0; n],
278            models: fresh_models(p.heads),
279            first: true,
280        }
281    }
282    #[inline]
283    fn pred(&self, j: usize) -> f64 {
284        let mu = self.s.mu[j] as f64 * self.step[j];
285        if self.first {
286            mu
287        } else {
288            mu + self.s.rho[j] as f64 / 16.0 * (self.prev[j] - mu)
289        }
290    }
291    #[inline]
292    fn ctx(&self, j: usize) -> usize {
293        let h = j / self.p.hd;
294        (h * NCLASS + self.s.class[j] as usize) * NACT + self.act[j] as usize
295    }
296    #[inline]
297    fn set(&mut self, j: usize, q: i64, st: f64) -> f64 {
298        let y = self.pred(j) + q as f64 * st;
299        self.act[j] = q.unsigned_abs().min(2) as u8;
300        y
301    }
302}
303
304fn steps(p: &KvParams) -> Vec<f64> {
305    vec![p.step; p.heads * p.hd]
306}
307
308fn tok_step(p: &KvParams, pos: usize) -> f64 {
309    if pos < p.sink {
310        1.0 / p.sink_div as f64
311    } else {
312        1.0
313    }
314}
315
316/// Channel of lane-local index `i` in channel group `g` (see [`KvParams::lane_ch`]).
317#[inline]
318pub fn lane_chan(hd: usize, w: usize, g: usize, i: usize) -> usize {
319    let base = g * w / 2;
320    if i < w / 2 {
321        base + i
322    } else {
323        hd / 2 + base + i - w / 2
324    }
325}
326
327/// Encode tokens `[pos0, pos0 + n)` (row-major `[n][heads*hd]`, already in the
328/// coded domain) as one independent block.
329fn encode_block(
330    p: &KvParams,
331    s: &Stats,
332    step: &[f64],
333    y: &[f64],
334    pos0: usize,
335    n: usize,
336) -> Vec<u8> {
337    let m = p.heads * p.hd;
338    let mut lane = Lane::new(p, s, step);
339    let mut e = Encoder::new();
340    for t in 0..n {
341        let ts = tok_step(p, pos0 + t);
342        let mut row = vec![0.0; m];
343        for j in 0..m {
344            let st = step[j] * ts;
345            let q = ((y[t * m + j] - lane.pred(j)) / st).round() as i64;
346            let ci = lane.ctx(j);
347            put(&mut e, &mut lane.models[ci], q);
348            row[j] = lane.set(j, q, st);
349        }
350        lane.prev = row;
351        lane.first = false;
352    }
353    e.finish()
354}
355
356fn decode_block(
357    p: &KvParams,
358    s: &Stats,
359    step: &[f64],
360    bytes: &[u8],
361    pos0: usize,
362    n: usize,
363    out: &mut [f64],
364) {
365    let m = p.heads * p.hd;
366    let mut lane = Lane::new(p, s, step);
367    let mut d = Decoder::new(bytes);
368    for t in 0..n {
369        let ts = tok_step(p, pos0 + t);
370        for j in 0..m {
371            let st = step[j] * ts;
372            let ci = lane.ctx(j);
373            let q = get(&mut d, &mut lane.models[ci]);
374            out[t * m + j] = lane.set(j, q, st);
375        }
376        lane.prev.copy_from_slice(&out[t * m..(t + 1) * m]);
377        lane.first = false;
378    }
379}
380
381/// GPU layout: encode tokens `[pos0, pos0 + n)` as one substream per head.
382fn encode_block_gpu(p: &KvParams, s: &Stats, y: &[f64], pos0: usize, n: usize) -> Vec<Vec<u8>> {
383    let (h, d) = (p.heads, p.hd);
384    let m = h * d;
385    let u = p.unit() as f64;
386    let (w, ng) = (p.lane_width(), p.lane_groups());
387    (0..h * ng)
388        .map(|lane| {
389            let (hh, g) = (lane / ng, lane % ng);
390            let mut models = fresh_models(1);
391            let mut e = Encoder::new();
392            let mut prev = vec![0i64; w];
393            let mut act = vec![0u8; w];
394            for t in 0..n {
395                let ts = if pos0 + t < p.sink {
396                    1
397                } else {
398                    p.sink_div as i64
399                };
400                for c in 0..w {
401                    let j = hh * d + lane_chan(d, w, g, c);
402                    let mu = s.mu[j];
403                    let pred = if t == 0 {
404                        mu
405                    } else {
406                        mu + ((s.rho[j] as i64 * (prev[c] - mu)) >> 4)
407                    };
408                    let q = ((y[t * m + j] / u - pred as f64) / ts as f64).round() as i64;
409                    let ci = s.class[j] as usize * NACT + act[c] as usize;
410                    put(&mut e, &mut models[ci], q);
411                    let r = pred + q * ts;
412                    assert!(
413                        r.abs() < 1 << 24,
414                        "kv gpu layout: value out of i24 range (step too small)"
415                    );
416                    prev[c] = r;
417                    act[c] = q.unsigned_abs().min(2) as u8;
418                }
419            }
420            e.finish()
421        })
422        .collect()
423}
424
425/// GPU layout decode of one (block, head, channel group) substream into
426/// `out[t][c]` (`n × hd`, f32; only the group's channels are written). This is
427/// the reference the GPU kernel matches.
428#[allow(clippy::too_many_arguments)]
429fn decode_lane_gpu(
430    p: &KvParams,
431    s: &Stats,
432    hh: usize,
433    g: usize,
434    bytes: &[u8],
435    pos0: usize,
436    n: usize,
437    out: &mut [f32],
438) {
439    let d = p.hd;
440    let u = p.unit();
441    let mut models = fresh_models(1);
442    let mut dec = Decoder::new(bytes);
443    let w = p.lane_width();
444    let mut prev = vec![0i64; w];
445    let mut act = vec![0u8; w];
446    for t in 0..n {
447        let ts = if pos0 + t < p.sink {
448            1
449        } else {
450            p.sink_div as i64
451        };
452        for c in 0..w {
453            let cc = lane_chan(d, w, g, c);
454            let j = hh * d + cc;
455            let mu = s.mu[j];
456            let pred = if t == 0 {
457                mu
458            } else {
459                mu + ((s.rho[j] as i64 * (prev[c] - mu)) >> 4)
460            };
461            let ci = s.class[j] as usize * NACT + act[c] as usize;
462            let q = get(&mut dec, &mut models[ci]);
463            let r = pred + q * ts;
464            prev[c] = r;
465            act[c] = q.unsigned_abs().min(2) as u8;
466            out[t * d + cc] = (r as i32 as f32) * u;
467        }
468    }
469}
470
471/// `[heads][n][hd]` f32 → token-major `[n][heads*hd]` f64 in the coded
472/// domain (pre-RoPE for K when enabled), positions starting at `pos0`.
473fn to_coded(p: &KvParams, x: &[f32], n: usize, pos0: usize) -> Vec<f64> {
474    let (h, d) = (p.heads, p.hd);
475    assert_eq!(x.len(), h * n * d, "shape mismatch");
476    let mut y = vec![0.0; n * h * d];
477    for t in 0..n {
478        for hh in 0..h {
479            for c in 0..d {
480                y[t * h * d + hh * d + c] = x[(hh * n + t) * d + c] as f64;
481            }
482        }
483        if p.rotated() {
484            rope_token(
485                &mut y[t * h * d..(t + 1) * h * d],
486                h,
487                d,
488                p.theta,
489                pos0 + t,
490                -1.0,
491            );
492        }
493    }
494    y
495}
496
497fn from_coded(p: &KvParams, y: &mut [f64], n: usize, pos0: usize) -> Vec<f32> {
498    let (h, d) = (p.heads, p.hd);
499    let mut x = vec![0f32; n * h * d];
500    for t in 0..n {
501        if p.rotated() {
502            rope_token(
503                &mut y[t * h * d..(t + 1) * h * d],
504                h,
505                d,
506                p.theta,
507                pos0 + t,
508                1.0,
509            );
510        }
511        for hh in 0..h {
512            for c in 0..d {
513                x[(hh * n + t) * d + c] = y[t * h * d + hh * d + c] as f32;
514            }
515        }
516    }
517    x
518}
519
520fn fit_stats(p: &KvParams, y: &[f64], n: usize, step: &[f64]) -> Stats {
521    let m = p.heads * p.hd;
522    let half = p.hd / 2;
523    let mut mean = vec![0.0; m];
524    for t in 0..n {
525        for j in 0..m {
526            mean[j] += y[t * m + j];
527        }
528    }
529    mean.iter_mut().for_each(|v| *v /= n.max(1) as f64);
530    let (mut c0, mut c1) = (vec![0.0; m], vec![0.0; m]);
531    for t in 0..n {
532        for j in 0..m {
533            let r = y[t * m + j] - mean[j];
534            c0[j] += r * r;
535            if t > 0 {
536                c1[j] += r * (y[(t - 1) * m + j] - mean[j]);
537            }
538        }
539    }
540    let mut rho = vec![0u8; m];
541    let mut var = vec![0.0; m];
542    for j in 0..m {
543        let a = if p.ar && c0[j] > 0.0 {
544            c1[j] / c0[j]
545        } else {
546            0.0
547        };
548        let a = (a * 16.0).round().clamp(0.0, 15.0);
549        rho[j] = a as u8;
550        let a = a / 16.0;
551        // residual variance of r_t - a r_{t-1}
552        var[j] = (c0[j] / n.max(1) as f64) * (1.0 - a * a).max(0.05);
553    }
554    let mut class = vec![0u8; m];
555    for j in 0..m {
556        let pair = if p.kind == Kind::K {
557            j - (j % p.hd) + (j % p.hd + half) % p.hd
558        } else {
559            j
560        };
561        let v = if p.kind == Kind::K {
562            0.5 * (var[j] + var[pair])
563        } else {
564            var[j]
565        };
566        let r = v.sqrt() / step[j];
567        class[j] = ((4.0 * r.max(1e-9).log2()).round() + 8.0).clamp(0.0, NCLASS as f64 - 1.0) as u8;
568    }
569    let mu = if p.gpu {
570        let u = p.unit() as f64;
571        (0..m).map(|j| (mean[j] / u).round() as i64).collect()
572    } else {
573        (0..m).map(|j| (mean[j] / step[j]).round() as i64).collect()
574    };
575    Stats { mu, rho, class }
576}
577
578/// Append-friendly encoder for one layer's K or V stream.
579pub struct KvEncoder {
580    /// Settings (fixed for the stream).
581    pub params: KvParams,
582    stats: Stats,
583    step: Vec<f64>,
584    blocks: Vec<Vec<u8>>,
585    /// GPU layout: per sealed block, one substream per head.
586    gblocks: Vec<Vec<Vec<u8>>>,
587    /// Pending tokens of the unsealed tail block, coded domain.
588    tail: Vec<f64>,
589    tokens: usize,
590}
591
592impl KvEncoder {
593    /// Start a stream from the prefill `[heads][n][hd]`; its statistics fix
594    /// the header.
595    pub fn new(params: KvParams, prefill: &[f32], n: usize) -> Self {
596        assert!(params.hd.is_multiple_of(2) && params.block > 0 && n > 0);
597        let step = steps(&params);
598        let y = to_coded(&params, prefill, n, 0);
599        let stats = fit_stats(&params, &y, n, &step);
600        if params.gpu {
601            assert!(
602                !(params.kind == Kind::K && params.pre_rope),
603                "gpu layout codes pre-RoPE K given by the caller (set pre_rope = false)"
604            );
605        }
606        let mut e = Self {
607            params,
608            stats,
609            step,
610            blocks: Vec::new(),
611            gblocks: Vec::new(),
612            tail: Vec::new(),
613            tokens: 0,
614        };
615        e.push_coded(y, n);
616        e
617    }
618
619    /// Append `n` new tokens `[heads][n][hd]`. Sealed blocks are never touched.
620    pub fn append(&mut self, x: &[f32], n: usize) {
621        let y = to_coded(&self.params, x, n, self.tokens);
622        self.push_coded(y, n);
623    }
624
625    fn push_coded(&mut self, y: Vec<f64>, n: usize) {
626        let m = self.params.heads * self.params.hd;
627        let b = self.params.block;
628        self.tail.extend_from_slice(&y);
629        self.tokens += n;
630        while self.tail.len() >= b * m {
631            let pos0 = self.sealed_blocks() * b;
632            if self.params.gpu {
633                let blk = encode_block_gpu(&self.params, &self.stats, &self.tail[..b * m], pos0, b);
634                self.gblocks.push(blk);
635            } else {
636                let blk = encode_block(
637                    &self.params,
638                    &self.stats,
639                    &self.step,
640                    &self.tail[..b * m],
641                    pos0,
642                    b,
643                );
644                self.blocks.push(blk);
645            }
646            self.tail.drain(..b * m);
647        }
648    }
649
650    /// Tokens seen so far.
651    pub fn tokens(&self) -> usize {
652        self.tokens
653    }
654
655    /// Number of sealed (final) blocks.
656    pub fn sealed_blocks(&self) -> usize {
657        self.blocks.len() + self.gblocks.len()
658    }
659
660    /// Serialize: header, sealed blocks, and the tail coded as a short block.
661    /// Only the tail (< `block` tokens) is coded here.
662    pub fn to_bytes(&self) -> Vec<u8> {
663        let p = &self.params;
664        let mut w = ByteWriter::new();
665        w.bytes(MAGIC);
666        w.u8(match p.kind {
667            Kind::K => 0,
668            Kind::V => 1,
669        } | (p.pre_rope as u8) << 1
670            | (p.ar as u8) << 2
671            | (p.gpu as u8) << 3
672            | ((p.gpu && p.lane_groups() > 1) as u8) << 4);
673        w.varint(p.heads as u64);
674        w.varint(p.hd as u64);
675        w.f64(p.theta);
676        w.f64(p.step);
677        w.varint(p.sink as u64);
678        w.varint(p.sink_div as u64);
679        w.varint(p.block as u64);
680        w.varint(self.tokens as u64);
681        if p.gpu && p.lane_groups() > 1 {
682            w.varint(p.lane_width() as u64);
683        }
684        for j in 0..p.heads * p.hd {
685            w.svarint(self.stats.mu[j]);
686            w.u8(self.stats.class[j] | if p.ar { 0 } else { 0x80 });
687            if p.ar {
688                // two channels' rho per byte would save ~1 B/channel; keep simple
689                w.u8(self.stats.rho[j]);
690            }
691        }
692        for blk in &self.blocks {
693            w.block(blk);
694        }
695        for blk in &self.gblocks {
696            blk.iter().for_each(|sub| w.block(sub));
697        }
698        let m = p.heads * p.hd;
699        let nt = self.tail.len() / m;
700        if nt > 0 {
701            let pos0 = self.sealed_blocks() * p.block;
702            if p.gpu {
703                encode_block_gpu(p, &self.stats, &self.tail, pos0, nt)
704                    .iter()
705                    .for_each(|sub| w.block(sub));
706            } else {
707                w.block(&encode_block(
708                    p,
709                    &self.stats,
710                    &self.step,
711                    &self.tail,
712                    pos0,
713                    nt,
714                ));
715            }
716        }
717        w.buf
718    }
719}
720
721/// Parsed stream: header plus block payload slices.
722pub struct KvStream<'a> {
723    /// Settings recovered from the header.
724    pub params: KvParams,
725    /// Total tokens.
726    pub tokens: usize,
727    stats: Stats,
728    blocks: Vec<&'a [u8]>,
729    /// Byte offset of each entry of `blocks` in the parsed buffer.
730    offsets: Vec<usize>,
731}
732
733impl<'a> KvStream<'a> {
734    /// Parse a stream produced by [`KvEncoder::to_bytes`].
735    pub fn parse(bytes: &'a [u8]) -> Result<Self, DecodeError> {
736        let mut r = ByteReader::new(bytes);
737        if r.take(4)? != MAGIC {
738            return Err(DecodeError::Invalid("not a TKV1 stream"));
739        }
740        let f = r.u8()?;
741        let kind = if f & 1 == 0 { Kind::K } else { Kind::V };
742        let heads = r.varint()? as usize;
743        let hd = r.varint()? as usize;
744        let theta = r.f64()?;
745        let step = r.f64()?;
746        let sink = r.varint()? as usize;
747        let sink_div = r.varint()? as u32;
748        let block = r.varint()? as usize;
749        let tokens = r.varint()? as usize;
750        let lane_ch = if f & 16 != 0 { r.varint()? as usize } else { 0 };
751        if !hd.is_multiple_of(2) || block == 0 || heads == 0 || sink_div == 0 {
752            return Err(DecodeError::Invalid("bad header"));
753        }
754        if lane_ch != 0 && (!lane_ch.is_multiple_of(2) || !hd.is_multiple_of(lane_ch)) {
755            return Err(DecodeError::Invalid("bad lane width"));
756        }
757        let ar = f & 4 != 0;
758        let gpu = f & 8 != 0;
759        let params = KvParams {
760            kind,
761            heads,
762            hd,
763            theta,
764            pre_rope: f & 2 != 0,
765            step,
766            sink,
767            sink_div,
768            block,
769            ar,
770            gpu,
771            lane_ch,
772        };
773        let m = heads * hd;
774        let (mut mu, mut rho, mut class) = (
775            Vec::with_capacity(m),
776            Vec::with_capacity(m),
777            Vec::with_capacity(m),
778        );
779        for _ in 0..m {
780            mu.push(r.svarint()?);
781            class.push(r.u8()? & 0x7f);
782            rho.push(if ar { r.u8()?.min(15) } else { 0 });
783        }
784        if class.iter().any(|&c| c as usize >= NCLASS) {
785            return Err(DecodeError::Invalid("class out of range"));
786        }
787        let nb = tokens.div_ceil(block) * if gpu { heads * params.lane_groups() } else { 1 };
788        let mut blocks = Vec::with_capacity(nb);
789        let mut offsets = Vec::with_capacity(nb);
790        for _ in 0..nb {
791            let b = r.block()?;
792            offsets.push(b.as_ptr() as usize - bytes.as_ptr() as usize);
793            blocks.push(b);
794        }
795        if gpu && mu.iter().any(|&v| v.abs() >= 1 << 24) {
796            return Err(DecodeError::Invalid("mean out of range"));
797        }
798        Ok(Self {
799            params,
800            tokens,
801            stats: Stats { mu, rho, class },
802            blocks,
803            offsets,
804        })
805    }
806
807    /// Decode tokens `[t0, t1)` as `[heads][t1-t0][hd]`, decoding only the
808    /// blocks that cover the range.
809    pub fn decode_range(&self, t0: usize, t1: usize) -> Vec<f32> {
810        assert!(t0 <= t1 && t1 <= self.tokens);
811        let p = &self.params;
812        if p.gpu {
813            return self.decode_range_gpu(t0, t1);
814        }
815        let m = p.heads * p.hd;
816        let step = steps(p);
817        let (b0, b1) = (t0 / p.block, t1.div_ceil(p.block));
818        let mut y = Vec::with_capacity((b1 - b0) * p.block * m);
819        let mut buf = vec![0.0; p.block * m];
820        for b in b0..b1 {
821            let pos0 = b * p.block;
822            let n = p.block.min(self.tokens - pos0);
823            decode_block(
824                p,
825                &self.stats,
826                &step,
827                self.blocks[b],
828                pos0,
829                n,
830                &mut buf[..n * m],
831            );
832            y.extend_from_slice(&buf[..n * m]);
833        }
834        let off = t0 - b0 * p.block;
835        let mut sel = y[off * m..(off + t1 - t0) * m].to_vec();
836        from_coded(p, &mut sel, t1 - t0, t0)
837    }
838
839    fn decode_range_gpu(&self, t0: usize, t1: usize) -> Vec<f32> {
840        let p = &self.params;
841        let (h, d, nt) = (p.heads, p.hd, t1 - t0);
842        let mut x = vec![0f32; h * nt * d];
843        let mut buf = vec![0f32; p.block * d];
844        for b in t0 / p.block..t1.div_ceil(p.block) {
845            let pos0 = b * p.block;
846            let n = p.block.min(self.tokens - pos0);
847            let ng = p.lane_groups();
848            for hh in 0..h {
849                for g in 0..ng {
850                    decode_lane_gpu(
851                        p,
852                        &self.stats,
853                        hh,
854                        g,
855                        self.blocks[(b * h + hh) * ng + g],
856                        pos0,
857                        n,
858                        &mut buf[..n * d],
859                    );
860                }
861                for t in pos0.max(t0)..(pos0 + n).min(t1) {
862                    let dst = (hh * nt + t - t0) * d;
863                    x[dst..dst + d].copy_from_slice(&buf[(t - pos0) * d..(t - pos0 + 1) * d]);
864                }
865            }
866        }
867        x
868    }
869
870    /// GPU layout tables for [`crate::kvgpu`]: per channel `mu` (`i32` bits),
871    /// `rho`, `class`, and per (block, head) lane `(byte offset, byte length,
872    /// first token, tokens)`.
873    pub fn gpu_tables(&self) -> Option<GpuTables> {
874        let p = &self.params;
875        if !p.gpu {
876            return None;
877        }
878        let ng = p.lane_groups();
879        let lanes = (0..self.blocks.len())
880            .map(|i| {
881                let (b, hh, g) = (i / (p.heads * ng), i / ng % p.heads, i % ng);
882                let pos0 = b * p.block;
883                [
884                    self.offsets[i] as u32,
885                    self.blocks[i].len() as u32,
886                    (pos0) as u32,
887                    p.block.min(self.tokens - pos0) as u32,
888                    hh as u32,
889                    g as u32,
890                ]
891            })
892            .collect();
893        Some(GpuTables {
894            mu: self.stats.mu.iter().map(|&v| v as i32).collect(),
895            rho: self.stats.rho.iter().map(|&v| v as u32).collect(),
896            class: self.stats.class.iter().map(|&v| v as u32).collect(),
897            lanes,
898        })
899    }
900}
901
902/// Per-stream tables the GPU decoder uploads (see [`KvStream::gpu_tables`]).
903pub struct GpuTables {
904    /// Channel mean in units of [`KvParams::unit`].
905    pub mu: Vec<i32>,
906    /// AR(1) coefficient in sixteenths.
907    pub rho: Vec<u32>,
908    /// Spread class.
909    pub class: Vec<u32>,
910    /// `[byte offset, byte length, first token, tokens, head, channel group]`
911    /// per lane.
912    pub lanes: Vec<[u32; 6]>,
913}
914
915/// Initial adaptive-model table shared by every GPU-layout stream:
916/// `[class][act][49]` with entry 0 the zero flag, 1..=24 the unary models and
917/// 25..=48 the mantissa models, each `p | p << 16` (fast, slow).
918pub fn gpu_prior_table() -> Vec<u32> {
919    let mut v = Vec::with_capacity(NCLASS * NACT * (1 + 2 * UBITS));
920    for c in fresh_models(1) {
921        for m in std::iter::once(&c.zero)
922            .chain(c.unary.iter())
923            .chain(c.mant.iter())
924        {
925            v.push(m.packed());
926        }
927    }
928    v
929}
930
931/// Models per context in [`gpu_prior_table`].
932pub const GPU_CTX_MODELS: usize = 1 + 2 * UBITS;
933/// Contexts per lane (classes × previous-magnitude bins).
934pub const GPU_CTXS: usize = NCLASS * NACT;
935
936/// One-shot encode of `[heads][n][hd]` (prefill = everything).
937pub fn encode(params: KvParams, x: &[f32], n: usize) -> Vec<u8> {
938    KvEncoder::new(params, x, n).to_bytes()
939}
940
941/// Decode a whole stream → (params, tokens, `[heads][tokens][hd]`).
942pub fn decode(bytes: &[u8]) -> Result<(KvParams, usize, Vec<f32>), DecodeError> {
943    let s = KvStream::parse(bytes)?;
944    let x = s.decode_range(0, s.tokens);
945    Ok((s.params, s.tokens, x))
946}
947
948/// Rate-targeted mode: the step whose one-shot stream is closest to (and not
949/// above) `bpv` bits per value, by bisection on log(step).
950pub fn step_for_bpv(mut params: KvParams, x: &[f32], n: usize, bpv: f64) -> f64 {
951    let vals = x.len() as f64;
952    let rms = (x.iter().map(|&v| v as f64 * v as f64).sum::<f64>() / vals)
953        .sqrt()
954        .max(1e-30);
955    let (mut lo, mut hi) = ((rms * 1e-5).ln(), (rms * 10.0).ln());
956    for _ in 0..30 {
957        let mid = 0.5 * (lo + hi);
958        params.step = mid.exp();
959        let b = 8.0 * encode(params.clone(), x, n).len() as f64 / vals;
960        if b > bpv {
961            lo = mid;
962        } else {
963            hi = mid;
964        }
965    }
966    hi.exp()
967}
968
969#[cfg(test)]
970mod tests {
971    use super::*;
972
973    fn synth(h: usize, n: usize, d: usize, seed: u64) -> Vec<f32> {
974        let mut s = seed;
975        let mut rnd = || {
976            s = s
977                .wrapping_mul(6364136223846793005)
978                .wrapping_add(1442695040888963407);
979            ((s >> 11) as f64 / (1u64 << 53) as f64) - 0.5
980        };
981        let mut x = vec![0f32; h * n * d];
982        for hh in 0..h {
983            for c in 0..d {
984                let mu = 3.0 * rnd();
985                let mut prev = 0.0;
986                for t in 0..n {
987                    prev = 0.5 * prev + rnd() * (1.0 + c as f64 / 16.0);
988                    x[(hh * n + t) * d + c] = (mu + prev) as f32;
989                }
990            }
991        }
992        x
993    }
994
995    fn pre(p: &KvParams, x: &[f32], n: usize) -> Vec<f64> {
996        to_coded(p, x, n, 0)
997    }
998
999    #[test]
1000    fn error_bound_and_append_and_ranges() {
1001        let (h, n, d) = (3, 300, 16);
1002        for kind in [Kind::K, Kind::V] {
1003            let x = synth(h, n, d, 7);
1004            let mut p = KvParams::new(kind, h, d, 0.05);
1005            p.block = 32;
1006            // one-shot
1007            let one = encode(p.clone(), &x, n);
1008            let (_, nt, y) = decode(&one).unwrap();
1009            assert_eq!(nt, n);
1010            let (a, b) = (pre(&p, &x, n), pre(&p, &y, n));
1011            for t in 0..n {
1012                let lim = p.step / 2.0 * tok_step(&p, t) + 1e-4;
1013                for j in 0..h * d {
1014                    let e = (a[t * h * d + j] - b[t * h * d + j]).abs();
1015                    assert!(e <= lim, "{kind:?} t={t} j={j} err {e} > {lim}");
1016                }
1017            }
1018            // prefill 100 then token-at-a-time append: same stats source differs,
1019            // so compare against its own decode and check sealed blocks are stable
1020            let slice = |t0: usize, t1: usize| -> Vec<f32> {
1021                let mut v = Vec::new();
1022                for hh in 0..h {
1023                    v.extend_from_slice(&x[(hh * n + t0) * d..(hh * n + t1) * d]);
1024                }
1025                v
1026            };
1027            let mut e = KvEncoder::new(p.clone(), &slice(0, 100), 100);
1028            let mut sealed: Vec<Vec<u8>> = Vec::new();
1029            for t in 100..n {
1030                e.append(&slice(t, t + 1), 1);
1031                if e.sealed_blocks() > sealed.len() {
1032                    sealed = e.blocks.clone();
1033                }
1034                assert_eq!(&e.blocks[..sealed.len()], &sealed[..]);
1035            }
1036            let bytes = e.to_bytes();
1037            let s = KvStream::parse(&bytes).unwrap();
1038            let full = s.decode_range(0, n);
1039            for (t0, t1) in [(0, 1), (31, 33), (100, 300), (250, 300), (64, 64)] {
1040                let r = s.decode_range(t0, t1);
1041                for hh in 0..h {
1042                    for t in t0..t1 {
1043                        for c in 0..d {
1044                            assert_eq!(
1045                                r[(hh * (t1 - t0) + t - t0) * d + c],
1046                                full[(hh * n + t) * d + c]
1047                            );
1048                        }
1049                    }
1050                }
1051            }
1052            let (a, b) = (pre(&p, &x, n), pre(&p, &full, n));
1053            let me = a
1054                .iter()
1055                .zip(&b)
1056                .map(|(u, v)| (u - v).abs())
1057                .fold(0.0, f64::max);
1058            assert!(me <= p.step / 2.0 + 1e-4);
1059        }
1060    }
1061
1062    #[test]
1063    fn gpu_layout_bound_ranges_append() {
1064        let (h, n, d) = (3, 300, 16);
1065        for (kind, lc) in [(Kind::K, 0), (Kind::V, 0), (Kind::K, 4), (Kind::V, 2)] {
1066            let x = synth(h, n, d, 11);
1067            let mut p = KvParams::new(kind, h, d, 0.05);
1068            p.block = 32;
1069            p.pre_rope = false;
1070            p.gpu = true;
1071            p.lane_ch = lc;
1072            let bytes = encode(p.clone(), &x, n);
1073            let s = KvStream::parse(&bytes).unwrap();
1074            let y = s.decode_range(0, n);
1075            for hh in 0..h {
1076                for t in 0..n {
1077                    let lim = p.step / 2.0 * tok_step(&p, t) + 1e-5;
1078                    for c in 0..d {
1079                        let i = (hh * n + t) * d + c;
1080                        assert!(((x[i] - y[i]) as f64).abs() <= lim, "{kind:?} t={t} c={c}");
1081                    }
1082                }
1083            }
1084            for (t0, t1) in [(0, 1), (31, 33), (250, 300)] {
1085                let r = s.decode_range(t0, t1);
1086                for hh in 0..h {
1087                    for t in t0..t1 {
1088                        assert_eq!(
1089                            r[(hh * (t1 - t0) + t - t0) * d..][..d],
1090                            y[(hh * n + t) * d..][..d]
1091                        );
1092                    }
1093                }
1094            }
1095            let tb = s.gpu_tables().unwrap();
1096            assert_eq!(tb.lanes.len(), n.div_ceil(32) * h * p.lane_groups());
1097        }
1098    }
1099
1100    #[test]
1101    fn rate_target() {
1102        let (h, n, d) = (2, 256, 32);
1103        let x = synth(h, n, d, 3);
1104        let p = KvParams::new(Kind::V, h, d, 1.0);
1105        let st = step_for_bpv(p.clone(), &x, n, 3.0);
1106        let b = 8.0 * encode(KvParams { step: st, ..p }, &x, n).len() as f64 / x.len() as f64;
1107        assert!(b <= 3.0 && b > 2.7, "bpv {b}");
1108    }
1109}