cram_codec/kvgpu.rs
1//! Host side of the GPU KV decoder: packs GPU-layout TKV streams
2//! ([`crate::kvcodec`], `gpu = true`) into the flat tables the device kernel
3//! reads (`tang-compute` `tkv_decode`, MSL source in
4//! `tang-compute/src/kernels/kvdec_msl.rs`). No GPU dependency here.
5//!
6//! One kernel thread per lane = one (stream, block, head) arithmetic-coded
7//! substream. Tables (all `u32`):
8//! - `stream`: every stream's bytes, concatenated, little-endian words;
9//! - `lanes`: [`LANE_WORDS`] words per lane: byte offset, byte length, first
10//! token, tokens, channel-table base, output base (elements), output token
11//! stride, scratch base (words), unit (`f32` bits), sink, sink divisor,
12//! lane width (channels), RoPE flag, RoPE theta (`f32` bits), first RoPE
13//! pair of the lane, head dim;
14//! - `chans`: 4 words per channel, in lane-local order per (stream, head,
15//! channel group): mean (`i32`), rho, class, dense class index within the
16//! group;
17//! - `prior`: [`crate::kvcodec::gpu_prior_table`];
18//! - scratch: `scratch_words` words of adaptive-model state.
19
20use crate::bitio::DecodeError;
21use crate::kvcodec::{gpu_prior_table, lane_chan, KvStream, GPU_CTX_MODELS};
22
23/// Words per lane descriptor.
24pub const LANE_WORDS: usize = 16;
25
26/// Flat decoder tables for a set of streams.
27#[derive(Default)]
28pub struct Batch {
29 /// Concatenated stream bytes as words.
30 pub stream: Vec<u32>,
31 /// Lane descriptors, [`LANE_WORDS`] each.
32 pub lanes: Vec<u32>,
33 /// Channel table, 4 words per channel.
34 pub chans: Vec<u32>,
35 /// Model scratch size in words.
36 pub scratch_words: usize,
37 bytes: Vec<u8>,
38}
39
40/// Where a stream's output goes: element index of (head, token) row start
41/// and the token stride, plus optional RoPE (K given pre-RoPE).
42pub struct Placement<'f> {
43 /// `(head, token) -> element offset` of that row's channel 0.
44 pub row: &'f dyn Fn(usize, usize) -> usize,
45 /// Apply half-split RoPE with this theta to each decoded token row.
46 pub rope_theta: Option<f32>,
47 /// Decode without writing the output (benchmark: the cost a fused
48 /// decode + attention kernel cannot avoid).
49 pub decode_only: bool,
50}
51
52impl Batch {
53 /// Empty batch.
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 /// Add one stream; every lane writes `row(head, t) + c` for its tokens.
59 /// Lanes covering `[t0, t1)` only (whole blocks) are added.
60 pub fn add(
61 &mut self,
62 bytes: &[u8],
63 place: &Placement,
64 t0: usize,
65 t1: usize,
66 ) -> Result<(), DecodeError> {
67 let s = KvStream::parse(bytes)?;
68 let tb = s
69 .gpu_tables()
70 .ok_or(DecodeError::Invalid("not a gpu-layout stream"))?;
71 let p = &s.params;
72 let base = self.bytes.len();
73 self.bytes.extend_from_slice(bytes);
74 while !self.bytes.len().is_multiple_of(4) {
75 self.bytes.push(0);
76 }
77 let chan0 = self.chans.len() / 4;
78 let (w, ng) = (p.lane_width(), p.lane_groups());
79 if w > 128 {
80 return Err(DecodeError::Invalid("gpu lane wider than 128 channels"));
81 }
82 // dense class index per (head, channel group), lane-local channel order
83 let mut ncls = vec![0usize; p.heads * ng];
84 for (hg, nc) in ncls.iter_mut().enumerate() {
85 let (hh, g) = (hg / ng, hg % ng);
86 let mut seen: Vec<u32> = Vec::new();
87 for i in 0..w {
88 let j = hh * p.hd + lane_chan(p.hd, w, g, i);
89 let cl = tb.class[j];
90 let di = seen.iter().position(|&v| v == cl).unwrap_or_else(|| {
91 seen.push(cl);
92 seen.len() - 1
93 });
94 self.chans
95 .extend_from_slice(&[tb.mu[j] as u32, tb.rho[j], cl, di as u32]);
96 }
97 *nc = seen.len();
98 }
99 let ctx_words = 3 * GPU_CTX_MODELS;
100 for l in &tb.lanes {
101 let [off, len, pos0, n, hh, g] = *l;
102 let (pos0, n, hh, g) = (pos0 as usize, n as usize, hh as usize, g as usize);
103 let hg = hh * ng + g;
104 if pos0 + n <= t0 || pos0 >= t1 {
105 continue;
106 }
107 let row0 = (place.row)(hh, pos0);
108 let stride = if n > 1 {
109 (place.row)(hh, pos0 + 1) - row0
110 } else {
111 p.hd
112 };
113 self.lanes.extend_from_slice(&[
114 (base + off as usize) as u32,
115 len,
116 pos0 as u32,
117 n as u32,
118 (chan0 + hg * w) as u32,
119 row0 as u32,
120 stride as u32,
121 self.scratch_words as u32,
122 p.unit().to_bits(),
123 p.sink as u32,
124 p.sink_div,
125 w as u32,
126 place.rope_theta.is_some() as u32 | (place.decode_only as u32) << 1,
127 place.rope_theta.unwrap_or(0.0).to_bits(),
128 (g * w / 2) as u32,
129 p.hd as u32,
130 ]);
131 self.scratch_words += ncls[hg] * ctx_words;
132 }
133 self.stream = self
134 .bytes
135 .chunks(4)
136 .map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]]))
137 .collect();
138 Ok(())
139 }
140
141 /// Number of lanes.
142 pub fn n_lanes(&self) -> usize {
143 self.lanes.len() / LANE_WORDS
144 }
145
146 /// The shared prior table.
147 pub fn prior() -> Vec<u32> {
148 gpu_prior_table()
149 }
150}