1use crate::pipeline::ArchStore;
24use crate::pool::Pool;
25use crate::qtensor::QTensor;
26use cortiq_core::{CmfModel, DoryConfig, ModelArch};
27use std::sync::Arc;
28
29pub const EPS: f32 = 1e-6;
31pub const ARCH: &str = "dory";
32pub const MAX_LOOPS: usize = 16;
34pub 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 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 pub sq: Vec<f32>,
82 pub sg: Vec<f32>,
84 pub alpha: Vec<f32>,
85 pub sliding: bool,
86 pub ordinal: usize,
88}
89
90pub struct MlpWeights {
91 pub gate: QTensor,
92 pub up: QTensor,
93 pub down: QTensor,
94 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 pub file: Option<Arc<CmfModel>>,
108 pub cfg: DoryCfg,
109 pub embed: QTensor,
110 pub head: QTensor,
111 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 pub gate: Option<Gate>,
120 pub prompt_loops: usize,
123 pub exact: Option<ExactRows>,
125 pub spill: Option<std::path::PathBuf>,
127}
128
129#[derive(Clone)]
135pub struct ExactRows {
136 pub weights: Arc<Vec<f32>>,
137 pub budget: usize,
138}
139
140pub const MAX_NEAR: usize = 8192;
142
143pub const EXACT_TAIL: usize = 512;
146
147const EXACT_NEIGHBOURS: usize = 2;
149
150pub 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
166pub trait LayerRows: Sync {
168 fn key(&self, kh: usize, r: usize) -> &[f32];
169 fn value(&self, kh: usize, r: usize) -> &[f32];
170}
171
172pub 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
189fn 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 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
257pub 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
335pub 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#[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#[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#[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 (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#[derive(Default)]
413pub struct KvSlot {
414 pub k: Vec<f32>,
415 pub v: Vec<f32>,
416 pub first: usize,
417 pub arch: bool,
419 pub qt: Vec<f32>,
421 pub far: Vec<crate::nystrom::NystromState>,
423 store: Option<SlotStore>,
425}
426
427struct SlotStore {
428 k: ArchStore,
429 v: ArchStore,
430 q: ArchStore,
432 rows: usize,
433}
434
435impl SlotStore {
436 fn new(dir: Option<&std::path::Path>) -> Result<Self, String> {
437 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 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#[derive(Default)]
466pub struct DoryState {
467 pub slots: Vec<Vec<KvSlot>>,
468 pub pos: usize,
469 scratch: Scratch,
470 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 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 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 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 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 #[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#[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
685fn 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#[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#[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#[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#[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#[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 rope: [Vec<f32>; 2],
853}
854
855fn fit(v: &mut Vec<f32>, n: usize) {
856 v.resize(n, 0.0);
857}
858
859#[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 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 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 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 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#[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 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 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 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 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
1193fn 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
1212fn 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
1225fn 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 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
1272fn 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
1283fn 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 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
1332pub 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#[derive(Default)]
1344pub struct Capture {
1345 pub input: Vec<f32>,
1346 pub loops: Vec<f32>,
1347}
1348
1349#[derive(Clone, Copy, PartialEq, Eq)]
1351pub enum Want {
1352 None,
1354 Last,
1355 All,
1356}
1357
1358#[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 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 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 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
1426pub 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#[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 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
1476pub 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
1489pub const HOST_CHUNK: usize = 64;
1491
1492#[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 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
1553pub 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 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 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 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}