1use crate::qtensor::QTensor;
26
27pub const UNDO_DEPTH: usize = 64;
31
32pub struct BoundedWeights {
34 pub wq: QTensor,
35 pub wk: QTensor,
36 pub wv: QTensor,
37 pub wo: QTensor,
38 pub sink_k: Vec<f32>,
40 pub sink_v: Vec<f32>,
42 pub sink: usize,
43 pub window: usize,
45 pub group_windows: Vec<usize>,
48}
49
50#[derive(Debug, Clone)]
55pub struct BoundedRope {
56 pub window: usize,
57 pub half: usize,
60 pub cos: Vec<f32>,
61 pub sin: Vec<f32>,
62}
63
64impl BoundedRope {
65 pub fn new(window: usize, inv_freq: &[f32], rope_scale: f32) -> Self {
69 let half = inv_freq.len();
70 let s2 = (rope_scale as f64) * (rope_scale as f64);
71 let mut cos = Vec::with_capacity(window * half);
72 let mut sin = Vec::with_capacity(window * half);
73 for delta in 0..window {
74 for &f in inv_freq {
75 let (sn, cs) = ((delta as f64) * (f as f64)).sin_cos();
76 cos.push((cs * s2) as f32);
77 sin.push((sn * s2) as f32);
78 }
79 }
80 Self {
81 window,
82 half,
83 cos,
84 sin,
85 }
86 }
87
88 #[inline]
90 pub fn rotate(&self, delta: usize, x: &[f32], out: &mut [f32]) {
91 let half = self.half;
92 let c = &self.cos[delta * half..(delta + 1) * half];
93 let s = &self.sin[delta * half..(delta + 1) * half];
94 for i in 0..half {
95 let x0 = x[i];
96 let x1 = x[i + half];
97 out[i] = x0 * c[i] - x1 * s[i];
98 out[i + half] = x0 * s[i] + x1 * c[i];
99 }
100 let r = 2 * half;
101 if x.len() > r {
102 out[r..x.len()].copy_from_slice(&x[r..]);
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct BoundedSnapshot {
110 ring_k: Vec<f32>,
111 ring_v: Vec<f32>,
112 seen: usize,
113}
114
115#[derive(Debug, Clone)]
120pub struct BoundedState {
121 pub window: usize,
122 pub num_kv_heads: usize,
123 pub head_dim: usize,
124 pub ring_k: Vec<f32>,
126 pub ring_v: Vec<f32>,
128 pub seen: usize,
130 undo_k: Vec<f32>,
133 undo_v: Vec<f32>,
134 undo_len: usize,
135 undo_head: usize,
136}
137
138impl BoundedState {
139 pub fn new(num_kv_heads: usize, head_dim: usize, window: usize) -> Self {
140 let n = num_kv_heads * window * head_dim;
141 let u = UNDO_DEPTH * num_kv_heads * head_dim;
142 Self {
143 window,
144 num_kv_heads,
145 head_dim,
146 ring_k: vec![0.0; n],
147 ring_v: vec![0.0; n],
148 seen: 0,
149 undo_k: vec![0.0; u],
150 undo_v: vec![0.0; u],
151 undo_len: 0,
152 undo_head: 0,
153 }
154 }
155
156 #[inline]
158 pub fn len(&self) -> usize {
159 self.seen.min(self.window)
160 }
161
162 #[inline]
163 pub fn is_empty(&self) -> bool {
164 self.seen == 0
165 }
166
167 #[inline]
169 pub fn head(&self) -> usize {
170 self.seen % self.window
171 }
172
173 pub fn state_bytes(&self) -> usize {
176 (self.ring_k.len() + self.ring_v.len()) * std::mem::size_of::<f32>()
177 + std::mem::size_of::<u64>()
178 }
179
180 pub fn clear(&mut self) {
182 self.ring_k.fill(0.0);
183 self.ring_v.fill(0.0);
184 self.seen = 0;
185 self.undo_len = 0;
186 self.undo_head = 0;
187 }
188
189 pub fn insert(&mut self, k: &[f32], v: &[f32]) {
192 let (kvh, hd, w) = (self.num_kv_heads, self.head_dim, self.window);
193 debug_assert_eq!(k.len(), kvh * hd);
194 debug_assert_eq!(v.len(), kvh * hd);
195 let slot = self.head();
196 let u = self.undo_head;
197 for h in 0..kvh {
198 let r = (h * w + slot) * hd;
199 let uo = (u * kvh + h) * hd;
200 self.undo_k[uo..uo + hd].copy_from_slice(&self.ring_k[r..r + hd]);
201 self.undo_v[uo..uo + hd].copy_from_slice(&self.ring_v[r..r + hd]);
202 self.ring_k[r..r + hd].copy_from_slice(&k[h * hd..(h + 1) * hd]);
203 self.ring_v[r..r + hd].copy_from_slice(&v[h * hd..(h + 1) * hd]);
204 }
205 self.undo_head = (u + 1) % UNDO_DEPTH;
206 self.undo_len = (self.undo_len + 1).min(UNDO_DEPTH);
207 self.seen += 1;
208 }
209
210 pub fn rollback(&mut self, n: usize) -> usize {
214 let (kvh, hd, w) = (self.num_kv_heads, self.head_dim, self.window);
215 let n = n.min(self.undo_len).min(self.seen);
216 for _ in 0..n {
217 self.seen -= 1;
218 let slot = self.seen % w;
219 let u = (self.undo_head + UNDO_DEPTH - 1) % UNDO_DEPTH;
220 for h in 0..kvh {
221 let r = (h * w + slot) * hd;
222 let uo = (u * kvh + h) * hd;
223 self.ring_k[r..r + hd].copy_from_slice(&self.undo_k[uo..uo + hd]);
224 self.ring_v[r..r + hd].copy_from_slice(&self.undo_v[uo..uo + hd]);
225 }
226 self.undo_head = u;
227 self.undo_len -= 1;
228 }
229 n
230 }
231
232 pub fn snapshot(&self) -> BoundedSnapshot {
233 BoundedSnapshot {
234 ring_k: self.ring_k.clone(),
235 ring_v: self.ring_v.clone(),
236 seen: self.seen,
237 }
238 }
239
240 pub fn restore(&mut self, s: &BoundedSnapshot) {
243 debug_assert_eq!(s.ring_k.len(), self.ring_k.len());
244 self.ring_k.copy_from_slice(&s.ring_k);
245 self.ring_v.copy_from_slice(&s.ring_v);
246 self.seen = s.seen;
247 self.undo_len = 0;
248 self.undo_head = 0;
249 }
250
251 pub fn same_state(&self, other: &BoundedState) -> bool {
253 self.window == other.window
254 && self.seen == other.seen
255 && self.ring_k == other.ring_k
256 && self.ring_v == other.ring_v
257 }
258
259 #[allow(clippy::too_many_arguments)]
264 pub fn attend(
265 &self,
266 q: &[f32],
267 num_heads: usize,
268 sink_k: &[f32],
269 sink_v: &[f32],
270 sink: usize,
271 group_windows: &[usize],
272 rope: &BoundedRope,
273 scale: f32,
274 out: &mut [f32],
275 ) {
276 let (kvh, hd, w) = (self.num_kv_heads, self.head_dim, self.window);
277 let nh = num_heads;
278 let hpk = nh / kvh.max(1);
279 debug_assert_eq!(hpk * kvh, nh);
280 debug_assert_eq!(q.len(), nh * hd);
281 debug_assert_eq!(out.len(), nh * hd);
282 debug_assert_eq!(sink_k.len(), kvh * sink * hd);
283 debug_assert_eq!(rope.window, w);
284 debug_assert!(group_windows.is_empty() || group_windows.len() == kvh);
285 let m = self.len();
286 let n = sink + m;
287 let mg = |g: usize| group_windows.get(g).map_or(m, |&gw| gw.min(m));
289 let head = self.head();
290 thread_local! {
291 static SCRATCH: std::cell::RefCell<(Vec<f32>, Vec<f32>)> =
292 const { std::cell::RefCell::new((Vec::new(), Vec::new())) };
293 }
294 SCRATCH.with(|s| {
295 let mut s = s.borrow_mut();
296 let (scores, qrot) = &mut *s;
297 scores.clear();
298 scores.resize(nh * n, 0.0);
299 qrot.clear();
300 qrot.resize(nh * hd, 0.0);
301 for h in 0..nh {
303 let g = h / hpk;
304 let qh = &q[h * hd..(h + 1) * hd];
305 for s in 0..sink {
306 let kr = &sink_k[(g * sink + s) * hd..(g * sink + s + 1) * hd];
307 scores[h * n + s] = crate::attention::dot_f32(qh, kr) * scale;
308 }
309 }
310 for d in 0..m {
313 let slot = (head + w - 1 - d) % w;
314 for h in 0..nh {
315 if d >= mg(h / hpk) {
316 continue;
317 }
318 rope.rotate(d, &q[h * hd..(h + 1) * hd], &mut qrot[h * hd..(h + 1) * hd]);
319 }
320 for h in 0..nh {
321 let g = h / hpk;
322 if d >= mg(g) {
323 continue;
324 }
325 let kr = &self.ring_k[(g * w + slot) * hd..(g * w + slot + 1) * hd];
326 scores[h * n + sink + d] =
327 crate::attention::dot_f32(&qrot[h * hd..(h + 1) * hd], kr) * scale;
328 }
329 }
330 for h in 0..nh {
333 let sc = &mut scores[h * n..h * n + sink + mg(h / hpk)];
334 let max = sc.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
335 let mut sum = 0.0f32;
336 for v in sc.iter_mut() {
337 *v = (*v - max).exp();
338 sum += *v;
339 }
340 if sum > 0.0 {
341 for v in sc.iter_mut() {
342 *v /= sum;
343 }
344 }
345 }
346 out.fill(0.0);
347 for h in 0..nh {
348 let g = h / hpk;
349 let oh = &mut out[h * hd..(h + 1) * hd];
350 let p = &scores[h * n..(h + 1) * n];
351 for s in 0..sink {
352 let vr = &sink_v[(g * sink + s) * hd..(g * sink + s + 1) * hd];
353 if p[s].abs() >= 1e-12 {
354 crate::attention::axpy_f32(oh, vr, p[s]);
355 }
356 }
357 for d in 0..mg(g) {
358 let slot = (head + w - 1 - d) % w;
359 let vr = &self.ring_v[(g * w + slot) * hd..(g * w + slot + 1) * hd];
360 let pw = p[sink + d];
361 if pw.abs() >= 1e-12 {
362 crate::attention::axpy_f32(oh, vr, pw);
363 }
364 }
365 }
366 });
367 }
368}
369
370pub struct BoundedAttnCfg<'a> {
373 pub num_heads: usize,
374 pub num_kv_heads: usize,
375 pub head_dim: usize,
376 pub hidden_size: usize,
377 pub scale: f32,
379 pub rope: &'a BoundedRope,
380 pub pool: Option<&'a crate::pool::Pool>,
381}
382
383pub fn bounded_attention(
386 hidden: &[f32],
387 w: &BoundedWeights,
388 cache: &mut crate::kv_cache::LayerKvCache,
389 cfg: &BoundedAttnCfg,
390) -> Vec<f32> {
391 let (nh, nkv, hd) = (cfg.num_heads, cfg.num_kv_heads, cfg.head_dim);
392 let mut q = crate::attention::take_buf(nh * hd);
393 let mut k = crate::attention::take_buf(nkv * hd);
394 let mut v = crate::attention::take_buf(nkv * hd);
395 w.wq.matvec(hidden, &mut q, cfg.pool);
396 w.wk.matvec(hidden, &mut k, cfg.pool);
397 w.wv.matvec(hidden, &mut v, cfg.pool);
398 let mut ao = crate::attention::take_buf(nh * hd);
399 cache.bounded_step(&q, &k, &v, w, cfg.rope, cfg.scale, nh, &mut ao);
400 let mut out = crate::attention::take_buf(cfg.hidden_size);
401 w.wo.matvec(&ao, &mut out, cfg.pool);
402 crate::attention::recycle_buf(&mut q);
403 crate::attention::recycle_buf(&mut k);
404 crate::attention::recycle_buf(&mut v);
405 crate::attention::recycle_buf(&mut ao);
406 out
407}
408
409pub fn bounded_attention_batch(
415 normed_all: &[f32],
416 b: usize,
417 w: &BoundedWeights,
418 cache: &mut crate::kv_cache::LayerKvCache,
419 cfg: &BoundedAttnCfg,
420) -> Vec<f32> {
421 let (nh, nkv, hd, hs) = (cfg.num_heads, cfg.num_kv_heads, cfg.head_dim, cfg.hidden_size);
422 debug_assert_eq!(normed_all.len(), b * hs);
423 let mut q_all = crate::attention::take_buf(b * nh * hd);
424 let mut k_all = crate::attention::take_buf(b * nkv * hd);
425 let mut v_all = crate::attention::take_buf(b * nkv * hd);
426 w.wq.matmat(normed_all, b, &mut q_all, cfg.pool);
427 w.wk.matmat(normed_all, b, &mut k_all, cfg.pool);
428 w.wv.matmat(normed_all, b, &mut v_all, cfg.pool);
429 let mut ao_all = crate::attention::take_buf(b * nh * hd);
430 for bi in 0..b {
431 let q = &q_all[bi * nh * hd..(bi + 1) * nh * hd];
432 let k = &k_all[bi * nkv * hd..(bi + 1) * nkv * hd];
433 let v = &v_all[bi * nkv * hd..(bi + 1) * nkv * hd];
434 let ao = &mut ao_all[bi * nh * hd..(bi + 1) * nh * hd];
435 cache.bounded_step(q, k, v, w, cfg.rope, cfg.scale, nh, ao);
436 }
437 let mut out = crate::attention::take_buf(b * hs);
438 w.wo.matmat(&ao_all, b, &mut out, cfg.pool);
439 crate::attention::recycle_buf(&mut q_all);
440 crate::attention::recycle_buf(&mut k_all);
441 crate::attention::recycle_buf(&mut v_all);
442 crate::attention::recycle_buf(&mut ao_all);
443 out
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 fn synth(n: usize, salt: u64, scale: f32) -> Vec<f32> {
451 (0..n)
452 .map(|i| {
453 let x = (i as u64)
454 .wrapping_mul(6364136223846793005)
455 .wrapping_add(salt.wrapping_mul(1442695040888963407) ^ 0x9E3779B97F4A7C15);
456 let x = (x ^ (x >> 31)).wrapping_mul(0xBF58476D1CE4E5B9);
457 (((x >> 11) as f64 / (1u64 << 53) as f64 - 0.5) as f32) * scale
458 })
459 .collect()
460 }
461
462 #[test]
466 fn relative_rotation_equals_absolute_pair() {
467 let hd = 16;
468 let inv = crate::attention::rope_inv_freq(hd, 10_000.0);
469 let rope = BoundedRope::new(64, &inv, 1.0);
470 for (t, j) in [(0usize, 0usize), (5, 5), (7, 3), (63, 0), (300, 250), (1000, 990)] {
471 let q = synth(hd, t as u64 + 1, 1.0);
472 let k = synth(hd, j as u64 + 77, 1.0);
473 let mut qa = q.clone();
474 let mut ka = k.clone();
475 crate::attention::rope_rotate_scaled(&mut qa, t, &inv, 1.0);
476 crate::attention::rope_rotate_scaled(&mut ka, j, &inv, 1.0);
477 let absolute: f64 = qa.iter().zip(&ka).map(|(a, b)| (*a as f64) * (*b as f64)).sum();
478 let mut qr = vec![0.0; hd];
479 rope.rotate(t - j, &q, &mut qr);
480 let relative: f64 = qr.iter().zip(&k).map(|(a, b)| (*a as f64) * (*b as f64)).sum();
481 assert!(
482 (absolute - relative).abs() < 2e-4,
483 "t={t} j={j}: absolute {absolute} vs relative {relative}"
484 );
485 }
486 }
487
488 #[test]
489 fn rollback_restores_overwritten_slots_bit_for_bit() {
490 let (kvh, hd, w) = (2, 4, 8);
491 let mut st = BoundedState::new(kvh, hd, w);
492 for p in 0..20 {
493 st.insert(&synth(kvh * hd, p, 1.0), &synth(kvh * hd, 100 + p, 1.0));
494 }
495 let snap = st.snapshot();
496 for p in 20..25 {
497 st.insert(&synth(kvh * hd, p, 1.0), &synth(kvh * hd, 100 + p, 1.0));
498 }
499 assert_eq!(st.rollback(5), 5);
500 assert!(st.same_state(&{
501 let mut s = BoundedState::new(kvh, hd, w);
502 s.restore(&snap);
503 s
504 }));
505 assert_eq!(st.seen, 20);
506 assert_eq!(st.len(), w);
507 }
508}