Skip to main content

cortiq_engine/
dory.rs

1//! NVIDIA Nemotron Labs Dory: a looped normalized GPT.
2//!
3//! The layer stack is three blocks, input, recurrent and output, and the
4//! recurrent block runs `loops` times per token with the input block's output
5//! mixed back in before each pass. A physical layer is one sub-layer, attention
6//! or a SwiGLU MLP, and everything lives on the unit sphere (nGPT):
7//!
8//! ```text
9//! attention  q,k,v,g = W·x; rotate q,k; q = s_q²·q̂, k = k̂ (per head)
10//!            a = softmax(q·k/√hd) over the window, y = Σ a·v (16 q heads over 4 kv heads)
11//!            y = (y ⊙ σ(g ⊙ s_g²))^ , out = O·y
12//! mlp        g,u = W·x ⊙ s·√hidden; y = (silu(g) ⊙ u)^ ; out = D·y
13//! residual   x = (x̂ + |α|·(ôut − x̂))^
14//! loop       state = input; h = (state + input)^; h = R(h); state = ĥ
15//! logits     H·h ⊙ scale / σ₀
16//! ```
17//!
18//! `per_loop` keeps a KV cache per loop; `last_loop` keeps one per recurrent
19//! attention layer, each loop overwriting this chunk's rows. Earlier rows then
20//! hold their last loop's keys, so the loop count can change from token to token
21//! and the cache has no holes.
22
23use crate::pipeline::ArchStore;
24use crate::pool::Pool;
25use crate::qtensor::QTensor;
26use cortiq_core::{CmfModel, DoryConfig, ModelArch};
27use std::sync::Arc;
28
29/// `justnorm`'s epsilon in the reference.
30pub const EPS: f32 = 1e-6;
31pub const ARCH: &str = "dory";
32/// The most loops a request may ask for.
33pub const MAX_LOOPS: usize = 16;
34/// Rows a chat turn may go back on the device: a sliding ring holds the window and
35/// this much, so a turn whose template rewrites the last answer keeps the rest.
36pub const REWIND: usize = 448;
37
38#[derive(Clone, Copy, PartialEq, Eq, Debug)]
39pub enum KvMode {
40    PerLoop,
41    LastLoop,
42}
43
44impl KvMode {
45    pub fn parse(s: &str) -> Option<Self> {
46        match s {
47            "per_loop" => Some(Self::PerLoop),
48            "last_loop" => Some(Self::LastLoop),
49            _ => None,
50        }
51    }
52}
53
54#[derive(Clone, Debug)]
55pub struct DoryCfg {
56    pub hidden: usize,
57    pub ffn: usize,
58    pub vocab: usize,
59    pub nh: usize,
60    pub nk: usize,
61    pub hd: usize,
62    pub window: usize,
63    pub n_in: usize,
64    pub n_rec: usize,
65    pub n_out: usize,
66    pub loops: usize,
67    pub kv: KvMode,
68    pub attn_scale: f32,
69    /// Full layers read at most this many keys and keep no more: a constant cost per
70    /// token and bounded memory at any context length (`CMF_DORY_FULL_WINDOW`).
71    pub full_window: Option<usize>,
72}
73
74pub struct AttnWeights {
75    pub q: QTensor,
76    pub k: QTensor,
77    pub v: QTensor,
78    pub g: QTensor,
79    pub o: QTensor,
80    /// `(sqk·√hd/σ₀)²`
81    pub sq: Vec<f32>,
82    /// `(s_gate·√g₀/σ₀)²`
83    pub sg: Vec<f32>,
84    pub alpha: Vec<f32>,
85    pub sliding: bool,
86    /// Position among the attention layers: the KV slot table's row.
87    pub ordinal: usize,
88}
89
90pub struct MlpWeights {
91    pub gate: QTensor,
92    pub up: QTensor,
93    pub down: QTensor,
94    /// `suv·√hidden`
95    pub sg: Vec<f32>,
96    pub su: Vec<f32>,
97    pub alpha: Vec<f32>,
98}
99
100pub enum Layer {
101    Attn(Box<AttnWeights>),
102    Mlp(Box<MlpWeights>),
103}
104
105pub struct Dory {
106    /// The file, for a device that binds its mapping as it is.
107    pub file: Option<Arc<CmfModel>>,
108    pub cfg: DoryCfg,
109    pub embed: QTensor,
110    pub head: QTensor,
111    /// `logit_scale/σ₀`
112    pub logit_scale: Vec<f32>,
113    pub layers: Vec<Layer>,
114    pub inv_full: Vec<f32>,
115    pub inv_sliding: Vec<f32>,
116    pub attn_layers: usize,
117    pub o1: Option<DoryO1>,
118    /// How a token leaves the loop early, on the last-loop cache.
119    pub gate: Option<Gate>,
120    /// Passes over a prompt chunk whose logits nobody reads (`CMF_DORY_PROMPT_LOOPS`):
121    /// its cache rows are all that stays of it. 0: as many as any other chunk.
122    pub prompt_loops: usize,
123    /// Rows of a sealed prompt the far field would lose (`CMF_DORY_EXACT=FILE:rows`).
124    pub exact: Option<ExactRows>,
125    /// A long prompt's rows go to files here, and to RAM without it (`CMF_O1_SPILL`).
126    pub spill: Option<std::path::PathBuf>,
127}
128
129/// Which rows of a prompt stay exact past the seal: passwords, codes and the like, found
130/// by what the layers do with them and not by what they look like. Per KV head of each
131/// full layer, the part of a key outside the span of the far field's landmarks and the
132/// value's norm, as ranks within the prompt; a linear score over them, fitted on planted
133/// facts (`scripts/dory/router/illum_budget.py`).
134#[derive(Clone)]
135pub struct ExactRows {
136    pub weights: Arc<Vec<f32>>,
137    pub budget: usize,
138}
139
140/// Keys a sealed token reads exactly, on any backend.
141pub const MAX_NEAR: usize = 8192;
142
143/// A sealed prompt's last rows read the chosen ones: a question carries what it found in
144/// the text on to the answer, so the choice comes before it.
145pub const EXACT_TAIL: usize = 512;
146
147/// Neighbours kept on each side of a chosen row: a fact is several tokens.
148const EXACT_NEIGHBOURS: usize = 2;
149
150/// One row in this many of a sealed prompt keeps its queries for the far field's rebuild:
151/// landmark queries are means over long segments, and a row's take 8 KB per full layer.
152pub const QT_STRIDE: usize = 8;
153
154fn rank_share(v: &[f32]) -> Vec<f32> {
155    let n = v.len();
156    let mut idx: Vec<u32> = (0..n as u32).collect();
157    idx.sort_unstable_by(|&a, &b| v[a as usize].total_cmp(&v[b as usize]));
158    let mut out = vec![0f32; n];
159    let d = (n.max(2) - 1) as f32;
160    for (i, &r) in idx.iter().enumerate() {
161        out[r as usize] = i as f32 / d;
162    }
163    out
164}
165
166/// One layer's rows for the choice of exact ones, however its cache lays them out.
167pub trait LayerRows: Sync {
168    fn key(&self, kh: usize, r: usize) -> &[f32];
169    fn value(&self, kh: usize, r: usize) -> &[f32];
170}
171
172/// `[row][nk][hd]`, the layout of Dory's slots.
173pub struct FlatRows<'a> {
174    pub k: &'a [f32],
175    pub v: &'a [f32],
176    pub nk: usize,
177    pub hd: usize,
178}
179
180impl LayerRows for FlatRows<'_> {
181    fn key(&self, kh: usize, r: usize) -> &[f32] {
182        &self.k[(r * self.nk + kh) * self.hd..(r * self.nk + kh + 1) * self.hd]
183    }
184    fn value(&self, kh: usize, r: usize) -> &[f32] {
185        &self.v[(r * self.nk + kh) * self.hd..(r * self.nk + kh + 1) * self.hd]
186    }
187}
188
189/// Per row, how much of a head's key lies outside the span of `m` segment means.
190fn key_residuals(l0: &dyn LayerRows, rows: usize, kh: usize, hd: usize, m: usize) -> Vec<f32> {
191    const RIDGE: f64 = 1e-4;
192    let at = |r: usize| l0.key(kh, r);
193    let mut l = vec![0f32; m * hd];
194    for a in 0..m {
195        let (lo, hi) = (a * rows / m, ((a + 1) * rows / m).max(a * rows / m + 1).min(rows));
196        let row = &mut l[a * hd..(a + 1) * hd];
197        for r in lo..hi {
198            crate::attention::axpy_f32(row, at(r), 1.0);
199        }
200        let f = 1.0 / (hi - lo) as f32;
201        row.iter_mut().for_each(|x| *x *= f);
202    }
203    // The inverse of the landmarks' Gram matrix, by Cholesky.
204    let mut g = vec![0f64; m * m];
205    for a in 0..m {
206        for b in 0..=a {
207            let v = crate::attention::dot_f32(&l[a * hd..(a + 1) * hd], &l[b * hd..(b + 1) * hd]) as f64;
208            g[a * m + b] = v + if a == b { RIDGE } else { 0.0 };
209        }
210    }
211    for a in 0..m {
212        for b in 0..=a {
213            let mut v = g[a * m + b];
214            for c in 0..b {
215                v -= g[a * m + c] * g[b * m + c];
216            }
217            g[a * m + b] = if a == b { v.max(1e-12).sqrt() } else { v / g[b * m + b] };
218        }
219    }
220    let mut inv = vec![0f32; m * m];
221    let mut col = vec![0f64; m];
222    for e in 0..m {
223        for a in 0..m {
224            let mut v = if a == e { 1.0 } else { 0.0 };
225            for c in 0..a {
226                v -= g[a * m + c] * col[c];
227            }
228            col[a] = v / g[a * m + a];
229        }
230        for a in (0..m).rev() {
231            let mut v = col[a];
232            for c in a + 1..m {
233                v -= g[c * m + a] * col[c];
234            }
235            col[a] = v / g[a * m + a];
236        }
237        for a in 0..m {
238            inv[a * m + e] = col[a] as f32;
239        }
240    }
241    let mut y = vec![0f32; m];
242    let mut c = vec![0f32; m];
243    (0..rows)
244        .map(|r| {
245            let k = at(r);
246            for a in 0..m {
247                y[a] = crate::attention::dot_f32(&l[a * hd..(a + 1) * hd], k);
248            }
249            for a in 0..m {
250                c[a] = crate::attention::dot_f32(&inv[a * m..(a + 1) * m], &y);
251            }
252            crate::attention::dot_f32(k, k) - crate::attention::dot_f32(&c, &y) - RIDGE as f32 * crate::attention::dot_f32(&c, &c)
253        })
254        .collect()
255}
256
257/// The rows to keep exact, ascending. `lo..hi` are the rows that can go, which leaves
258/// out the sinks and the window. A row goes with its neighbours until the budget is spent.
259pub fn pick_exact_rows(x: &ExactRows, layers: &[&dyn LayerRows], rows: usize, nk: usize, hd: usize, m: usize, lo: usize, hi: usize) -> Vec<usize> {
260    let hi = hi.min(rows);
261    if hi <= lo || rows < 2 {
262        return Vec::new();
263    }
264    let dump = std::env::var("CMF_DORY_EXACT_FEATURES").ok();
265    if x.weights.len() != layers.len() * 2 * nk && dump.is_none() {
266        return Vec::new();
267    }
268    let m = m.min(rows).max(1);
269    let tasks: Vec<(usize, usize)> = (0..layers.len()).flat_map(|l| (0..nk).map(move |h| (l, h))).collect();
270    let threads = std::thread::available_parallelism().map_or(4, |n| n.get()).min(16).min(tasks.len()).max(1);
271    let next = std::sync::atomic::AtomicUsize::new(0);
272    let total = std::sync::Mutex::new(vec![0f32; rows]);
273    let table = std::sync::Mutex::new(vec![0f32; if dump.is_some() { rows * tasks.len() * 2 } else { 0 }]);
274    std::thread::scope(|sc| {
275        for _ in 0..threads {
276            sc.spawn(|| loop {
277                let t = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
278                let Some(&(l, kh)) = tasks.get(t) else { break };
279                let res = rank_share(&key_residuals(layers[l], rows, kh, hd, m));
280                let vn: Vec<f32> = (0..rows)
281                    .map(|r| {
282                        let v = layers[l].value(kh, r);
283                        crate::attention::dot_f32(v, v)
284                    })
285                    .collect();
286                let vn = rank_share(&vn);
287                if dump.is_some() {
288                    let mut tb = table.lock().unwrap();
289                    let (f0, f1) = ((l * 2) * nk + kh, (l * 2 + 1) * nk + kh);
290                    let nf = tasks.len() * 2;
291                    for r in 0..rows {
292                        tb[r * nf + f0] = res[r];
293                        tb[r * nf + f1] = vn[r];
294                    }
295                }
296                if x.weights.len() == tasks.len() * 2 {
297                    let (wr, wv) = (x.weights[(l * 2) * nk + kh], x.weights[(l * 2 + 1) * nk + kh]);
298                    let mut tot = total.lock().unwrap();
299                    for r in 0..rows {
300                        tot[r] += wr * res[r] + wv * vn[r];
301                    }
302                }
303            });
304        }
305    });
306    if let Some(f) = dump {
307        let tb = table.into_inner().unwrap();
308        let b: Vec<u8> = tb.iter().flat_map(|v| v.to_le_bytes()).collect();
309        let _ = std::fs::write(f, b);
310    }
311    let score = total.into_inner().unwrap();
312    if let Ok(f) = std::env::var("CMF_DORY_EXACT_LOG") {
313        let b: Vec<u8> = score.iter().flat_map(|v| v.to_le_bytes()).collect();
314        let _ = std::fs::write(f, b);
315    }
316    let mut order: Vec<u32> = (lo as u32..hi as u32).collect();
317    order.sort_unstable_by(|&a, &b| score[b as usize].total_cmp(&score[a as usize]));
318    let mut keep = vec![false; rows];
319    let mut count = 0usize;
320    'pick: for r in order {
321        let r = r as usize;
322        for j in r.saturating_sub(EXACT_NEIGHBOURS).max(lo)..(r + EXACT_NEIGHBOURS + 1).min(hi) {
323            if count >= x.budget {
324                break 'pick;
325            }
326            if !keep[j] {
327                keep[j] = true;
328                count += 1;
329            }
330        }
331    }
332    (0..rows).filter(|&r| keep[r]).collect()
333}
334
335/// The order rows go into a seal: the sinks, the rows kept exact, then the rest as they
336/// came, so the window stays the last rows and the kept ones leave the far field.
337pub fn seal_order(rows: usize, sink: usize, keep: &[usize]) -> Vec<u32> {
338    let mut chosen = vec![false; rows];
339    keep.iter().for_each(|&r| chosen[r] = true);
340    (0..sink.min(rows))
341        .chain(keep.iter().copied())
342        .chain((sink.min(rows)..rows).filter(|&r| !chosen[r]))
343        .map(|r| r as u32)
344        .collect()
345}
346
347/// A slot's far field, one state per KV head, over its rows taken in `order`. `ks` and `vs` are
348/// `[row][nk][hd]`, `qs` `[row / stride][nh][hd]`; `kept` rows after the sinks stay exact.
349#[allow(clippy::too_many_arguments)]
350pub fn far_states(c: &DoryCfg, o1: DoryO1, ks: &[f32], vs: &[f32], qs: &[f32], stride: usize, rows: usize, order: &[u32], kept: usize) -> Vec<crate::nystrom::NystromState> {
351    far_states_geom(c.nh, c.nk, c.hd, o1, ks, vs, qs, stride, rows, order, kept)
352}
353
354/// [`far_states`] for any attention geometry: `nh` query heads over `nk` KV heads of
355/// `hd`, keys and values `[row][nk][hd]`, the query trace `[row / stride][nh][hd]`
356/// (the Flash-Next device path seals through this one).
357#[allow(clippy::too_many_arguments)]
358pub fn far_states_geom(nh: usize, nk: usize, hd: usize, o1: DoryO1, ks: &[f32], vs: &[f32], qs: &[f32], stride: usize, rows: usize, order: &[u32], kept: usize) -> Vec<crate::nystrom::NystromState> {
359    let rep = nh / nk;
360    let mut states = Vec::with_capacity(nk);
361    std::thread::scope(|sc| {
362        let mut hs = Vec::with_capacity(nk);
363        for kh in 0..nk {
364            hs.push(sc.spawn(move || {
365                let mut kg = vec![0f32; rows * hd];
366                let mut vg = vec![0f32; rows * hd];
367                let mut qg: Vec<Vec<f32>> = (0..rep).map(|_| vec![0f32; rows * hd]).collect();
368                for (r, &from) in order.iter().enumerate() {
369                    let from = from as usize;
370                    let at = (from * nk + kh) * hd;
371                    kg[r * hd..(r + 1) * hd].copy_from_slice(&ks[at..at + hd]);
372                    vg[r * hd..(r + 1) * hd].copy_from_slice(&vs[at..at + hd]);
373                    for (h, q) in qg.iter_mut().enumerate() {
374                        let at = (from / stride * nh + kh * rep + h) * hd;
375                        q[r * hd..(r + 1) * hd].copy_from_slice(&qs[at..at + hd]);
376                    }
377                }
378                let mut n = crate::nystrom::NystromState::new_group(o1.m, o1.w, o1.sink + kept, rep);
379                let refs: Vec<&[f32]> = qg.iter().map(|q| &q[..]).collect();
380                n.prefill_group(&refs, &kg, &vg, rows, hd, hd);
381                n
382            }));
383        }
384        for h in hs {
385            states.push(h.join().expect("seal worker"));
386        }
387    });
388    states
389}
390
391/// O(1) far field for the full-attention layers (`nystrom`): `CMF_DORY_O1=m,w,sink,seal`.
392/// A slot runs exact up to position `seal`, then an exact window of `w` keys plus a
393/// skeleton of `m` landmarks. Sliding layers are bounded already.
394#[derive(Clone, Copy, Debug)]
395pub struct DoryO1 {
396    pub m: usize,
397    pub w: usize,
398    pub sink: usize,
399    pub seal: usize,
400}
401
402impl DoryO1 {
403    pub fn from_env() -> Option<Self> {
404        let v = std::env::var("CMF_DORY_O1").ok()?;
405        let n: Vec<usize> = v.split(',').filter_map(|x| x.trim().parse().ok()).collect();
406        // Below the window plus a margin the state would be exact only.
407        (n.len() == 4).then(|| Self { m: n[0], w: n[1], sink: n[2], seal: n[3].max(n[1] + n[2] + 16) })
408    }
409}
410
411/// One KV slot: rows `first..first + len` of keys and values, `[row][nk][hd]`.
412#[derive(Default)]
413pub struct KvSlot {
414    pub k: Vec<f32>,
415    pub v: Vec<f32>,
416    pub first: usize,
417    /// Sealed, and its rows still kept for the choice of exact ones.
418    pub arch: bool,
419    /// Every row's queries `[row][nh][hd]`, until the O(1) seal; past it, the chunk's.
420    pub qt: Vec<f32>,
421    /// One state per KV head after the seal.
422    pub far: Vec<crate::nystrom::NystromState>,
423    /// The rows of a sealed prompt but for the chunk being read, in RAM or in files.
424    store: Option<SlotStore>,
425}
426
427struct SlotStore {
428    k: ArchStore,
429    v: ArchStore,
430    /// One row in `QT_STRIDE`.
431    q: ArchStore,
432    rows: usize,
433}
434
435impl SlotStore {
436    fn new(dir: Option<&std::path::Path>) -> Result<Self, String> {
437        // Two sequences of one process may seal at once.
438        static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
439        let n = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
440        let at = |kind: &str| ArchStore::new(dir, format!("dory-host-{n}-{kind}"));
441        Ok(Self { k: at("k")?, v: at("v")?, q: at("q")?, rows: 0 })
442    }
443
444    /// A chunk's rows, from position `pos`; its queries are `[row][nh][hd]`.
445    fn push(&mut self, k: &[f32], v: &[f32], q: &[f32], kv_row: usize, q_row: usize, pos: usize) -> Result<(), String> {
446        self.k.push(k)?;
447        self.v.push(v)?;
448        for (r, row) in q.chunks_exact(q_row).enumerate() {
449            if (pos + r) % QT_STRIDE == 0 {
450                self.q.push(row)?;
451            }
452        }
453        self.rows += k.len() / kv_row;
454        Ok(())
455    }
456}
457
458impl KvSlot {
459    fn rows(&self, row: usize) -> usize {
460        if row == 0 { 0 } else { self.k.len() / row }
461    }
462}
463
464/// Host KV state: `slots[attention ordinal][loop]`.
465#[derive(Default)]
466pub struct DoryState {
467    pub slots: Vec<Vec<KvSlot>>,
468    pub pos: usize,
469    scratch: Scratch,
470    /// The logits of a token whose first read-out ended it.
471    exit_logits: Option<Vec<f32>>,
472    exits: ExitRate,
473    pub gate_tokens: u64,
474    pub gate_one: u64,
475}
476
477impl DoryState {
478    pub fn clear(&mut self) {
479        for row in &mut self.slots {
480            for s in row {
481                s.k.clear();
482                s.v.clear();
483                s.first = 0;
484                s.arch = false;
485                s.store = None;
486                s.qt.clear();
487                s.far.clear();
488            }
489        }
490        self.pos = 0;
491    }
492
493    /// Forget the rows from position `n` on.
494    pub fn rollback(&mut self, n: usize, kv_row: usize) {
495        for row in &mut self.slots {
496            for s in row {
497                let end = s.first + s.rows(kv_row);
498                if end > n {
499                    let keep = n.saturating_sub(s.first) * kv_row;
500                    s.k.truncate(keep);
501                    s.v.truncate(keep);
502                }
503            }
504        }
505        self.pos = self.pos.min(n);
506    }
507}
508
509fn load_vec(model: &CmfModel, name: &str, n: usize) -> Result<Vec<f32>, String> {
510    let v = crate::loader::load_f32(model, name, &crate::loader::Overlay::None)?;
511    if v.len() != n {
512        return Err(format!("{name}: {} values, wanted {n}", v.len()));
513    }
514    Ok(v)
515}
516
517impl Dory {
518    pub fn from_model(model: &Arc<CmfModel>) -> Result<Self, String> {
519        let arch: &ModelArch = model.arch();
520        let rec: &DoryConfig = arch.dory.as_ref().ok_or("dory: no arch record")?;
521        let kv = KvMode::parse(&rec.recurrent_kv_cache_mode)
522            .ok_or_else(|| format!("dory: KV mode {}", rec.recurrent_kv_cache_mode))?;
523        let (hidden, ffn, nh, nk, hd) =
524            (arch.hidden_size, arch.intermediate_size, arch.num_attention_heads, arch.num_kv_heads, arch.head_dim);
525        let std = rec.initializer_range;
526        let alpha_f = (rec.ngpt_alpha_init / std) as f32;
527        let sqk_f = ((hd as f64).sqrt() / std) as f32;
528        let gate_f = (rec.gate_logit_scale_init.sqrt() / std) as f32;
529        let suv_f = (hidden as f64).sqrt() as f32;
530        let q_size = nh * hd;
531        let t = |name: String| QTensor::from_model(model, &name);
532        let mut layers = Vec::with_capacity(rec.layer_pattern.len());
533        let mut ordinal = 0usize;
534        for (li, kind) in rec.layer_pattern.chars().enumerate() {
535            let p = format!("model.layers.{li}");
536            let m = format!("{p}.mixer");
537            if kind == '-' {
538                let scale = |name: &str| -> Result<Vec<f32>, String> {
539                    let mut v = load_vec(model, &format!("{m}.{name}"), ffn)?;
540                    v.iter_mut().for_each(|x| *x *= suv_f);
541                    Ok(v)
542                };
543                let mut alpha = load_vec(model, &format!("{p}.mlp_alpha"), hidden)?;
544                alpha.iter_mut().for_each(|x| *x = (*x * alpha_f).abs());
545                layers.push(Layer::Mlp(Box::new(MlpWeights {
546                    gate: t(format!("{m}.gate_proj.weight"))?,
547                    up: t(format!("{m}.up_proj.weight"))?,
548                    down: t(format!("{m}.down_proj.weight"))?,
549                    sg: scale("suv_gate")?,
550                    su: scale("suv_up")?,
551                    alpha,
552                })));
553            } else {
554                let sq_vec = |name: &str, f: f32| -> Result<Vec<f32>, String> {
555                    let mut v = load_vec(model, &format!("{m}.{name}"), q_size)?;
556                    v.iter_mut().for_each(|x| {
557                        let s = *x * f;
558                        *x = s * s;
559                    });
560                    Ok(v)
561                };
562                let mut alpha = load_vec(model, &format!("{p}.attn_alpha"), hidden)?;
563                alpha.iter_mut().for_each(|x| *x = (*x * alpha_f).abs());
564                layers.push(Layer::Attn(Box::new(AttnWeights {
565                    q: t(format!("{m}.q_proj.weight"))?,
566                    k: t(format!("{m}.k_proj.weight"))?,
567                    v: t(format!("{m}.v_proj.weight"))?,
568                    g: t(format!("{m}.g_proj.weight"))?,
569                    o: t(format!("{m}.o_proj.weight"))?,
570                    sq: sq_vec("sqk", sqk_f)?,
571                    sg: sq_vec("s_gate", gate_f)?,
572                    alpha,
573                    sliding: kind == 'S',
574                    ordinal,
575                })));
576                ordinal += 1;
577            }
578        }
579        let mut logit_scale = load_vec(model, "lm_head.logit_scale", arch.vocab_size)?;
580        let inv = 1.0 / std as f32;
581        logit_scale.iter_mut().for_each(|x| *x *= inv);
582        let mut cfg = DoryCfg {
583            hidden,
584            ffn,
585            vocab: arch.vocab_size,
586            nh,
587            nk,
588            hd,
589            window: rec.sliding_window,
590            n_in: rec.n_input_layers,
591            n_rec: rec.n_recurrent_layers,
592            n_out: rec.n_output_layers,
593            loops: rec.n_recurrent_loops,
594            kv,
595            attn_scale: 1.0 / (hd as f32).sqrt(),
596            full_window: std::env::var("CMF_DORY_FULL_WINDOW").ok().and_then(|v| v.parse().ok()),
597        };
598        if let Some(n) = std::env::var("CMF_DORY_LOOPS").ok().and_then(|v| v.parse::<usize>().ok()) {
599            if n > MAX_LOOPS {
600                return Err(format!("CMF_DORY_LOOPS={n}: want at most {MAX_LOOPS}"));
601            }
602            cfg.loops = n;
603        }
604        if let Ok(v) = std::env::var("CMF_DORY_KV") {
605            cfg.kv = KvMode::parse(&v).ok_or_else(|| format!("CMF_DORY_KV={v}: want per_loop or last_loop"))?;
606        }
607        let auto = std::env::var("CMF_DORY_GATE").as_deref() == Ok("auto");
608        let mut gate = gate_from_env();
609        let mut o1 = DoryO1::from_env();
610        let mut prompt_loops: usize = std::env::var("CMF_DORY_PROMPT_LOOPS").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
611        let mut exact = exact_from_env();
612        // A turbo file runs so unless a flag says otherwise; a fixed loop count is one.
613        if let (Some(t), None) = (&rec.turbo, std::env::var_os("CMF_DORY_LOOPS")) {
614            if std::env::var_os("CMF_DORY_KV").is_none() {
615                cfg.kv = KvMode::LastLoop;
616            }
617            if gate.is_none() && cfg.kv == KvMode::LastLoop {
618                gate = Some(Gate::Recon(Arc::new(Topologies::from_model(model)?), t.exit_bias));
619            }
620            if o1.is_none() && std::env::var("CMF_DORY_O1").as_deref() != Ok("off") {
621                let [m, w, sink, seal] = t.o1;
622                o1 = Some(DoryO1 { m, w, sink, seal: seal.max(w + sink + 16) });
623            }
624            if std::env::var_os("CMF_DORY_PROMPT_LOOPS").is_none() {
625                prompt_loops = 1;
626            }
627            if t.exact_rows > 0 && std::env::var_os("CMF_DORY_EXACT").is_none() && o1.is_some() {
628                let w = crate::loader::load_f32(model, "dory.exact.weights", &crate::loader::Overlay::None)?;
629                exact = Some(ExactRows { weights: Arc::new(w), budget: t.exact_rows });
630            }
631        }
632        if let (Some(x), Some(o)) = (&exact, &o1) {
633            if o.w + o.sink + x.budget > MAX_NEAR {
634                return Err(format!("dory: window {} + sinks {} + exact rows {} go over the near field's {MAX_NEAR}", o.w, o.sink, x.budget));
635            }
636        }
637        // `--loops auto` without a router in the file: the top probability decides.
638        if auto && gate.is_none() {
639            gate = Some(Gate::Conf(0.7));
640        }
641        Ok(Self {
642            file: Some(model.clone()),
643            cfg,
644            embed: t("model.embed_tokens.weight".into())?,
645            head: t("lm_head.weight".into())?,
646            logit_scale,
647            layers,
648            inv_full: crate::attention::rope_inv_freq(hd, rec.rope_theta_full as f32),
649            inv_sliding: crate::attention::rope_inv_freq(hd, rec.rope_theta_sliding as f32),
650            attn_layers: ordinal,
651            o1,
652            gate,
653            prompt_loops,
654            exact,
655            spill: std::env::var_os("CMF_O1_SPILL").map(Into::into),
656        })
657    }
658
659    /// The passes over one chunk of a forward.
660    pub fn chunk_loops(&self, loops: usize, want: Want) -> usize {
661        if want == Want::None && self.prompt_loops > 0 && self.cfg.kv == KvMode::LastLoop {
662            loops.min(self.prompt_loops)
663        } else {
664            loops
665        }
666    }
667
668    /// Which loop's cache a layer uses.
669    #[inline]
670    fn slot_loop(&self, li: usize, loop_i: usize) -> usize {
671        let c = &self.cfg;
672        if c.kv == KvMode::PerLoop && li >= c.n_in && li < c.n_in + c.n_rec { loop_i } else { 0 }
673    }
674}
675
676/// `x / sqrt(Σx² + ε)`
677#[inline]
678pub fn justnorm(x: &mut [f32]) {
679    let r = 1.0 / (crate::attention::dot_f32(x, x) + EPS).sqrt();
680    for v in x.iter_mut() {
681        *v *= r;
682    }
683}
684
685/// The nGPT residual, written into `x`.
686fn slerp(x: &mut [f32], out: &mut [f32], alpha: &[f32]) {
687    justnorm(x);
688    justnorm(out);
689    for ((a, b), al) in x.iter_mut().zip(out.iter()).zip(alpha) {
690        *a += *al * (*b - *a);
691    }
692    justnorm(x);
693}
694
695/// `exp` on eight lanes, written so a compiler vectorizes it (Cephes `expf`, within
696/// 2e-7 on the range an activation or a softmax reaches).
697#[inline(always)]
698fn exp8(x: &[f32; 8]) -> [f32; 8] {
699    let mut r = [0f32; 8];
700    for i in 0..8 {
701        let v = x[i].clamp(-87.0, 88.0);
702        let n = (v * std::f32::consts::LOG2_E + 0.5).floor();
703        let g = v - n * 0.693_359_4 + n * 2.121_944_4e-4;
704        let p = ((((((1.987_569_1e-4 * g + 1.398_2e-3) * g + 8.333_452e-3) * g + 4.166_579_6e-2) * g + 1.666_666_5e-1) * g + 0.5) * g * g) + g + 1.0;
705        r[i] = p * f32::from_bits(((n as i32 + 127) << 23) as u32);
706    }
707    r
708}
709
710macro_rules! lanes8 {
711    ($body:ident, $avx:ident, ($($a:ident : $t:ty),*)) => {
712        #[cfg(target_arch = "x86_64")]
713        #[target_feature(enable = "avx2,fma")]
714        unsafe fn $avx($($a: $t),*) {
715            $body($($a),*)
716        }
717    };
718}
719
720/// `g[i] = silu(g[i]·a[i])·u[i]·b[i]`
721#[inline(always)]
722fn silu_body(g: &mut [f32], u: &[f32], a: &[f32], b: &[f32]) {
723    let n = g.len() & !7;
724    for i in (0..n).step_by(8) {
725        let mut x = [0f32; 8];
726        let mut m = [0f32; 8];
727        for j in 0..8 {
728            x[j] = g[i + j] * a[i + j];
729            m[j] = -x[j];
730        }
731        let e = exp8(&m);
732        for j in 0..8 {
733            g[i + j] = x[j] / (1.0 + e[j]) * (u[i + j] * b[i + j]);
734        }
735    }
736    for i in n..g.len() {
737        let x = g[i] * a[i];
738        g[i] = x / (1.0 + (-x).exp()) * (u[i] * b[i]);
739    }
740}
741lanes8!(silu_body, silu_avx2, (g: &mut [f32], u: &[f32], a: &[f32], b: &[f32]));
742
743fn silu_rows(g: &mut [f32], u: &[f32], a: &[f32], b: &[f32]) {
744    #[cfg(target_arch = "x86_64")]
745    if crate::qtensor::avx2_enabled() {
746        return unsafe { silu_avx2(g, u, a, b) };
747    }
748    silu_body(g, u, a, b)
749}
750
751/// `y[i] = y[i]·σ(g[i]·s[i])`
752#[inline(always)]
753fn gate_body(y: &mut [f32], g: &[f32], s: &[f32]) {
754    let n = y.len() & !7;
755    for i in (0..n).step_by(8) {
756        let mut m = [0f32; 8];
757        for j in 0..8 {
758            m[j] = -(g[i + j] * s[i + j]);
759        }
760        let e = exp8(&m);
761        for j in 0..8 {
762            y[i + j] /= 1.0 + e[j];
763        }
764    }
765    for i in n..y.len() {
766        y[i] /= 1.0 + (-(g[i] * s[i])).exp();
767    }
768}
769lanes8!(gate_body, gate_avx2, (y: &mut [f32], g: &[f32], s: &[f32]));
770
771fn gate_rows(y: &mut [f32], g: &[f32], s: &[f32]) {
772    #[cfg(target_arch = "x86_64")]
773    if crate::qtensor::avx2_enabled() {
774        return unsafe { gate_avx2(y, g, s) };
775    }
776    gate_body(y, g, s)
777}
778
779/// Softmax of a row of scores, in place.
780#[inline(always)]
781fn soft_body(row: &mut [f32]) {
782    let n = row.len();
783    let mut top = f32::NEG_INFINITY;
784    for v in row.iter() {
785        top = top.max(*v);
786    }
787    let mut den = 0f32;
788    let blocks = n & !7;
789    for i in (0..blocks).step_by(8) {
790        let mut m = [0f32; 8];
791        for j in 0..8 {
792            m[j] = row[i + j] - top;
793        }
794        let e = exp8(&m);
795        let mut acc = 0f32;
796        for j in 0..8 {
797            row[i + j] = e[j];
798            acc += e[j];
799        }
800        den += acc;
801    }
802    for v in row[blocks..].iter_mut() {
803        *v = (*v - top).exp();
804        den += *v;
805    }
806    let inv = 1.0 / den;
807    for v in row.iter_mut() {
808        *v *= inv;
809    }
810}
811lanes8!(soft_body, soft_avx2, (row: &mut [f32]));
812
813fn soft_row(row: &mut [f32]) {
814    #[cfg(target_arch = "x86_64")]
815    if crate::qtensor::avx2_enabled() {
816        return unsafe { soft_avx2(row) };
817    }
818    soft_body(row)
819}
820
821struct SendPtr(*mut f32);
822unsafe impl Send for SendPtr {}
823unsafe impl Sync for SendPtr {}
824impl SendPtr {
825    #[inline]
826    fn at(&self, i: usize) -> *mut f32 {
827        unsafe { self.0.add(i) }
828    }
829}
830
831fn for_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize) + Sync)) {
832    match pool {
833        Some(p) if n > 1 => p.run_rows(n, &|a, b| (a..b).for_each(f)),
834        _ => (0..n).for_each(f),
835    }
836}
837
838thread_local! {
839    static SCORES: std::cell::RefCell<Vec<f32>> = const { std::cell::RefCell::new(Vec::new()) };
840}
841
842/// The chunk's scratch, kept between forwards: a token touches 112 layers.
843#[derive(Default)]
844pub struct Scratch {
845    q: Vec<f32>,
846    k: Vec<f32>,
847    v: Vec<f32>,
848    g: Vec<f32>,
849    u: Vec<f32>,
850    out: Vec<f32>,
851    /// `[kind][row][cos half | sin half]`: sliding, full.
852    rope: [Vec<f32>; 2],
853}
854
855fn fit(v: &mut Vec<f32>, n: usize) {
856    v.resize(n, 0.0);
857}
858
859/// One attention sub-layer over the rows `h[t][hidden]`, positions from `pos0`.
860#[allow(clippy::too_many_arguments)]
861fn attn_layer(
862    m: &Dory,
863    w: &AttnWeights,
864    slot: &mut KvSlot,
865    h: &[f32],
866    t: usize,
867    pos0: usize,
868    overwrite: bool,
869    pool: Option<&Pool>,
870    sc: &mut Scratch,
871) {
872    let c = &m.cfg;
873    let (nh, nk, hd, hidden) = (c.nh, c.nk, c.hd, c.hidden);
874    let q_size = nh * hd;
875    let kv_row = nk * hd;
876    fit(&mut sc.q, t * q_size);
877    fit(&mut sc.k, t * kv_row);
878    fit(&mut sc.v, t * kv_row);
879    fit(&mut sc.g, t * q_size);
880    fit(&mut sc.out, t * hidden);
881    if t == 1 && !crate::gptq_capture::capturing() {
882        QTensor::matvec_many([&w.q, &w.k, &w.v, &w.g], h, [&mut sc.q[..], &mut sc.k[..], &mut sc.v[..], &mut sc.g[..]], pool);
883    } else {
884        w.q.matmat(h, t, &mut sc.q, pool);
885        w.k.matmat(h, t, &mut sc.k, pool);
886        w.v.matmat(h, t, &mut sc.v, pool);
887        w.g.matmat(h, t, &mut sc.g, pool);
888    }
889    let half = hd / 2;
890    let rope = &sc.rope[usize::from(!w.sliding)];
891    for r in 0..t {
892        let table = &rope[r * hd..(r + 1) * hd];
893        let (cs, sn) = table.split_at(half);
894        let rotate = |row: &mut [f32]| {
895            let (a, b) = row.split_at_mut(half);
896            for j in 0..half {
897                let (x, y) = (a[j], b[j]);
898                a[j] = x * cs[j] - y * sn[j];
899                b[j] = y * cs[j] + x * sn[j];
900            }
901            justnorm(row);
902        };
903        for (hi, row) in sc.q[r * q_size..(r + 1) * q_size].chunks_exact_mut(hd).enumerate() {
904            rotate(row);
905            for (x, s) in row.iter_mut().zip(&w.sq[hi * hd..(hi + 1) * hd]) {
906                *x *= *s;
907            }
908        }
909        for row in sc.k[r * kv_row..(r + 1) * kv_row].chunks_exact_mut(hd) {
910            rotate(row);
911        }
912    }
913    let rep = nh / nk;
914    // A sealed slot: the exact window and the far skeleton, row by row.
915    if !slot.far.is_empty() {
916        if slot.arch {
917            if overwrite {
918                let keep = slot.k.len() - t * kv_row;
919                slot.k.truncate(keep);
920                slot.v.truncate(keep);
921                let keep = slot.qt.len() - t * q_size;
922                slot.qt.truncate(keep);
923            }
924            slot.k.extend_from_slice(&sc.k);
925            slot.v.extend_from_slice(&sc.v);
926            slot.qt.extend_from_slice(&sc.q);
927        }
928        let mut y = vec![0f32; rep * hd];
929        for r in 0..t {
930            for kh in 0..nk {
931                let at_q = r * q_size + kh * rep * hd;
932                let at_k = (r * nk + kh) * hd;
933                let (q, k, v) = (&sc.q[at_q..at_q + rep * hd], &sc.k[at_k..at_k + hd], &sc.v[at_k..at_k + hd]);
934                if overwrite {
935                    slot.far[kh].restep_group(q, k, v, &mut y);
936                } else {
937                    slot.far[kh].step_group(q, k, v, &mut y);
938                }
939                gate_rows(&mut y, &sc.g[at_q..at_q + rep * hd], &w.sg[kh * rep * hd..(kh + 1) * rep * hd]);
940                sc.g[at_q..at_q + rep * hd].copy_from_slice(&y);
941            }
942        }
943    } else {
944        if !w.sliding && m.o1.is_some() {
945            if overwrite {
946                let keep = slot.qt.len() - t * q_size;
947                slot.qt.truncate(keep);
948            }
949            slot.qt.extend_from_slice(&sc.q);
950        }
951        // The chunk's rows join the cache; each one attends only up to itself.
952        if overwrite {
953            let keep = slot.k.len() - t * kv_row;
954            slot.k.truncate(keep);
955            slot.v.truncate(keep);
956        }
957        slot.k.extend_from_slice(&sc.k);
958        slot.v.extend_from_slice(&sc.v);
959        let (first, keys, vals) = (slot.first, &slot.k[..], &slot.v[..]);
960        let scale = c.attn_scale;
961        let res = SendPtr(sc.g.as_mut_ptr());
962        let window = if w.sliding { c.window } else { c.full_window.unwrap_or(usize::MAX) };
963        let q_ref = &sc.q;
964        let sg = &w.sg;
965        for_rows(pool, t * nk, &|task| {
966            let (r, kh) = (task / nk, task % nk);
967            let p = pos0 + r;
968            let lo = p.saturating_add(1).saturating_sub(window).max(first);
969            let n = p + 1 - lo;
970            let at_q = r * q_size + kh * rep * hd;
971            SCORES.with(|buf| {
972                let mut buf = buf.borrow_mut();
973                buf.resize(rep * (n + hd), 0.0);
974                let (scores, ys) = buf.split_at_mut(rep * n);
975                // A key once, against each query head of its group.
976                for e in 0..n {
977                    let row = (lo + e - first) * kv_row + kh * hd;
978                    let key = &keys[row..row + hd];
979                    for h in 0..rep {
980                        let qr = &q_ref[at_q + h * hd..at_q + (h + 1) * hd];
981                        scores[h * n + e] = crate::attention::dot_f32(qr, key) * scale;
982                    }
983                }
984                ys.iter_mut().for_each(|v| *v = 0.0);
985                for h in 0..rep {
986                    soft_row(&mut scores[h * n..(h + 1) * n]);
987                }
988                for e in 0..n {
989                    let row = (lo + e - first) * kv_row + kh * hd;
990                    let val = &vals[row..row + hd];
991                    for h in 0..rep {
992                        crate::attention::axpy_f32(&mut ys[h * hd..(h + 1) * hd], val, scores[h * n + e]);
993                    }
994                }
995                // `g` becomes the gated attention output.
996                let g = unsafe { std::slice::from_raw_parts_mut(res.at(at_q), rep * hd) };
997                gate_rows(ys, g, &sg[kh * rep * hd..(kh + 1) * rep * hd]);
998                g.copy_from_slice(ys);
999            });
1000        });
1001    }
1002    for row in sc.g.chunks_exact_mut(q_size) {
1003        justnorm(row);
1004    }
1005    if t == 1 && !crate::gptq_capture::capturing() {
1006        w.o.matvec(&sc.g, &mut sc.out, pool);
1007    } else {
1008        w.o.matmat(&sc.g, t, &mut sc.out, pool);
1009    }
1010}
1011
1012fn mlp_layer(w: &MlpWeights, h: &[f32], t: usize, ffn: usize, hidden: usize, pool: Option<&Pool>, sc: &mut Scratch) {
1013    fit(&mut sc.g, t * ffn);
1014    fit(&mut sc.u, t * ffn);
1015    fit(&mut sc.out, t * hidden);
1016    let single = t == 1 && !crate::gptq_capture::capturing();
1017    if single {
1018        QTensor::matvec_many([&w.gate, &w.up], h, [&mut sc.g[..], &mut sc.u[..]], pool);
1019    } else {
1020        w.gate.matmat(h, t, &mut sc.g, pool);
1021        w.up.matmat(h, t, &mut sc.u, pool);
1022    }
1023    for (gr, ur) in sc.g.chunks_exact_mut(ffn).zip(sc.u.chunks_exact(ffn)) {
1024        silu_rows(gr, ur, &w.sg, &w.su);
1025        justnorm(gr);
1026    }
1027    if single {
1028        w.down.matvec(&sc.g, &mut sc.out, pool);
1029    } else {
1030        w.down.matmat(&sc.g, t, &mut sc.out, pool);
1031    }
1032}
1033
1034/// The rows' final hidden states, through `loops` passes of the recurrent block.
1035#[allow(clippy::too_many_arguments)]
1036fn stack(
1037    m: &Dory,
1038    st: &mut DoryState,
1039    h: &mut Vec<f32>,
1040    t: usize,
1041    pos0: usize,
1042    loops: usize,
1043    pool: Option<&Pool>,
1044    capture: Option<&mut Capture>,
1045) {
1046    let c = &m.cfg;
1047    let hidden = c.hidden;
1048    let mut sc = std::mem::take(&mut st.scratch);
1049    let half = c.hd / 2;
1050    for (ti, inv) in [&m.inv_sliding, &m.inv_full].iter().enumerate() {
1051        fit(&mut sc.rope[ti], t * c.hd);
1052        for (r, row) in sc.rope[ti].chunks_exact_mut(c.hd).enumerate() {
1053            let at = (pos0 + r) as f64;
1054            for j in 0..half {
1055                let (sn, cs) = (at * inv[j] as f64).sin_cos();
1056                row[j] = cs as f32;
1057                row[half + j] = sn as f32;
1058            }
1059        }
1060    }
1061    let run = |li: usize, loop_i: usize, h: &mut [f32], st: &mut DoryState, sc: &mut Scratch| {
1062        match &m.layers[li] {
1063            Layer::Attn(w) => {
1064                let sl = m.slot_loop(li, loop_i);
1065                let row = &mut st.slots[w.ordinal];
1066                if row.len() <= sl {
1067                    row.resize_with(sl + 1, KvSlot::default);
1068                }
1069                let recurrent = li >= c.n_in && li < c.n_in + c.n_rec;
1070                // A second read-out replaces the first one's rows too.
1071                let overwrite = c.kv == KvMode::LastLoop && (recurrent || li >= c.n_in + c.n_rec) && loop_i > 0;
1072                attn_layer(m, w, &mut row[sl], h, t, pos0, overwrite, pool, sc);
1073                for (x, o) in h.chunks_exact_mut(hidden).zip(sc.out.chunks_exact_mut(hidden)) {
1074                    slerp(x, o, &w.alpha);
1075                }
1076            }
1077            Layer::Mlp(w) => {
1078                mlp_layer(w, h, t, c.ffn, hidden, pool, sc);
1079                for (x, o) in h.chunks_exact_mut(hidden).zip(sc.out.chunks_exact_mut(hidden)) {
1080                    slerp(x, o, &w.alpha);
1081                }
1082            }
1083        }
1084    };
1085    for li in 0..c.n_in {
1086        run(li, 0, h, st, &mut sc);
1087    }
1088    let input = h.clone();
1089    let mut capture = capture;
1090    if let Some(cp) = capture.as_deref_mut() {
1091        cp.input.extend_from_slice(&input);
1092    }
1093    let mut state = input.clone();
1094    // Under a gate one row runs its first pass here, and the rest below.
1095    let early = t == 1 && m.gate.is_some();
1096    for i in 0..if early { 1 } else { loops } {
1097        for ((x, s), a) in h.iter_mut().zip(&state).zip(&input) {
1098            *x = *s + *a;
1099        }
1100        for row in h.chunks_exact_mut(hidden) {
1101            justnorm(row);
1102        }
1103        for li in c.n_in..c.n_in + c.n_rec {
1104            run(li, i, h, st, &mut sc);
1105        }
1106        state.copy_from_slice(h);
1107        for row in state.chunks_exact_mut(hidden) {
1108            justnorm(row);
1109        }
1110        if let Some(cp) = capture.as_deref_mut() {
1111            cp.loops.extend_from_slice(&state);
1112        }
1113    }
1114    // An early exit: one pass, a decision, and the second only if it asks for one.
1115    if let (true, Some(g)) = (t == 1 && early, m.gate.clone()) {
1116        let mut stay = false;
1117        let mut readout = false;
1118        if let Gate::Recon(top, bias) = &g {
1119            stay = top.score(&state) < *bias;
1120        }
1121        h.copy_from_slice(&state);
1122        if let Gate::Conf(th) = &g {
1123            if st.exits.probe() {
1124                readout = true;
1125                for li in c.n_in + c.n_rec..c.n_in + c.n_rec + c.n_out {
1126                    run(li, 0, h, st, &mut sc);
1127                }
1128                let mut lg = Vec::with_capacity(c.vocab);
1129                head(m, h, 1, Want::Last, pool, &mut lg);
1130                stay = top_prob(&lg) < *th;
1131                st.exits.note(!stay);
1132                if !stay {
1133                    st.exit_logits = Some(lg);
1134                }
1135            } else {
1136                stay = true;
1137            }
1138        } else if !stay {
1139            for li in c.n_in + c.n_rec..c.n_in + c.n_rec + c.n_out {
1140                run(li, 0, h, st, &mut sc);
1141            }
1142        }
1143        if stay {
1144            for ((x, s), a) in h.iter_mut().zip(&state).zip(&input) {
1145                *x = *s + *a;
1146            }
1147            justnorm(h);
1148            for li in c.n_in..c.n_in + c.n_rec {
1149                run(li, 1, h, st, &mut sc);
1150            }
1151            justnorm(h);
1152            for li in c.n_in + c.n_rec..c.n_in + c.n_rec + c.n_out {
1153                run(li, usize::from(readout), h, st, &mut sc);
1154            }
1155        }
1156        st.gate_tokens += 1;
1157        st.gate_one += u64::from(!stay);
1158    } else {
1159        h.copy_from_slice(&state);
1160        for li in c.n_in + c.n_rec..c.n_in + c.n_rec + c.n_out {
1161            run(li, 0, h, st, &mut sc);
1162        }
1163    }
1164    st.scratch = sc;
1165    if st.slots.iter().flatten().any(|s| s.arch) {
1166        store_chunk(c, st, pos0);
1167    }
1168    if let Some(o1) = m.o1 {
1169        if pos0 + t >= o1.seal {
1170            seal_far(m, st, o1);
1171        }
1172    }
1173    // Sliding layers need only the window behind the next position. Trimming
1174    // waits for the chunk's end: a later last-loop pass reads what is behind it.
1175    let kv_row = c.nk * c.hd;
1176    for w in m.layers.iter().filter_map(|l| match l {
1177        Layer::Attn(w) => Some(w),
1178        _ => None,
1179    }) {
1180        let keep = if w.sliding { c.window } else { c.full_window.unwrap_or(usize::MAX / 4) };
1181        for s in st.slots[w.ordinal].iter_mut() {
1182            let n = s.rows(kv_row);
1183            if n > 2 * keep {
1184                let drop = n - keep;
1185                s.k.drain(..drop * kv_row);
1186                s.v.drain(..drop * kv_row);
1187                s.first += drop;
1188            }
1189        }
1190    }
1191}
1192
1193/// A sealed chunk's rows join the store; a later pass over the chunk has rewritten them by now.
1194fn store_chunk(c: &DoryCfg, st: &mut DoryState, pos: usize) {
1195    let (kv_row, q_row) = (c.nk * c.hd, c.nh * c.hd);
1196    let mut failed = None;
1197    for s in st.slots.iter_mut().flatten().filter(|s| s.arch) {
1198        let Some(a) = s.store.as_mut() else { continue };
1199        if let Err(e) = a.push(&s.k, &s.v, &s.qt, kv_row, q_row, pos) {
1200            failed = Some(e);
1201            break;
1202        }
1203        s.k.clear();
1204        s.v.clear();
1205        s.qt.clear();
1206    }
1207    if let Some(e) = failed {
1208        give_up_rows(st, &e);
1209    }
1210}
1211
1212/// The rows did not fit where they were going: the far field stays as first sealed, with no
1213/// exact rows.
1214fn give_up_rows(st: &mut DoryState, why: &str) {
1215    tracing::warn!("dory: {why}: the prompt's rows are dropped, and none stays exact past the seal");
1216    for s in st.slots.iter_mut().flatten().filter(|s| s.arch) {
1217        s.arch = false;
1218        s.store = None;
1219        s.k = Vec::new();
1220        s.v = Vec::new();
1221        s.qt = Vec::new();
1222    }
1223}
1224
1225/// Freeze the full-attention slots' landmarks over what they hold, and go on from them row by
1226/// row (`nystrom::NystromState::prefill_group`). Before a prompt's end, with a choice of exact
1227/// rows to make, the rows go on to a store.
1228fn seal_far(m: &Dory, st: &mut DoryState, o1: DoryO1) {
1229    let c = &m.cfg;
1230    let (nk, hd, q_row) = (c.nk, c.hd, c.nh * c.hd);
1231    let kv_row = nk * hd;
1232    let mut sealed = false;
1233    for o in full_slots(m) {
1234        for s in st.slots[o].iter_mut() {
1235            if !s.far.is_empty() || s.k.is_empty() {
1236                continue;
1237            }
1238            sealed = true;
1239            let t = s.k.len() / kv_row;
1240            let order = seal_order(t, o1.sink, &[]);
1241            s.far = far_states(c, o1, &s.k, &s.v, &s.qt, 1, t, &order, 0);
1242            if m.exact.is_some() {
1243                let mut a = match SlotStore::new(m.spill.as_deref()) {
1244                    Ok(a) => a,
1245                    Err(e) => {
1246                        tracing::warn!("dory: {e}");
1247                        s.qt = Vec::new();
1248                        s.k = Vec::new();
1249                        s.v = Vec::new();
1250                        continue;
1251                    }
1252                };
1253                match a.push(&s.k, &s.v, &s.qt, kv_row, q_row, 0) {
1254                    Ok(()) => {
1255                        s.arch = true;
1256                        s.store = Some(a);
1257                    }
1258                    Err(e) => tracing::warn!("dory: {e}"),
1259                }
1260            }
1261            s.qt = Vec::new();
1262            s.k = Vec::new();
1263            s.v = Vec::new();
1264        }
1265    }
1266    // Every slot or none: the choice of rows reads all the layers.
1267    if sealed && m.exact.is_some() && st.slots.iter().flatten().any(|s| !s.far.is_empty() && !s.arch) {
1268        give_up_rows(st, "a store failed");
1269    }
1270}
1271
1272/// The full-attention layers' slots.
1273fn full_slots(m: &Dory) -> Vec<usize> {
1274    m.layers
1275        .iter()
1276        .filter_map(|l| match l {
1277            Layer::Attn(w) if !w.sliding => Some(w.ordinal),
1278            _ => None,
1279        })
1280        .collect()
1281}
1282
1283/// A prompt's end on the host, before its last rows: the far field built anew over all the rows,
1284/// the ones it should not lose out of it. A copy of a fact there drowns the exact one.
1285fn finish_exact(m: &Dory, st: &mut DoryState) {
1286    let Some(o1) = m.o1 else { return };
1287    let Some(x) = m.exact.as_ref() else { return };
1288    if !st.slots.iter().flatten().any(|s| s.arch) {
1289        return;
1290    }
1291    let c = &m.cfg;
1292    let (nh, nk, hd) = (c.nh, c.nk, c.hd);
1293    let kv_row = nk * hd;
1294    let t0 = std::time::Instant::now();
1295    let full = full_slots(m);
1296    let mut frozen = Vec::new();
1297    let mut rows = 0;
1298    let mut fail = None;
1299    for &o in &full {
1300        for sl in 0..st.slots[o].len() {
1301            let s = &mut st.slots[o][sl];
1302            let Some(a) = s.store.take() else { continue };
1303            rows = a.rows;
1304            match (a.k.freeze(), a.v.freeze(), a.q.freeze()) {
1305                (Ok(k), Ok(v), Ok(q)) => frozen.push((o, sl, k, v, q)),
1306                (Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => fail = Some(e),
1307            }
1308        }
1309    }
1310    if let Some(e) = fail {
1311        give_up_rows(st, &e);
1312        return;
1313    }
1314    let keep = {
1315        let views: Vec<FlatRows> = frozen.iter().filter(|e| e.1 == 0).map(|e| FlatRows { k: e.2.as_slice(), v: e.3.as_slice(), nk, hd }).collect();
1316        let layers: Vec<&dyn LayerRows> = views.iter().map(|v| v as &dyn LayerRows).collect();
1317        pick_exact_rows(x, &layers, rows, nk, hd, o1.m, o1.sink, rows.saturating_sub(o1.w))
1318    };
1319    let order = seal_order(rows, o1.sink, &keep);
1320    // One slot at a time: memory holds the far field of one layer on top of the rows.
1321    for (o, sl, k, v, q) in frozen {
1322        let (ks, vs, qs) = (k.as_slice(), v.as_slice(), q.as_slice());
1323        debug_assert_eq!(qs.len(), rows.div_ceil(QT_STRIDE) * nh * hd);
1324        debug_assert_eq!(ks.len(), rows * kv_row);
1325        let s = &mut st.slots[o][sl];
1326        s.far = far_states(c, o1, ks, vs, qs, QT_STRIDE, rows, &order, keep.len());
1327        s.arch = false;
1328    }
1329    tracing::info!("dory: far field rebuilt over {rows} rows, {} kept exact, in {:.2} s", keep.len(), t0.elapsed().as_secs_f64());
1330}
1331
1332/// The host's KV bytes: `(exact rows, O(1) states)`.
1333pub fn host_kv_bytes(st: &DoryState) -> (usize, usize) {
1334    let (mut exact, mut far) = (0, 0);
1335    for s in st.slots.iter().flatten() {
1336        exact += (s.k.len() + s.v.len()) * 4;
1337        far += s.far.iter().map(|n| n.memory_bytes()).sum::<usize>();
1338    }
1339    (exact, far)
1340}
1341
1342/// Per-row signals of a forward, for the loop router.
1343#[derive(Default)]
1344pub struct Capture {
1345    pub input: Vec<f32>,
1346    pub loops: Vec<f32>,
1347}
1348
1349/// What a forward leaves in `logits`.
1350#[derive(Clone, Copy, PartialEq, Eq)]
1351pub enum Want {
1352    /// Nothing: the KV state moved, no logits are read.
1353    None,
1354    Last,
1355    All,
1356}
1357
1358/// How a token leaves the loop early: by its top probability after one pass
1359/// (`conf:0.7`), which reads the output block and the head before deciding, or by
1360/// patent-1 reconstruction over the state after one pass (`recon:FILE:bias`).
1361#[derive(Clone)]
1362pub enum Gate {
1363    Conf(f32),
1364    Recon(std::sync::Arc<Topologies>, f32),
1365}
1366
1367pub struct Topologies {
1368    dim: usize,
1369    rank: usize,
1370    me: Vec<f32>,
1371    be: Vec<f32>,
1372    mh: Vec<f32>,
1373    bh: Vec<f32>,
1374}
1375
1376impl Topologies {
1377    /// The router a turbo file carries.
1378    fn from_model(model: &CmfModel) -> Result<Self, String> {
1379        let dim = model.arch().hidden_size;
1380        let v = |n: &str| crate::loader::load_f32(model, n, &crate::loader::Overlay::None);
1381        let (me, be, mh, bh) = (v("dory.router.easy.mean")?, v("dory.router.easy.basis")?, v("dory.router.hard.mean")?, v("dory.router.hard.basis")?);
1382        if me.len() != dim || mh.len() != dim || be.is_empty() || be.len() % dim != 0 || bh.len() != be.len() {
1383            return Err("dory.router: shapes".into());
1384        }
1385        Ok(Self { dim, rank: be.len() / dim, me, be, mh, bh })
1386    }
1387
1388    fn read(path: &str) -> Option<Self> {
1389        let b = std::fs::read(path).ok()?;
1390        let w: Vec<f32> = b.chunks_exact(4).map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]])).collect();
1391        let (rank, dim) = (w[0] as usize, w[1] as usize);
1392        let mut at = 2;
1393        let mut take = |n: usize| {
1394            let v = w[at..at + n].to_vec();
1395            at += n;
1396            v
1397        };
1398        let me = take(dim);
1399        let be = take(rank * dim);
1400        let mh = take(dim);
1401        let bh = take(rank * dim);
1402        Some(Self { dim, rank, me, be, mh, bh })
1403    }
1404
1405    /// Reconstruction error of `x` by one topology.
1406    fn err(&self, x: &[f32], mu: &[f32], b: &[f32]) -> f32 {
1407        let y: Vec<f32> = x.iter().zip(mu).map(|(a, m)| a - m).collect();
1408        let mut e: f32 = y.iter().map(|v| v * v).sum();
1409        for r in b.chunks_exact(self.dim).take(self.rank) {
1410            let d: f32 = r.iter().zip(&y).map(|(a, c)| a * c).sum();
1411            e -= d * d;
1412        }
1413        e
1414    }
1415
1416    /// Positive: the easy class reconstructs it better.
1417    pub fn score(&self, x: &[f32]) -> f32 {
1418        self.err(x, &self.mh, &self.bh) - self.err(x, &self.me, &self.be)
1419    }
1420}
1421
1422fn exact_from_env() -> Option<ExactRows> {
1423    exact_from_var("CMF_DORY_EXACT")
1424}
1425
1426/// `FILE:rows` from a variable; a file that is not there gives no weights, which only
1427/// dumps the features.
1428pub fn exact_from_var(name: &str) -> Option<ExactRows> {
1429    let v = std::env::var(name).ok()?;
1430    let (file, rows) = v.rsplit_once(':')?;
1431    let b = std::fs::read(file).unwrap_or_default();
1432    let w: Vec<f32> = b.chunks_exact(4).map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]])).collect();
1433    Some(ExactRows { weights: Arc::new(w.get(1..).unwrap_or(&[]).to_vec()), budget: rows.parse().ok()? })
1434}
1435
1436fn gate_from_env() -> Option<Gate> {
1437    let v = std::env::var("CMF_DORY_GATE").ok()?;
1438    let mut it = v.split(':');
1439    match it.next()? {
1440        "conf" => Some(Gate::Conf(it.next()?.parse().ok()?)),
1441        "recon" => {
1442            let t = Topologies::read(it.next()?)?;
1443            Some(Gate::Recon(Arc::new(t), it.next()?.parse().ok()?))
1444        }
1445        _ => None,
1446    }
1447}
1448
1449/// On text where the first read-out rarely ends a token it only costs time: below this
1450/// rate of early exits a token goes straight to the second pass, and one in eight still
1451/// reads out, to notice when the text turns easy.
1452#[derive(Clone, Copy)]
1453pub struct ExitRate {
1454    rate: f32,
1455    tokens: u32,
1456}
1457
1458impl Default for ExitRate {
1459    fn default() -> Self {
1460        Self { rate: 1.0, tokens: 0 }
1461    }
1462}
1463
1464impl ExitRate {
1465    /// Is this token worth a read-out after its first pass?
1466    pub fn probe(&mut self) -> bool {
1467        self.tokens = self.tokens.wrapping_add(1);
1468        self.rate >= 0.35 || self.tokens % 8 == 0
1469    }
1470
1471    pub fn note(&mut self, exited: bool) {
1472        self.rate += 0.1 * (f32::from(u8::from(exited)) - self.rate);
1473    }
1474}
1475
1476/// The top probability of a row of logits, without an exp per entry of the vocabulary.
1477pub fn top_prob(lg: &[f32]) -> f32 {
1478    let top = lg.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
1479    let mut den = 0f64;
1480    for &x in lg {
1481        let t = x - top;
1482        if t > -20.0 {
1483            den += (t as f64).exp();
1484        }
1485    }
1486    (1.0 / den) as f32
1487}
1488
1489/// Rows of a forward on the host.
1490pub const HOST_CHUNK: usize = 64;
1491
1492/// Host forward of a chunk.
1493#[allow(clippy::too_many_arguments)]
1494pub fn forward_host(
1495    m: &Dory,
1496    st: &mut DoryState,
1497    ids: &[u32],
1498    pos0: usize,
1499    loops: usize,
1500    want: Want,
1501    pool: Option<&Pool>,
1502    capture: Option<&mut Capture>,
1503    logits: &mut Vec<f32>,
1504) {
1505    let c = &m.cfg;
1506    if st.slots.is_empty() {
1507        st.slots.resize_with(m.attn_layers, Vec::new);
1508    }
1509    let mut capture = capture;
1510    let mut done = 0usize;
1511    logits.clear();
1512    let tail_from = (pos0 + ids.len()).saturating_sub(EXACT_TAIL);
1513    // q8's batch product has no fast path on x86: a row at a time goes 8 times faster
1514    // there, through the fused matvecs.
1515    let scalar_q8 = cfg!(target_arch = "x86_64")
1516        && m.layers.iter().any(|l| matches!(l, Layer::Attn(w) if matches!(w.q, QTensor::Mapped { dtype: cortiq_core::TensorDtype::Q8_2f, .. })));
1517    for chunk in ids.chunks(if scalar_q8 { 1 } else { HOST_CHUNK }) {
1518        let t = chunk.len();
1519        if pos0 + done >= tail_from {
1520            finish_exact(m, st);
1521        }
1522        let mut h = vec![0f32; t * c.hidden];
1523        for (i, row) in h.chunks_exact_mut(c.hidden).enumerate() {
1524            if (chunk[i] as usize) < m.embed.rows() {
1525                m.embed.row_f32(chunk[i] as usize, row);
1526            }
1527        }
1528        let w = if done + t == ids.len() || want == Want::All { want } else { Want::None };
1529        stack(m, st, &mut h, t, pos0 + done, m.chunk_loops(loops, w), pool, capture.as_deref_mut());
1530        done += t;
1531        st.pos = pos0 + done;
1532        if let Some(lg) = st.exit_logits.take() {
1533            if want != Want::None {
1534                if want == Want::Last {
1535                    logits.clear();
1536                }
1537                logits.extend_from_slice(&lg);
1538            }
1539            continue;
1540        }
1541        match want {
1542            Want::All => head(m, &h, t, Want::All, pool, logits),
1543            Want::Last if done == ids.len() => {
1544                logits.clear();
1545                head(m, &h, t, Want::Last, pool, logits)
1546            }
1547            _ => {}
1548        }
1549    }
1550    finish_exact(m, st);
1551}
1552
1553/// The head over the wanted rows, with the per-token logit scale: `logits` grows.
1554pub fn head(m: &Dory, h: &[f32], t: usize, want: Want, pool: Option<&Pool>, logits: &mut Vec<f32>) {
1555    let c = &m.cfg;
1556    let (rows, from) = match want {
1557        Want::None => return,
1558        Want::Last => (1, t - 1),
1559        Want::All => (t, 0),
1560    };
1561    let at = logits.len();
1562    logits.resize(at + rows * c.vocab, 0.0);
1563    let x = &h[from * c.hidden..t * c.hidden];
1564    if rows == 1 && !crate::gptq_capture::capturing() {
1565        m.head.matvec(x, &mut logits[at..], pool);
1566    } else {
1567        m.head.matmat(x, rows, &mut logits[at..], pool);
1568    }
1569    for row in logits[at..].chunks_exact_mut(c.vocab) {
1570        for (x, s) in row.iter_mut().zip(&m.logit_scale) {
1571            *x *= *s;
1572        }
1573    }
1574}
1575
1576#[cfg(test)]
1577mod tests {
1578    use super::*;
1579
1580    fn tiny(window: usize, kv: KvMode, loops: usize) -> Dory {
1581        let (hidden, ffn, nh, nk, hd, vocab) = (16usize, 16usize, 4usize, 2usize, 8usize, 11usize);
1582        let mut seed = 1u32;
1583        let mut rnd = move |n: usize| -> Vec<f32> {
1584            (0..n)
1585                .map(|_| {
1586                    seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
1587                    ((seed >> 8) as f32 / (1u32 << 24) as f32 - 0.5) * 0.7
1588                })
1589                .collect()
1590        };
1591        let mat = |v: Vec<f32>, r: usize, c: usize| QTensor::from_f32(v, r, c);
1592        let q_size = nh * hd;
1593        let kv_row = nk * hd;
1594        let pattern = "S-*-S-*-S-*-";
1595        let mut layers = Vec::new();
1596        let mut ordinal = 0;
1597        for ch in pattern.chars() {
1598            if ch == '-' {
1599                layers.push(Layer::Mlp(Box::new(MlpWeights {
1600                    gate: mat(rnd(ffn * hidden), ffn, hidden),
1601                    up: mat(rnd(ffn * hidden), ffn, hidden),
1602                    down: mat(rnd(hidden * ffn), hidden, ffn),
1603                    sg: vec![1.5; ffn],
1604                    su: vec![1.2; ffn],
1605                    alpha: vec![0.3; hidden],
1606                })));
1607            } else {
1608                layers.push(Layer::Attn(Box::new(AttnWeights {
1609                    q: mat(rnd(q_size * hidden), q_size, hidden),
1610                    k: mat(rnd(kv_row * hidden), kv_row, hidden),
1611                    v: mat(rnd(kv_row * hidden), kv_row, hidden),
1612                    g: mat(rnd(q_size * hidden), q_size, hidden),
1613                    o: mat(rnd(hidden * q_size), hidden, q_size),
1614                    sq: vec![4.0; q_size],
1615                    sg: vec![2.0; q_size],
1616                    alpha: vec![0.2; hidden],
1617                    sliding: ch == 'S',
1618                    ordinal,
1619                })));
1620                ordinal += 1;
1621            }
1622        }
1623        Dory {
1624            file: None,
1625            cfg: DoryCfg {
1626                hidden,
1627                ffn,
1628                vocab,
1629                nh,
1630                nk,
1631                hd,
1632                window,
1633                n_in: 4,
1634                n_rec: 4,
1635                n_out: 4,
1636                loops,
1637                kv,
1638                attn_scale: 1.0 / (hd as f32).sqrt(),
1639                full_window: None,
1640            },
1641            embed: mat(rnd(vocab * hidden), vocab, hidden),
1642            head: mat(rnd(vocab * hidden), vocab, hidden),
1643            logit_scale: vec![3.0; vocab],
1644            layers,
1645            inv_full: crate::attention::rope_inv_freq(hd, 1.0e7),
1646            inv_sliding: crate::attention::rope_inv_freq(hd, 1.0e4),
1647            attn_layers: ordinal,
1648            o1: None,
1649            gate: None,
1650            prompt_loops: 0,
1651            exact: None,
1652            spill: None,
1653        }
1654    }
1655
1656    fn run(m: &Dory, ids: &[u32], chunk: usize) -> Vec<f32> {
1657        let mut st = DoryState::default();
1658        let mut logits = Vec::new();
1659        let mut at = 0;
1660        for c in ids.chunks(chunk) {
1661            logits.clear();
1662            forward_host(m, &mut st, c, at, m.cfg.loops, Want::Last, None, None, &mut logits);
1663            at += c.len();
1664        }
1665        logits
1666    }
1667
1668    #[test]
1669    fn exp8_tracks_the_library_exp() {
1670        for v in [-80.0f32, -20.0, -3.3, -1.0, -0.01, 0.0, 0.5, 2.7, 13.0, 60.0] {
1671            let e = exp8(&[v; 8])[3];
1672            assert!((e / v.exp() - 1.0).abs() < 1e-6, "{v}: {e} {}", v.exp());
1673        }
1674    }
1675
1676    #[test]
1677    fn activation_matches_the_scalar_form() {
1678        let n = 21;
1679        let mut g: Vec<f32> = (0..n).map(|i| (i as f32 - 10.0) * 0.37).collect();
1680        let g0 = g.clone();
1681        let u: Vec<f32> = (0..n).map(|i| 0.1 + i as f32 * 0.01).collect();
1682        let (a, b) = (vec![1.3f32; n], vec![0.7f32; n]);
1683        silu_rows(&mut g, &u, &a, &b);
1684        for i in 0..n {
1685            let x = g0[i] * a[i];
1686            let want = x / (1.0 + (-x).exp()) * (u[i] * b[i]);
1687            assert!((g[i] - want).abs() <= 1e-5 * want.abs().max(1.0), "{i}: {} {want}", g[i]);
1688        }
1689    }
1690
1691    #[test]
1692    fn a_chunk_and_its_rows_give_the_same_logits() {
1693        let ids: Vec<u32> = (0..23).map(|i| (i * 7 % 11) as u32).collect();
1694        // Past the window on the sliding layers, per-loop caches.
1695        let m = tiny(5, KvMode::PerLoop, 2);
1696        let (a, b, c) = (run(&m, &ids, 1), run(&m, &ids, 4), run(&m, &ids, 64));
1697        for ((x, y), z) in a.iter().zip(&b).zip(&c) {
1698            assert!((x - y).abs() < 2e-4 && (x - z).abs() < 2e-4, "{x} {y} {z}");
1699        }
1700    }
1701
1702    #[test]
1703    fn the_last_loop_cache_rewrites_a_position_in_place() {
1704        let m = tiny(5, KvMode::LastLoop, 2);
1705        let mut st = DoryState::default();
1706        let mut logits = Vec::new();
1707        forward_host(&m, &mut st, &[1, 2, 3, 4, 5, 6, 7, 8], 0, 2, Want::Last, None, None, &mut logits);
1708        let kv_row = m.cfg.nk * m.cfg.hd;
1709        for row in &st.slots {
1710            assert_eq!(row.len(), 1, "one slot per layer");
1711            assert_eq!(row[0].k.len(), 8 * kv_row);
1712        }
1713        assert!(logits.iter().all(|v| v.is_finite()));
1714    }
1715
1716    #[test]
1717    fn a_far_field_seals_and_goes_on_under_the_last_loop_cache() {
1718        let mut m = tiny(5, KvMode::LastLoop, 2);
1719        m.o1 = Some(DoryO1 { m: 4, w: 6, sink: 0, seal: 24 });
1720        let mut st = DoryState::default();
1721        let mut logits = Vec::new();
1722        let ids: Vec<u32> = (0..40).map(|i| (i * 5 % 11) as u32).collect();
1723        forward_host(&m, &mut st, &ids[..30], 0, 2, Want::Last, None, None, &mut logits);
1724        for (at, id) in ids.iter().enumerate().skip(30) {
1725            logits.clear();
1726            forward_host(&m, &mut st, std::slice::from_ref(id), at, 2, Want::Last, None, None, &mut logits);
1727            assert!(logits.iter().all(|v| v.is_finite()));
1728        }
1729        let (exact, far) = host_kv_bytes(&st);
1730        assert!(far > 0 && exact > 0, "the full layers went over to the far field");
1731    }
1732
1733    #[test]
1734    fn exact_rows_take_what_the_landmarks_cannot_span() {
1735        // Keys in a thin subspace, one row off it: the row and its neighbours come first.
1736        let (rows, nk, hd) = (400usize, 1usize, 16usize);
1737        let mut ks = vec![0f32; rows * hd];
1738        for r in 0..rows {
1739            ks[r * hd] = 1.0;
1740            ks[r * hd + 1] = (r as f32 * 0.01).sin() * 0.1;
1741        }
1742        ks[200 * hd + 7] = 1.0;
1743        let vs = vec![1f32; rows * hd];
1744        let x = ExactRows { weights: Arc::new(vec![1.0, 0.0]), budget: 5 };
1745        let keep = pick_exact_rows(&x, &[&FlatRows { k: &ks, v: &vs, nk, hd }], rows, nk, hd, 8, 4, 300);
1746        assert_eq!(keep, vec![198, 199, 200, 201, 202]);
1747    }
1748
1749    #[test]
1750    fn a_prompt_keeps_its_chosen_rows_past_the_seal() {
1751        let mut m = tiny(5, KvMode::LastLoop, 2);
1752        m.o1 = Some(DoryO1 { m: 4, w: 6, sink: 1, seal: 24 });
1753        let layers = m.layers.iter().filter(|l| matches!(l, Layer::Attn(w) if !w.sliding)).count();
1754        m.exact = Some(ExactRows { weights: Arc::new(vec![1.0; layers * 2 * m.cfg.nk]), budget: 3 });
1755        let mut st = DoryState::default();
1756        let mut logits = Vec::new();
1757        let ids: Vec<u32> = (0..40).map(|i| (i * 5 % 11) as u32).collect();
1758        forward_host(&m, &mut st, &ids, 0, 2, Want::Last, None, None, &mut logits);
1759        let mut seen = 0;
1760        for s in st.slots.iter().flatten().filter(|s| !s.far.is_empty()) {
1761            assert!(!s.arch && s.k.is_empty(), "the rows are gone from the exact store");
1762            assert_eq!(s.far[0].device_view().sink_len, 4);
1763            seen += 1;
1764        }
1765        assert!(seen > 0);
1766        logits.clear();
1767        forward_host(&m, &mut st, &[3], 40, 2, Want::Last, None, None, &mut logits);
1768        assert!(logits.iter().all(|v| v.is_finite()));
1769    }
1770
1771    #[test]
1772    fn a_prompt_past_the_seal_reads_the_same_from_ram_and_from_files() {
1773        let build = |spill: Option<std::path::PathBuf>| {
1774            let mut m = tiny(5, KvMode::LastLoop, 2);
1775            m.o1 = Some(DoryO1 { m: 4, w: 6, sink: 1, seal: 24 });
1776            let layers = m.layers.iter().filter(|l| matches!(l, Layer::Attn(w) if !w.sliding)).count();
1777            m.exact = Some(ExactRows { weights: Arc::new(vec![1.0; layers * 2 * m.cfg.nk]), budget: 3 });
1778            m.spill = spill;
1779            m
1780        };
1781        let ids: Vec<u32> = (0..700).map(|i| (i * 5 % 11) as u32).collect();
1782        let dir = std::env::temp_dir().join(format!("cortiq-dory-spill-test-{}", std::process::id()));
1783        let mut got = Vec::new();
1784        for spill in [None, Some(dir.clone())] {
1785            let m = build(spill);
1786            let mut st = DoryState::default();
1787            let mut logits = Vec::new();
1788            forward_host(&m, &mut st, &ids, 0, 2, Want::Last, None, None, &mut logits);
1789            assert!(st.slots.iter().flatten().all(|s| !s.arch && s.store.is_none()));
1790            got.push(logits);
1791        }
1792        assert!(got[0].iter().all(|v| v.is_finite()));
1793        assert_eq!(got[0], got[1]);
1794        // A store in files is gone with the rebuild.
1795        assert_eq!(std::fs::read_dir(&dir).map_or(0, |d| d.count()), 0, "files left in {}", dir.display());
1796        let _ = std::fs::remove_dir_all(&dir);
1797    }
1798
1799    #[test]
1800    fn a_spill_directory_that_cannot_be_made_leaves_the_far_field_working() {
1801        let mut m = tiny(5, KvMode::LastLoop, 2);
1802        m.o1 = Some(DoryO1 { m: 4, w: 6, sink: 1, seal: 24 });
1803        let layers = m.layers.iter().filter(|l| matches!(l, Layer::Attn(w) if !w.sliding)).count();
1804        m.exact = Some(ExactRows { weights: Arc::new(vec![1.0; layers * 2 * m.cfg.nk]), budget: 3 });
1805        let file = std::env::temp_dir().join(format!("cortiq-dory-spill-file-{}", std::process::id()));
1806        std::fs::write(&file, b"x").unwrap();
1807        m.spill = Some(file.join("no"));
1808        let mut st = DoryState::default();
1809        let mut logits = Vec::new();
1810        let ids: Vec<u32> = (0..100).map(|i| (i * 5 % 11) as u32).collect();
1811        forward_host(&m, &mut st, &ids, 0, 2, Want::Last, None, None, &mut logits);
1812        assert!(logits.iter().all(|v| v.is_finite()));
1813        assert!(st.slots.iter().flatten().all(|s| !s.arch));
1814        let _ = std::fs::remove_file(&file);
1815    }
1816
1817    #[test]
1818    fn rollback_keeps_the_prefix_a_new_turn_extends() {
1819        let m = tiny(50, KvMode::PerLoop, 2);
1820        let ids: Vec<u32> = (0..12).map(|i| (i * 3 % 11) as u32).collect();
1821        let want = run(&m, &ids, 1);
1822        let mut st = DoryState::default();
1823        let mut logits = Vec::new();
1824        forward_host(&m, &mut st, &ids[..9], 0, 2, Want::Last, None, None, &mut logits);
1825        st.rollback(5, m.cfg.nk * m.cfg.hd);
1826        forward_host(&m, &mut st, &ids[5..], 5, 2, Want::Last, None, None, &mut logits);
1827        for (x, y) in logits.iter().zip(&want) {
1828            assert!((x - y).abs() < 2e-4, "{x} {y}");
1829        }
1830    }
1831
1832    #[test]
1833    fn top_prob_of_a_sure_row() {
1834        let mut lg = vec![-30.0f32; 100];
1835        lg[7] = 30.0;
1836        assert!(top_prob(&lg) > 0.999);
1837        assert!((top_prob(&[1.0, 1.0]) - 0.5).abs() < 1e-5);
1838    }
1839}