Skip to main content

cortiq_engine/
dsv4.rs

1//! DeepSeek-V4 blocks that no other supported architecture has.
2//!
3//! Transcribed from the reference `inference/model.py` + `inference/kernel.py`
4//! shipped with the checkpoint, not inferred from tensor names — the pieces
5//! below have enough hidden structure (a second normalization on the heads, a
6//! bias that steers selection but not weights, a mixing matrix normalized by
7//! Sinkhorn) that guessing produces a model which *almost* answers.
8//!
9//! Each function is the smallest unit the reference defines, so it can be
10//! checked on its own. The forward that stitches them together comes after
11//! the attention and compressor land.
12
13/// Hyper-connections. The hidden state of this model is not a vector: it is
14/// `hc` copies of one (`hc_mult`, 4 in the release). A block folds them to
15/// one, runs attention or the FFN, then expands back — there is no ordinary
16/// residual anywhere in the stack.
17///
18/// `mixes` is the per-token projection `F.linear(x.flatten(), hc_fn) * rsqrt`
19/// of length `(2 + hc) * hc`; it splits into three parts:
20///   * `pre[j]`  — how much of copy `j` goes into the folded vector,
21///   * `post[j]` — how much of the block's output returns to copy `j`,
22///   * `comb`    — an `hc x hc` matrix mixing the old copies into the new.
23///
24/// `comb` is made doubly stochastic by Sinkhorn: a row softmax, then
25/// alternating row/column normalization. The reference runs the column step
26/// once before the loop and `iters - 1` times inside it, which is why the
27/// loop below starts from the column-normalized matrix.
28pub fn hc_split_sinkhorn(
29    mixes: &[f32],
30    hc_scale: &[f32; 3],
31    hc_base: &[f32],
32    hc: usize,
33    iters: usize,
34    eps: f32,
35    pre: &mut [f32],
36    post: &mut [f32],
37    comb: &mut [f32],
38) {
39    debug_assert_eq!(mixes.len(), (2 + hc) * hc);
40    debug_assert_eq!(comb.len(), hc * hc);
41    for j in 0..hc {
42        pre[j] = sigmoid(mixes[j] * hc_scale[0] + hc_base[j]) + eps;
43        // The post weights carry a factor 2 in the reference — with a
44        // sigmoid alone the block's output could never exceed the residual.
45        post[j] = 2.0 * sigmoid(mixes[j + hc] * hc_scale[1] + hc_base[j + hc]);
46    }
47    for j in 0..hc {
48        for k in 0..hc {
49            let idx = j * hc + k + hc * 2;
50            comb[j * hc + k] = mixes[idx] * hc_scale[2] + hc_base[idx];
51        }
52    }
53    // row softmax + eps
54    for j in 0..hc {
55        let row = &mut comb[j * hc..(j + 1) * hc];
56        let m = row.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
57        let mut sum = 0.0;
58        for v in row.iter_mut() {
59            *v = (*v - m).exp();
60            sum += *v;
61        }
62        for v in row.iter_mut() {
63            *v = *v / sum + eps;
64        }
65    }
66    // one column normalization, then (iters - 1) row/column rounds
67    normalize_cols(comb, hc, eps);
68    for _ in 0..iters.saturating_sub(1) {
69        normalize_rows(comb, hc, eps);
70        normalize_cols(comb, hc, eps);
71    }
72}
73
74fn normalize_rows(m: &mut [f32], n: usize, eps: f32) {
75    for j in 0..n {
76        let s: f32 = m[j * n..(j + 1) * n].iter().sum::<f32>() + eps;
77        for v in m[j * n..(j + 1) * n].iter_mut() {
78            *v /= s;
79        }
80    }
81}
82
83fn normalize_cols(m: &mut [f32], n: usize, eps: f32) {
84    for k in 0..n {
85        let mut s = eps;
86        for j in 0..n {
87            s += m[j * n + k];
88        }
89        for j in 0..n {
90            m[j * n + k] /= s;
91        }
92    }
93}
94
95#[inline]
96fn sigmoid(x: f32) -> f32 {
97    1.0 / (1.0 + (-x).exp())
98}
99
100/// The projection feeding `hc_split_sinkhorn`: the `hc` copies are flattened
101/// to one `hc*dim` vector, RMS-scaled (no learned weight — the reference uses
102/// a bare rsqrt of the mean square), and projected by `hc_fn` `[mix_hc, hc*dim]`.
103pub fn hc_mixes(
104    x_flat: &[f32],
105    hc_fn: &[f32],
106    mix_hc: usize,
107    eps: f32,
108    pool: Option<&crate::pool::Pool>,
109    out: &mut [f32],
110) {
111    let n = x_flat.len();
112    debug_assert_eq!(hc_fn.len(), mix_hc * n);
113    debug_assert_eq!(out.len(), mix_hc);
114    let ms = x_flat.iter().map(|v| v * v).sum::<f32>() / n as f32;
115    let rsqrt = 1.0 / (ms + eps).sqrt();
116    // A dense f32 matvec of mix_hc rows over hc*dim — 1.6 MB read per call on
117    // the release, and TWO calls per layer, so 135 MB a token. It ran on one
118    // thread and cost more than the whole attention block.
119    match pool {
120        Some(p) if n >= 4096 => {
121            let addr = crate::pool::SendMut::new(out.as_mut_ptr());
122            p.run_rows(mix_hc, &|start, end| {
123                for i in start..end {
124                    let row = &hc_fn[i * n..(i + 1) * n];
125                    let v = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
126                    unsafe { *addr.at(i) = v };
127                }
128            });
129        }
130        _ => {
131            for (i, o) in out.iter_mut().enumerate() {
132                let row = &hc_fn[i * n..(i + 1) * n];
133                *o = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
134            }
135        }
136    }
137}
138
139/// Fold `hc` copies into one vector: `y = Σ_j pre[j] · x[j]`.
140pub fn hc_fold(x: &[f32], pre: &[f32], hc: usize, dim: usize, out: &mut [f32]) {
141    debug_assert_eq!(x.len(), hc * dim);
142    out.fill(0.0);
143    for j in 0..hc {
144        let w = pre[j];
145        let src = &x[j * dim..(j + 1) * dim];
146        for (o, v) in out.iter_mut().zip(src) {
147            *o += w * v;
148        }
149    }
150}
151
152/// Expand the block's output back into `hc` copies:
153/// `y[j] = post[j] · out + Σ_k comb[k][j] · residual[k]`.
154///
155/// Note the transpose: the reference sums over the SECOND-to-last axis of
156/// `comb.unsqueeze(-1) * residual.unsqueeze(-2)`, i.e. copy `k` of the
157/// residual contributes to new copy `j` with weight `comb[k][j]`.
158pub fn hc_expand(
159    block_out: &[f32],
160    residual: &[f32],
161    post: &[f32],
162    comb: &[f32],
163    hc: usize,
164    dim: usize,
165    out: &mut [f32],
166) {
167    debug_assert_eq!(residual.len(), hc * dim);
168    debug_assert_eq!(out.len(), hc * dim);
169    for j in 0..hc {
170        let dst = &mut out[j * dim..(j + 1) * dim];
171        let p = post[j];
172        for (d, o) in dst.iter_mut().enumerate() {
173            *o = p * block_out[d];
174        }
175        for k in 0..hc {
176            let w = comb[k * hc + j];
177            let src = &residual[k * dim..(k + 1) * dim];
178            for (o, v) in dst.iter_mut().zip(src) {
179                *o += w * v;
180            }
181        }
182    }
183}
184
185/// The head fold, run once after the last layer: same shape as `hc_fold`'s
186/// weights but WITHOUT Sinkhorn — a plain sigmoid gate per copy.
187pub fn hc_head_pre(mixes: &[f32], scale: f32, base: &[f32], hc: usize, eps: f32, pre: &mut [f32]) {
188    for j in 0..hc {
189        pre[j] = sigmoid(mixes[j] * scale + base[j]) + eps;
190    }
191}
192
193/// MoE routing. Three details decide whether this model answers or merely
194/// produces fluent text:
195///   * the score is `sqrt(softplus(x))`, not a softmax or a sigmoid;
196///   * the selection bias shifts WHICH experts win but never the weights —
197///     those come from the pre-bias scores;
198///   * the weights are renormalized over the chosen experts, then scaled.
199///
200/// `bias` is `None` on the hash layers, where `indices` come from a
201/// token-id table instead (see `hash_route`).
202/// `forced` fixes the chosen experts (the hash layers' token-id table). They
203/// have to be known here rather than swapped in afterwards: the weights are
204/// the scores gathered at whichever indices win, so substituting the indices
205/// later leaves every weight attached to a different expert.
206pub fn route(
207    scores_in: &[f32],
208    bias: Option<&[f32]>,
209    top_k: usize,
210    route_scale: f32,
211    forced: Option<&[usize]>,
212    mask: Option<&[bool]>,
213    indices: &mut Vec<usize>,
214    weights: &mut Vec<f32>,
215) {
216    let n = scores_in.len();
217    let mut scores = Vec::with_capacity(n);
218    for &s in scores_in {
219        // softplus, guarded like the reference's F.softplus (linear past 20)
220        let sp = if s > 20.0 { s } else { (1.0 + s.exp()).ln() };
221        scores.push(sp.sqrt());
222    }
223    indices.clear();
224    weights.clear();
225    match forced {
226        Some(f) => indices.extend(f.iter().copied()),
227        None => {
228            let mut shifted: Vec<f32> = match bias {
229                Some(b) => scores.iter().zip(b).map(|(s, b)| s + b).collect(),
230                None => scores.clone(),
231            };
232            if let Some(m) = mask {
233                for (i, s) in shifted.iter_mut().enumerate() {
234                    if !m.get(i).copied().unwrap_or(true) {
235                        *s = f32::NEG_INFINITY;
236                    }
237                }
238            }
239            for _ in 0..top_k.min(n) {
240                let mut best = 0usize;
241                let mut bv = f32::NEG_INFINITY;
242                for (i, &v) in shifted.iter().enumerate() {
243                    if v > bv {
244                        bv = v;
245                        best = i;
246                    }
247                }
248                if !bv.is_finite() {
249                    break;
250                }
251                indices.push(best);
252                shifted[best] = f32::NEG_INFINITY;
253            }
254        }
255    }
256    // The weight is always the PRE-bias score of the chosen expert.
257    for &i in indices.iter() {
258        weights.push(scores.get(i).copied().unwrap_or(0.0));
259    }
260    let sum: f32 = weights.iter().sum();
261    if sum > 0.0 {
262        for w in weights.iter_mut() {
263            *w = *w / sum * route_scale;
264        }
265    }
266}
267
268/// Hash layers: the experts of token `tid` are a row of the `tid2eid` table,
269/// and the router does not run at all. Their weights still come from the
270/// scored path (the reference gathers `original_scores` at those indices).
271pub fn hash_route(tid2eid: &[f32], vocab: usize, top_k: usize, tid: u32) -> Vec<usize> {
272    let row = (tid as usize).min(vocab.saturating_sub(1)) * top_k;
273    (0..top_k)
274        .map(|k| tid2eid.get(row + k).copied().unwrap_or(0.0) as usize)
275        .collect()
276}
277
278/// Rotary on the LAST `rd` dims only — the rest of the head carries no
279/// position. `inverse` runs the rotation backwards, which the reference
280/// applies to the attention OUTPUT before the o-projection (the value
281/// stream carries the same rope-tagged tail as the keys, and it has to be
282/// untagged again). Missing that step leaves a model that reads fluently
283/// and attends to the wrong offsets.
284pub fn rope_tail(v: &mut [f32], inv_freq: &[f32], pos: usize, rd: usize, inverse: bool) {
285    let n = v.len();
286    debug_assert!(
287        rd <= n && rd % 2 == 0,
288        "rope tail {rd} wider than the vector {n}"
289    );
290    // A tail wider than the vector is a configuration mistake, and `n - rd`
291    // would wrap into an index in the billions rather than say so.
292    let rd = rd.min(n) & !1;
293    let base = n - rd;
294    // ADJACENT pairs, not halves. The reference forms its complex numbers
295    // with `unflatten(-1, (-1, 2))` + `view_as_complex`, i.e. (x0,x1),
296    // (x2,x3), … — the interleaved convention. Half-split pairing agrees
297    // with it exactly at position 0, where the rotation is the identity,
298    // and disagrees everywhere else. That is why short answers came out
299    // right and everything longer drifted, repeated itself and could not
300    // count: every position past the first was rotated into the wrong
301    // basis.
302    for i in 0..rd / 2 {
303        let theta = pos as f32 * inv_freq[i];
304        let (s, c) = (theta.sin(), theta.cos());
305        let s = if inverse { -s } else { s };
306        let a = v[base + 2 * i];
307        let b = v[base + 2 * i + 1];
308        v[base + 2 * i] = a * c - b * s;
309        v[base + 2 * i + 1] = a * s + b * c;
310    }
311}
312
313/// RMS normalize in place with no learned weight — the reference applies
314/// this to each attention head AFTER `wq_b`, on top of the `q_norm` that
315/// already normalized the LoRA rank. Two normalizations, not one.
316pub fn rms_inplace(v: &mut [f32], eps: f32) {
317    let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
318    let inv = 1.0 / (ms + eps).sqrt();
319    for x in v.iter_mut() {
320        *x *= inv;
321    }
322}
323
324/// Attention over an explicit position LIST (window ⊕ compressed), with a
325/// learned per-head sink. The sink is an extra logit with no value vector:
326/// it lets a head attend to "nothing", so its softmax denominator carries
327/// `exp(sink - max)` while contributing no output. Index `usize::MAX`
328/// marks a masked slot (the reference writes -1 into topk_idxs).
329pub fn sparse_attend(
330    q: &[f32],
331    kv: &[f32],
332    idxs: &[usize],
333    sink: f32,
334    scale: f32,
335    head_dim: usize,
336    out: &mut [f32],
337) {
338    let mut m = sink;
339    let mut scores = Vec::with_capacity(idxs.len());
340    for &p in idxs {
341        if p == usize::MAX {
342            scores.push(f32::NEG_INFINITY);
343            continue;
344        }
345        let k = &kv[p * head_dim..(p + 1) * head_dim];
346        let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum::<f32>() * scale;
347        m = m.max(dot);
348        scores.push(dot);
349    }
350    let mut denom = (sink - m).exp();
351    out.fill(0.0);
352    for (&p, &s) in idxs.iter().zip(&scores) {
353        if p == usize::MAX {
354            continue;
355        }
356        let w = (s - m).exp();
357        denom += w;
358        let v = &kv[p * head_dim..(p + 1) * head_dim];
359        for (o, x) in out.iter_mut().zip(v) {
360            *o += w * x;
361        }
362    }
363    if std::env::var("CMF_ATTN_DEBUG").is_ok() {
364        eprintln!(
365            "    [port] positions={} score={:?} sink={sink:.4} denom={denom:.4} |q|={:.3}",
366            idxs.iter().filter(|&&p| p != usize::MAX).count(),
367            scores
368                .iter()
369                .map(|x| (x * 10000.0).round() / 10000.0)
370                .collect::<Vec<_>>(),
371            q.iter().map(|x| x * x).sum::<f32>().sqrt()
372        );
373    }
374    let inv = 1.0 / denom;
375    for o in out.iter_mut() {
376        *o *= inv;
377    }
378}
379
380/// The grouped low-rank output projection: heads are split into `groups`,
381/// each group's slice is projected to `lora` by its own block of `wo_a`,
382/// and the concatenation goes through `wo_b`. `wo_a` is stored
383/// `[groups, lora, per_group]`.
384/// `wo_a_row` is `(row, x) -> dot`, reading one row of `wo_a` against the
385/// slice of `attn` its group owns; `wo_b` is the plain projection of the
386/// concatenated groups. Both arrive as closures so the caller can serve them
387/// straight from quantized tensors.
388pub fn o_project(
389    attn: &[f32],
390    wo_a_row: &(dyn Fn(usize, &[f32], &mut [f32]) -> f32 + Sync),
391    scratch_len: usize,
392    wo_b: &dyn Fn(&[f32], &mut [f32]),
393    groups: usize,
394    lora: usize,
395    pool: Option<&crate::pool::Pool>,
396    out: &mut [f32],
397) {
398    let per_group = attn.len() / groups;
399    let mut mid = vec![0.0f32; groups * lora];
400    let slice_of = |i: usize| {
401        let g = i / lora;
402        &attn[g * per_group..(g + 1) * per_group]
403    };
404    match pool {
405        // Each row of `mid` is one dot product against its group's slice —
406        // independent, so the rows split cleanly. This is the largest
407        // single-threaded cost in the decode otherwise: on the release
408        // checkpoint wo_a is 33M weights, read once per layer per token.
409        Some(p) if mid.len() >= 256 => {
410            let addr = crate::pool::SendMut::new(mid.as_mut_ptr());
411            p.run_rows(mid.len(), &|start, end| {
412                let mut sc = vec![0.0f32; scratch_len];
413                for i in start..end {
414                    let v = wo_a_row(i, slice_of(i), &mut sc);
415                    unsafe { *addr.at(i) = v };
416                }
417            });
418        }
419        _ => {
420            let mut sc = vec![0.0f32; scratch_len];
421            for (i, m) in mid.iter_mut().enumerate() {
422                *m = wo_a_row(i, slice_of(i), &mut sc);
423            }
424        }
425    }
426    wo_b(&mid, out);
427}
428
429pub fn compress_window(
430    kv: &[f32],
431    score: &[f32],
432    ape: &[f32],
433    ratio: usize,
434    width: usize,
435    out: &mut [f32],
436) {
437    debug_assert_eq!(kv.len(), ratio * width);
438    debug_assert_eq!(ape.len(), ratio * width);
439    let biased: Vec<f32> = score.iter().zip(ape).map(|(s, a)| s + a).collect();
440    pool_by_score(kv, &biased, ratio, width, out);
441}
442
443/// Softmax over the `slots` axis, per dimension, then the weighted sum —
444/// the pooling both the plain and the overlapping compressor end in.
445/// `-inf` scores are how an absent slot votes for nothing, so the
446/// max-subtraction has to survive a whole column of them.
447pub fn pool_by_score(kv: &[f32], score: &[f32], slots: usize, width: usize, out: &mut [f32]) {
448    debug_assert_eq!(kv.len(), slots * width);
449    debug_assert_eq!(score.len(), slots * width);
450    out.fill(0.0);
451    for d in 0..width {
452        let mut m = f32::NEG_INFINITY;
453        for t in 0..slots {
454            m = m.max(score[t * width + d]);
455        }
456        if !m.is_finite() {
457            continue;
458        }
459        let mut denom = 0.0;
460        for t in 0..slots {
461            denom += (score[t * width + d] - m).exp();
462        }
463        if denom <= 0.0 {
464            continue;
465        }
466        for t in 0..slots {
467            out[d] += ((score[t * width + d] - m).exp() / denom) * kv[t * width + d];
468        }
469    }
470}
471
472/// The overlapping compressor (the release uses it wherever the ratio is 4).
473///
474/// Each token contributes `2*d` values: the first half belongs to the window
475/// that started half a stride earlier, the second half to the current one.
476/// At fold time the reference pools `2*ratio` entries of width `d` — the
477/// PREVIOUS window's slots taking their first half, the current window's
478/// slots taking their second half — then the current window becomes the
479/// previous one. An absent previous window votes with `-inf`.
480#[allow(clippy::too_many_arguments)]
481pub fn compress_window_overlap(
482    prev_kv: &[f32],
483    prev_score: &[f32],
484    cur_kv: &[f32],
485    cur_score: &[f32],
486    ratio: usize,
487    d: usize,
488    out: &mut [f32],
489) {
490    let slots = 2 * ratio;
491    let mut kv = vec![0.0f32; slots * d];
492    let mut sc = vec![f32::NEG_INFINITY; slots * d];
493    let have_prev = prev_kv.len() == ratio * 2 * d;
494    for t in 0..ratio {
495        if have_prev {
496            // the previous window's slots, first half of the dimensions
497            kv[t * d..(t + 1) * d].copy_from_slice(&prev_kv[t * 2 * d..t * 2 * d + d]);
498            sc[t * d..(t + 1) * d].copy_from_slice(&prev_score[t * 2 * d..t * 2 * d + d]);
499        }
500        // the current window's slots, second half
501        let src = t * 2 * d + d;
502        let dst = (ratio + t) * d;
503        kv[dst..dst + d].copy_from_slice(&cur_kv[src..src + d]);
504        sc[dst..dst + d].copy_from_slice(&cur_score[src..src + d]);
505    }
506    pool_by_score(&kv, &sc, slots, d, out);
507}
508
509/// The sparse indexer's scoring pass. For each query it ranks the
510/// compressed positions and keeps the best `topk`.
511///
512/// Three details from the reference that a shape-only reading misses:
513///   * the query comes from the SHARED LoRA output `qr` (the output of
514///     `q_norm(wq_a(x))`, before attention's own `wq_b`), through the
515///     indexer's own `wq_b` — not from attention's queries;
516///   * scores are **relu'd** before the per-head weighting, so a head can
517///     only ever vote for a position, never against it;
518///   * the per-head weights are a projection of the hidden state scaled by
519///     `head_dim^-0.5 * n_heads^-0.5`.
520///
521/// `causal_limit` is the number of compressed positions this query may see
522/// (`(pos + 1) / ratio`); anything at or past it is masked.
523#[allow(clippy::too_many_arguments)]
524pub fn index_scores(
525    q_heads: &[f32],
526    kv: &[f32],
527    head_weights: &[f32],
528    n_heads: usize,
529    head_dim: usize,
530    n_pos: usize,
531    causal_limit: usize,
532    pool: Option<&crate::pool::Pool>,
533    out: &mut Vec<f32>,
534) {
535    out.clear();
536    out.resize(n_pos, 0.0);
537    let score_at = |t: usize| -> f32 {
538        if t >= causal_limit {
539            return f32::NEG_INFINITY;
540        }
541        let k = &kv[t * head_dim..(t + 1) * head_dim];
542        let mut acc = 0.0;
543        for h in 0..n_heads {
544            let q = &q_heads[h * head_dim..(h + 1) * head_dim];
545            let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum();
546            // relu BEFORE weighting: a head votes for a position or abstains
547            acc += dot.max(0.0) * head_weights[h];
548        }
549        acc
550    };
551    // Positions are independent, and their number grows with the context —
552    // this was the one loop in the attention step still walking the whole
553    // compressed axis on one thread.
554    match pool {
555        Some(p) if n_pos >= 64 => {
556            let addr = crate::pool::SendMut::new(out.as_mut_ptr());
557            p.run_rows(n_pos, &|start, end| {
558                for t in start..end {
559                    unsafe { *addr.at(t) = score_at(t) };
560                }
561            });
562        }
563        _ => {
564            for (t, o) in out.iter_mut().enumerate() {
565                *o = score_at(t);
566            }
567        }
568    }
569}
570
571/// Top-`k` positions by score, ties broken by the lower index so the choice
572/// is deterministic across backends. Masked slots (-inf) never win, and a
573/// short history simply returns fewer than `k`.
574pub fn top_k_positions(scores: &[f32], k: usize, out: &mut Vec<usize>) {
575    out.clear();
576    // When k reaches the whole list there is nothing to choose: every finite
577    // position wins, and they come out in index order anyway. The general
578    // path is k rounds of argmax — O(k·n) — and at index_topk = 512 against a
579    // compressed axis that is still shorter than that, it was doing 160k
580    // comparisons a layer to arrive at "all of them". This grows with the
581    // context, which is exactly when it hurts.
582    if k >= scores.len() {
583        out.extend(
584            scores
585                .iter()
586                .enumerate()
587                .filter(|(_, v)| v.is_finite())
588                .map(|(i, _)| i),
589        );
590        return;
591    }
592    let mut taken = vec![false; scores.len()];
593    for _ in 0..k.min(scores.len()) {
594        let mut best = usize::MAX;
595        let mut bv = f32::NEG_INFINITY;
596        for (i, &v) in scores.iter().enumerate() {
597            if !taken[i] && v > bv && v.is_finite() {
598                bv = v;
599                best = i;
600            }
601        }
602        if best == usize::MAX {
603            break;
604        }
605        taken[best] = true;
606        out.push(best);
607    }
608    out.sort_unstable();
609}
610
611/// SwiGLU expert: `w2(silu(w1(x)) * w3(x))`, with the routing weight folded
612/// in before the down projection exactly as the reference does.
613///
614/// `limit` is the reference's `swiglu_limit` (10.0 in the release), and its
615/// asymmetry is not a typo: `up` is clamped on BOTH sides, `gate` only from
616/// above — the reference leaves silu's negative tail alone. A limit of 0
617/// disables the clamp, which is also what the reference does.
618#[allow(clippy::too_many_arguments)]
619pub fn expert_swiglu(
620    x: &[f32],
621    w1: &dyn Fn(&[f32], &mut [f32]),
622    w3: &dyn Fn(&[f32], &mut [f32]),
623    w2: &dyn Fn(&[f32], &mut [f32]),
624    inter: usize,
625    weight: f32,
626    limit: f32,
627    out: &mut [f32],
628) {
629    let mut gate = vec![0.0f32; inter];
630    let mut up = vec![0.0f32; inter];
631    w1(x, &mut gate);
632    w3(x, &mut up);
633    if limit > 0.0 {
634        for u in up.iter_mut() {
635            *u = u.clamp(-limit, limit);
636        }
637        for g in gate.iter_mut() {
638            *g = g.min(limit);
639        }
640    }
641    for (g, u) in gate.iter_mut().zip(&up) {
642        let silu = *g / (1.0 + (-*g).exp());
643        *g = silu * u * weight;
644    }
645    w2(&gate, out);
646}
647
648/// Everything one layer needs that is not a plain matrix: the shapes and
649/// scalars the reference reads out of `ModelArgs`.
650#[derive(Debug, Clone, Copy)]
651pub struct Dsv4Cfg {
652    pub dim: usize,
653    pub n_heads: usize,
654    pub head_dim: usize,
655    pub rope_head_dim: usize,
656    pub q_lora_rank: usize,
657    pub o_lora_rank: usize,
658    pub o_groups: usize,
659    pub hc_mult: usize,
660    pub hc_sinkhorn_iters: usize,
661    pub hc_eps: f32,
662    pub norm_eps: f32,
663    pub n_routed_experts: usize,
664    pub top_k: usize,
665    pub moe_inter: usize,
666    pub route_scale: f32,
667    /// The reference's `swiglu_limit`; 0 disables the clamp.
668    pub swiglu_limit: f32,
669    /// Sliding-window size (`window_size`, 128 in the release).
670    pub window: usize,
671    pub index_topk: usize,
672    pub vocab: usize,
673}
674
675/// The per-block hyper-connection cycle, which is the same shape around
676/// attention and around the FFN: fold the copies, normalize, run the
677/// block, expand back. `block` sees a plain `dim`-vector and knows nothing
678/// about the copies — that separation is what keeps attention and the MoE
679/// free of hyper-connection bookkeeping.
680///
681/// `hc_fn` is `[mix_hc, hc*dim]`, `hc_base` is `[mix_hc]`, `hc_scale` is 3.
682#[allow(clippy::too_many_arguments)]
683#[allow(clippy::too_many_arguments)]
684pub fn hc_block<F: FnMut(&[f32], &mut [f32])>(
685    state: &mut [f32],
686    hc_fn: &[f32],
687    hc_scale: &[f32; 3],
688    hc_base: &[f32],
689    norm_w: &[f32],
690    cfg: &Dsv4Cfg,
691    scratch: &mut HcScratch,
692    pool: Option<&crate::pool::Pool>,
693    mut block: F,
694) {
695    let (hc, dim) = (cfg.hc_mult, cfg.dim);
696    let mix_hc = (2 + hc) * hc;
697    hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
698    hc_split_sinkhorn(
699        &scratch.mixes,
700        hc_scale,
701        hc_base,
702        hc,
703        cfg.hc_sinkhorn_iters,
704        cfg.hc_eps,
705        &mut scratch.pre,
706        &mut scratch.post,
707        &mut scratch.comb,
708    );
709    hc_fold(state, &scratch.pre, hc, dim, &mut scratch.folded);
710    // RMSNorm with the layer's learned weight, on the folded vector.
711    let ms = scratch.folded.iter().map(|v| v * v).sum::<f32>() / dim as f32;
712    let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
713    for (v, w) in scratch.folded.iter_mut().zip(norm_w) {
714        *v = *v * inv * w;
715    }
716    block(&scratch.folded, &mut scratch.block_out);
717    scratch.residual.copy_from_slice(state);
718    hc_expand(
719        &scratch.block_out,
720        &scratch.residual,
721        &scratch.post,
722        &scratch.comb,
723        hc,
724        dim,
725        state,
726    );
727}
728
729/// Reusable buffers for `hc_block` — one allocation per pipeline, not per
730/// layer per token.
731pub struct HcScratch {
732    pub mixes: Vec<f32>,
733    pub pre: Vec<f32>,
734    pub post: Vec<f32>,
735    pub comb: Vec<f32>,
736    pub folded: Vec<f32>,
737    pub block_out: Vec<f32>,
738    pub residual: Vec<f32>,
739}
740
741impl HcScratch {
742    pub fn new(cfg: &Dsv4Cfg) -> Self {
743        let (hc, dim) = (cfg.hc_mult, cfg.dim);
744        Self {
745            mixes: vec![0.0; (2 + hc) * hc],
746            pre: vec![0.0; hc],
747            post: vec![0.0; hc],
748            comb: vec![0.0; hc * hc],
749            folded: vec![0.0; dim],
750            block_out: vec![0.0; dim],
751            residual: vec![0.0; hc * dim],
752        }
753    }
754}
755
756/// The final fold, after the last layer: `hc` copies to one vector, with a
757/// plain sigmoid gate (no Sinkhorn), then the model's output norm.
758pub fn hc_head_fold(
759    state: &[f32],
760    hc_fn: &[f32],
761    hc_scale: f32,
762    hc_base: &[f32],
763    cfg: &Dsv4Cfg,
764    pool: Option<&crate::pool::Pool>,
765    out: &mut [f32],
766) {
767    let (hc, dim) = (cfg.hc_mult, cfg.dim);
768    let mut mixes = vec![0.0f32; hc];
769    hc_mixes(state, hc_fn, hc, cfg.norm_eps, pool, &mut mixes);
770    let mut pre = vec![0.0f32; hc];
771    hc_head_pre(&mixes, hc_scale, hc_base, hc, cfg.hc_eps, &mut pre);
772    hc_fold(state, &pre, hc, dim, out);
773}
774
775/// One layer's weights. Everything quantized rides as `QTensor` so the
776/// existing kernels (and the mmap) serve them; the small fp32 pieces —
777/// norms, the hyper-connection projections, the sink, the compressor's
778/// position bias — are plain vectors, exactly as the reference keeps them
779/// in fp32 regardless of the checkpoint's storage dtype.
780pub struct Dsv4Layer {
781    pub attn_norm: Vec<f32>,
782    pub ffn_norm: Vec<f32>,
783    // attention: the double LoRA, the compressed KV, the grouped output
784    pub wq_a: crate::qtensor::QTensor,
785    pub q_norm: Vec<f32>,
786    pub wq_b: crate::qtensor::QTensor,
787    pub wkv: crate::qtensor::QTensor,
788    pub kv_norm: Vec<f32>,
789    pub wo_a: crate::qtensor::QTensor,
790    pub wo_b: crate::qtensor::QTensor,
791    pub attn_sink: Vec<f32>,
792    /// `None` on the pure sliding-window layers (`compress_ratio == 0`).
793    pub compressor: Option<Dsv4Compressor>,
794    /// Only on the layers whose ratio is 4.
795    pub indexer: Option<Dsv4Indexer>,
796    // hyper-connections, one set for the attention half and one for the FFN
797    pub hc_attn_fn: Vec<f32>,
798    pub hc_attn_base: Vec<f32>,
799    pub hc_attn_scale: [f32; 3],
800    pub hc_ffn_fn: Vec<f32>,
801    pub hc_ffn_base: Vec<f32>,
802    pub hc_ffn_scale: [f32; 3],
803    // MoE
804    pub gate: crate::qtensor::QTensor,
805    /// noaux_tc selection bias — `None` on the hash layers.
806    pub gate_bias: Option<Vec<f32>>,
807    /// Token-id → expert table on the hash layers, `None` elsewhere.
808    pub tid2eid: Option<Vec<f32>>,
809    pub experts: Vec<Dsv4Expert>,
810    pub shared: Dsv4Expert,
811    /// Task-conditional restriction over the routed experts
812    /// (`CMF_MOE_MASK` + `CMF_MOE_MASK_COVER`): `false` experts are not
813    /// selectable and the weights renormalize over what remains. `None` on
814    /// the hash layers — their table names specific experts, so masking
815    /// there would silently reroute rather than restrict.
816    pub mask: Option<Vec<bool>>,
817}
818
819pub struct Dsv4Expert {
820    pub w1: crate::qtensor::QTensor,
821    pub w2: crate::qtensor::QTensor,
822    pub w3: crate::qtensor::QTensor,
823}
824
825pub struct Dsv4Compressor {
826    pub wkv: crate::qtensor::QTensor,
827    pub wgate: crate::qtensor::QTensor,
828    pub norm: Vec<f32>,
829    /// `[ratio, coff*head_dim]` — the in-window position bias.
830    pub ape: Vec<f32>,
831    pub ratio: usize,
832    /// Overlapping windows (the reference sets this when ratio == 4), which
833    /// doubles the projection width.
834    pub overlap: bool,
835}
836
837pub struct Dsv4Indexer {
838    pub wq_b: crate::qtensor::QTensor,
839    pub weights_proj: crate::qtensor::QTensor,
840    pub compressor: Dsv4Compressor,
841}
842
843/// Model-global pieces: the embedding, the output head and the final
844/// hyper-connection fold.
845pub struct Dsv4Globals {
846    /// RoPE frequencies for the layers that carry a KV compressor: base
847    /// `compress_rope_theta` (160 000 in the release) WITH YaRN.
848    pub inv_freq_compress: Vec<f32>,
849    /// …and for the pure sliding-window layers: base `rope_theta` (10 000)
850    /// with YaRN OFF. The reference picks per layer:
851    ///   if compress_ratio { original_seq_len, compress_rope_theta }
852    ///   else              { 0, rope_theta }   // "disable YaRN"
853    /// One shared table gets both groups wrong — the model still retrieves
854    /// facts, because attention still attends, but every position is rotated
855    /// by the wrong angle, so it repeats itself and cannot count.
856    pub inv_freq_window: Vec<f32>,
857    pub embed: crate::qtensor::QTensor,
858    pub norm: Vec<f32>,
859    pub head: crate::qtensor::QTensor,
860    pub hc_head_fn: Vec<f32>,
861    pub hc_head_base: Vec<f32>,
862    pub hc_head_scale: f32,
863}
864
865/// Per-sequence state. The compressor and the indexer each keep their own
866/// compressed cache and a partial window, so decode picks up mid-window
867/// exactly where prefill left off.
868pub struct Dsv4State {
869    /// Sliding-window KV per layer, `[window, head_dim]` ring.
870    pub window: Vec<Vec<f32>>,
871    /// Compressed KV per layer, appended once per `ratio` tokens.
872    pub compressed: Vec<Vec<f32>>,
873    /// The indexer's own compressed cache per layer.
874    pub index_kv: Vec<Vec<f32>>,
875    /// Partial window being accumulated, per layer: kv and score streams.
876    pub pending_kv: Vec<Vec<f32>>,
877    pub pending_score: Vec<Vec<f32>>,
878    /// The window before it, kept only by the overlapping compressor —
879    /// its fold reads half its dimensions from the previous stride.
880    pub prev_kv: Vec<Vec<f32>>,
881    pub prev_score: Vec<Vec<f32>>,
882    /// The indexer's compressor runs alongside the attention one and keeps
883    /// its own window — same shape, different width and different weights.
884    pub pending_ix_kv: Vec<Vec<f32>>,
885    pub pending_ix_score: Vec<Vec<f32>>,
886    pub prev_ix_kv: Vec<Vec<f32>>,
887    pub prev_ix_score: Vec<Vec<f32>>,
888    pub pos: usize,
889    /// Identifies this sequence's caches on the device. A fresh state gets a
890    /// fresh id, so a device buffer left over from the previous conversation
891    /// can never be read as if it belonged to this one.
892    pub kv_id: u64,
893    /// When the token graph owns a layer's caches, the CONTENTS live on the
894    /// card and only these counts stay here — how much of the window is
895    /// filled, and how many compressed entries each cache holds. All three
896    /// follow from the position, so keeping them costs nothing and reading
897    /// them back would cost a round trip.
898    pub dev_filled: Vec<usize>,
899    pub dev_n_comp: Vec<usize>,
900    pub dev_n_ix: Vec<usize>,
901    /// True once this sequence has run a layer on the card with the device
902    /// owning its state. The host copies above are stale from then on, so
903    /// the CPU path must not be used for that layer again.
904    pub dev_owned: bool,
905    /// The device-layer set of the FIRST chained token. If it ever differs,
906    /// some layer's caches are on the wrong side and the answer would be
907    /// quietly wrong — the loop refuses instead.
908    pub dev_set: Vec<bool>,
909    /// Which layers run their MoE on the card from a PARTIAL expert pack.
910    /// Their walk attention must stay on the host: the device attention
911    /// frame and the device MoE frame of one layer share pooled slots and
912    /// poison each other across tokens (see `attention_step`).
913    pub partial_set: Vec<bool>,
914    /// More than one layer walks past the device prefix. The stale-slot
915    /// poison needs a CHAIN of walk frames handing state through the pooled
916    /// slots; a single tail layer (the canonical shape) never chains and
917    /// its device attention is measured exact.
918    pub split_deep: bool,
919}
920
921impl Dsv4State {
922    pub fn new(layers: usize) -> Self {
923        use std::sync::atomic::{AtomicU64, Ordering};
924        static NEXT: AtomicU64 = AtomicU64::new(1);
925        Self {
926            kv_id: NEXT.fetch_add(1, Ordering::Relaxed),
927            dev_filled: vec![0; layers],
928            dev_n_comp: vec![0; layers],
929            dev_n_ix: vec![0; layers],
930            dev_owned: false,
931            dev_set: Vec::new(),
932            partial_set: Vec::new(),
933            split_deep: false,
934            window: vec![Vec::new(); layers],
935            compressed: vec![Vec::new(); layers],
936            index_kv: vec![Vec::new(); layers],
937            pending_kv: vec![Vec::new(); layers],
938            pending_score: vec![Vec::new(); layers],
939            prev_kv: vec![Vec::new(); layers],
940            prev_score: vec![Vec::new(); layers],
941            pending_ix_kv: vec![Vec::new(); layers],
942            pending_ix_score: vec![Vec::new(); layers],
943            prev_ix_kv: vec![Vec::new(); layers],
944            prev_ix_score: vec![Vec::new(); layers],
945            pos: 0,
946        }
947    }
948}
949
950/// One attention block for a single position. `hidden` is the folded,
951/// normalized vector `hc_block` hands over; the result goes back to it.
952///
953/// The order matters and is the reference's: q through the LoRA pair with
954/// a normalization at each end, kv compressed to one head's width, rope on
955/// the tails, the window and the compressed positions concatenated into
956/// one index list, sparse attention with the sink, the INVERSE rope on the
957/// output, then the grouped low-rank projection.
958#[allow(clippy::too_many_arguments)]
959/// Advance one compressor by a token and return its folded entry when the
960/// window closes. Both the attention compressor and the indexer's own run
961/// through here — the indexer's was simply never called, so its cache stayed
962/// empty and every layer that has an indexer selected ZERO compressed
963/// positions, discarding a correctly-built long-range memory.
964#[allow(clippy::too_many_arguments)]
965fn compressor_step(
966    cp: &Dsv4Compressor,
967    hidden: &[f32],
968    pos: usize,
969    rd: usize,
970    norm_eps: f32,
971    inv_freq: &[f32],
972    pool: Option<&crate::pool::Pool>,
973    pending_kv: &mut Vec<f32>,
974    pending_score: &mut Vec<f32>,
975    prev_kv: &mut Vec<f32>,
976    prev_score: &mut Vec<f32>,
977) -> Option<Vec<f32>> {
978    let width = cp.wkv.rows();
979    let ew = if cp.overlap { width / 2 } else { width };
980    let mut ckv = vec![0.0f32; width];
981    let mut cscore = vec![0.0f32; width];
982    // Same input, so one dispatch instead of two — and this runs twice a
983    // layer (the compressor and the indexer's own), 43 layers a token.
984    crate::qtensor::QTensor::matvec_many(
985        [&cp.wkv, &cp.wgate],
986        hidden,
987        [&mut ckv, &mut cscore],
988        pool,
989    );
990    if cp.overlap {
991        // The reference biases the score as the token arrives and keeps it
992        // biased across the shift, so ape is added ONCE, here.
993        let slot = pos % cp.ratio;
994        for (c, a) in cscore
995            .iter_mut()
996            .zip(&cp.ape[slot * width..(slot + 1) * width])
997        {
998            *c += a;
999        }
1000    }
1001    pending_kv.extend_from_slice(&ckv);
1002    pending_score.extend_from_slice(&cscore);
1003    if pending_kv.len() / width < cp.ratio {
1004        return None;
1005    }
1006    let mut folded = vec![0.0f32; ew];
1007    if cp.overlap {
1008        compress_window_overlap(
1009            prev_kv,
1010            prev_score,
1011            pending_kv,
1012            pending_score,
1013            cp.ratio,
1014            ew,
1015            &mut folded,
1016        );
1017        *prev_kv = std::mem::take(pending_kv);
1018        *prev_score = std::mem::take(pending_score);
1019    } else {
1020        compress_window(
1021            pending_kv,
1022            pending_score,
1023            &cp.ape,
1024            cp.ratio,
1025            width,
1026            &mut folded,
1027        );
1028    }
1029    rms_weighted(&mut folded, &cp.norm, norm_eps);
1030    // The entry carries the same rope-tagged tail as a window key, at the
1031    // position of the window's first token.
1032    rope_tail(&mut folded, inv_freq, pos + 1 - cp.ratio, rd, false);
1033    pending_kv.clear();
1034    pending_score.clear();
1035    Some(folded)
1036}
1037
1038/// `CMF_DSV4_PROFILE=1` accumulates wall time per stage and prints the split
1039/// when the process ends. Guessing which half of a layer costs what is how
1040/// one ends up optimising the cheap one: the fused attention block came out a
1041/// wash on the release checkpoint, and no amount of reasoning about MAC
1042/// counts settles whether that is because attention was already cheap or
1043/// because the device arm was slow.
1044pub(crate) mod prof {
1045    use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
1046
1047    pub static ATTN_NS: AtomicU64 = AtomicU64::new(0);
1048    pub static MOE_NS: AtomicU64 = AtomicU64::new(0);
1049    pub static CALLS: AtomicU64 = AtomicU64::new(0);
1050    /// Everything in a layer that is neither attention nor the experts: the
1051    /// hyper-connection fold and expand, the two norms, the residual.
1052    pub static HC_NS: AtomicU64 = AtomicU64::new(0);
1053    /// The head: final norm plus lm_head over 129280 rows.
1054    pub static HEAD_NS: AtomicU64 = AtomicU64::new(0);
1055    /// Host prep of the device layer: attention_step's CPU half (indexer,
1056    /// compressor, qr) — suspected owner of the unaccounted milliseconds.
1057    pub static PREP_NS: AtomicU64 = AtomicU64::new(0);
1058    /// The per-layer KV/window cache uploads before the frame.
1059    pub static CACHEW_NS: AtomicU64 = AtomicU64::new(0);
1060    /// The whole forward, so the buckets can be checked against a total
1061    /// instead of against a guess. 78 ms of measured work in a 108 ms token
1062    /// left 30 ms that no counter had ever looked at.
1063    pub static ALL_NS: AtomicU64 = AtomicU64::new(0);
1064    pub static TOKENS: AtomicU64 = AtomicU64::new(0);
1065
1066    /// One token = one visit to layer zero. Counting `moe_step` calls instead
1067    /// counts layers.
1068    pub fn note_layer(li: usize) {
1069        CALLS.fetch_add(1, Ordering::Relaxed);
1070        if li == 0 {
1071            // The first token pays for the whole expert set reaching the card
1072            // — tens of seconds of it. Left in, that one-time cost is divided
1073            // by every later call and reads as a per-call price: it is what
1074            // made "the host encodes for 4.45 ms a layer" out of an upload
1075            // that happens once. Everything measured before the SECOND token
1076            // starts is therefore thrown away, and the report describes
1077            // steady state, which is the only thing worth optimising.
1078            // `swap` and not a TOKENS comparison: resetting TOKENS to 1 made
1079            // the test true again on every later token, so the report
1080            // described one token instead of the run.
1081            if TOKENS.fetch_add(1, Ordering::Relaxed) == 1 && !ZEROED.swap(true, Ordering::Relaxed)
1082            {
1083                for a in [
1084                    &ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS, &PREP_NS, &CACHEW_NS,
1085                ] {
1086                    a.store(0, Ordering::Relaxed);
1087                }
1088                TOKENS.store(1, Ordering::Relaxed);
1089                #[cfg(feature = "gpu")]
1090                for a in [
1091                    &crate::gpu_wgpu::MOE_ENC_NS,
1092                    &crate::gpu_wgpu::MOE_WAIT_NS,
1093                    &crate::gpu_wgpu::MOE_BUFS_NS,
1094                    &crate::gpu_wgpu::MOE_UP_NS,
1095                    &crate::gpu_wgpu::MOE_PASS_NS,
1096                    &crate::gpu_wgpu::ATT_ENC_NS,
1097                    &crate::gpu_wgpu::ATT_WAIT_NS,
1098                    &crate::gpu_wgpu::CHAIN_ENC_NS,
1099                    &crate::gpu_wgpu::CHAIN_WAIT_NS,
1100                    &crate::gpu_wgpu::CHAIN_LAYERS,
1101                    &crate::gpu_wgpu::CHAIN_RUNS,
1102                    &crate::gpu_wgpu::SUBMITS,
1103                    &crate::gpu_wgpu::PASSES,
1104                ] {
1105                    a.store(0, Ordering::Relaxed);
1106                }
1107            }
1108        }
1109    }
1110    static REPORT: AtomicBool = AtomicBool::new(false);
1111    /// The one-time "drop the first token's numbers" latch.
1112    static ZEROED: AtomicBool = AtomicBool::new(false);
1113
1114    pub fn on() -> bool {
1115        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1116        *ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
1117    }
1118
1119    /// Print once, from wherever the last caller happens to be — a process
1120    /// that exits through several paths would otherwise report zero or twice.
1121    pub fn report() {
1122        if !on() || REPORT.swap(true, Ordering::Relaxed) {
1123            return;
1124        }
1125        // CALLS counts layer visits, not tokens — dividing by it and calling
1126        // the result "per token" is off by the layer count, which is 43 on
1127        // the release and reads as a plausible number either way.
1128        let calls = CALLS.load(Ordering::Relaxed).max(1);
1129        let toks = TOKENS.load(Ordering::Relaxed).max(1);
1130        let (a, m) = (
1131            ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
1132            MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
1133        );
1134        let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
1135        // HC_NS wraps the FFN half's hc_block WHOLE, and moe_step runs
1136        // inside that block — so the raw counter double-counts every MoE
1137        // millisecond as hyper-connection time. Reported as the difference:
1138        // the glue alone. (This inflation is what made moving the
1139        // hyper-connections to the card look like a 19 ms win when the glue
1140        // is ~4.)
1141        let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
1142            - MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
1143            .max(0.0);
1144        let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
1145        let prep = PREP_NS.load(Ordering::Relaxed) as f64 / 1e6;
1146        let cw = CACHEW_NS.load(Ordering::Relaxed) as f64 / 1e6;
1147        eprintln!(
1148            "[dsv4-profile] layer HOST PREP: {:.0} ms/token, KV uploads: {:.0} ms/token",
1149            prep / toks as f64,
1150            cw / toks as f64
1151        );
1152        #[cfg(feature = "gpu")]
1153        {
1154            let f = crate::gpu_wgpu::DSV4_FILLS.load(Ordering::Relaxed);
1155            let fb = crate::gpu_wgpu::DSV4_FILL_BYTES.load(Ordering::Relaxed);
1156            eprintln!(
1157                "[dsv4-profile] SLOT UPLOADS: {:.1} experts/token, {:.0} MB/token",
1158                f as f64 / toks as f64,
1159                fb as f64 / 1e6 / toks as f64
1160            );
1161        }
1162        eprintln!(
1163            "[dsv4-profile] {calls} layer calls over {toks} tokens | \
1164             per token: attention {:.0} ms, MoE {:.0} ms, hyper-connections+norms {:.0} ms, \
1165             head {:.0} ms | per call: attention {:.2}, MoE {:.2}, connections {:.2}",
1166            a / toks as f64,
1167            m / toks as f64,
1168            hc / toks as f64,
1169            hd / toks as f64,
1170            a / calls as f64,
1171            m / calls as f64,
1172            hc / calls as f64,
1173        );
1174        eprintln!(
1175            "[dsv4-profile] whole pass {:.0} ms per token; outside the counters {:.0} ms",
1176            all / toks as f64,
1177            (all - a - m - hd) / toks as f64,
1178        );
1179        #[cfg(feature = "gpu")]
1180        {
1181            let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1182            let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1183            if ae + aw > 0.0 {
1184                eprintln!(
1185                    "[dsv4-profile] attention frame per call: encoding {:.2} ms, \
1186                     submit and wait {:.2} ms",
1187                    ae / calls as f64,
1188                    aw / calls as f64,
1189                );
1190            }
1191            // At the OUTER level on purpose: this used to sit inside the MoE
1192            // frame's own report, and the chain does not use the MoE frame —
1193            // so the one number that says where a chained token goes was
1194            // printed only when the chain was not running.
1195            let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
1196            let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
1197            if ub > 0 && un > 0 {
1198                eprintln!(
1199                    "[dsv4-profile] weight UPLOAD: {:.1} GB in {:.1} s ({:.0} MB/s)",
1200                    ub as f64 / 1e9,
1201                    un as f64 / 1e9,
1202                    ub as f64 / (un as f64 / 1e9) / 1e6,
1203                );
1204            }
1205            let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
1206            if sub > 0 {
1207                eprintln!(
1208                    "[dsv4-profile] SUBMITS to the card: {:.1} per token, PASSES {:.0} \
1209                     ({:.1} per layer)",
1210                    sub as f64 / toks as f64,
1211                    crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
1212                    crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
1213                );
1214            }
1215            let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
1216            if cl > 0 {
1217                let toks2 = toks.max(1) as f64;
1218                eprintln!(
1219                    "[dsv4-profile] CHAIN per token: encoding {:.2} ms, \
1220                     wait {:.2} ms ({} layers, {} submits)",
1221                    crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1222                    crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
1223                    cl / toks.max(1),
1224                    crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
1225                );
1226            }
1227            let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
1228            let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
1229            if e + wt > 0.0 {
1230                let ns = |a: &std::sync::atomic::AtomicU64| {
1231                    a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
1232                };
1233                eprintln!(
1234                    "[dsv4-profile] MoE frame per call: encoding {:.2} ms, \
1235                     submit and wait {:.2} ms",
1236                    e / calls as f64,
1237                    wt / calls as f64,
1238                );
1239                let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
1240                if an > 0 {
1241                    let g = |i: usize| {
1242                        crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
1243                            / 1e6
1244                            / an as f64
1245                    };
1246                    eprintln!(
1247                        "[dsv4-profile]   ATTENTION ON THE CARD per call: single {:.3} ms, \
1248                         scores {:.3} ms, apply {:.3} ms",
1249                        g(0),
1250                        g(1),
1251                        g(2),
1252                    );
1253                }
1254                let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
1255                let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
1256                if gn > 0 && gns > 0 {
1257                    eprintln!(
1258                        "[dsv4-profile]   MoE ON THE CARD: {:.3} ms per call ({gn} samples)",
1259                        gns as f64 / 1e6 / gn as f64,
1260                    );
1261                } else if gn > 0 {
1262                    // Zero across thousands of samples is a broken query, not
1263                    // an instant kernel, and printing it as a time is how a
1264                    // profile starts lying.
1265                    eprintln!(
1266                        "[dsv4-profile]   MoE ON THE CARD: the timestamps returned ZERO on {gn} samples — \
1267                         the time query did not work, do not use the number"
1268                    );
1269                }
1270                eprintln!(
1271                    "[dsv4-profile]   of the encoding: expert buffers {:.2} ms, \
1272                     uploads {:.2} ms, passes {:.2} ms",
1273                    ns(&crate::gpu_wgpu::MOE_BUFS_NS),
1274                    ns(&crate::gpu_wgpu::MOE_UP_NS),
1275                    ns(&crate::gpu_wgpu::MOE_PASS_NS),
1276                );
1277            }
1278        }
1279    }
1280}
1281
1282/// Print the per-token split, if `CMF_DSV4_PROFILE` asked for one.
1283pub fn profile_report() {
1284    prof::report();
1285}
1286
1287/// `CMF_DSV4_GPU_ATTN=1` moves the attention block onto the device as one
1288/// submission. Off by default: it needs every attention weight in q4tp and a
1289/// working wgpu context, and a frame that declines mid-layer after the state
1290/// has been advanced would be worse than one that never ran.
1291fn gpu_attn_enabled() -> bool {
1292    #[cfg(feature = "gpu")]
1293    {
1294        use std::sync::OnceLock;
1295        static ON: OnceLock<bool> = OnceLock::new();
1296        *ON.get_or_init(|| {
1297            let want = std::env::var("CMF_DSV4_GPU_ATTN")
1298                .map(|v| v != "0")
1299                .unwrap_or(true);
1300            let have = want && crate::gpu::backend_available();
1301            if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
1302                tracing::warn!(
1303                    "CMF_DSV4_GPU_ATTN is set but there is no device — the attention block                      stays on the CPU. Check CMF_GPU=wgpu and the Vulkan ICD."
1304                );
1305            }
1306            if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
1307                eprintln!("dsv4 frame: requested={want} available={have}");
1308            }
1309            have
1310        })
1311    }
1312    #[cfg(not(feature = "gpu"))]
1313    {
1314        false
1315    }
1316}
1317
1318/// The device half of `attention_step`. Returns false — having changed
1319/// nothing — whenever it cannot do the whole block, so the caller's CPU path
1320/// is still correct to run.
1321#[cfg(feature = "gpu")]
1322#[allow(clippy::too_many_arguments)]
1323fn attn_frame(
1324    l: &Dsv4Layer,
1325    cfg: &Dsv4Cfg,
1326    st: &Dsv4State,
1327    li: usize,
1328    hidden: &[f32],
1329    qn: &[f32],
1330    idxs: &[usize],
1331    inv_freq: &[f32],
1332    pos: usize,
1333    win_len: usize,
1334    scale: f32,
1335    // Present: the frame also does this layer's hyper-connection handover
1336    // and leaves the MoE half's input on the card. `out` may then be empty.
1337    hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
1338    out: &mut [f32],
1339) -> bool {
1340    let hd = cfg.head_dim;
1341    let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
1342        l.wq_a.model_idx(),
1343        l.wq_b.model_idx(),
1344        l.wo_a.model_idx(),
1345        l.wo_b.model_idx(),
1346    ) else {
1347        return false;
1348    };
1349    let Some(model) = l.wq_b.model_arc() else {
1350        return false;
1351    };
1352    // Fixed window region, then the compressed tail — so a token writes one
1353    // window slot's worth of movement and whatever the compressor just added,
1354    // not the whole cache. `cap` has to cover the longest run this sequence
1355    // will reach; the compressed axis grows by one entry per `ratio` tokens.
1356    let n_comp = st.compressed[li].len() / hd;
1357    let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
1358    let kv_id = st.kv_id;
1359    // The window is rewritten whole. A ring would write one slot instead of
1360    // 128 — 2 KB against 256 — and was tried: it bought NOTHING (the cost is
1361    // per-dispatch driver bookkeeping, not the copy) and moved perplexity by
1362    // 6e-5 because the attended positions arrive in a different order and the
1363    // softmax accumulates differently. Not a trade worth making.
1364    if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
1365        return false;
1366    }
1367    // The compressed axis only ever grows, so write the TAIL. Rewriting it
1368    // whole was 22 MB a token at 1024 positions — the cache write, not the
1369    // arithmetic, was what the attention block had left to pay.
1370    // The compressed tail is written WHOLE every token. Writing only the new
1371    // part was tried and gave nothing measurable, and the bookkeeping it
1372    // needs — a per-layer tail count invalidated by every buffer growth — is
1373    // exactly the kind of state that drifts silently and shows up as a model
1374    // that stops early. Not worth carrying for zero.
1375    if n_comp > 0
1376        && !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, cfg.window * hd, &st.compressed[li], cap)
1377    {
1378        return false;
1379    }
1380    let idx32: Vec<u32> = idxs
1381        .iter()
1382        .map(|&p| {
1383            if p < win_len {
1384                p as u32
1385            } else {
1386                (cfg.window + (p - win_len)) as u32
1387            }
1388        })
1389        .collect();
1390    let w = crate::gpu_wgpu::Dsv4AttnW {
1391        wq_a,
1392        wq_b,
1393        wo_a,
1394        wo_b,
1395        q_norm: &l.q_norm,
1396        sink: &l.attn_sink,
1397    };
1398    let g = crate::gpu_wgpu::Dsv4AttnGeom {
1399        dim: cfg.dim,
1400        nh: cfg.n_heads,
1401        hd,
1402        rd: cfg.rope_head_dim,
1403        q_lora: cfg.q_lora_rank,
1404        o_lora: cfg.o_lora_rank,
1405        o_groups: cfg.o_groups,
1406        eps: cfg.norm_eps,
1407        scale,
1408        bf16: false,
1409        q_rms: true,
1410        o_fp8: false,
1411    };
1412    // The host fold, explicitly. The frame used to read this half's input
1413    // from the pooled x2 slot — which a device MoE frame of the SAME layer
1414    // overwrites each token with the NEXT layer's input, so the second
1415    // token of any chain+partial configuration attended over garbage
1416    // (perplexity 5.3 against the 4.578 gold on every budget small enough
1417    // to split a layer). The host has the exact vector either way; one
1418    // hidden-width upload per call is what correctness costs.
1419    crate::gpu_wgpu::dsv4_attn_frame(
1420        &model,
1421        &w,
1422        g,
1423        hidden,
1424        Some(qn),
1425        None,
1426        kv_id,
1427        li,
1428        &idx32,
1429        inv_freq,
1430        pos,
1431        hc,
1432        out,
1433    )
1434}
1435
1436/// What the host still owes the device before a layer frame can run: the
1437/// shared LoRA vector the indexer reads, and the attended position list.
1438#[derive(Default)]
1439pub struct AttnPrep {
1440    pub qr: Vec<f32>,
1441    pub idxs: Vec<usize>,
1442    pub win_len: usize,
1443}
1444
1445#[allow(clippy::too_many_arguments)]
1446pub fn attention_step(
1447    hidden: &[f32],
1448    l: &Dsv4Layer,
1449    cfg: &Dsv4Cfg,
1450    st: &mut Dsv4State,
1451    li: usize,
1452    // Chosen by the caller from the layer's kind — see Dsv4Globals.
1453    inv_freq: &[f32],
1454    pool: Option<&crate::pool::Pool>,
1455    // When set, stop once the caches are advanced and the index list is
1456    // built, and hand those back instead of running attention: the layer
1457    // frame does the rest on the device.
1458    prep_out: Option<&mut AttnPrep>,
1459    out: &mut [f32],
1460) {
1461    let _t0 = prof::on().then(std::time::Instant::now);
1462    let _guard = scopeguard_attn(_t0);
1463    let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
1464    let pos = st.pos;
1465    if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
1466        eprintln!(
1467            "    [port] rd={rd} frequencies={} inv_freq[0..4]={:?}",
1468            inv_freq.len(),
1469            &inv_freq[..4.min(inv_freq.len())]
1470        );
1471    }
1472
1473    // ── q and kv: both read the same hidden state, so they go out as ONE
1474    // dispatch. The norms after them differ, and they stay separate.
1475    // (q: wq_a → q_norm → wq_b → per-head norm → rope tail;
1476    //  kv: one head's width, shared by every query head.)
1477    let mut qr = vec![0.0f32; cfg.q_lora_rank];
1478    let mut kv = vec![0.0f32; hd];
1479    crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
1480    rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
1481    // The queries are built further down, after the frame has had its chance
1482    // at the whole block. `qr` is needed either way: the indexer reads it.
1483    // A PARTIAL layer walks its attention on the host. Its device MoE
1484    // frame refills the pooled walk slots (x2, the hyper-connection state)
1485    // each token with the NEXT layer's values, so the same layer's device
1486    // attention frame attends over the previous token's leftovers on the
1487    // second token — measured as perplexity 5.3 against the 4.578 gold on
1488    // every budget small enough to split a layer, and exact the moment
1489    // that one layer's attention walks on the host. Layers whose MoE runs
1490    // on the HOST keep their device attention: nothing refills their
1491    // slots mid-walk, and the MAX_LI ladder measures them bit-exact.
1492    // …and it spreads: the partial layer's MoE frame cycles slots that the
1493    // FOLLOWING host-MoE layers' device attention also reads, so in any
1494    // configuration that holds a partial layer, every layer past the chain
1495    // prefix walks its attention on the host. A configuration with no
1496    // partial layer keeps device attention everywhere — the canonical
1497    // stand and the MAX_LI ladder both measure that bit-exact.
1498    let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
1499    let past_chain =
1500        st.dev_owned && (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
1501    if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
1502        eprintln!(
1503            "[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
1504            st.pos,
1505            st.dev_owned,
1506            st.dev_set.len(),
1507            st.partial_set.len()
1508        );
1509    }
1510    let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
1511
1512    rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
1513    rope_tail(&mut kv, inv_freq, pos, rd, false);
1514
1515    // ── the compressor: accumulate `ratio` tokens, then fold them into
1516    // one compressed entry. The reference fires when (pos+1) % ratio == 0,
1517    // so a partial window simply waits — which is why the state carries
1518    // the pending streams across tokens.
1519    if let Some(cp) = &l.compressor {
1520        let mut pk = std::mem::take(&mut st.pending_kv[li]);
1521        let mut ps = std::mem::take(&mut st.pending_score[li]);
1522        let mut qk = std::mem::take(&mut st.prev_kv[li]);
1523        let mut qs = std::mem::take(&mut st.prev_score[li]);
1524        let entry = compressor_step(
1525            cp,
1526            hidden,
1527            pos,
1528            rd,
1529            cfg.norm_eps,
1530            inv_freq,
1531            pool,
1532            &mut pk,
1533            &mut ps,
1534            &mut qk,
1535            &mut qs,
1536        );
1537        st.pending_kv[li] = pk;
1538        st.pending_score[li] = ps;
1539        st.prev_kv[li] = qk;
1540        st.prev_score[li] = qs;
1541        if let Some(e) = entry {
1542            st.compressed[li].extend_from_slice(&e);
1543        }
1544    }
1545    // The indexer scores against ITS OWN compressed cache, built by its own
1546    // compressor. Without this the cache is empty, `n_ix` is zero, and every
1547    // indexer layer picks no compressed positions at all — the long-range
1548    // memory is built and then never read.
1549    if let Some(ix) = &l.indexer {
1550        let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
1551        let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
1552        let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
1553        let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
1554        let entry = compressor_step(
1555            &ix.compressor,
1556            hidden,
1557            pos,
1558            rd,
1559            cfg.norm_eps,
1560            inv_freq,
1561            pool,
1562            &mut pk,
1563            &mut ps,
1564            &mut qk,
1565            &mut qs,
1566        );
1567        st.pending_ix_kv[li] = pk;
1568        st.pending_ix_score[li] = ps;
1569        st.prev_ix_kv[li] = qk;
1570        st.prev_ix_score[li] = qs;
1571        if let Some(e) = entry {
1572            st.index_kv[li].extend_from_slice(&e);
1573        }
1574    }
1575
1576    st.window[li].extend_from_slice(&kv);
1577    // The reference keeps the window in a ring of `window_size`; holding the
1578    // last N in order is the same set, and without this the "window" grows
1579    // for the whole generation — wrong attention AND unbounded memory.
1580    let cap = cfg.window * hd;
1581    if st.window[li].len() > cap {
1582        let drop = st.window[li].len() - cap;
1583        st.window[li].drain(..drop);
1584    }
1585    let win_len = st.window[li].len() / hd;
1586    let n_pos = win_len + st.compressed[li].len() / hd;
1587
1588    // Index list: every window position, plus whatever the indexer picked
1589    // (or, without an indexer, every compressed position).
1590    //
1591    // CMF_DSV4_NO_COMPRESSED=1 attends to the sliding window ALONE. That is
1592    // not a mode anyone should serve — it drops the model's long-range
1593    // memory — but it separates two failure modes that look identical from
1594    // the outside: output that degrades because the compressed path is
1595    // wrong, and output that degrades because the weights are too coarse.
1596    let mut idxs: Vec<usize> = (0..win_len).collect();
1597    if !st.compressed[li].is_empty() && !no_compressed() {
1598        let n_comp = st.compressed[li].len() / hd;
1599        match &l.indexer {
1600            Some(ix) => {
1601                // The indexer scores from the SHARED LoRA output through
1602                // its own wq_b — not from attention's queries — and its
1603                // per-head weights are a projection of the hidden state,
1604                // scaled by head_dim^-0.5 * n_heads^-0.5 as the reference
1605                // folds into `weights_proj`'s output.
1606                //
1607                // The reference also applies a randomized Hadamard rotation
1608                // to the queries here and to the keys in the indexer's
1609                // compressor, then simulates FP4 on both. That transform is
1610                // orthogonal (`hadamard_transform` scaled by d^-0.5) and it
1611                // hits BOTH sides of the same dot product, so it cancels:
1612                // its purpose is to condition the FP4 quantization, which we
1613                // do not do either. Omitting the pair is exact, and keeping
1614                // f32 is strictly more accurate than the reference — not an
1615                // approximation to be fixed later.
1616                let ih = ix.weights_proj.rows();
1617                let idim = ix.wq_b.rows() / ih.max(1);
1618                let mut qi = vec![0.0f32; ix.wq_b.rows()];
1619                ix.wq_b.matvec(&qr, &mut qi, pool);
1620                for h in 0..ih {
1621                    rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
1622                }
1623                let mut hw = vec![0.0f32; ih];
1624                ix.weights_proj.matvec(hidden, &mut hw, pool);
1625                let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
1626                for w in hw.iter_mut() {
1627                    *w *= sc_factor;
1628                }
1629                let n_ix = st.index_kv[li].len() / idim.max(1);
1630                let mut sc = Vec::new();
1631                index_scores(
1632                    &qi,
1633                    &st.index_kv[li],
1634                    &hw,
1635                    ih,
1636                    idim,
1637                    n_ix.min(n_comp),
1638                    n_ix.min(n_comp),
1639                    pool,
1640                    &mut sc,
1641                );
1642                let mut picked = Vec::new();
1643                top_k_positions(&sc, cfg.index_topk, &mut picked);
1644                idxs.extend(picked.into_iter().map(|p| win_len + p));
1645            }
1646            None => idxs.extend((0..n_comp).map(|p| win_len + p)),
1647        }
1648    }
1649    debug_assert!(idxs.iter().all(|&p| p < n_pos));
1650    if let Some(p) = prep_out {
1651        p.qr = qr;
1652        p.idxs = idxs;
1653        p.win_len = win_len;
1654        return;
1655    }
1656
1657    // ── the whole block on the device, or nothing ──
1658    let scale = (hd as f32).powf(-0.5);
1659    #[cfg(feature = "gpu")]
1660    if on_gpu
1661        && {
1662            if std::env::var("CMF_DSV4_XCHK").is_ok() {
1663                // The frame reads this half's input from the card's x2
1664                // slot; the host walked its own. Disagreement = the
1665                // chain→walk handoff, and the number says by how much.
1666                if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
1667                    let md = hidden
1668                        .iter()
1669                        .zip(card.iter())
1670                        .map(|(a, b)| (a - b).abs())
1671                        .fold(0.0f32, f32::max);
1672                    eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
1673                }
1674            }
1675            true
1676        }
1677        && attn_frame(
1678            l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
1679        )
1680    {
1681        return;
1682    }
1683
1684    // ── queries: wq_b, then a norm and the rope tail per head ──
1685    let mut q = vec![0.0f32; cfg.n_heads * hd];
1686    l.wq_b.matvec(&qr, &mut q, pool);
1687    for h in 0..cfg.n_heads {
1688        let head = &mut q[h * hd..(h + 1) * hd];
1689        rms_inplace(head, cfg.norm_eps);
1690        rope_tail(head, inv_freq, pos, rd, false);
1691    }
1692    let mut cache: Vec<f32> = st.window[li].clone();
1693    cache.extend_from_slice(&st.compressed[li]);
1694
1695    // ── sparse attention per head, then the inverse rope ──
1696    let mut attn = vec![0.0f32; cfg.n_heads * hd];
1697    for h in 0..cfg.n_heads {
1698        let qh = &q[h * hd..(h + 1) * hd];
1699        // Straight into this head's slice of the output: the scratch vector
1700        // that used to sit here was an allocation and a copy per head, so 64
1701        // of each per layer per token, for a value that was never read
1702        // anywhere else.
1703        let oh = &mut attn[h * hd..(h + 1) * hd];
1704        sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
1705        rope_tail(oh, inv_freq, pos, rd, true);
1706    }
1707
1708    // ── grouped low-rank output ──
1709    // Read the two blocks through the quantized readers. Materializing them
1710    // here instead costs ~270 MB of dequantization per layer per token on
1711    // the release checkpoint (wo_a and wo_b are 33M weights each), which is
1712    // the difference between decoding and not.
1713    o_project(
1714        &attn,
1715        &|r, x, sc| l.wo_a.row_dot(r, x, sc),
1716        l.wo_a.cols(),
1717        &|mid, dst| l.wo_b.matvec(mid, dst, pool),
1718        cfg.o_groups,
1719        cfg.o_lora_rank,
1720        pool,
1721        out,
1722    );
1723}
1724
1725/// RMSNorm with a learned weight, in place.
1726pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
1727    let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
1728    let inv = 1.0 / (ms + eps).sqrt();
1729    for (x, g) in v.iter_mut().zip(w) {
1730        *x = *x * inv * g;
1731    }
1732}
1733
1734// The MoE half of a block: route, run the chosen experts plus the shared one,
1735// and sum. `token_id` is only read on the hash layers. Per-layer expert
1736// routing mass is also recorded here for task-conditional expert sets
1737// (`CMF_MOE_STATS`). An older implementation counted every top-k winner as
1738// one. That is the wrong quantity for DeepSeek-V4: a weak eighth route and
1739// the dominant route then consume the same `cover` budget, so a compact mask
1740// can retain frequent noise while dropping a rarer expert that carries much
1741// more of the block output. Accumulate the normalized route weights as
1742// fixed-point integers instead. The JSON stays the same `{layer: [u64]}`
1743// shape and old count files remain valid inputs because the mask builder only
1744// compares relative mass within a layer.
1745//
1746// Decode drives this from one thread; the pool parallelizes inside the
1747// matvecs, below this point.
1748thread_local! {
1749    static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
1750        const { std::cell::RefCell::new(Vec::new()) };
1751}
1752
1753fn route_stats_on() -> bool {
1754    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1755    *ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
1756}
1757
1758fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, routed: &[(usize, f32)]) {
1759    ROUTE_COUNTS.with(|c| {
1760        let mut c = c.borrow_mut();
1761        if c.len() <= li.max(n_layers_hint) {
1762            c.resize(li.max(n_layers_hint) + 1, Vec::new());
1763        }
1764        let row = &mut c[li];
1765        if row.len() < n_experts {
1766            row.resize(n_experts, 0);
1767        }
1768        for &(e, weight) in routed {
1769            if e < row.len() {
1770                // One unit keeps a finite selected route visible even if a
1771                // future quantized router rounds an extremely small weight
1772                // below the fixed-point scale.
1773                let mass = (weight.abs() as f64 * 1_000_000.0).round() as u64;
1774                row[e] = row[e].saturating_add(mass.max(1));
1775            }
1776        }
1777    });
1778}
1779
1780/// Take the recorded routing field, leaving the counters empty.
1781pub fn take_route_counts() -> Vec<Vec<u64>> {
1782    ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
1783}
1784
1785/// Charge elapsed time to a counter when it goes out of scope — the two
1786/// steps have several early returns each, and a timer that only stops on the
1787/// long path measures the short one as free.
1788struct Charge(
1789    Option<std::time::Instant>,
1790    &'static std::sync::atomic::AtomicU64,
1791);
1792impl Drop for Charge {
1793    fn drop(&mut self) {
1794        if let Some(t) = self.0 {
1795            self.1.fetch_add(
1796                t.elapsed().as_nanos() as u64,
1797                std::sync::atomic::Ordering::Relaxed,
1798            );
1799        }
1800    }
1801}
1802fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
1803    Charge(t, &prof::ATTN_NS)
1804}
1805fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
1806    if t.is_some() {
1807        prof::note_layer(li);
1808    }
1809    Charge(t, &prof::MOE_NS)
1810}
1811
1812/// The whole token, one submission per layer. Returns false having changed
1813/// nothing if the device declines any layer — the caller's loop is then still
1814/// correct to run.
1815#[cfg(feature = "gpu")]
1816#[allow(clippy::too_many_arguments)]
1817fn dsv4_layer_loop(
1818    state: &mut [f32],
1819    layers: &[Dsv4Layer],
1820    g: &Dsv4Globals,
1821    cfg: &Dsv4Cfg,
1822    st: &mut Dsv4State,
1823    token_id: u32,
1824    inv_freq: &[f32],
1825    pool: Option<&crate::pool::Pool>,
1826    scratch: &mut HcScratch,
1827) -> bool {
1828    let dim = cfg.dim;
1829    let freqs_of = |l: &Dsv4Layer| -> &[f32] {
1830        let f = if l.compressor.is_some() {
1831            &g.inv_freq_compress
1832        } else {
1833            &g.inv_freq_window
1834        };
1835        if f.is_empty() { inv_freq } else { f.as_slice() }
1836    };
1837    // PRE-FLIGHT. The prep inside the loop advances the window and the
1838    // compressor caches, so a refusal halfway leaves state that the CPU
1839    // fallback would advance a SECOND time — which is not a slow answer but a
1840    // wrong one. Everything that can decline is therefore asked before the
1841    // first byte of state moves. The expert upload happens here too, which is
1842    // where it belonged anyway.
1843    // The head goes to the card BEFORE the experts ask for room. It is the
1844    // single most-used tensor in the file — every token reads all of it —
1845    // and it is a rounding error next to the expert stack: 265 MB against
1846    // ninety-odd gigabytes on the release. Uploaded in first-touch order it
1847    // arrived last, after the budget was gone, and stayed on the host for
1848    // the life of the process.
1849    {
1850        static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1851        if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1852            if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
1853                let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
1854                tracing::info!("dsv4: head on the card: {}", if ok { "yes" } else { "no" });
1855            }
1856        }
1857    }
1858    let mut on_dev = vec![false; layers.len()];
1859    let mut partial_dev = vec![false; layers.len()];
1860    let mut attn_ready = vec![false; layers.len()];
1861    // Two phases are essential for the unified pool: first pin every small
1862    // attention/compressor skeleton, then give all remaining weight budget
1863    // to the one expert arena. Allocating the arena after layer zero alone
1864    // would honestly fit it, but starve layer one's attention weights.
1865    for (li, l) in layers.iter().enumerate() {
1866        if l.wq_a.model_idx().is_none()
1867            || l.wq_b.model_idx().is_none()
1868            || l.wo_a.model_idx().is_none()
1869            || l.wo_b.model_idx().is_none()
1870        {
1871            return false;
1872        }
1873        let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1874            return false;
1875        };
1876        // A layer whose experts do not fit is not a reason to abandon the
1877        // token: 100 GB of experts against a 98 GB card means SOME layer will
1878        // always miss. Those run on the host, with the state fetched and put
1879        // back around them — two transfers for the few that need it.
1880        // The attention weights have to be asked for too. Experts fill the
1881        // card first, and a wo_b that misses at layer 11 used to surface as a
1882        // mid-loop refusal — after the caches had advanced, which the CPU
1883        // fallback then advanced again.
1884        // …and, when the layer is to prepare itself, everything that
1885        // preparation reads: the KV projection, both compressors and the
1886        // indexer. Leaving them out is how the chain came to refuse ninety
1887        // times a token on the release — the experts had taken the card by
1888        // the time `dsv4_encode_prep` asked, and it declined silently into a
1889        // fallback that looked like "the chain simply does not help".
1890        let mut want = vec![
1891            l.wq_a.model_idx(),
1892            l.wq_b.model_idx(),
1893            l.wo_a.model_idx(),
1894            l.wo_b.model_idx(),
1895        ];
1896        if chain_enabled() {
1897            want.push(l.wkv.model_idx());
1898            if let Some(cp) = &l.compressor {
1899                want.push(cp.wkv.model_idx());
1900                want.push(cp.wgate.model_idx());
1901            }
1902            if let Some(ix) = &l.indexer {
1903                want.push(ix.wq_b.model_idx());
1904                want.push(ix.weights_proj.model_idx());
1905                want.push(ix.compressor.wkv.model_idx());
1906                want.push(ix.compressor.wgate.model_idx());
1907            }
1908        }
1909        attn_ready[li] = want
1910            .into_iter()
1911            .flatten()
1912            .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
1913    }
1914    for (li, l) in layers.iter().enumerate() {
1915        let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
1916            return false;
1917        };
1918        let gu_q2 = l
1919            .experts
1920            .first()
1921            .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1922        // Size expert storage only after EVERY layer's skeleton is resident.
1923        // The old per-layer packs could interleave these allocations; one
1924        // global allocation cannot, so the ordering is now explicit.
1925        let pk = pack_for(l, cfg, li);
1926        if let Some(pk) = pk {
1927            let dn_q2 = l
1928                .experts
1929                .first()
1930                .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
1931            let experts_ok = if pk.global.is_some() {
1932                crate::gpu_wgpu::dsv4_global_moe_ready(&model)
1933            } else {
1934                crate::gpu_wgpu::dsv4_experts_ready(
1935                    &model,
1936                    &pk.tensors,
1937                    cfg.moe_inter,
1938                    dim,
1939                    gu_q2,
1940                    dn_q2,
1941                )
1942            };
1943            on_dev[li] = attn_ready[li] && experts_ok && pk.route_complete();
1944            partial_dev[li] = attn_ready[li] && experts_ok && !pk.route_complete();
1945        }
1946    }
1947    let active_dev: Vec<bool> = on_dev
1948        .iter()
1949        .zip(&partial_dev)
1950        .map(|(&full, &partial)| full || partial)
1951        .collect();
1952    if !active_dev.iter().any(|&x| x) {
1953        return false;
1954    }
1955    // The attention gate below needs to know about partial layers BEFORE
1956    // the decode path commits the device set — a perplexity run only ever
1957    // prefills, and with this left empty every split budget scored the
1958    // model wrong (measured; see `attention_step`).
1959    if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
1960        st.partial_set = partial_dev.clone();
1961        st.split_deep = active_dev
1962            .iter()
1963            .zip(&partial_dev)
1964            .filter(|(a, p)| !**a || **p)
1965            .count()
1966            > 1;
1967    }
1968
1969    // Which layers the card actually took, said once. A layer that falls to
1970    // the host costs an order of magnitude more than one that does not, and
1971    // "the GPU path is on" hid the difference between all of them and most.
1972    {
1973        static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
1974        if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
1975            let host: Vec<usize> = active_dev
1976                .iter()
1977                .enumerate()
1978                .filter(|&(_, d)| !*d)
1979                .map(|(i, _)| i)
1980                .collect();
1981            let partial: Vec<(usize, usize)> = partial_dev
1982                .iter()
1983                .enumerate()
1984                .filter(|&(_, d)| *d)
1985                .filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
1986                .collect();
1987            if host.is_empty() && partial.is_empty() {
1988                tracing::info!("dsv4: all {} layers on the card", on_dev.len());
1989            } else {
1990                tracing::info!(
1991                    "dsv4: {} of {} layers use the card; partial {:?}; on the host {:?}",
1992                    active_dev.len() - host.len(),
1993                    on_dev.len(),
1994                    partial,
1995                    host,
1996                );
1997            }
1998        }
1999    }
2000
2001    // Layer zero's opening fold has no frame before it to have prepared it.
2002    let (mut folded, post0, comb0) = hc_fold_norm(
2003        state,
2004        &layers[0].hc_attn_fn,
2005        &layers[0].hc_attn_scale,
2006        &layers[0].hc_attn_base,
2007        &layers[0].attn_norm,
2008        cfg,
2009        pool,
2010    );
2011    if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
2012    {
2013        return false;
2014    }
2015    // The device-owned set must not move once a token has run on it — but
2016    // the two directions are not the same risk. At a tight budget the set
2017    // GROWS between tokens as more weights finish uploading, and a layer that
2018    // merely joined can be left on the host: its caches are there and nothing
2019    // is inconsistent. Refusing on that was costing the whole fast path once
2020    // per token — 125 times in a 48-token run on an emulated 24 GB card, on
2021    // which the engine is slow enough already.
2022    //
2023    // A layer LEAVING the set is the dangerous direction: its caches are on
2024    // the card and the host would advance its own. That still refuses.
2025    if st.dev_owned && st.dev_set != active_dev {
2026        let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
2027            .filter(|&i| st.dev_set[i] && !active_dev[i])
2028            .collect();
2029        if !left.is_empty() {
2030            tracing::warn!("layers {left:?} left the card — caches on different sides");
2031            return false;
2032        }
2033        // A layer that was active remains device-owned. Its full/partial mode
2034        // is still derived from the current pack; only cache ownership is
2035        // sticky across tokens.
2036    }
2037    let chain = chain_enabled();
2038    // CMF_DSV4_LAYERS_PROBE=N — TIMING ONLY, the answer is garbage. Runs the
2039    // first N layers and leaves the rest alone. Decode time against N is a
2040    // line whose SLOPE is the per-layer cost and whose intercept is
2041    // everything that happens once a token. Unlike the skip probe it does
2042    // not change what a layer does — which on a MoE model is the difference
2043    // between a measurement and an artefact, because dropping any stage
2044    // changes the routing and the routing changes what the experts cost.
2045    let layer_cap = {
2046        static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2047        *N.get_or_init(|| {
2048            std::env::var("CMF_DSV4_LAYERS_PROBE")
2049                .ok()
2050                .and_then(|v| v.parse::<usize>().ok())
2051                .unwrap_or(usize::MAX)
2052        })
2053    };
2054    let mut run: Vec<usize> = Vec::new();
2055    let mut sink_out = vec![0.0f32; dim];
2056    // `state` starts current on both sides. A device run makes the host copy
2057    // stale unless that same run carries it home. Tracking this explicitly
2058    // avoids a separate state fence before a host layer and, for a final host
2059    // layer, the old upload-immediately-followed-by-readback pair.
2060    let mut state_on_host = true;
2061    for (li, l) in layers.iter().enumerate() {
2062        if li >= layer_cap {
2063            break;
2064        }
2065        // The device path never ticked the profiler, so every per-token
2066        // number it printed described the two host-path tokens at the start
2067        // of a run — the ones that also pay for the upload. Ticking here is
2068        // what makes the chain's encode-and-wait split a per-token figure at
2069        // all.
2070        if prof::on() {
2071            prof::note_layer(li);
2072        }
2073        if chain && on_dev[li] {
2074            // Hash layers used to break the run in two: their forced expert
2075            // list changes per token, went through the (tag, len) upload
2076            // pool, and every layer of a submission shared one buffer. The
2077            // list has a per-layer slot now, so they chain like the rest.
2078            run.push(li);
2079            // CMF_DSV4_CHAIN_MAX=N caps a run's length. Diagnostic, not a
2080            // tuning knob: length-1 runs put ONE layer per submission, which
2081            // separates "the layer frame is wrong" from "layers in one
2082            // encoder contaminate each other" in a single ppl run.
2083            if run.len() >= chain_max() || dspark_wants(li) {
2084                let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2085                let captured = *run.last().unwrap();
2086                if !dsv4_chain_run(
2087                    layers,
2088                    &run,
2089                    cfg,
2090                    g,
2091                    st,
2092                    token_id,
2093                    &mut folded,
2094                    Some(state),
2095                    1,
2096                    &[],
2097                    need_qn,
2098                    pool,
2099                ) {
2100                    return false;
2101                }
2102                state_on_host = true;
2103                verify_fp("walk", st.pos, captured, state);
2104                dspark_note(captured, state, cfg);
2105                run.clear();
2106            }
2107            continue;
2108        }
2109        if chain && !run.is_empty() {
2110            // The very next layer is on the host, so bring its state back in
2111            // the chain's existing readback. Reading it in a second submit
2112            // below cost one fence per token on the release's 42+1 split.
2113            if !dsv4_chain_run(
2114                layers,
2115                &run,
2116                cfg,
2117                g,
2118                st,
2119                token_id,
2120                &mut folded,
2121                Some(state),
2122                1,
2123                &[],
2124                run[0] == 0 || !on_dev[run[0] - 1],
2125                pool,
2126            ) {
2127                return false;
2128            }
2129            state_on_host = true;
2130            let last = *run.last().unwrap();
2131            verify_fp("walk", st.pos, last, state);
2132            dspark_note(last, state, cfg);
2133        }
2134        run.clear();
2135        if partial_dev[li] && chain1_on() {
2136            if let Some(home) = dsv4_chain1_layer(
2137                state,
2138                &mut folded,
2139                layers,
2140                l,
2141                cfg,
2142                st,
2143                token_id,
2144                li,
2145                freqs_of(l),
2146                pool,
2147                state_on_host,
2148            ) {
2149                // chain1 advances the canonical host mirrors and rewrites
2150                // the device window/compressed cache from them, but it does
2151                // not go through dsv4_chain_run (the usual owner of these
2152                // arithmetic counters).  A speculative token-axis pass that
2153                // takes over on the next token must start from the same
2154                // extents, otherwise its very first partial layer attends to
2155                // an empty/stale prefix.
2156                st.dev_filled[li] = (st.window[li].len() / cfg.head_dim).min(cfg.window);
2157                st.dev_n_comp[li] = st.compressed[li].len() / cfg.head_dim;
2158                st.dev_n_ix[li] = l.indexer.as_ref().map_or(0, |ix| {
2159                    let ih = ix.weights_proj.rows();
2160                    let idim = ix.wq_b.rows() / ih.max(1);
2161                    st.index_kv[li].len() / idim.max(1)
2162                });
2163                state_on_host = home;
2164                if verify_fp_on(st.pos) {
2165                    if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2166                        return false;
2167                    }
2168                    state_on_host = true;
2169                    verify_fp("walk", st.pos, li, state);
2170                }
2171                // The draft captures THIS layer's state — which lives on
2172                // the card when no cold came home. Noting the stale host
2173                // array fed the draft garbage: 1265 drafted, 0 accepted.
2174                if dspark_wants(li) {
2175                    if !state_on_host && crate::gpu_wgpu::dsv4_state_read(state) {
2176                        state_on_host = true;
2177                    }
2178                    if state_on_host {
2179                        dspark_note(li, state, cfg);
2180                    }
2181                }
2182                continue;
2183            }
2184        }
2185        if partial_dev[li] && partial_walk_on() {
2186            // Attention and the resident expert subset stay on the card. The
2187            // router still sees every expert and returns only the winners
2188            // that did not fit; those are completed on the CPU and their
2189            // exact linear contribution is added back to device state.
2190            let Some(home) = dsv4_partial_layer(
2191                state,
2192                &mut folded,
2193                layers,
2194                l,
2195                cfg,
2196                st,
2197                token_id,
2198                li,
2199                freqs_of(l),
2200                pool,
2201                state_on_host,
2202            ) else {
2203                return false;
2204            };
2205            state_on_host = home;
2206            if home {
2207                dspark_note(li, state, cfg);
2208            }
2209            continue;
2210        }
2211        if !on_dev[li] {
2212            if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
2213                return false;
2214            }
2215            state_on_host = true;
2216            let freqs = freqs_of(l);
2217            hc_block(
2218                state,
2219                &l.hc_attn_fn,
2220                &l.hc_attn_scale,
2221                &l.hc_attn_base,
2222                &l.attn_norm,
2223                cfg,
2224                scratch,
2225                pool,
2226                |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
2227            );
2228            hc_block(
2229                state,
2230                &l.hc_ffn_fn,
2231                &l.hc_ffn_scale,
2232                &l.hc_ffn_base,
2233                &l.ffn_norm,
2234                cfg,
2235                scratch,
2236                pool,
2237                // The layer the card had no room for. Its experts are
2238                // reached one matvec at a time and the probe sends each to
2239                // the device — right per op, and a fence per op: this one
2240                // layer is why a token that submits ONCE for 42 layers
2241                // submits 13 times. CMF_DSV4_HOST_CPU_MOE=1 keeps them on
2242                // the host instead, trading arithmetic for round trips.
2243                |f, o| {
2244                    if host_cpu_moe() {
2245                        crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
2246                    } else {
2247                        moe_step(f, l, cfg, token_id, li, pool, o)
2248                    }
2249                },
2250            );
2251            // Only a following DEVICE layer needs the fold/hc slots and an
2252            // uploaded state. Consecutive host layers consume `state`
2253            // directly, and a final host layer is already exactly where the
2254            // head needs it — uploading then reading it back was pure sync.
2255            if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
2256                let n = &layers[li + 1];
2257                let (f, p2, c2) = hc_fold_norm(
2258                    state,
2259                    &n.hc_attn_fn,
2260                    &n.hc_attn_scale,
2261                    &n.hc_attn_base,
2262                    &n.attn_norm,
2263                    cfg,
2264                    pool,
2265                );
2266                folded = f;
2267                if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
2268                    return false;
2269                }
2270                if !crate::gpu_wgpu::dsv4_state_write(state) {
2271                    return false;
2272                }
2273            }
2274            dspark_note(li, state, cfg);
2275            continue;
2276        }
2277        let mut prep = AttnPrep::default();
2278        let _tp = prof::on().then(std::time::Instant::now);
2279        attention_step(
2280            &folded,
2281            l,
2282            cfg,
2283            st,
2284            li,
2285            freqs_of(l),
2286            pool,
2287            Some(&mut prep),
2288            &mut sink_out,
2289        );
2290        if let Some(t) = _tp {
2291            prof::PREP_NS.fetch_add(
2292                t.elapsed().as_nanos() as u64,
2293                std::sync::atomic::Ordering::Relaxed,
2294            );
2295        }
2296        // The caches the frame will read.
2297        let hd = cfg.head_dim;
2298        let n_comp = st.compressed[li].len() / hd;
2299        let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2300        let kv_id = st.kv_id;
2301        let _tc = prof::on().then(std::time::Instant::now);
2302        if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2303            || (n_comp > 0
2304                && !crate::gpu_wgpu::dsv4_cache_write(
2305                    kv_id,
2306                    li,
2307                    cfg.window * hd,
2308                    &st.compressed[li],
2309                    cap,
2310                ))
2311        {
2312            return false;
2313        }
2314        if let Some(t) = _tc {
2315            prof::CACHEW_NS.fetch_add(
2316                t.elapsed().as_nanos() as u64,
2317                std::sync::atomic::Ordering::Relaxed,
2318            );
2319        }
2320        let idx32: Vec<u32> = prep
2321            .idxs
2322            .iter()
2323            .map(|&p| {
2324                if p < prep.win_len {
2325                    p as u32
2326                } else {
2327                    (cfg.window + (p - prep.win_len)) as u32
2328                }
2329            })
2330            .collect();
2331        let Some(pk) = pack_for(l, cfg, li) else {
2332            return false;
2333        };
2334        let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2335            l.wq_a.model_idx(),
2336            l.wq_b.model_idx(),
2337            l.wo_a.model_idx(),
2338            l.wo_b.model_idx(),
2339        ) else {
2340            return false;
2341        };
2342        let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
2343            return false;
2344        };
2345        let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
2346            let v: Vec<usize> = if pk.needs_remap() {
2347                hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2348            } else {
2349                hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
2350                    .into_iter()
2351                    .map(|gi| pk.to_slot[gi])
2352                    .collect()
2353            };
2354            if v.contains(&usize::MAX) {
2355                None
2356            } else {
2357                Some(v)
2358            }
2359        });
2360        if l.tid2eid.is_some() && forced.is_none() {
2361            return false;
2362        }
2363        let nxt = layers.get(li + 1);
2364        let w = crate::gpu_wgpu::Dsv4LayerW {
2365            attn: crate::gpu_wgpu::Dsv4AttnW {
2366                wq_a,
2367                wq_b,
2368                wo_a,
2369                wo_b,
2370                q_norm: &l.q_norm,
2371                sink: &l.attn_sink,
2372            },
2373            moe: crate::gpu_wgpu::Dsv4MoeW {
2374                router: &[],
2375                experts: &pk.tensors,
2376                logits: &[],
2377                // The PACK's bias, whose address outlives the process: the
2378                // frame's const cache is keyed on it, and a per-layer Vec
2379                // here handed every layer the first layer's — the exact
2380                // transient-Vec trap the const_buf war story describes,
2381                // reintroduced by this session and caught because the OFF
2382                // baseline moved.
2383                bias: pk.bias.as_deref(),
2384                mask: pk.mask.as_deref(),
2385                forced: forced.as_deref(),
2386                remap: pk.needs_remap().then_some(pk.remap.as_slice()),
2387                global: None,
2388                has_shared: true,
2389                shared_weight: 1.0,
2390                preweighted: false,
2391                qwen_softmax: false,
2392            },
2393            hc_ffn_fn: &l.hc_ffn_fn,
2394            hc_ffn_scale: &l.hc_ffn_scale,
2395            hc_ffn_base: &l.hc_ffn_base,
2396            hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2397            hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2398            hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2399            ffn_norm: &l.ffn_norm,
2400            next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2401            next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2402            next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2403            router: &pk.router,
2404        };
2405        let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2406            attn: crate::gpu_wgpu::Dsv4AttnGeom {
2407                dim,
2408                nh: cfg.n_heads,
2409                hd,
2410                rd: cfg.rope_head_dim,
2411                q_lora: cfg.q_lora_rank,
2412                o_lora: cfg.o_lora_rank,
2413                o_groups: cfg.o_groups,
2414                eps: cfg.norm_eps,
2415                scale: (hd as f32).powf(-0.5),
2416                bf16: false,
2417                q_rms: true,
2418                o_fp8: false,
2419            },
2420            moe: crate::gpu_wgpu::Dsv4MoeGeom {
2421                hidden: dim,
2422                inter: cfg.moe_inter,
2423                top_k: cfg.top_k,
2424                route_scale: cfg.route_scale,
2425                swiglu_limit: cfg.swiglu_limit,
2426                gu_q2: l.experts.first().is_some_and(|e| {
2427                    e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
2428                }),
2429                bf16: false,
2430            },
2431            hc: cfg.hc_mult,
2432            hc_eps: cfg.hc_eps,
2433            sinkhorn_iters: cfg.hc_sinkhorn_iters,
2434        };
2435        let mut next = vec![0.0f32; dim];
2436        if !crate::gpu_wgpu::dsv4_layer_frame(
2437            &model,
2438            &w,
2439            geom,
2440            kv_id,
2441            li,
2442            Some(&prep.qr),
2443            &idx32,
2444            freqs_of(l),
2445            st.pos,
2446            &mut next,
2447            None,
2448            None,
2449            &mut Vec::new(),
2450        ) {
2451            return false;
2452        }
2453        state_on_host = false;
2454        folded = next;
2455        dspark_note(li, state, cfg);
2456    }
2457    let mut state_home = false;
2458    if chain {
2459        if !run.is_empty() {
2460            // The token's LAST run brings the state back with it. Only the
2461            // last: an earlier run's state is one the layers after it still
2462            // change.
2463            let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
2464            let last_on_dev = *on_dev.last().unwrap_or(&false);
2465            let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
2466            let ok = if carry {
2467                let r = dsv4_chain_run(
2468                    layers,
2469                    &run,
2470                    cfg,
2471                    g,
2472                    st,
2473                    token_id,
2474                    &mut folded,
2475                    Some(state),
2476                    1,
2477                    &[],
2478                    need_qn,
2479                    pool,
2480                );
2481                state_home = r;
2482                state_on_host = r;
2483                if r {
2484                    dspark_note(*run.last().unwrap(), state, cfg);
2485                }
2486                r
2487            } else {
2488                let r = dsv4_chain_run(
2489                    layers,
2490                    &run,
2491                    cfg,
2492                    g,
2493                    st,
2494                    token_id,
2495                    &mut folded,
2496                    None,
2497                    1,
2498                    &[],
2499                    need_qn,
2500                    pool,
2501                );
2502                if r {
2503                    state_on_host = false;
2504                }
2505                r
2506            };
2507            if !ok {
2508                return false;
2509            }
2510        }
2511        if st.dev_set.is_empty() {
2512            st.dev_set = active_dev.clone();
2513            st.partial_set = partial_dev.clone();
2514            // The set is committed, so the card must keep it. Eviction by
2515            // score is right while the set is still being chosen and wrong
2516            // afterwards: an evicted layer drops off the card while its
2517            // caches stay there, and the loop then refuses the whole fast
2518            // path rather than read state from two sides.
2519            let mut idxs = Vec::new();
2520            for (li, l) in layers.iter().enumerate() {
2521                if !active_dev.get(li).copied().unwrap_or(false) {
2522                    continue;
2523                }
2524                for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
2525                    idxs.extend(t.model_idx());
2526                }
2527                if let Some(pk) = pack_for(l, cfg, li) {
2528                    for &(a, b, c) in &pk.tensors {
2529                        idxs.extend([a, b, c]);
2530                    }
2531                }
2532            }
2533            // Why a HOST layer stayed on the host, said in numbers. Its MoE
2534            // can still run on the card with a partial pack — `moe_frame` has
2535            // the remap and hands cold picks back — so the interesting figure
2536            // is how many experts it got. Zero means the upload order never
2537            // reached it; a few hundred means the readiness gate refused. The
2538            // two have different fixes and reading the code cannot tell them
2539            // apart.
2540            for (li, l) in layers.iter().enumerate() {
2541                if active_dev.get(li).copied().unwrap_or(false) {
2542                    continue;
2543                }
2544                let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
2545                tracing::info!(
2546                    "layer {li} on the host: packed {packed} experts of {}",
2547                    cfg.n_routed_experts
2548                );
2549            }
2550            let pinned = layers
2551                .iter()
2552                .find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
2553                .map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
2554            tracing::info!(
2555                "pinned on the card: {pinned} tensors of {} layers",
2556                on_dev.iter().filter(|&&x| x).count()
2557            );
2558        }
2559    }
2560    if state_home || state_on_host {
2561        return true;
2562    }
2563    crate::gpu_wgpu::dsv4_state_read(state)
2564}
2565
2566/// Run a layer whose attention skeleton fits but only a subset of its MoE
2567/// experts does. This path is selected from the live VRAM budget, never from
2568/// a layer number. It is exact: routing spans all experts and cold winners
2569/// are folded back into the hyper-connection state before the next layer.
2570#[cfg(feature = "gpu")]
2571#[allow(clippy::too_many_arguments)]
2572fn dsv4_partial_layer(
2573    state: &mut [f32],
2574    folded: &mut Vec<f32>,
2575    layers: &[Dsv4Layer],
2576    l: &Dsv4Layer,
2577    cfg: &Dsv4Cfg,
2578    st: &mut Dsv4State,
2579    token_id: u32,
2580    li: usize,
2581    freqs: &[f32],
2582    pool: Option<&crate::pool::Pool>,
2583    state_on_host: bool,
2584) -> Option<bool> {
2585    let dim = cfg.dim;
2586    // The self-poisoning this walk was parked for: its frames read the
2587    // pooled post/comb/state slots, and whatever layer ran a frame LAST —
2588    // on this token or the previous one — left its own there. The walk
2589    // now seeds its OWN slots from the state it holds at entry, and is
2590    // immune to the neighbours. The state is home whenever the previous
2591    // layer exited through this walk or the host branch; a device exit
2592    // (full-layer frame) leaves it on the card, where the slots are
2593    // already this token's — nothing to reseed then.
2594    if state_on_host {
2595        let (f, post, comb) = hc_fold_norm(
2596            state,
2597            &l.hc_attn_fn,
2598            &l.hc_attn_scale,
2599            &l.hc_attn_base,
2600            &l.attn_norm,
2601            cfg,
2602            pool,
2603        );
2604        *folded = f;
2605        if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2606            || !crate::gpu_wgpu::dsv4_state_write(state)
2607        {
2608            return None;
2609        }
2610    }
2611    let mut prep = AttnPrep::default();
2612    let mut sink = vec![0.0f32; dim];
2613    attention_step(
2614        folded,
2615        l,
2616        cfg,
2617        st,
2618        li,
2619        freqs,
2620        pool,
2621        Some(&mut prep),
2622        &mut sink,
2623    );
2624    let hd = cfg.head_dim;
2625    let n_comp = st.compressed[li].len() / hd;
2626    let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2627    if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
2628        || (n_comp > 0
2629            && !crate::gpu_wgpu::dsv4_cache_write(
2630                st.kv_id,
2631                li,
2632                cfg.window * hd,
2633                &st.compressed[li],
2634                cap,
2635            ))
2636    {
2637        return None;
2638    }
2639    let a_tail = crate::gpu_wgpu::Dsv4HcTail {
2640        fn_: &l.hc_ffn_fn,
2641        scale: &l.hc_ffn_scale,
2642        base: &l.hc_ffn_base,
2643        norm: &l.ffn_norm,
2644        hc: cfg.hc_mult,
2645        sinkhorn_iters: cfg.hc_sinkhorn_iters,
2646        hc_eps: cfg.hc_eps,
2647        eps: cfg.norm_eps,
2648    };
2649    let scale = (cfg.head_dim as f32).powf(-0.5);
2650    // Optional FreeToken-style split point. Reading the normed FFN input
2651    // here adds one fence between attention and MoE, but it also lets the
2652    // host predict and execute cold experts while the resident experts are
2653    // running on the GPU. Keep the old no-readback path as the default: on
2654    // a warm/full pack the extra fence has nothing to hide and only hurts.
2655    let mut ffn_input = if cpu_overlap_on() {
2656        vec![0.0f32; dim]
2657    } else {
2658        Vec::new()
2659    };
2660    if !attn_frame(
2661        l,
2662        cfg,
2663        st,
2664        li,
2665        folded,
2666        &prep.qr,
2667        &prep.idxs,
2668        freqs,
2669        st.pos,
2670        prep.win_len,
2671        scale,
2672        Some(&a_tail),
2673        &mut ffn_input,
2674    ) {
2675        return None;
2676    }
2677    let nxt = layers.get(li + 1);
2678    let forced = l
2679        .tid2eid
2680        .as_ref()
2681        .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2682    let mut next = vec![0.0f32; dim];
2683    let (cold_sum, cold_count) = moe_frame(
2684        &ffn_input,
2685        l,
2686        cfg,
2687        li,
2688        &[],
2689        forced.as_deref(),
2690        pool,
2691        Some(&a_tail),
2692        // Do not pre-fold the next layer yet. That fold reuses the canonical
2693        // `post` slot; a cold correction still needs THIS layer's post. Once
2694        // the corrected state is home, the exact next fold is cheap on the
2695        // host and seeds either another partial frame or the next full run.
2696        None,
2697        &mut next,
2698    )?;
2699    // The resident contribution has already been expanded on the device. If
2700    // there were cold winners, add `post[j] * cold_sum` and retrieve the
2701    // corrected state in that submission; otherwise a plain readback is
2702    // enough. This state handoff is what makes partial layers composable at
2703    // arbitrary positions, not just at the tail of one checkpoint.
2704    let state_ok = if cold_count == 0 {
2705        crate::gpu_wgpu::dsv4_state_read(state)
2706    } else {
2707        crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
2708    };
2709    if !state_ok {
2710        return None;
2711    }
2712    if let Some(n) = nxt {
2713        let (f, post, comb) = hc_fold_norm(
2714            state,
2715            &n.hc_attn_fn,
2716            &n.hc_attn_scale,
2717            &n.hc_attn_base,
2718            &n.attn_norm,
2719            cfg,
2720            pool,
2721        );
2722        *folded = f;
2723        if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2724            || !crate::gpu_wgpu::dsv4_state_write(state)
2725        {
2726            return None;
2727        }
2728    }
2729    // NB: the CALLER notes this layer for the draft's ring — a note here
2730    // as well double-counts the capture and fails `dspark_take`'s
2731    // completeness check (seen 4 of 3, measured), which reads exactly like
2732    // the starvation it was meant to fix.
2733    Some(true)
2734}
2735
2736/// `CMF_DSV4_CHAIN1=1`: a partial layer runs as ONE submission — attention,
2737/// folds and the subset MoE in a single frame, the state staying on the
2738/// card when every winner was resident (the common case once the slots
2739/// warm). Cold winners pay the walk's exact correction from the preserved
2740/// post. Enabled by default after parity and long-run measurements on RTX
2741/// 5090, RTX PRO 6000 and A40; `CMF_DSV4_CHAIN1=0` keeps the old bisect.
2742#[cfg(feature = "gpu")]
2743fn chain1_on() -> bool {
2744    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2745    *ON.get_or_init(|| {
2746        std::env::var("CMF_DSV4_CHAIN1")
2747            .map(|v| v != "0")
2748            .unwrap_or(true)
2749    })
2750}
2751
2752/// `CMF_DSV4_CPU_OVERLAP=1`: on the two-frame partial walk, read the exact
2753/// normalized MoE input after attention and use it to overlap cold CPU
2754/// experts with the resident GPU frame. This is deliberately independent
2755/// from `CMF_DSV4_PARTIAL_WALK`: it is a measured alternative to chain-of-one,
2756/// not a new default for full packs.
2757#[cfg(feature = "gpu")]
2758fn cpu_overlap_on() -> bool {
2759    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2760    *ON.get_or_init(|| std::env::var("CMF_DSV4_CPU_OVERLAP").is_ok_and(|v| v != "0"))
2761}
2762
2763#[cfg(feature = "gpu")]
2764#[allow(clippy::too_many_arguments)]
2765fn dsv4_chain1_layer(
2766    state: &mut [f32],
2767    folded: &mut Vec<f32>,
2768    layers: &[Dsv4Layer],
2769    l: &Dsv4Layer,
2770    cfg: &Dsv4Cfg,
2771    st: &mut Dsv4State,
2772    token_id: u32,
2773    li: usize,
2774    freqs: &[f32],
2775    pool: Option<&crate::pool::Pool>,
2776    state_on_host: bool,
2777) -> Option<bool> {
2778    let dim = cfg.dim;
2779    let pk = pack_for(l, cfg, li)?;
2780    if pk.route_complete() {
2781        return None;
2782    }
2783    let model = l.experts.first().and_then(|e| e.w1.model_arc())?;
2784    // The same entry self-seed the repaired walk uses: the frame reads the
2785    // pooled post/comb/state slots, and this layer's own are the only ones
2786    // it may trust.
2787    if state_on_host {
2788        let (f, post, comb) = hc_fold_norm(
2789            state,
2790            &l.hc_attn_fn,
2791            &l.hc_attn_scale,
2792            &l.hc_attn_base,
2793            &l.attn_norm,
2794            cfg,
2795            pool,
2796        );
2797        *folded = f;
2798        if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
2799            || !crate::gpu_wgpu::dsv4_state_write(state)
2800        {
2801            return None;
2802        }
2803    }
2804    let mut prep = AttnPrep::default();
2805    let mut sink = vec![0.0f32; dim];
2806    attention_step(
2807        folded,
2808        l,
2809        cfg,
2810        st,
2811        li,
2812        freqs,
2813        pool,
2814        Some(&mut prep),
2815        &mut sink,
2816    );
2817    let hd = cfg.head_dim;
2818    let n_comp = st.compressed[li].len() / hd;
2819    let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
2820    let kv_id = st.kv_id;
2821    if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
2822        || (n_comp > 0
2823            && !crate::gpu_wgpu::dsv4_cache_write(
2824                kv_id,
2825                li,
2826                cfg.window * hd,
2827                &st.compressed[li],
2828                cap,
2829            ))
2830    {
2831        return None;
2832    }
2833    let idx32: Vec<u32> = prep
2834        .idxs
2835        .iter()
2836        .map(|&p| {
2837            if p < prep.win_len {
2838                p as u32
2839            } else {
2840                (cfg.window + (p - prep.win_len)) as u32
2841            }
2842        })
2843        .collect();
2844    let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
2845        l.wq_a.model_idx(),
2846        l.wq_b.model_idx(),
2847        l.wo_a.model_idx(),
2848        l.wo_b.model_idx(),
2849    ) else {
2850        return None;
2851    };
2852    // Under the subset contract the forced list stays GLOBAL: the remap
2853    // either finds each hash winner a slot or returns it cold.
2854    let forced: Option<Vec<usize>> = l
2855        .tid2eid
2856        .as_ref()
2857        .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
2858    let global_remap = pk
2859        .global
2860        .as_ref()
2861        .map(|gl| gl.pool.remap(gl.layer, cfg.n_routed_experts));
2862    let dynv = pk.dynslots.lock().unwrap();
2863    let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
2864    let nxt = layers.get(li + 1);
2865    let w = crate::gpu_wgpu::Dsv4LayerW {
2866        attn: crate::gpu_wgpu::Dsv4AttnW {
2867            wq_a,
2868            wq_b,
2869            wo_a,
2870            wo_b,
2871            q_norm: &l.q_norm,
2872            sink: &l.attn_sink,
2873        },
2874        moe: crate::gpu_wgpu::Dsv4MoeW {
2875            router: &[],
2876            experts: &pk.tensors,
2877            logits: &[],
2878            // GLOBAL bias under the subset contract — the ranking spans
2879            // every expert, so a packed-order bias would misalign it.
2880            bias: pk.bias.as_deref(),
2881            mask: pk.mask.as_deref(),
2882            forced: forced.as_deref(),
2883            remap: Some(live_remap),
2884            global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
2885                pool_uid: gl.pool.uid,
2886                shared_slot: gl.shared_slot,
2887                segment_slots: gl.pool.segment_slots as u32,
2888            }),
2889            has_shared: true,
2890            shared_weight: 1.0,
2891            preweighted: false,
2892            qwen_softmax: false,
2893        },
2894        hc_ffn_fn: &l.hc_ffn_fn,
2895        hc_ffn_scale: &l.hc_ffn_scale,
2896        hc_ffn_base: &l.hc_ffn_base,
2897        hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
2898        hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
2899        hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
2900        ffn_norm: &l.ffn_norm,
2901        next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
2902        next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
2903        next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
2904        router: &pk.router,
2905    };
2906    let geom = crate::gpu_wgpu::Dsv4LayerGeom {
2907        attn: crate::gpu_wgpu::Dsv4AttnGeom {
2908            dim,
2909            nh: cfg.n_heads,
2910            hd,
2911            rd: cfg.rope_head_dim,
2912            q_lora: cfg.q_lora_rank,
2913            o_lora: cfg.o_lora_rank,
2914            o_groups: cfg.o_groups,
2915            eps: cfg.norm_eps,
2916            scale: (hd as f32).powf(-0.5),
2917            bf16: false,
2918            q_rms: true,
2919            o_fp8: false,
2920        },
2921        moe: crate::gpu_wgpu::Dsv4MoeGeom {
2922            hidden: dim,
2923            inter: cfg.moe_inter,
2924            top_k: cfg.top_k,
2925            route_scale: cfg.route_scale,
2926            swiglu_limit: cfg.swiglu_limit,
2927            gu_q2: l
2928                .experts
2929                .first()
2930                .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
2931            bf16: false,
2932        },
2933        hc: cfg.hc_mult,
2934        hc_eps: cfg.hc_eps,
2935        sinkhorn_iters: cfg.hc_sinkhorn_iters,
2936    };
2937    let mut next = vec![0.0f32; dim];
2938    let mut cold: Vec<(usize, f32)> = Vec::new();
2939    let mut routed: Vec<(usize, f32)> = Vec::new();
2940    let mut cold_x: Vec<f32> = Vec::new();
2941    let need_routes = route_stats_on() || pk.global.is_some();
2942    if !crate::gpu_wgpu::dsv4_layer_frame(
2943        &model,
2944        &w,
2945        geom,
2946        kv_id,
2947        li,
2948        Some(&prep.qr),
2949        &idx32,
2950        freqs,
2951        st.pos,
2952        &mut next,
2953        Some(&mut cold),
2954        need_routes.then_some(&mut routed),
2955        &mut cold_x,
2956    ) {
2957        return None;
2958    }
2959    if route_stats_on() {
2960        record_route(li, layers.len(), cfg.n_routed_experts, &routed);
2961    }
2962    drop(dynv);
2963    // The cold/readback slot already carries the device's real winners.
2964    // Feed all of them back to the allocator: this both installs misses and
2965    // refreshes hit ages, without a host-side router prediction or another
2966    // fence. A prediction disagreement therefore remains impossible here.
2967    if let Some(gl) = pk.global.as_ref() {
2968        let picks: Vec<usize> = routed.iter().map(|&(gi, _)| gi).collect();
2969        gl.pool
2970            .ensure_picks(&model, gl.layer, &picks, &l.experts, cfg.top_k, 1);
2971    }
2972    if cold.is_empty() {
2973        // Every winner was resident: the state stays on the card and the
2974        // frame's own next-fold is exact. This is the single-submission
2975        // path the whole function exists for.
2976        *folded = next;
2977        return Some(false);
2978    }
2979    // Cold winners: complete on the frame's own normed input, correct the
2980    // device state from the preserved post, and bring it home.
2981    if cold_x.len() < dim {
2982        return None;
2983    }
2984    let mut cold_sum = vec![0.0f32; dim];
2985    {
2986        let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
2987            .iter()
2988            .map(|_| std::sync::Mutex::new(Vec::new()))
2989            .collect();
2990        let (cold_ref, results_ref, x_ref) = (&cold, &results, &cold_x[..dim]);
2991        std::thread::scope(|sc| {
2992            for i in 0..cold_ref.len() {
2993                let (gi, wt) = cold_ref[i];
2994                let Some(exp) = l.experts.get(gi) else {
2995                    continue;
2996                };
2997                let r = &results_ref[i];
2998                sc.spawn(move || {
2999                    let mut a = vec![0.0f32; cfg.dim];
3000                    crate::gpu::cpu_scope(|| run_expert(x_ref, exp, cfg, wt, None, &mut a));
3001                    *r.lock().unwrap() = a;
3002                });
3003            }
3004        });
3005        for r in &results {
3006            let a = r.lock().unwrap();
3007            for (o, v) in cold_sum.iter_mut().zip(a.iter()) {
3008                *o += v;
3009            }
3010        }
3011    }
3012    if !crate::gpu_wgpu::dsv4_state_add_cold_preserved(&cold_sum, cfg.hc_mult, state, kv_id, li) {
3013        return None;
3014    }
3015    // Reactive refill: the winners the slots did not hold are the likeliest
3016    // winners of the NEXT token — pull them in now, LRU-evicting.
3017    if pk.global.is_none() {
3018        let mut dynv = pk.dynslots.lock().unwrap();
3019        dynv.clock += 1;
3020        let clock = dynv.clock;
3021        for &(gi, _) in &cold {
3022            if gi >= dynv.remap.len() || dynv.remap[gi] != u32::MAX {
3023                continue;
3024            }
3025            let victim = (0..dynv.owner.len())
3026                .filter(|&sl| dynv.last[sl] != clock)
3027                .min_by_key(|&sl| dynv.last[sl]);
3028            let Some(victim) = victim else { break };
3029            let Some(exp) = l.experts.get(gi) else {
3030                continue;
3031            };
3032            let t3 = (|| {
3033                Some((
3034                    exp.w1.model_idx()?,
3035                    exp.w3.model_idx()?,
3036                    exp.w2.model_idx()?,
3037                ))
3038            })();
3039            let Some(t3) = t3 else { continue };
3040            let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
3041            let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
3042            if !crate::gpu_wgpu::dsv4_slot_fill(
3043                &model,
3044                pack_first,
3045                victim,
3046                gi,
3047                t3,
3048                cfg.moe_inter,
3049                cfg.dim,
3050                gu_q2,
3051            ) {
3052                break;
3053            }
3054            let old = dynv.owner[victim] as usize;
3055            if old < dynv.remap.len() {
3056                dynv.remap[old] = u32::MAX;
3057            }
3058            dynv.remap[gi] = victim as u32;
3059            dynv.owner[victim] = gi as u32;
3060            dynv.last[victim] = clock;
3061            dynv.mutated = true;
3062        }
3063    }
3064    if let Some(n) = nxt {
3065        let (f, post, comb) = hc_fold_norm(
3066            state,
3067            &n.hc_attn_fn,
3068            &n.hc_attn_scale,
3069            &n.hc_attn_base,
3070            &n.attn_norm,
3071            cfg,
3072            pool,
3073        );
3074        *folded = f;
3075        if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb) {
3076            return None;
3077        }
3078    }
3079    Some(true)
3080}
3081
3082#[cfg(feature = "gpu")]
3083fn chain_max() -> usize {
3084    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
3085    *N.get_or_init(|| {
3086        std::env::var("CMF_DSV4_CHAIN_MAX")
3087            .ok()
3088            .and_then(|v| v.parse().ok())
3089            .unwrap_or(usize::MAX)
3090    })
3091}
3092
3093/// `CMF_DSV4_CHAIN=1`: put a run of consecutive device-capable layers in ONE
3094/// submission. Off by default until it has been measured on a real card.
3095#[cfg(feature = "gpu")]
3096/// `CMF_DSV4_HOST_CPU_MOE=1`: a layer that fell off the card runs its MoE on
3097/// the host WITHOUT the per-op device route — one fence a token instead of
3098/// one a matvec. Whether that wins is a measurement.
3099/// `CMF_DSV4_PARTIAL_WALK=1`: the fused device walk of a partial layer.
3100/// OFF until its self-poisoning is repaired: its attention frame reads the
3101/// pooled slots its own MoE frame rewrote on the previous token, so every
3102/// token after the first attends over leftovers — the drafts it captures
3103/// от такого состояния never match the verify (acceptance 0, measured).
3104/// The host branch walks these layers correctly; the pack stays resident
3105/// for the verify tail.
3106fn partial_walk_on() -> bool {
3107    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3108    *ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
3109}
3110
3111fn host_cpu_moe() -> bool {
3112    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3113    *ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
3114}
3115
3116fn chain_enabled() -> bool {
3117    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3118    *ON.get_or_init(|| {
3119        std::env::var("CMF_DSV4_CHAIN")
3120            .map(|v| v != "0")
3121            .unwrap_or(true)
3122    })
3123}
3124
3125/// Encode a maximal run of consecutive device-capable layers and submit it
3126/// ONCE. Every layer in the run builds its own attention inputs on the card,
3127/// so nothing comes back between them — that is the whole saving.
3128///
3129/// The run's state belongs to the device from here on: `st.window`,
3130/// `st.compressed` and the compressor streams for these layers are stale on
3131/// the host afterwards, and only the counts in `st.dev_*` are kept. A layer
3132/// that has ever been in a run must therefore never be handed to the CPU
3133/// path again, which `dev_owned` records.
3134#[cfg(feature = "gpu")]
3135#[allow(clippy::too_many_arguments)]
3136fn dsv4_chain_run(
3137    layers: &[Dsv4Layer],
3138    run: &[usize],
3139    cfg: &Dsv4Cfg,
3140    g: &Dsv4Globals,
3141    st: &mut Dsv4State,
3142    token_id: u32,
3143    // In AND out: the run reads the fold it starts from and MUST leave the
3144    // fold it produced, because whatever follows — a host layer, or the next
3145    // run after a cap — seeds from this. Passing it read-only left every
3146    // later segment starting from a stale fold: exact with one unbroken run,
3147    // release-scale garbage the moment anything splits the chain.
3148    folded: &mut Vec<f32>,
3149    // When present, the hyper-connection state rides home in the run's own
3150    // submission instead of costing a second fence afterwards. Only the
3151    // token's LAST run passes it — an earlier one would read a state the
3152    // layers after it still change.
3153    state_out: Option<&mut [f32]>,
3154    // How many consecutive tokens this run carries. One is decode; more is a
3155    // prompt chunk or a speculative verify, which are the same shape of work.
3156    batch: usize,
3157    // Their ids, needed only when `batch > 1`: a hash layer forces its expert
3158    // list from the token's id, so the batch needs one list per token and the
3159    // single `token_id` above cannot supply them.
3160    batch_ids: &[u32],
3161    // Whether the device's qn buffer is stale: true at layer zero and after
3162    // a host layer. When the previous layer was chained, its frame's tail
3163    // already left THIS layer's LoRA vector on the card, and recomputing it
3164    // here was a full wq_a matvec on the CPU per run — at CHAIN_MAX=1 that
3165    // is one per LAYER, which is how a 43-fence path measured slower than
3166    // an 86-fence one.
3167    need_qn: bool,
3168    pool: Option<&crate::pool::Pool>,
3169) -> bool {
3170    if run.is_empty() {
3171        return true;
3172    }
3173    let (dim, hd) = (cfg.dim, cfg.head_dim);
3174    let first = run[0];
3175    let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
3176        return false;
3177    };
3178    // Batch callers seed every token's fold and qn in its own slot. Seeding
3179    // the legacy shared slot here is not merely redundant: `folded` carries
3180    // only the eventual LAST output and is empty before the batch runs.
3181    if batch <= 1 && need_qn {
3182        let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
3183        layers[first].wq_a.matvec(folded, &mut qn0, pool);
3184        rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
3185        if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
3186            return false;
3187        }
3188    } else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
3189        return false;
3190    }
3191
3192    // Held apart from the borrowing structs below, which point into them.
3193    let mut packs = Vec::with_capacity(run.len());
3194    let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
3195    for &li in run {
3196        let Some(pk) = pack_for(&layers[li], cfg, li) else {
3197            return false;
3198        };
3199        // A chain cannot complete a cold pick between dependent layers, so
3200        // every expert OPEN in the route must be resident and the pack must
3201        // not have mutated. A masked-complete or hot-reordered pack carries
3202        // its immutable global-to-slot remap into the frame.
3203        // The verify batch reached here with cap-limited partial packs
3204        // and accepted 0 of 625 drafts — wrong experts, plausible sums.
3205        if !pk.route_complete() || pk.is_mutated() {
3206            return false;
3207        }
3208        let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
3209            let v: Vec<usize> = if pk.needs_remap() {
3210                hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3211            } else {
3212                hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
3213                    .into_iter()
3214                    .map(|gi| pk.to_slot[gi])
3215                    .collect()
3216            };
3217            if v.contains(&usize::MAX) {
3218                None
3219            } else {
3220                Some(v)
3221            }
3222        });
3223        if layers[li].tid2eid.is_some() && forced.is_none() {
3224            return false;
3225        }
3226        forceds.push(forced);
3227        packs.push(pk);
3228    }
3229
3230    let mut items = Vec::with_capacity(run.len());
3231    let mut freqs = Vec::with_capacity(run.len());
3232    for (i, &li) in run.iter().enumerate() {
3233        let l = &layers[li];
3234        let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
3235            l.wq_a.model_idx(),
3236            l.wq_b.model_idx(),
3237            l.wo_a.model_idx(),
3238            l.wo_b.model_idx(),
3239            l.wkv.model_idx(),
3240        ) else {
3241            return false;
3242        };
3243        let comp = match &l.compressor {
3244            None => None,
3245            Some(cp) => {
3246                let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
3247                    return false;
3248                };
3249                Some((
3250                    crate::gpu_wgpu::Dsv4CompW {
3251                        wkv: a,
3252                        wgate: b,
3253                        norm: &cp.norm,
3254                        ape: &cp.ape,
3255                    },
3256                    crate::gpu_wgpu::Dsv4CompGeom {
3257                        width: cp.wkv.rows(),
3258                        hidden: dim,
3259                        ratio: cp.ratio,
3260                        overlap: cp.overlap,
3261                        rope_dim: cfg.rope_head_dim,
3262                        eps: cfg.norm_eps,
3263                    },
3264                ))
3265            }
3266        };
3267        let ix = match &l.indexer {
3268            None => None,
3269            Some(ixr) => {
3270                let cp = &ixr.compressor;
3271                let (Some(a), Some(b), Some(qb), Some(wp)) = (
3272                    cp.wkv.model_idx(),
3273                    cp.wgate.model_idx(),
3274                    ixr.wq_b.model_idx(),
3275                    ixr.weights_proj.model_idx(),
3276                ) else {
3277                    return false;
3278                };
3279                let ih = ixr.weights_proj.rows();
3280                Some((
3281                    crate::gpu_wgpu::Dsv4CompW {
3282                        wkv: a,
3283                        wgate: b,
3284                        norm: &cp.norm,
3285                        ape: &cp.ape,
3286                    },
3287                    crate::gpu_wgpu::Dsv4CompGeom {
3288                        width: cp.wkv.rows(),
3289                        hidden: dim,
3290                        ratio: cp.ratio,
3291                        overlap: cp.overlap,
3292                        rope_dim: cfg.rope_head_dim,
3293                        eps: cfg.norm_eps,
3294                    },
3295                    crate::gpu_wgpu::Dsv4IxW {
3296                        wq_b: qb,
3297                        weights_proj: wp,
3298                    },
3299                    crate::gpu_wgpu::Dsv4IxGeom {
3300                        ih,
3301                        idim: ixr.wq_b.rows() / ih.max(1),
3302                        q_lora: cfg.q_lora_rank,
3303                        hidden: dim,
3304                        rope_dim: cfg.rope_head_dim,
3305                        eps: cfg.norm_eps,
3306                        top_k: cfg.index_topk,
3307                        window: cfg.window,
3308                    },
3309                ))
3310            }
3311        };
3312        // The cache has to be big enough BEFORE the frame appends into it:
3313        // a chained layer never calls dsv4_cache_write, which is what used
3314        // to create and grow it.
3315        let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
3316            if cp.overlap {
3317                cp.wkv.rows() / 2
3318            } else {
3319                cp.wkv.rows()
3320            }
3321        });
3322        let comp_extra = l
3323            .compressor
3324            .as_ref()
3325            .map_or(0, |cp| batch.max(1).div_ceil(cp.ratio.max(1)));
3326        let need = cfg.window * hd
3327            + (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
3328            + (batch.max(1) + 1) * hd;
3329        if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
3330            return false;
3331        }
3332        let ew_c = comp.as_ref().map_or(
3333            0,
3334            |(_, cg)| {
3335                if cg.overlap { cg.width / 2 } else { cg.width }
3336            },
3337        );
3338        let ew_i = ix.as_ref().map_or(
3339            0,
3340            |(_, cg, _, _)| {
3341                if cg.overlap { cg.width / 2 } else { cg.width }
3342            },
3343        );
3344        let prep = crate::gpu_wgpu::Dsv4Prep {
3345            wkv,
3346            kv_norm: &l.kv_norm,
3347            comp,
3348            ix,
3349            filled: st.dev_filled[li],
3350            window: cfg.window,
3351            n_comp: st.dev_n_comp[li],
3352            n_ix: st.dev_n_ix[li],
3353            comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
3354            ix_dst_off: st.dev_n_ix[li] * ew_i,
3355            idx_cap: cfg.window
3356                + if l.indexer.is_some() {
3357                    cfg.index_topk
3358                } else {
3359                    st.dev_n_comp[li] + comp_extra + 1
3360                },
3361        };
3362        let nxt = layers.get(li + 1);
3363        let w = crate::gpu_wgpu::Dsv4LayerW {
3364            attn: crate::gpu_wgpu::Dsv4AttnW {
3365                wq_a,
3366                wq_b,
3367                wo_a,
3368                wo_b,
3369                q_norm: &l.q_norm,
3370                sink: &l.attn_sink,
3371            },
3372            moe: crate::gpu_wgpu::Dsv4MoeW {
3373                router: &packs[i].router,
3374                experts: &packs[i].tensors,
3375                logits: &[],
3376                // The PACK's slice, not a per-run Vec: the address stability
3377                // is the whole point (see Pack::bias).
3378                bias: packs[i].bias.as_deref(),
3379                mask: packs[i].mask.as_deref(),
3380                forced: forceds[i].as_deref(),
3381                remap: packs[i].needs_remap().then_some(packs[i].remap.as_slice()),
3382                global: None,
3383                has_shared: true,
3384                shared_weight: 1.0,
3385                preweighted: false,
3386                qwen_softmax: false,
3387            },
3388            hc_ffn_fn: &l.hc_ffn_fn,
3389            hc_ffn_scale: &l.hc_ffn_scale,
3390            hc_ffn_base: &l.hc_ffn_base,
3391            hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
3392            hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
3393            hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
3394            ffn_norm: &l.ffn_norm,
3395            next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
3396            next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
3397            next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
3398            router: &packs[i].router,
3399        };
3400        let geom = crate::gpu_wgpu::Dsv4LayerGeom {
3401            attn: crate::gpu_wgpu::Dsv4AttnGeom {
3402                dim,
3403                nh: cfg.n_heads,
3404                hd,
3405                rd: cfg.rope_head_dim,
3406                q_lora: cfg.q_lora_rank,
3407                o_lora: cfg.o_lora_rank,
3408                o_groups: cfg.o_groups,
3409                eps: cfg.norm_eps,
3410                scale: (hd as f32).powf(-0.5),
3411                bf16: false,
3412                q_rms: true,
3413                o_fp8: false,
3414            },
3415            moe: crate::gpu_wgpu::Dsv4MoeGeom {
3416                hidden: dim,
3417                inter: cfg.moe_inter,
3418                top_k: cfg.top_k,
3419                route_scale: cfg.route_scale,
3420                swiglu_limit: cfg.swiglu_limit,
3421                gu_q2: l.experts.first().is_some_and(|e| {
3422                    e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3423                }),
3424                bf16: false,
3425            },
3426            hc: cfg.hc_mult,
3427            hc_eps: cfg.hc_eps,
3428            sinkhorn_iters: cfg.hc_sinkhorn_iters,
3429        };
3430        freqs.push(if l.compressor.is_some() {
3431            g.inv_freq_compress.as_slice()
3432        } else {
3433            g.inv_freq_window.as_slice()
3434        });
3435        items.push((w, geom, prep));
3436    }
3437
3438    let mut out = vec![0.0f32; dim * batch.max(1)];
3439    if batch > 1 {
3440        // A batch keeps its own state per token. When a host tail follows,
3441        // all of those states ride home beside the folds in the same fence.
3442        // One forced row per token: same layers, the hash rows re-derived
3443        // from each token's own id.
3444        let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
3445        for t in 0..batch {
3446            let id = batch_ids.get(t).copied().unwrap_or(token_id);
3447            let mut row = Vec::with_capacity(run.len());
3448            for (i, &li) in run.iter().enumerate() {
3449                row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
3450                    let v: Vec<usize> = if packs[i].needs_remap() {
3451                        hash_route(tbl, cfg.vocab, cfg.top_k, id)
3452                    } else {
3453                        hash_route(tbl, cfg.vocab, cfg.top_k, id)
3454                            .into_iter()
3455                            .map(|gi| packs[i].to_slot[gi])
3456                            .collect()
3457                    };
3458                    if v.contains(&usize::MAX) {
3459                        None
3460                    } else {
3461                        Some(v)
3462                    }
3463                }));
3464                if layers[li].tid2eid.is_some() && row[i].is_none() {
3465                    return false;
3466                }
3467            }
3468            forced_pt.push(row);
3469        }
3470        if !crate::gpu_wgpu::dsv4_chain_batch(
3471            &model,
3472            &items,
3473            st.kv_id,
3474            first,
3475            &freqs,
3476            st.pos,
3477            batch,
3478            Some(&forced_pt),
3479            &mut out,
3480            state_out,
3481        ) {
3482            return false;
3483        }
3484        // The caller wants the LAST token's fold: it is the one whose logits
3485        // continue the sequence.
3486        *folded = out[(batch - 1) * dim..batch * dim].to_vec();
3487    } else {
3488        if !crate::gpu_wgpu::dsv4_layer_chain(
3489            &model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
3490        ) {
3491            return false;
3492        }
3493        *folded = out;
3494    }
3495    // The device advanced these; the host keeps only the arithmetic. A batch
3496    // advanced them once per token, in order, so the host replays the same
3497    // rule that many times rather than inventing a closed form for it.
3498    for (i, &li) in run.iter().enumerate() {
3499        for t in 0..batch.max(1) {
3500            let pos = st.pos + t;
3501            st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
3502            if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
3503                if (pos + 1) % cg.ratio == 0 {
3504                    st.dev_n_ix[li] += 1;
3505                }
3506            }
3507            if let Some((_, cg)) = items[i].2.comp.as_ref() {
3508                if (pos + 1) % cg.ratio == 0 {
3509                    st.dev_n_comp[li] += 1;
3510                }
3511            }
3512        }
3513    }
3514    st.dev_owned = true;
3515    true
3516}
3517
3518/// `CMF_DSV4_HC_DEV=0` puts the hyper-connections back on the host.
3519#[cfg(feature = "gpu")]
3520fn hc_on_device() -> bool {
3521    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3522    *ON.get_or_init(|| {
3523        // OPT-IN. On the release checkpoint this path reads 3.234 against
3524        // the CPU's 3.282 — divergent — and the speed is unchanged, so there
3525        // is no trade to weigh: it must not be the default until it is
3526        // exact. The toy's near-agreement (129.787 vs 129.792) hid a real
3527        // fault the release exposes.
3528        std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0") && crate::gpu::backend_available()
3529    })
3530}
3531
3532/// The two-frame path with the hyper-connections on the card.
3533///
3534/// The host still prepares each layer's attention inputs — the compressor,
3535/// the indexer and the window, which are exact there — but it no longer
3536/// folds, Sinkhorns or norms, and it no longer carries the MoE half's input
3537/// between the halves: the attention frame leaves it on the device and the
3538/// MoE frame reads it from there. One readback a layer instead of two, and
3539/// 19 ms of host arithmetic a token gone.
3540#[cfg(feature = "gpu")]
3541#[allow(clippy::too_many_arguments)]
3542fn dsv4_two_frame_loop(
3543    state: &mut [f32],
3544    layers: &[Dsv4Layer],
3545    g: &Dsv4Globals,
3546    cfg: &Dsv4Cfg,
3547    st: &mut Dsv4State,
3548    token_id: u32,
3549    inv_freq: &[f32],
3550    pool: Option<&crate::pool::Pool>,
3551    scratch: &mut HcScratch,
3552) -> bool {
3553    let dim = cfg.dim;
3554    let freqs_of = |l: &Dsv4Layer| -> &[f32] {
3555        let f = if l.compressor.is_some() {
3556            &g.inv_freq_compress
3557        } else {
3558            &g.inv_freq_window
3559        };
3560        if f.is_empty() { inv_freq } else { f.as_slice() }
3561    };
3562    // Layer zero's fold has no frame before it, exactly as in the layer path.
3563    let (mut folded, post0, comb0) = hc_fold_norm(
3564        state,
3565        &layers[0].hc_attn_fn,
3566        &layers[0].hc_attn_scale,
3567        &layers[0].hc_attn_base,
3568        &layers[0].attn_norm,
3569        cfg,
3570        pool,
3571    );
3572    if !crate::gpu_wgpu::dsv4_state_write(state) || !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
3573    {
3574        return false;
3575    }
3576    // PRE-FLIGHT, before the first byte of state moves: a mid-loop refusal
3577    // would hand the token back to the ordinary loop AFTER these caches
3578    // advanced, and the second advance is not a slow answer but a wrong one.
3579    // The same discipline the layer loop states in the same words.
3580    let mut on_dev = vec![false; layers.len()];
3581    for (li, l) in layers.iter().enumerate() {
3582        let Some(pk) = pack_for(l, cfg, li) else {
3583            return false;
3584        };
3585        let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
3586            return false;
3587        };
3588        let gu_q2 = l
3589            .experts
3590            .first()
3591            .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
3592        let attn_ok = [
3593            l.wq_a.model_idx(),
3594            l.wq_b.model_idx(),
3595            l.wo_a.model_idx(),
3596            l.wo_b.model_idx(),
3597        ]
3598        .into_iter()
3599        .flatten()
3600        .all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
3601        on_dev[li] = attn_ok
3602            && pk.route_complete()
3603            && crate::gpu_wgpu::dsv4_experts_ready(
3604                &model,
3605                &pk.tensors,
3606                cfg.moe_inter,
3607                dim,
3608                gu_q2,
3609                l.experts.first().is_some_and(|e| {
3610                    e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
3611                }),
3612            );
3613    }
3614    if !on_dev.iter().any(|&x| x) {
3615        return false;
3616    }
3617    let mut sink = vec![0.0f32; dim];
3618    for (li, l) in layers.iter().enumerate() {
3619        // A layer the card cannot hold runs on the host WHOLE, with the
3620        // state fetched and put back around it — the mixed ownership the
3621        // layer loop already proved out.
3622        if !on_dev[li] {
3623            if !crate::gpu_wgpu::dsv4_state_read(state) {
3624                return false;
3625            }
3626            let freqs = freqs_of(l);
3627            hc_block(
3628                state,
3629                &l.hc_attn_fn,
3630                &l.hc_attn_scale,
3631                &l.hc_attn_base,
3632                &l.attn_norm,
3633                cfg,
3634                scratch,
3635                pool,
3636                |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
3637            );
3638            hc_block(
3639                state,
3640                &l.hc_ffn_fn,
3641                &l.hc_ffn_scale,
3642                &l.hc_ffn_base,
3643                &l.ffn_norm,
3644                cfg,
3645                scratch,
3646                pool,
3647                |f, o| moe_step(f, l, cfg, token_id, li, pool, o),
3648            );
3649            let nref = layers.get(li + 1).unwrap_or(l);
3650            let (f, p2, c2) = hc_fold_norm(
3651                state,
3652                &nref.hc_attn_fn,
3653                &nref.hc_attn_scale,
3654                &nref.hc_attn_base,
3655                &nref.attn_norm,
3656                cfg,
3657                pool,
3658            );
3659            folded = f;
3660            if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
3661                || !crate::gpu_wgpu::dsv4_state_write(state)
3662            {
3663                return false;
3664            }
3665            continue;
3666        }
3667        // The host's half: the caches and the attended list, untouched.
3668        let mut prep = AttnPrep::default();
3669        attention_step(
3670            &folded,
3671            l,
3672            cfg,
3673            st,
3674            li,
3675            freqs_of(l),
3676            pool,
3677            Some(&mut prep),
3678            &mut sink,
3679        );
3680        let hd = cfg.head_dim;
3681        let n_comp = st.compressed[li].len() / hd;
3682        let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
3683        if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
3684            || (n_comp > 0
3685                && !crate::gpu_wgpu::dsv4_cache_write(
3686                    st.kv_id,
3687                    li,
3688                    cfg.window * hd,
3689                    &st.compressed[li],
3690                    cap,
3691                ))
3692        {
3693            return false;
3694        }
3695        let _idx32: Vec<u32> = prep
3696            .idxs
3697            .iter()
3698            .map(|&p| {
3699                if p < prep.win_len {
3700                    p as u32
3701                } else {
3702                    (cfg.window + (p - prep.win_len)) as u32
3703                }
3704            })
3705            .collect();
3706        let nxt = layers.get(li + 1);
3707        let a_tail = crate::gpu_wgpu::Dsv4HcTail {
3708            fn_: &l.hc_ffn_fn,
3709            scale: &l.hc_ffn_scale,
3710            base: &l.hc_ffn_base,
3711            norm: &l.ffn_norm,
3712            hc: cfg.hc_mult,
3713            sinkhorn_iters: cfg.hc_sinkhorn_iters,
3714            hc_eps: cfg.hc_eps,
3715            eps: cfg.norm_eps,
3716        };
3717        let scale = (cfg.head_dim as f32).powf(-0.5);
3718        if !attn_frame(
3719            l,
3720            cfg,
3721            st,
3722            li,
3723            &folded,
3724            &prep.qr,
3725            &prep.idxs,
3726            freqs_of(l),
3727            st.pos,
3728            prep.win_len,
3729            scale,
3730            Some(&a_tail),
3731            &mut [],
3732        ) {
3733            return false;
3734        }
3735        let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
3736            fn_: &n.hc_attn_fn,
3737            scale: &n.hc_attn_scale,
3738            base: &n.hc_attn_base,
3739            norm: &n.attn_norm,
3740            hc: cfg.hc_mult,
3741            sinkhorn_iters: cfg.hc_sinkhorn_iters,
3742            hc_eps: cfg.hc_eps,
3743            eps: cfg.norm_eps,
3744        });
3745        let mut next = vec![0.0f32; dim];
3746        let pair = m_tail
3747            .as_ref()
3748            .zip(nxt)
3749            .map(|(t, n)| (t, n.attn_norm.as_slice()));
3750        let forced = l
3751            .tid2eid
3752            .as_ref()
3753            .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
3754        if moe_frame(
3755            &[],
3756            l,
3757            cfg,
3758            li,
3759            &[],
3760            forced.as_deref(),
3761            pool,
3762            Some(&a_tail),
3763            pair,
3764            &mut next,
3765        )
3766        .is_none()
3767        {
3768            return false;
3769        }
3770        folded = next;
3771    }
3772    let _ = scratch;
3773    crate::gpu_wgpu::dsv4_state_read(state)
3774}
3775
3776/// The host half of one hyper-connection block: mixes, Sinkhorn, fold, norm.
3777/// The device does this for every layer but the first, whose state it has not
3778/// seen yet.
3779#[cfg(feature = "gpu")]
3780#[allow(clippy::too_many_arguments)]
3781fn hc_fold_norm(
3782    state: &[f32],
3783    hc_fn: &[f32],
3784    hc_scale: &[f32; 3],
3785    hc_base: &[f32],
3786    norm_w: &[f32],
3787    cfg: &Dsv4Cfg,
3788    pool: Option<&crate::pool::Pool>,
3789) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
3790    let (hc, dim) = (cfg.hc_mult, cfg.dim);
3791    let mix_hc = (2 + hc) * hc;
3792    let mut mixes = vec![0.0f32; mix_hc];
3793    hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
3794    let mut pre = vec![0.0f32; hc];
3795    let mut post = vec![0.0f32; hc];
3796    let mut comb = vec![0.0f32; hc * hc];
3797    hc_split_sinkhorn(
3798        &mixes,
3799        hc_scale,
3800        hc_base,
3801        hc,
3802        cfg.hc_sinkhorn_iters,
3803        cfg.hc_eps,
3804        &mut pre,
3805        &mut post,
3806        &mut comb,
3807    );
3808    let mut folded = vec![0.0f32; dim];
3809    hc_fold(state, &pre, hc, dim, &mut folded);
3810    rms_weighted(&mut folded, norm_w, cfg.norm_eps);
3811    // post and comb travel with the fold: the frame's opening expand needs
3812    // exactly those, and they are not recoverable from the state alone.
3813    (folded, post, comb)
3814}
3815
3816/// `CMF_DSV4_GPU_LAYER=1`: one submission per layer instead of two, with the
3817/// hyper-connection glue and the router on the device.
3818///
3819/// CORRECT — perplexity 5.211 against the CPU's 5.211 on the release, 128.576
3820/// against 128.576 on the toy — and SLOWER on this hardware: 6.0 tok/s where
3821/// the two-frame path gets 9.3. The reason is not the frame, it is the
3822/// all-or-nothing granularity underneath it. A layer whose experts miss VRAM
3823/// runs entirely on the host, attention included (6.5 ms a call against 0.9),
3824/// and with 100 GB of experts against a 98 GB card a fifth of the layers
3825/// miss. The two-frame path only loses the MoE half of those layers.
3826///
3827/// So the barrier it saves is real and the fallback it forces costs more. The
3828/// fix is the granularity: pack the experts that FIT, route over all of them
3829/// anyway, and run the few cold picks of a token on the host — per EXPERT,
3830/// not per layer. Then no layer ever leaves the device and this frame wins by
3831/// the 15 ms a token it was built to save.
3832#[cfg(feature = "gpu")]
3833fn gpu_layer_enabled() -> bool {
3834    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3835    *ON.get_or_init(|| {
3836        std::env::var("CMF_DSV4_GPU_LAYER")
3837            .map(|v| v != "0")
3838            .unwrap_or(true)
3839            && crate::gpu::backend_available()
3840    })
3841}
3842
3843/// The packed expert set of one layer: which globals made it in, and their
3844/// directory indices in packing order with the shared expert last. Built once
3845/// — the mask does not change during a run — and keyed by layer.
3846#[cfg(feature = "gpu")]
3847struct PackDyn {
3848    remap: Vec<u32>,
3849    owner: Vec<u32>,
3850    last: Vec<u64>,
3851    clock: u64,
3852    /// Per-expert recent-use tally (halved every 64 tokens): the q* rule.
3853    /// A slot upload only pays for itself when the expert is REUSED —
3854    /// FreeToken's split — so a fetch needs `seen >= CMF_DSV4_FETCH_MIN_SEEN`
3855    /// prior recent picks; a first-timer stays a cold pick and the CPU
3856    /// reads it at the shelf. The automatic default comes from a small
3857    /// one-time host→device bandwidth probe; an explicit env still wins.
3858    seen: Vec<u16>,
3859    /// Set on the first slot refill. The chain and the batch verify hand
3860    /// the device `remap: None` and trust the banks to still hold the
3861    /// BUILD-TIME packing — a mutated pack must never be claimed by them.
3862    mutated: bool,
3863}
3864
3865/// One logical cache line in the model-wide expert pool. `layer` is the
3866/// first expert tensor's directory index rather than the ordinal: trunk and
3867/// MTP stages both use small ordinal numbers, while this identity is unique
3868/// for the lifetime of the mapped model.
3869#[cfg(feature = "gpu")]
3870#[derive(Clone, Copy)]
3871struct GlobalOwner {
3872    layer: usize,
3873    expert: usize,
3874    pinned: bool,
3875}
3876
3877#[cfg(feature = "gpu")]
3878struct GlobalPoolDyn {
3879    slot_for: std::collections::HashMap<(usize, usize), u32>,
3880    owner: Vec<Option<GlobalOwner>>,
3881    last: Vec<u64>,
3882    seen: std::collections::HashMap<(usize, usize), u16>,
3883    occupancy: std::collections::HashMap<usize, usize>,
3884    clock: u64,
3885}
3886
3887/// FreeToken's unified `(layer, expert) -> slot` table, with one deliberate
3888/// addition learned from this engine's routing traces: each trunk layer gets
3889/// a small protected floor. A plain global LRU under a deterministic
3890/// layer-by-layer sweep measured 4.7% hits, while equal per-layer LRUs hit
3891/// about 70%. The floor prevents cyclic scan eviction; every slot above it is
3892/// still borrowed and evicted globally, so skewed layers use otherwise idle
3893/// capacity.
3894#[cfg(feature = "gpu")]
3895struct GlobalPool {
3896    uid: u64,
3897    capacity: usize,
3898    segment_slots: usize,
3899    floor: usize,
3900    state: std::sync::Mutex<GlobalPoolDyn>,
3901}
3902
3903#[cfg(feature = "gpu")]
3904impl GlobalPool {
3905    fn remap(&self, layer: usize, n: usize) -> Vec<u32> {
3906        let st = self.state.lock().unwrap();
3907        debug_assert_eq!(self.capacity, st.owner.len());
3908        (0..n)
3909            .map(|e| st.slot_for.get(&(layer, e)).copied().unwrap_or(u32::MAX))
3910            .collect()
3911    }
3912
3913    fn seed_layer(
3914        &self,
3915        model: &std::sync::Arc<cortiq_core::CmfModel>,
3916        layer: usize,
3917        shared: (usize, usize, usize),
3918        _routed: &[(usize, (usize, usize, usize))],
3919    ) -> Option<u32> {
3920        let mut st = self.state.lock().unwrap();
3921        let shared_key = (layer, usize::MAX);
3922        let shared_slot = if let Some(&slot) = st.slot_for.get(&shared_key) {
3923            slot
3924        } else {
3925            let slot = st.owner.iter().position(Option::is_none)?;
3926            if !crate::gpu_wgpu::dsv4_global_slot_fill(model, slot, shared) {
3927                return None;
3928            }
3929            st.owner[slot] = Some(GlobalOwner {
3930                layer,
3931                expert: usize::MAX,
3932                pinned: true,
3933            });
3934            st.slot_for.insert(shared_key, slot as u32);
3935            *st.occupancy.entry(layer).or_insert(0) += 1;
3936            slot as u32
3937        };
3938        // Do not fill the remaining 40+ GiB by expert id. Route locality is
3939        // checkpoint- and prompt-dependent; the old eager seed spent a
3940        // minute uploading rows that the first token immediately replaced.
3941        // Empty slots are populated by the real top-k before its dispatch.
3942        Some(shared_slot)
3943    }
3944
3945    fn ensure_picks(
3946        &self,
3947        model: &std::sync::Arc<cortiq_core::CmfModel>,
3948        layer: usize,
3949        picks: &[usize],
3950        experts: &[Dsv4Expert],
3951        quota: usize,
3952        min_seen: u16,
3953    ) -> Vec<u32> {
3954        let mut st = self.state.lock().unwrap();
3955        st.clock = st.clock.saturating_add(1);
3956        let clock = st.clock;
3957        if clock % 64 == 0 {
3958            for v in st.seen.values_mut() {
3959                *v >>= 1;
3960            }
3961        }
3962        for &expert in picks {
3963            let key = (layer, expert);
3964            let seen = st.seen.entry(key).or_insert(0);
3965            *seen = seen.saturating_add(1);
3966            if let Some(&slot) = st.slot_for.get(&key) {
3967                st.last[slot as usize] = clock;
3968            }
3969        }
3970        let mut fetched = 0usize;
3971        for &expert in picks {
3972            if fetched >= quota {
3973                break;
3974            }
3975            let key = (layer, expert);
3976            if st.slot_for.contains_key(&key) || st.seen.get(&key).copied().unwrap_or(0) < min_seen
3977            {
3978                continue;
3979            }
3980            let Some(exp) = experts.get(expert) else {
3981                continue;
3982            };
3983            let Some(tensors) = (|| {
3984                Some((
3985                    exp.w1.model_idx()?,
3986                    exp.w3.model_idx()?,
3987                    exp.w2.model_idx()?,
3988                ))
3989            })() else {
3990                continue;
3991            };
3992            let empty = st.owner.iter().position(Option::is_none);
3993            // First evict from a layer that currently borrows above its
3994            // floor. Do not evict any expert required by this very dispatch.
3995            let over_floor = |o: GlobalOwner, occ: &std::collections::HashMap<usize, usize>| {
3996                occ.get(&o.layer).copied().unwrap_or(0) > self.floor
3997            };
3998            let eligible =
3999                |o: GlobalOwner| !o.pinned && !(o.layer == layer && picks.contains(&o.expert));
4000            let victim = empty
4001                .or_else(|| {
4002                    st.owner
4003                        .iter()
4004                        .enumerate()
4005                        .filter_map(|(slot, &o)| {
4006                            o.filter(|&x| eligible(x) && over_floor(x, &st.occupancy))
4007                                .map(|_| slot)
4008                        })
4009                        .min_by_key(|&slot| st.last[slot])
4010                })
4011                .or_else(|| {
4012                    // If every layer sits exactly at its floor, replace within
4013                    // the requesting layer. This is per-layer LRU behaviour and
4014                    // cannot trigger the cyclic global scan collapse.
4015                    st.owner
4016                        .iter()
4017                        .enumerate()
4018                        .filter_map(|(slot, &o)| {
4019                            o.filter(|&x| eligible(x) && x.layer == layer).map(|_| slot)
4020                        })
4021                        .min_by_key(|&slot| st.last[slot])
4022                })
4023                .or_else(|| {
4024                    // A new/MTP layer has no protected share yet. Let it borrow
4025                    // the globally oldest unpinned line; trunk floors are a
4026                    // locality guarantee, not a permanent admission ban.
4027                    st.owner
4028                        .iter()
4029                        .enumerate()
4030                        .filter_map(|(slot, &o)| o.filter(|&x| eligible(x)).map(|_| slot))
4031                        .min_by_key(|&slot| st.last[slot])
4032                });
4033            let Some(victim) = victim else { break };
4034            if !crate::gpu_wgpu::dsv4_global_slot_fill(model, victim, tensors) {
4035                break;
4036            }
4037            if let Some(old) = st.owner[victim] {
4038                st.slot_for.remove(&(old.layer, old.expert));
4039                if let Some(n) = st.occupancy.get_mut(&old.layer) {
4040                    *n = n.saturating_sub(1);
4041                }
4042            }
4043            st.owner[victim] = Some(GlobalOwner {
4044                layer,
4045                expert,
4046                pinned: false,
4047            });
4048            st.slot_for.insert(key, victim as u32);
4049            *st.occupancy.entry(layer).or_insert(0) += 1;
4050            st.last[victim] = clock;
4051            fetched += 1;
4052        }
4053        drop(st);
4054        self.remap(layer, experts.len())
4055    }
4056
4057    /// Reserve a small immutable slice of the unified arena for the draft.
4058    /// The draft graph cannot pause between its dependent stages to repair a
4059    /// slot that the exact trunk evicted, so its bounded resident subset is
4060    /// pinned.  These are still the SAME physical banks: no second expert
4061    /// allocation and no duplicate upload cache are created.
4062    fn pin_picks(
4063        &self,
4064        model: &std::sync::Arc<cortiq_core::CmfModel>,
4065        layer: usize,
4066        picks: &[usize],
4067        experts: &[Dsv4Expert],
4068    ) -> Vec<u32> {
4069        let remap = self.ensure_picks(model, layer, picks, experts, picks.len(), 1);
4070        let mut st = self.state.lock().unwrap();
4071        for &expert in picks {
4072            if let Some(&slot) = st.slot_for.get(&(layer, expert)) {
4073                if let Some(owner) = st.owner[slot as usize].as_mut() {
4074                    owner.pinned = true;
4075                }
4076            }
4077        }
4078        remap
4079    }
4080}
4081
4082#[cfg(feature = "gpu")]
4083#[derive(Clone)]
4084struct GlobalPack {
4085    pool: std::sync::Arc<GlobalPool>,
4086    layer: usize,
4087    shared_slot: u32,
4088}
4089
4090#[cfg(feature = "gpu")]
4091impl Pack {
4092    /// True once any slot was refilled away from the build-time packing.
4093    fn is_mutated(&self) -> bool {
4094        self.global.is_some() || self.dynslots.lock().unwrap().mutated
4095    }
4096
4097    /// Complete means complete for the route the model will actually take.
4098    /// A task mask can close most of the 256 rows; packing every OPEN row is
4099    /// then a full device layer, not a partial layer with 200 imaginary cold
4100    /// experts. Hash layers deliberately carry no mask because their forced
4101    /// rows remain the exact checkpoint contract.
4102    fn route_complete(&self) -> bool {
4103        if self.global.is_some() {
4104            return false;
4105        }
4106        let need = self
4107            .mask
4108            .as_deref()
4109            .map_or(self.remap.len(), |m| m.iter().filter(|&&x| x != 0).count());
4110        self.globals.len() >= need
4111    }
4112
4113    /// A global-to-slot table is needed for a masked set and for a complete
4114    /// pack whose hot-first order is not identity. Without it a full but
4115    /// reordered pack silently runs the right router index on the wrong bank.
4116    fn needs_remap(&self) -> bool {
4117        self.global.is_some()
4118            || self.mask.is_some()
4119            || self
4120                .remap
4121                .iter()
4122                .enumerate()
4123                .any(|(i, &slot)| slot != i as u32)
4124    }
4125}
4126
4127/// The packed expert set of one layer: which globals made it in, and their
4128/// directory indices in packing order with the shared expert last. Keyed by
4129/// layer; the STATIC fields are built once, the dynamic slot state evolves.
4130#[cfg(feature = "gpu")]
4131struct Pack {
4132    /// The router as dense f32, expanded once. It is 4 MB a layer against a
4133    /// 112 GB model, it lives as long as the process — so the address-keyed
4134    /// device cache is sound for it, unlike anything built per call.
4135    router: Vec<f32>,
4136    /// global expert id -> packed slot, `usize::MAX` for the ones left out.
4137    to_slot: Vec<usize>,
4138    /// The same, as the u32 table the router reads.
4139    remap: Vec<u32>,
4140    /// packed order, globals only (shared is not in here).
4141    globals: Vec<usize>,
4142    tensors: Vec<(usize, usize, usize)>,
4143    /// Global 0/1 route mask consumed by the GPU router. Stable storage is
4144    /// part of the pack because the device constant cache keys by address.
4145    /// None on exact/hash routing.
4146    mask: Option<Vec<u32>>,
4147    /// FreeToken-style dynamic slots: the packed subset FOLLOWS the router
4148    /// instead of staying whatever load-time frequency guessed. `remap` here
4149    /// is the LIVE table (the immutable `remap` above is the initial state
4150    /// and stays only as the build artifact); `owner[slot]` is the global
4151    /// expert id occupying the slot; `last[slot]`/`clock` drive LRU. The
4152    /// device bank buffers accept `write_buffer` at slot offsets, and the
4153    /// frame re-uploads the remap every call — so a refill is two queue
4154    /// writes and no cache invalidation anywhere.
4155    dynslots: std::sync::Mutex<PackDyn>,
4156    /// The noaux_tc bias in GLOBAL order, kept here because it is the same
4157    /// every token and the pack lives as long as the process. Global order is
4158    /// required by masked/remapped routing; its stable address lets many
4159    /// layers share one submission. A bias uploaded through
4160    /// the per-call pool is written by every layer of a run BEFORE the run's
4161    /// single submit — queue writes do not interleave with passes — so every
4162    /// layer routed with the LAST layer's bias. On the release every scored
4163    /// layer carries one, which is the 50.280.
4164    bias: Option<Vec<f32>>,
4165    /// Present only on the descriptor-indexed Q4TP path. `remap` above is
4166    /// merely the build-time snapshot there; every frame takes a fresh map
4167    /// from this common allocator after its refills/evictions.
4168    global: Option<GlobalPack>,
4169}
4170
4171#[cfg(feature = "gpu")]
4172/// Candidate order for a budget-limited pack: hottest expert first, by the
4173/// measured tally `CMF_DSV4_PACK_FREQ` points at (`layer<TAB>expert<TAB>count`
4174/// lines). None when the variable is unset, the file is unreadable, or the
4175/// tally has nothing for this layer — the caller keeps id order then. Ties
4176/// and untallied experts follow in id order, so the choice is deterministic.
4177fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
4178    use std::collections::HashMap;
4179    use std::sync::OnceLock;
4180    static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
4181    let map = FREQ
4182        .get_or_init(|| {
4183            let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
4184            let text = match std::fs::read_to_string(&path) {
4185                Ok(t) => t,
4186                Err(e) => {
4187                    eprintln!("CMF_DSV4_PACK_FREQ={path} is unreadable ({e}) — ordering by id");
4188                    return None;
4189                }
4190            };
4191            let mut m = HashMap::new();
4192            for line in text.lines() {
4193                let mut it = line.split('\t');
4194                if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
4195                    if let (Ok(l), Ok(e), Ok(c)) =
4196                        (l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
4197                    {
4198                        *m.entry((l, e)).or_insert(0) += c;
4199                    }
4200                }
4201            }
4202            Some(m)
4203        })
4204        .as_ref()?;
4205    if !(0..n).any(|e| map.contains_key(&(li, e))) {
4206        return None;
4207    }
4208    let mut idx: Vec<usize> = (0..n).collect();
4209    idx.sort_by_key(|&e| {
4210        (
4211            std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)),
4212            e,
4213        )
4214    });
4215    Some(idx)
4216}
4217
4218#[cfg(feature = "gpu")]
4219fn global_pool_for(
4220    model: &std::sync::Arc<cortiq_core::CmfModel>,
4221    cfg: &Dsv4Cfg,
4222    gu_q2: bool,
4223    dn_q2: bool,
4224) -> Option<std::sync::Arc<GlobalPool>> {
4225    use std::collections::HashMap;
4226    use std::sync::{Arc, Mutex, OnceLock};
4227    if dn_q2
4228        || !crate::gpu_wgpu::dsv4_global_moe_supported()
4229        || std::env::var("CMF_MOE_MASK").is_ok()
4230    {
4231        return None;
4232    }
4233    static POOLS: OnceLock<Mutex<HashMap<u64, Arc<GlobalPool>>>> = OnceLock::new();
4234    let pools = POOLS.get_or_init(|| Mutex::new(HashMap::new()));
4235    if let Some(p) = pools.lock().unwrap().get(&model.uid()).cloned() {
4236        return Some(p);
4237    }
4238    let requested = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, false);
4239    let (capacity, segment_slots) =
4240        crate::gpu_wgpu::dsv4_global_moe_create(model, requested, cfg.moe_inter, cfg.dim, gu_q2)?;
4241    let layers = model.header.arch.num_layers.max(1);
4242    let p = Arc::new(GlobalPool {
4243        uid: model.uid(),
4244        capacity,
4245        segment_slots,
4246        // At least shared + one routed line remain protected per layer when
4247        // geometry permits it. Larger cards naturally raise the floor.
4248        floor: (capacity / layers).max(2),
4249        state: Mutex::new(GlobalPoolDyn {
4250            slot_for: HashMap::new(),
4251            owner: vec![None; capacity],
4252            last: vec![0; capacity],
4253            seen: HashMap::new(),
4254            occupancy: HashMap::new(),
4255            clock: 0,
4256        }),
4257    });
4258    pools.lock().unwrap().insert(model.uid(), p.clone());
4259    Some(p)
4260}
4261
4262#[cfg(feature = "gpu")]
4263fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
4264    use std::collections::HashMap;
4265    use std::sync::{Arc, Mutex, OnceLock};
4266    static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
4267        OnceLock::new();
4268    let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
4269    // Keyed by the layer's IDENTITY, not its ordinal. The draft's three
4270    // stages are layers too and they number 0, 1, 2 — under an ordinal key
4271    // they would be handed the trunk's first three packs: another layer's
4272    // router, another layer's tensor indices, another layer's bias. The gate
4273    // tensor is what actually distinguishes them.
4274    let model_uid = l
4275        .experts
4276        .first()
4277        .and_then(|e| e.w1.model_arc())
4278        .map_or(0, |m| m.uid());
4279    // Dense f32 routers need not have a directory handle, so the gate index
4280    // alone can be `None` for every layer. Pair the ordinal with the first
4281    // expert's mapped identity; model UID keeps long-lived multi-model
4282    // servers separate, while the expert index distinguishes trunk and MTP
4283    // layers that reuse ordinal 0/1/2.
4284    let first_expert = l
4285        .experts
4286        .first()
4287        .and_then(|e| e.w1.model_idx())
4288        .unwrap_or(usize::MAX);
4289    let key = (model_uid, li, first_expert);
4290    if let Some(v) = cache.lock().unwrap().get(&key) {
4291        return v.clone();
4292    }
4293    // `CMF_DSV4_PACK_MAX_LI=N` — do not pack layers above N at all. A layer
4294    // with no pack stays wholly host-owned, which is what both the batched
4295    // prefill and a speculative verify need of the tail: a device-owned
4296    // partial layer can join neither the batch (incomplete pack) nor the
4297    // causal host tail (its caches live on the card). This also carves the
4298    // VRAM the tail would have taken for the draft's own pack.
4299    if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
4300        if v.parse::<usize>().is_ok_and(|max| li > max) {
4301            cache.lock().unwrap().insert(key, None);
4302            return None;
4303        }
4304    }
4305    let build = || -> Option<Arc<Pack>> {
4306        let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4307        let mut globals = Vec::new();
4308        let mut tensors = Vec::new();
4309        let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
4310            Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
4311        };
4312        let route_mask: Option<Vec<u32>> = if l.tid2eid.is_none() {
4313            l.mask
4314                .as_deref()
4315                .map(|m| m.iter().map(|&open| u32::from(open)).collect())
4316        } else {
4317            None
4318        };
4319        // How many experts the card still has room for, minus one for the
4320        // shared expert, which always rides. Everything past that stays on the
4321        // host and is reached through the remap — the router still ranges over
4322        // all of them, so this costs speed and not a single bit of quality.
4323        let gu_q2 = l
4324            .experts
4325            .first()
4326            .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4327        let dn_q2 = l
4328            .experts
4329            .first()
4330            .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4331        // Exact default on Q4TP: one physical/logical cache for every
4332        // `(layer, expert)` pair. Explicit mask experiments keep the old
4333        // local path so this branch never combines two independent changes
4334        // to model semantics.
4335        if route_mask.is_none() {
4336            if let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) {
4337                if let Some(gp) = global_pool_for(&model, cfg, gu_q2, dn_q2) {
4338                    let layer_key = first_expert;
4339                    let order = pack_freq_order(li, l.experts.len())
4340                        .unwrap_or_else(|| (0..l.experts.len()).collect());
4341                    let routed: Vec<_> = order
4342                        .into_iter()
4343                        .filter_map(|gi| Some((gi, idx3(&l.experts[gi])?)))
4344                        .collect();
4345                    let shared = idx3(&l.shared)?;
4346                    let shared_slot = gp.seed_layer(&model, layer_key, shared, &routed)?;
4347                    let remap = gp.remap(layer_key, cfg.n_routed_experts);
4348                    let to_slot: Vec<usize> = remap
4349                        .iter()
4350                        .map(|&s| {
4351                            if s == u32::MAX {
4352                                usize::MAX
4353                            } else {
4354                                s as usize
4355                            }
4356                        })
4357                        .collect();
4358                    let globals: Vec<usize> = remap
4359                        .iter()
4360                        .enumerate()
4361                        .filter_map(|(e, &s)| (s != u32::MAX).then_some(e))
4362                        .collect();
4363                    let (rows, cols) = (l.gate.rows(), l.gate.cols());
4364                    let mut router = vec![0.0f32; rows * cols];
4365                    for r in 0..rows {
4366                        l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4367                    }
4368                    return Some(Arc::new(Pack {
4369                        bias: l.gate_bias.clone(),
4370                        mask: None,
4371                        router,
4372                        to_slot,
4373                        remap: remap.clone(),
4374                        globals,
4375                        // Physical tensors live in the global GPU bank map,
4376                        // not in a second layer-local concatenation.
4377                        tensors: Vec::new(),
4378                        dynslots: std::sync::Mutex::new(PackDyn {
4379                            remap,
4380                            owner: Vec::new(),
4381                            last: Vec::new(),
4382                            clock: 0,
4383                            mutated: true,
4384                            seen: vec![0; cfg.n_routed_experts],
4385                        }),
4386                        global: Some(GlobalPack {
4387                            pool: gp,
4388                            layer: layer_key,
4389                            shared_slot,
4390                        }),
4391                    }));
4392                }
4393            }
4394        }
4395        // Pack what fits and leave the rest to the host. The router still
4396        // ranges over every expert; a missing winner is returned as a cold
4397        // pick and completed on the CPU. This is deliberately budget-driven,
4398        // not layer-driven: the same model scales from a small card (more
4399        // partial/host layers) to a large one (all experts resident) without
4400        // a checkpoint-specific cutoff.
4401        // `CMF_DSV4_PACK_MAX=N` caps the packing directly, so a toy can
4402        // reproduce the subset path without needing a card that runs out.
4403        if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
4404            .ok()
4405            .and_then(|v| v.parse::<usize>().ok())
4406        {
4407            let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
4408            let mut globals = Vec::new();
4409            let mut tensors = Vec::new();
4410            for (gi, e) in l.experts.iter().enumerate() {
4411                if route_mask
4412                    .as_deref()
4413                    .is_some_and(|m| m.get(gi).copied().unwrap_or(1) == 0)
4414                {
4415                    continue;
4416                }
4417                if globals.len() >= n {
4418                    break;
4419                }
4420                to_slot[gi] = globals.len();
4421                globals.push(gi);
4422                tensors.push(idx3(e)?);
4423            }
4424            tensors.push(idx3(&l.shared)?);
4425            let (rows, cols) = (l.gate.rows(), l.gate.cols());
4426            let mut router = vec![0.0f32; rows * cols];
4427            for r in 0..rows {
4428                l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4429            }
4430            let remap: Vec<u32> = to_slot
4431                .iter()
4432                .map(|&sl| {
4433                    if sl == usize::MAX {
4434                        u32::MAX
4435                    } else {
4436                        sl as u32
4437                    }
4438                })
4439                .collect();
4440            return Some(Arc::new(Pack {
4441                bias: l.gate_bias.clone(),
4442                mask: route_mask.clone(),
4443                router,
4444                to_slot,
4445                dynslots: std::sync::Mutex::new(PackDyn {
4446                    remap: remap.clone(),
4447                    owner: globals.iter().map(|&g| g as u32).collect(),
4448                    last: vec![0; globals.len()],
4449                    clock: 0,
4450                    mutated: false,
4451                    seen: vec![0; cfg.n_routed_experts],
4452                }),
4453                remap,
4454                globals,
4455                tensors,
4456                global: None,
4457            }));
4458        }
4459        let dn_q2_fit = l
4460            .experts
4461            .first()
4462            .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
4463        let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
4464            .saturating_sub(1);
4465        // A greedy pack starves the tail: the first layers take the whole
4466        // expert budget and the tail falls off the device chain. Divide the
4467        // room still available by the layers still to pack. This depends on
4468        // format and VRAM geometry, not a card name: a small card gives every
4469        // layer a useful partial pack; when the whole model fits the quotient
4470        // naturally reaches all experts. Hash-routed head layers stay whole
4471        // whenever possible because their checkpoint table names exact rows.
4472        //
4473        // CMF_DSV4_PACK_LAYER_CAP=N remains an override; 0 restores the old
4474        // greedy packing for a performance bisect.
4475        let remaining_layers = l
4476            .experts
4477            .first()
4478            .and_then(|e| e.w1.model_arc())
4479            .map(|m| m.header.arch.num_layers.saturating_sub(li).max(1))
4480            .unwrap_or(1);
4481        let auto_cap = if l.mask.is_some() {
4482            // The mask is already the layer-specific cap. Its total mass was
4483            // counted by dspark_reserve_note before packing, so an additional
4484            // equal-per-layer cap only turns naturally uneven masked layers
4485            // partial and makes batched verification reject every draft.
4486            room
4487        } else if l.tid2eid.is_some() && room >= cfg.n_routed_experts {
4488            cfg.n_routed_experts
4489        } else {
4490            room.div_ceil(remaining_layers).max(1)
4491        };
4492        let room = match std::env::var("CMF_DSV4_PACK_LAYER_CAP")
4493            .ok()
4494            .and_then(|v| v.parse::<usize>().ok())
4495        {
4496            Some(0) => room,
4497            Some(cap) => room.min(cap),
4498            None => room.min(auto_cap),
4499        };
4500        // When the budget packs a SUBSET, which subset matters: a partial
4501        // layer completes its cold picks from the host, so every resident
4502        // expert that the routing actually reaches is host work saved.
4503        // `CMF_DSV4_PACK_FREQ` names a measured tally
4504        // (`CMF_DSV4_TRUNK_PICK_DUMP` wrote it) and reorders the candidates
4505        // hottest-first; layers absent from the tally keep id order. The
4506        // router still ranges over every expert either way — residency
4507        // choice changes speed, never a bit of the answer.
4508        let order =
4509            pack_freq_order(li, l.experts.len()).unwrap_or_else(|| (0..l.experts.len()).collect());
4510        for gi in order {
4511            let e = &l.experts[gi];
4512            if l.mask
4513                .as_deref()
4514                .is_some_and(|m| !m.get(gi).copied().unwrap_or(true))
4515            {
4516                continue;
4517            }
4518            if globals.len() >= room {
4519                break;
4520            }
4521            to_slot[gi] = globals.len();
4522            globals.push(gi);
4523            match idx3(e) {
4524                Some(t) => tensors.push(t),
4525                None => {
4526                    if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4527                        eprintln!("layer {li}: expert {gi} has no indices in the catalog");
4528                    }
4529                    return None;
4530                }
4531            }
4532        }
4533        if globals.is_empty() {
4534            // Two very different causes, and blaming the mask for the other
4535            // one sent a reader looking for a mask that was never set: an
4536            // actual empty mask, or a VRAM budget with no room left for even
4537            // one expert (`room` is 0, which is what a nearly-full card does
4538            // to the last layers).
4539            if room == 0 {
4540                static SAID_ZERO: std::sync::atomic::AtomicBool =
4541                    std::sync::atomic::AtomicBool::new(false);
4542                if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
4543                    tracing::warn!(
4544                        "from layer {li} on, the VRAM budget has no room for even one \
4545                         expert — the remaining weights stay mmap-backed and are read on demand"
4546                    );
4547                }
4548            } else {
4549                tracing::warn!("layer {li}: the mask left no expert");
4550            }
4551            return None;
4552        }
4553        tensors.push(idx3(&l.shared)?); // shared rides last, as the kernels expect
4554        let (rows, cols) = (l.gate.rows(), l.gate.cols());
4555        let mut router = vec![0.0f32; rows * cols];
4556        for r in 0..rows {
4557            l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
4558        }
4559        let remap: Vec<u32> = to_slot
4560            .iter()
4561            .map(|&sl| {
4562                if sl == usize::MAX {
4563                    u32::MAX
4564                } else {
4565                    sl as u32
4566                }
4567            })
4568            .collect();
4569        Some(Arc::new(Pack {
4570            bias: l.gate_bias.clone(),
4571            mask: route_mask,
4572            router,
4573            to_slot,
4574            dynslots: std::sync::Mutex::new(PackDyn {
4575                remap: remap.clone(),
4576                owner: globals.iter().map(|&g| g as u32).collect(),
4577                last: vec![0; globals.len()],
4578                clock: 0,
4579                mutated: false,
4580                seen: vec![0; cfg.n_routed_experts],
4581            }),
4582            remap,
4583            globals,
4584            tensors,
4585            global: None,
4586        }))
4587    };
4588    let v = build();
4589    cache.lock().unwrap().insert(key, v.clone());
4590    v
4591}
4592
4593/// The whole MoE block in one submission, experts resident (default on;
4594/// `CMF_DSV4_GPU_MOE2=0` restores the host path). Returns false having
4595/// changed nothing if it cannot — a missing pack, a refused budget — so the
4596/// caller's CPU path stays correct to run. The early divergence this frame
4597/// once carried (0.44 relative, perplexity 5.162 vs 5.211) was the partial
4598/// -capture and hidden-seed defects, fixed since: perplexity gold 4.578 is
4599/// bit-exact against the CPU on every budget from 64 to 96.5 GB.
4600#[cfg(feature = "gpu")]
4601fn moe_frame(
4602    hidden: &[f32],
4603    l: &Dsv4Layer,
4604    cfg: &Dsv4Cfg,
4605    li: usize,
4606    logits: &[f32],
4607    forced: Option<&[usize]>,
4608    pool: Option<&crate::pool::Pool>,
4609    // The state handover: expand always when the device owns the state,
4610    // fold only when there is a next layer.
4611    hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
4612    hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
4613    out: &mut [f32],
4614) -> Option<(Vec<f32>, usize)> {
4615    macro_rules! no {
4616        ($($t:tt)*) => {{
4617            if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
4618                eprintln!("MoE frame declined: {}", format_args!($($t)*));
4619            }
4620            return None;
4621        }};
4622    }
4623    let Some(pk) = pack_for(l, cfg, li) else {
4624        no!("layer {li}: the expert packing was not built");
4625    };
4626    // The router is a small f32 tensor and is usually NOT mapped; the handle
4627    // has to come from something that is.
4628    let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
4629        no!("layer {li}: the experts are not mapped from the file");
4630    };
4631    let subset = !pk.route_complete();
4632    let needs_remap = pk.needs_remap();
4633    // Dynamic slots (the FreeToken move): predict this token's winners on
4634    // the host and pull the missing ones into LRU slots BEFORE the frame
4635    // runs — up to CMF_DSV4_FETCH_MAX experts a layer a token. The device
4636    // still routes for real, so a wrong prediction costs one unused fill
4637    // and never a wrong number: an unmapped winner comes back as a cold
4638    // pick and the CPU completes it, exactly as before.
4639    fn fetch_quota() -> usize {
4640        static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
4641        *Q.get_or_init(|| {
4642            std::env::var("CMF_DSV4_FETCH_MAX")
4643                .ok()
4644                .and_then(|v| v.parse().ok())
4645                .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
4646        })
4647    }
4648    fn fetch_min_seen() -> u16 {
4649        static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
4650        *M.get_or_init(|| {
4651            std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
4652                .ok()
4653                .and_then(|v| v.parse().ok())
4654                .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
4655        })
4656    }
4657    let mut dynv = pk.dynslots.lock().unwrap();
4658    // The winners, from the same logits the device will rank — used by the
4659    // slot refill below AND by the FreeToken-style overlap: the picks that
4660    // will NOT be resident are computed on the CPU while the device frame
4661    // runs, instead of serially after its wait.
4662    let mut pidx = Vec::new();
4663    let mut pwt = Vec::new();
4664    // A chained caller may leave the FFN input only on the device. The
4665    // overlap path intentionally reads it back; when it does, score a HOST
4666    // prediction without changing the device's routing source. The GPU still
4667    // recomputes and ranks its own logits when `logits` is empty, so a rounding
4668    // disagreement can waste an early CPU result but cannot change a token.
4669    let mut predicted_logits = Vec::new();
4670    let prediction_logits: &[f32] = if subset && logits.is_empty() && !hidden.is_empty() {
4671        predicted_logits.resize(cfg.n_routed_experts, 0.0);
4672        l.gate.matvec(hidden, &mut predicted_logits, pool);
4673        &predicted_logits
4674    } else {
4675        logits
4676    };
4677    if subset && !prediction_logits.is_empty() {
4678        route(
4679            prediction_logits,
4680            l.gate_bias.as_deref(),
4681            cfg.top_k,
4682            cfg.route_scale,
4683            forced,
4684            l.mask.as_deref(),
4685            &mut pidx,
4686            &mut pwt,
4687        );
4688    }
4689    let global_remap = pk.global.as_ref().map(|gl| {
4690        gl.pool.ensure_picks(
4691            &model, gl.layer, &pidx, &l.experts,
4692            // A global demand cache has empty lines during warmup and each
4693            // CPU cold completion costs far more than one expert upload.
4694            // Materialise every predicted winner immediately, FreeToken
4695            // style; device routing still returns any prediction drift as an
4696            // exact cold pick.
4697            cfg.top_k, 1,
4698        )
4699    });
4700    if subset && fetch_quota() > 0 && !pidx.is_empty() && !dynv.owner.is_empty() {
4701        dynv.clock += 1;
4702        let clock = dynv.clock;
4703        if clock % 64 == 0 {
4704            for v in dynv.seen.iter_mut() {
4705                *v >>= 1;
4706            }
4707        }
4708        for &pick in &pidx {
4709            dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
4710            let sl = dynv.remap[pick];
4711            if sl != u32::MAX {
4712                dynv.last[sl as usize] = clock;
4713            }
4714        }
4715        let mut fetched = 0usize;
4716        for &pick in &pidx {
4717            if fetched >= fetch_quota() {
4718                break;
4719            }
4720            if dynv.remap[pick] != u32::MAX {
4721                continue;
4722            }
4723            if dynv.seen[pick] < fetch_min_seen() {
4724                continue; // one-shot so far: the CPU reads it at the shelf
4725            }
4726            // Victim: the LRU slot among those this token does not need.
4727            let victim = (0..dynv.owner.len())
4728                .filter(|&sl| dynv.last[sl] != clock)
4729                .min_by_key(|&sl| dynv.last[sl]);
4730            let Some(victim) = victim else { break };
4731            let Some(exp) = l.experts.get(pick) else {
4732                continue;
4733            };
4734            let t3 = (|| {
4735                Some((
4736                    exp.w1.model_idx()?,
4737                    exp.w3.model_idx()?,
4738                    exp.w2.model_idx()?,
4739                ))
4740            })();
4741            let Some(t3) = t3 else { continue };
4742            let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
4743            let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
4744            if !crate::gpu_wgpu::dsv4_slot_fill(
4745                &model,
4746                pack_first,
4747                victim,
4748                pick,
4749                t3,
4750                cfg.moe_inter,
4751                cfg.dim,
4752                gu_q2,
4753            ) {
4754                break;
4755            }
4756            let old = dynv.owner[victim] as usize;
4757            if old < dynv.remap.len() {
4758                dynv.remap[old] = u32::MAX;
4759            }
4760            dynv.remap[pick] = victim as u32;
4761            dynv.owner[victim] = pick as u32;
4762            dynv.last[victim] = clock;
4763            dynv.mutated = true;
4764            fetched += 1;
4765        }
4766    }
4767    // With a complete pack the forced row is translated to packed numbering.
4768    // With a subset it stays global: the router's remap either finds its slot
4769    // or returns the forced expert as a cold pick, exactly like a scored one.
4770    let fpack: Option<Vec<usize>> = match forced {
4771        Some(f) if needs_remap => Some(f.to_vec()),
4772        Some(f) => {
4773            let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
4774            if v.contains(&usize::MAX) {
4775                no!("layer {li}: the hash layer names an expert outside the packing");
4776            }
4777            Some(v)
4778        }
4779        None => None,
4780    };
4781    // Routing ranges over EVERY expert; the remap turns a winner into a slot
4782    // or marks it cold. Nothing is masked, so nothing is lost.
4783    // Empty logits are the device-scored case: the frame computes them from
4784    // pk.router, whose rows are already in global order, so there is nothing
4785    // to reorder — and indexing an empty slice is how this line greeted the
4786    // first engaged run.
4787    let lg: Vec<f32> = if logits.is_empty() || needs_remap {
4788        logits.to_vec()
4789    } else {
4790        pk.globals.iter().map(|&g| logits[g]).collect()
4791    };
4792    let live_remap = global_remap.as_deref().unwrap_or(dynv.remap.as_slice());
4793    let w = crate::gpu_wgpu::Dsv4MoeW {
4794        router: &pk.router,
4795        experts: &pk.tensors,
4796        logits: &lg,
4797        bias: pk.bias.as_deref(),
4798        mask: pk.mask.as_deref(),
4799        forced: fpack.as_deref(),
4800        remap: needs_remap.then_some(live_remap),
4801        global: pk.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
4802            pool_uid: gl.pool.uid,
4803            shared_slot: gl.shared_slot,
4804            segment_slots: gl.pool.segment_slots as u32,
4805        }),
4806        has_shared: true,
4807        shared_weight: 1.0,
4808        preweighted: false,
4809        qwen_softmax: false,
4810    };
4811    let g = crate::gpu_wgpu::Dsv4MoeGeom {
4812        hidden: cfg.dim,
4813        inter: cfg.moe_inter,
4814        top_k: cfg.top_k,
4815        route_scale: cfg.route_scale,
4816        swiglu_limit: cfg.swiglu_limit,
4817        gu_q2: l
4818            .experts
4819            .first()
4820            .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
4821        bf16: false,
4822    };
4823    let mut cold = Vec::new();
4824    let mut cold_x = Vec::new();
4825    // The FreeToken overlap: the predicted winners that will NOT be
4826    // resident are computed on the CPU WHILE the device frame runs,
4827    // instead of serially after its wait. Unweighted (weight 1) — the
4828    // device's own cold weights scale the result at the merge, so a
4829    // routing drift between the host's ranking and the card's costs one
4830    // wasted thread, never a wrong number. Only the per-layer path has
4831    // the input on the host (`hidden` non-empty); the chain keeps its
4832    // own economy.
4833    let overlap: Vec<usize> = if !hidden.is_empty() {
4834        pidx.iter()
4835            .copied()
4836            .filter(|&pick| live_remap.get(pick).copied().unwrap_or(u32::MAX) == u32::MAX)
4837            .collect()
4838    } else {
4839        Vec::new()
4840    };
4841    let mut early: std::collections::HashMap<usize, Vec<f32>> = std::collections::HashMap::new();
4842    let frame_ok = std::thread::scope(|sc| {
4843        let handles: Vec<_> = overlap
4844            .iter()
4845            .filter_map(|&gi| l.experts.get(gi).map(|exp| (gi, exp)))
4846            .map(|(gi, exp)| {
4847                sc.spawn(move || {
4848                    let mut a = vec![0.0f32; cfg.dim];
4849                    crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, 1.0, None, &mut a));
4850                    (gi, a)
4851                })
4852            })
4853            .collect();
4854        let ok = crate::gpu_wgpu::dsv4_moe_frame(
4855            &model,
4856            &w,
4857            g,
4858            hidden,
4859            &mut cold,
4860            &mut cold_x,
4861            hc_cur,
4862            hc_next,
4863            out,
4864        );
4865        for h in handles {
4866            if let Ok((gi, a)) = h.join() {
4867                early.insert(gi, a);
4868            }
4869        }
4870        ok
4871    });
4872    if !frame_ok {
4873        return None;
4874    }
4875    // The picks the card had no room for, finished here and added in. Their
4876    // weights already carry the top-k normalisation the device applied.
4877    if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
4878        let csum: f32 = cold.iter().map(|c| c.1).sum();
4879        eprintln!(
4880            "[cold] layer {li}: {} of {} returned | cold sum {csum:.4} | \
4881             route_scale {:.4} | {:?}",
4882            cold.len(),
4883            cfg.top_k,
4884            cfg.route_scale,
4885            &cold[..cold.len().min(3)]
4886        );
4887    }
4888    let mut acc = vec![0.0f32; cfg.dim];
4889    let mut cold_sum = vec![0.0f32; cfg.dim];
4890    let cold_input = if hidden.is_empty() {
4891        cold_x.as_slice()
4892    } else {
4893        hidden
4894    };
4895    // Cold means out-of-core by contract. The tensors remain mmap-backed:
4896    // missing pages are faulted from the CMF file and the OS may evict
4897    // them again under RAM pressure. Do not let the generic matvec probe
4898    // turn this into an unbounded second GPU cache behind the packer's
4899    // back.
4900    //
4901    // The unit of parallelism is the EXPERT, not the row: a 2048-row
4902    // matvec split across 380 workers is five rows per worker — all
4903    // dispatch, no arithmetic. One worker per cold expert, whole matvecs
4904    // inside (inner pool None), was the difference between ~7 ms and ~1 ms
4905    // per cold expert on the 384-core stand. cpu_scope is thread-local, so
4906    // it sits INSIDE the worker closure.
4907    if !early.is_empty() {
4908        // The overlap already computed (most of) the cold picks; scale by
4909        // the DEVICE's weight and add in cold order — the same order the
4910        // serial path used, so parity holds. A cold pick the prediction
4911        // missed (ranking drift) is computed inline, cpu_scope'd.
4912        for &(gi, wt) in &cold {
4913            if let Some(a) = early.get(&gi) {
4914                for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4915                    *o += v * wt;
4916                    *sum += v * wt;
4917                }
4918                continue;
4919            }
4920            let Some(exp) = l.experts.get(gi) else {
4921                continue;
4922            };
4923            crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4924            for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4925                *o += a;
4926                *sum += a;
4927            }
4928        }
4929        return Some((cold_sum, cold.len()));
4930    }
4931    match pool {
4932        Some(p) if cold.len() > 1 => {
4933            let results: Vec<std::sync::Mutex<Vec<f32>>> = cold
4934                .iter()
4935                .map(|_| std::sync::Mutex::new(Vec::new()))
4936                .collect();
4937            let (cold_ref, results_ref) = (&cold, &results);
4938            p.run_rows(cold.len(), &move |cs, ce| {
4939                for i in cs..ce {
4940                    let (gi, wt) = cold_ref[i];
4941                    let Some(exp) = l.experts.get(gi) else {
4942                        continue;
4943                    };
4944                    let mut a = vec![0.0f32; cfg.dim];
4945                    crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, None, &mut a));
4946                    *results_ref[i].lock().unwrap() = a;
4947                }
4948            });
4949            // Serial reduce in cold order — the accumulation order the
4950            // scalar path had, so parity holds bit for bit.
4951            for r in &results {
4952                let a = r.lock().unwrap();
4953                if a.is_empty() {
4954                    continue;
4955                }
4956                for ((o, sum), v) in out.iter_mut().zip(&mut cold_sum).zip(a.iter()) {
4957                    *o += v;
4958                    *sum += v;
4959                }
4960            }
4961        }
4962        _ => {
4963            for &(gi, wt) in &cold {
4964                let Some(exp) = l.experts.get(gi) else {
4965                    continue;
4966                };
4967                crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
4968                for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
4969                    *o += a;
4970                    *sum += a;
4971                }
4972            }
4973        }
4974    }
4975    Some((cold_sum, cold.len()))
4976}
4977
4978/// How much of each layer's compressed cache already sits on the card. ONE
4979/// map: a reader and a writer with a `static` each are two maps, and the
4980/// reader would never see a thing the writer put down.
4981/// The reallocation counter as of the last successful tail write. Any change
4982/// means some buffer was rebuilt and every tail count is stale.
4983#[cfg(feature = "gpu")]
4984fn last_grew(now: u64) -> u64 {
4985    use std::sync::atomic::{AtomicU64, Ordering};
4986    static SEEN: AtomicU64 = AtomicU64::new(0);
4987    let was = SEEN.load(Ordering::Relaxed);
4988    if was != now {
4989        SEEN.store(now, Ordering::Relaxed);
4990        compressed_map().lock().unwrap().clear();
4991        return u64::MAX; // force a full write this round
4992    }
4993    now
4994}
4995
4996#[cfg(feature = "gpu")]
4997fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
4998    use std::collections::HashMap;
4999    use std::sync::{Mutex, OnceLock};
5000    static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
5001    W.get_or_init(|| Mutex::new(HashMap::new()))
5002}
5003
5004#[cfg(feature = "gpu")]
5005fn compressed_written(kv_id: u64, li: usize) -> usize {
5006    compressed_map()
5007        .lock()
5008        .unwrap()
5009        .get(&(kv_id, li))
5010        .copied()
5011        .unwrap_or(0)
5012}
5013
5014/// Reset to zero whenever a write fails or the buffer grows — a grown buffer
5015/// keeps none of its contents.
5016#[cfg(feature = "gpu")]
5017fn note_compressed(kv_id: u64, li: usize, n: usize) {
5018    compressed_map().lock().unwrap().insert((kv_id, li), n);
5019}
5020
5021#[cfg(feature = "gpu")]
5022fn gpu_moe2_enabled() -> bool {
5023    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5024    *ON.get_or_init(|| {
5025        std::env::var("CMF_DSV4_GPU_MOE2")
5026            .map(|v| v != "0")
5027            .unwrap_or(true)
5028            && crate::gpu::backend_available()
5029    })
5030}
5031
5032pub fn moe_step(
5033    hidden: &[f32],
5034    l: &Dsv4Layer,
5035    cfg: &Dsv4Cfg,
5036    token_id: u32,
5037    // Layer index — only used to bucket routing statistics.
5038    li: usize,
5039    pool: Option<&crate::pool::Pool>,
5040    out: &mut [f32],
5041) {
5042    let _t0 = prof::on().then(std::time::Instant::now);
5043    let _guard = scopeguard_moe(_t0, li);
5044    let mut logits = vec![0.0f32; cfg.n_routed_experts];
5045    l.gate.matvec(hidden, &mut logits, pool);
5046    let (mut idx, mut w) = (Vec::new(), Vec::new());
5047    route(
5048        &logits,
5049        l.gate_bias.as_deref(),
5050        cfg.top_k,
5051        cfg.route_scale,
5052        l.tid2eid
5053            .as_ref()
5054            .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
5055            .as_deref(),
5056        l.mask.as_deref(),
5057        &mut idx,
5058        &mut w,
5059    );
5060    if route_stats_on() {
5061        let routed: Vec<(usize, f32)> = idx.iter().copied().zip(w.iter().copied()).collect();
5062        record_route(li, 0, cfg.n_routed_experts, &routed);
5063    }
5064    // Same trace the generic MoE path writes (`CMF_MOE_TRACE`): one
5065    // `layer:e1,e2,…` line per routed token. The first arena run on this
5066    // architecture measured a 4.5% hit rate — random level for the arena's
5067    // size — and only a per-token trace can say whether that is the
5068    // router's true entropy or the cache structure destroying locality.
5069    crate::pipeline::moe_trace_at(li as i32, &idx);
5070    // The whole block on the device, in one submission, or nothing. Routing
5071    // happens there too — the logits above are what it starts from, so the
5072    // CPU's own choice is discarded rather than second-guessed.
5073    #[cfg(feature = "gpu")]
5074    if gpu_moe2_enabled() && crate::gpu::enabled_here() {
5075        let forced = l
5076            .tid2eid
5077            .as_ref()
5078            .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
5079        if moe_frame(
5080            hidden,
5081            l,
5082            cfg,
5083            li,
5084            &logits,
5085            forced.as_deref(),
5086            pool,
5087            None,
5088            None,
5089            out,
5090        )
5091        .is_some()
5092        {
5093            // CMF_DSV4_MOE_CHECK=1 recomputes the same block on the CPU and
5094            // reports where they part. A wrong MoE does not fail — it answers
5095            // differently — and the toy agreed bit for bit while the release
5096            // did not, so the difference lives in something the toy has no
5097            // instance of. Only a per-layer number will say which.
5098            if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
5099                let mut want = vec![0.0f32; out.len()];
5100                let mut acc = vec![0.0f32; cfg.dim];
5101                // This is a CPU oracle. Without cpu_scope the generic
5102                // quantized matvec probe uploaded a second copy of every
5103                // selected expert, so the diagnostic itself exhausted VRAM
5104                // after otherwise-correct global-pool layers.
5105                crate::gpu::cpu_scope(|| {
5106                    for (e, &ei) in idx.iter().enumerate() {
5107                        let Some(exp) = l.experts.get(ei) else {
5108                            continue;
5109                        };
5110                        run_expert(
5111                            hidden,
5112                            exp,
5113                            cfg,
5114                            w.get(e).copied().unwrap_or(0.0),
5115                            pool,
5116                            &mut acc,
5117                        );
5118                        for (o, a) in want.iter_mut().zip(&acc) {
5119                            *o += a;
5120                        }
5121                    }
5122                    run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5123                    for (o, a) in want.iter_mut().zip(&acc) {
5124                        *o += a;
5125                    }
5126                });
5127                let num: f32 = want
5128                    .iter()
5129                    .zip(out.iter())
5130                    .map(|(a, b)| (a - b) * (a - b))
5131                    .sum();
5132                let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5133                let rel = (num / den).sqrt();
5134                if rel > 1e-3 {
5135                    let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
5136                    eprintln!(
5137                        "[MoE frame] layer {li}: divergence {rel:.3e} | selected {} | \
5138                         packed {packed} of {} | hash={} | offset={}",
5139                        idx.len(),
5140                        cfg.n_routed_experts,
5141                        l.tid2eid.is_some(),
5142                        l.gate_bias.is_some()
5143                    );
5144                }
5145            }
5146            return;
5147        }
5148    }
5149    // Cheap tally for the batching question: how many DISTINCT experts a
5150    // group of tokens reaches. If five tokens want thirty different experts,
5151    // a batched MoE reads thirty weights and amortises nothing — which is
5152    // the difference between a speculative verify that pays for itself and
5153    // one that does not. Disarmed it costs one thread-local read.
5154    PICK_TALLY.with(|t| {
5155        if let Some(v) = t.borrow_mut().as_mut() {
5156            v.push((li, idx.to_vec()));
5157        }
5158    });
5159    if dump_path().is_some() {
5160        PICKED.with(|p| {
5161            let mut p = p.borrow_mut();
5162            if p.len() <= li {
5163                p.resize(li + 1, Vec::new());
5164            }
5165            p[li] = idx.clone();
5166        });
5167    }
5168    // One submission for the whole block — the chosen experts plus the
5169    // shared one. Per-expert dispatches are what made MoE slow elsewhere,
5170    // and the device keeps the weights across tokens, so the cost is the
5171    // arithmetic rather than the traffic. A refusal (missing kernel, mixed
5172    // layouts, weights that do not fit the budget) falls to the CPU whole,
5173    // never half.
5174    // CORRECT but SLOWER, so off by default. Parity holds on real weights
5175    // (perplexity 6.808 → 6.839 at 64 tokens, 5.102 → 5.146 at 200), and an
5176    // honest alternating A/B says 1.0 tok/s against the CPU's 2.2. The first
5177    // measurement claimed the opposite — 0.7 → 2.0 — because the CPU arm ran
5178    // first and paged in 158 GB for the GPU arm to inherit.
5179    //
5180    // The cost is not arithmetic, it is round trips: this submits and reads
5181    // back once per layer, forty-three times a token, and a discrete card
5182    // charges milliseconds for each. Fixing it means one submission per
5183    // token — the whole-token graph — not a faster kernel.
5184    //
5185    // `CMF_DSV4_GPU_MOE=1` opts in.
5186    fn gpu_moe_on() -> bool {
5187        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5188        *ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
5189    }
5190    if gpu_moe_on() && crate::gpu::enabled_here() {
5191        let mut jobs = Vec::with_capacity(idx.len() + 1);
5192        let mut model_ref = None;
5193        let mut ok = true;
5194        for (e, &ei) in idx.iter().enumerate() {
5195            let Some(exp) = l.experts.get(ei) else {
5196                continue;
5197            };
5198            ok &= crate::pipeline::moe_push_job_parts(
5199                &exp.w1,
5200                &exp.w3,
5201                &exp.w2,
5202                hidden,
5203                w.get(e).copied().unwrap_or(0.0),
5204                cfg.swiglu_limit,
5205                &mut jobs,
5206                &mut model_ref,
5207            )
5208            .is_some();
5209        }
5210        ok &= crate::pipeline::moe_push_job_parts(
5211            &l.shared.w1,
5212            &l.shared.w3,
5213            &l.shared.w2,
5214            hidden,
5215            1.0,
5216            cfg.swiglu_limit,
5217            &mut jobs,
5218            &mut model_ref,
5219        )
5220        .is_some();
5221        if ok {
5222            if let Some(m) = model_ref.as_ref() {
5223                if crate::gpu::moe_block(m, &jobs, out) {
5224                    // CMF_DSV4_GPU_CHECK=1 recomputes the same block on the
5225                    // CPU and reports the divergence. A GPU MoE that is wrong
5226                    // does not fail — it answers differently — so the only way
5227                    // to know is to ask both.
5228                    if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
5229                        let mut want = vec![0.0f32; out.len()];
5230                        let mut acc = vec![0.0f32; cfg.dim];
5231                        for (e, &ei) in idx.iter().enumerate() {
5232                            let Some(exp) = l.experts.get(ei) else {
5233                                continue;
5234                            };
5235                            run_expert(
5236                                hidden,
5237                                exp,
5238                                cfg,
5239                                w.get(e).copied().unwrap_or(0.0),
5240                                pool,
5241                                &mut acc,
5242                            );
5243                            for (o, a) in want.iter_mut().zip(&acc) {
5244                                *o += a;
5245                            }
5246                        }
5247                        run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
5248                        for (o, a) in want.iter_mut().zip(&acc) {
5249                            *o += a;
5250                        }
5251                        let num: f32 = want
5252                            .iter()
5253                            .zip(out.iter())
5254                            .map(|(a, b)| (a - b) * (a - b))
5255                            .sum();
5256                        let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
5257                        eprintln!(
5258                            "[dsv4-gpu] layer {li}: divergence {:.3e} | |CPU|={:.5} |GPU|={:.5} | experts {}",
5259                            (num / den).sqrt(),
5260                            den.sqrt(),
5261                            out.iter().map(|x| x * x).sum::<f32>().sqrt(),
5262                            jobs.len()
5263                        );
5264                    }
5265                    return;
5266                }
5267            }
5268        }
5269    }
5270    out.fill(0.0);
5271    // Layers the packer had no room for land here whole. Same shape as the
5272    // frame's cold completion: one worker per expert (the shared one rides
5273    // as an extra job), whole matvecs inside, cpu_scope INSIDE the worker —
5274    // on the main thread it would gate nothing, and the generic matvec
5275    // would upload every expert to the card tensor by tensor, which is
5276    // exactly the per-token PCIe churn this path exists to avoid.
5277    let jobs: Vec<(Option<usize>, f32)> = idx
5278        .iter()
5279        .enumerate()
5280        .map(|(e, &ei)| (Some(ei), w.get(e).copied().unwrap_or(0.0)))
5281        .chain(std::iter::once((None, 1.0)))
5282        .collect();
5283    match pool {
5284        Some(p) if jobs.len() > 1 => {
5285            let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5286                .iter()
5287                .map(|_| std::sync::Mutex::new(Vec::new()))
5288                .collect();
5289            let (jobs_ref, results_ref) = (&jobs, &results);
5290            p.run_rows(jobs.len(), &move |cs, ce| {
5291                for i in cs..ce {
5292                    let (ei, wt) = jobs_ref[i];
5293                    let exp = match ei {
5294                        Some(ei) => match l.experts.get(ei) {
5295                            Some(x) => x,
5296                            None => continue,
5297                        },
5298                        None => &l.shared,
5299                    };
5300                    let mut a = vec![0.0f32; cfg.dim];
5301                    crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, None, &mut a));
5302                    *results_ref[i].lock().unwrap() = a;
5303                }
5304            });
5305            for r in &results {
5306                let a = r.lock().unwrap();
5307                for (o, v) in out.iter_mut().zip(a.iter()) {
5308                    *o += v;
5309                }
5310            }
5311        }
5312        _ => {
5313            let mut acc = vec![0.0f32; cfg.dim];
5314            for &(ei, wt) in &jobs {
5315                let exp = match ei {
5316                    Some(ei) => match l.experts.get(ei) {
5317                        Some(x) => x,
5318                        None => continue,
5319                    },
5320                    None => &l.shared,
5321                };
5322                crate::gpu::cpu_scope(|| run_expert(hidden, exp, cfg, wt, pool, &mut acc));
5323                for (o, a) in out.iter_mut().zip(&acc) {
5324                    *o += a;
5325                }
5326            }
5327        }
5328    }
5329}
5330
5331/// The routed and shared experts both come through here, so the clamp and
5332/// the weight folding have exactly one implementation — `expert_swiglu`.
5333fn run_expert(
5334    x: &[f32],
5335    e: &Dsv4Expert,
5336    cfg: &Dsv4Cfg,
5337    weight: f32,
5338    pool: Option<&crate::pool::Pool>,
5339    out: &mut [f32],
5340) {
5341    expert_swiglu(
5342        x,
5343        &|src, dst| e.w1.matvec(src, dst, pool),
5344        &|src, dst| e.w3.matvec(src, dst, pool),
5345        &|src, dst| e.w2.matvec(src, dst, pool),
5346        cfg.moe_inter,
5347        weight,
5348        cfg.swiglu_limit,
5349        out,
5350    );
5351}
5352
5353/// The same expert computation for several inputs, streaming each selected
5354/// weight once. Used by DSpark's trained five-position block: running five
5355/// ordinary `moe_step`s rereads the shared expert five times and every
5356/// coincident routed expert once per position.
5357fn moe_step_block(
5358    xs: &[f32],
5359    b: usize,
5360    l: &Dsv4Layer,
5361    cfg: &Dsv4Cfg,
5362    token_ids: &[u32],
5363    tally_layer: usize,
5364    pool: Option<&crate::pool::Pool>,
5365    out: &mut [f32],
5366) {
5367    let (dim, inter) = (cfg.dim, cfg.moe_inter);
5368    debug_assert_eq!(xs.len(), b * dim);
5369    debug_assert_eq!(out.len(), b * dim);
5370    out.fill(0.0);
5371
5372    let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
5373    l.gate.matmat(xs, b, &mut logits, pool);
5374    let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
5375    let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
5376    for bi in 0..b {
5377        let mut idx = Vec::new();
5378        let mut wt = Vec::new();
5379        let forced = l.tid2eid.as_ref().map(|tbl| {
5380            hash_route(
5381                tbl,
5382                cfg.vocab,
5383                cfg.top_k,
5384                token_ids.get(bi).copied().unwrap_or(0),
5385            )
5386        });
5387        route(
5388            &logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
5389            l.gate_bias.as_deref(),
5390            cfg.top_k,
5391            cfg.route_scale,
5392            forced.as_deref(),
5393            l.mask.as_deref(),
5394            &mut idx,
5395            &mut wt,
5396        );
5397        PICK_TALLY.with(|t| {
5398            if let Some(v) = t.borrow_mut().as_mut() {
5399                v.push((tally_layer, idx.clone()));
5400            }
5401        });
5402        picks.push(idx);
5403        weights.push(wt);
5404    }
5405
5406    // Preserve the scalar path's accumulation order by keeping every routed
5407    // slot separate; grouping below changes only when a weight is read.
5408    let mut routed = vec![0.0f32; b * cfg.top_k * dim];
5409    // Group the token slots by expert first — the list is also the unit of
5410    // parallelism below.
5411    let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5412    for ei in 0..l.experts.len() {
5413        let mut jobs = Vec::new();
5414        for bi in 0..b {
5415            for (slot, &picked) in picks[bi].iter().enumerate() {
5416                if picked == ei {
5417                    jobs.push((bi, slot, weights[bi][slot]));
5418                }
5419            }
5420        }
5421        if !jobs.is_empty() {
5422            active.push((ei, jobs));
5423        }
5424    }
5425    // One expert's forward, single-threaded, returning the scaled down
5426    // projections in job order.
5427    let expert_fwd =
5428        |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5429            let e = &l.experts[ei];
5430            let n = jobs.len();
5431            let mut xj = vec![0.0f32; n * dim];
5432            for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5433                xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5434            }
5435            let mut gate = vec![0.0f32; n * inter];
5436            let mut up = vec![0.0f32; n * inter];
5437            e.w1.matmat(&xj, n, &mut gate, inner);
5438            e.w3.matmat(&xj, n, &mut up, inner);
5439            for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5440                let (gj, uj) = (
5441                    &mut gate[j * inter..(j + 1) * inter],
5442                    &mut up[j * inter..(j + 1) * inter],
5443                );
5444                if cfg.swiglu_limit > 0.0 {
5445                    for u in uj.iter_mut() {
5446                        *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5447                    }
5448                    for g in gj.iter_mut() {
5449                        *g = g.min(cfg.swiglu_limit);
5450                    }
5451                }
5452                for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5453                    *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5454                }
5455            }
5456            let mut down = vec![0.0f32; n * dim];
5457            e.w2.matmat(&gate, n, &mut down, inner);
5458            down
5459        };
5460    // ~370 non-resident experts a token used to run this loop ONE AFTER
5461    // ANOTHER: a 2048-row matvec cannot occupy a big pool, and the loop
5462    // serialised the only real parallelism there is — across experts.
5463    // Measured on a 384-core host with DeepSeek-V4-Flash: ~3.3 s/token
5464    // flat across every fetch-side improvement, because the wall was
5465    // here. Parallel across experts, each single-threaded and pinned to
5466    // the CPU on ITS OWN worker (cpu_scope is thread-local, so it must
5467    // be entered inside the closure, not around the pool call — the
5468    // documented trap).
5469    match pool {
5470        Some(p) if active.len() > 1 => {
5471            let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5472                .iter()
5473                .map(|_| std::sync::Mutex::new(Vec::new()))
5474                .collect();
5475            let active_ref = &active;
5476            let results_ref = &results;
5477            let fwd = &expert_fwd;
5478            p.run_rows(active_ref.len(), &move |s, e| {
5479                for i in s..e {
5480                    let (ei, jobs) = &active_ref[i];
5481                    let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5482                    *results_ref[i].lock().unwrap() = d;
5483                }
5484            });
5485            for (i, (_, jobs)) in active.iter().enumerate() {
5486                let down = results[i].lock().unwrap();
5487                for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5488                    routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5489                        .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5490                }
5491            }
5492        }
5493        _ => {
5494            for (ei, jobs) in &active {
5495                let down = expert_fwd(*ei, jobs, pool);
5496                for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5497                    routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
5498                        .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5499                }
5500            }
5501        }
5502    }
5503
5504    // Shared expert: all positions always use it, so this is the highest
5505    // certainty weight-sharing win in the block.
5506    let mut sg = vec![0.0f32; b * inter];
5507    let mut su = vec![0.0f32; b * inter];
5508    l.shared.w1.matmat(xs, b, &mut sg, pool);
5509    l.shared.w3.matmat(xs, b, &mut su, pool);
5510    for bi in 0..b {
5511        let (gj, uj) = (
5512            &mut sg[bi * inter..(bi + 1) * inter],
5513            &mut su[bi * inter..(bi + 1) * inter],
5514        );
5515        if cfg.swiglu_limit > 0.0 {
5516            for u in uj.iter_mut() {
5517                *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5518            }
5519            for g in gj.iter_mut() {
5520                *g = g.min(cfg.swiglu_limit);
5521            }
5522        }
5523        for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5524            *g = (*g / (1.0 + (-*g).exp())) * u;
5525        }
5526    }
5527    let mut shared = vec![0.0f32; b * dim];
5528    l.shared.w2.matmat(&sg, b, &mut shared, pool);
5529
5530    for bi in 0..b {
5531        let dst = &mut out[bi * dim..(bi + 1) * dim];
5532        for slot in 0..picks[bi].len() {
5533            let src = &routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim];
5534            for (o, &v) in dst.iter_mut().zip(src) {
5535                *o += v;
5536            }
5537        }
5538        for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
5539            *o += v;
5540        }
5541    }
5542}
5543
5544/// Complete only the routed experts absent from a partial GPU pack.  Jobs
5545/// are grouped by expert so coincident speculative positions stream that
5546/// expert's weights once; per-token slot accumulation remains in route order,
5547/// matching the ordinary exact cold-correction path.
5548fn cold_step_block(
5549    xs: &[f32],
5550    b: usize,
5551    l: &Dsv4Layer,
5552    cfg: &Dsv4Cfg,
5553    cold: &[Vec<(usize, f32)>],
5554    pool: Option<&crate::pool::Pool>,
5555    out: &mut [f32],
5556) {
5557    let (dim, inter) = (cfg.dim, cfg.moe_inter);
5558    debug_assert_eq!(xs.len(), b * dim);
5559    debug_assert_eq!(cold.len(), b);
5560    debug_assert_eq!(out.len(), b * dim);
5561    out.fill(0.0);
5562    let slots = cold.iter().map(Vec::len).max().unwrap_or(0);
5563    if slots == 0 {
5564        return;
5565    }
5566    // The ordinary partial walk evaluates every cold winner with matvec.
5567    // The grouped arm streams a coincident expert once for the whole verify
5568    // block; release-scale fingerprints and row-zero logits were identical,
5569    // and it moved the fixed A40 bench 1.9 → 2.3 tok/s.  Keep the scalar arm
5570    // as a parity escape hatch for a new quant layout/adapter.
5571    let grouped = std::env::var("CMF_DSV4_COLD_MATMAT")
5572        .map(|v| v != "0")
5573        .unwrap_or(true);
5574    if !grouped {
5575        let jobs: Vec<(usize, usize, usize, f32)> = cold
5576            .iter()
5577            .enumerate()
5578            .flat_map(|(bi, row)| {
5579                row.iter()
5580                    .enumerate()
5581                    .map(move |(slot, &(ei, wt))| (bi, slot, ei, wt))
5582            })
5583            .collect();
5584        let results: Vec<std::sync::Mutex<Vec<f32>>> = jobs
5585            .iter()
5586            .map(|_| std::sync::Mutex::new(Vec::new()))
5587            .collect();
5588        match pool {
5589            Some(p) if jobs.len() > 1 => {
5590                let (jobs_ref, results_ref) = (&jobs, &results);
5591                p.run_rows(jobs.len(), &move |s, e| {
5592                    for i in s..e {
5593                        let (_, _, ei, wt) = jobs_ref[i];
5594                        let Some(exp) = l.experts.get(ei) else {
5595                            continue;
5596                        };
5597                        let bi = jobs_ref[i].0;
5598                        let mut a = vec![0.0f32; dim];
5599                        crate::gpu::cpu_scope(|| {
5600                            run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, None, &mut a)
5601                        });
5602                        *results_ref[i].lock().unwrap() = a;
5603                    }
5604                });
5605            }
5606            _ => {
5607                for (i, &(bi, _, ei, wt)) in jobs.iter().enumerate() {
5608                    let Some(exp) = l.experts.get(ei) else {
5609                        continue;
5610                    };
5611                    let mut a = vec![0.0f32; dim];
5612                    crate::gpu::cpu_scope(|| {
5613                        run_expert(&xs[bi * dim..(bi + 1) * dim], exp, cfg, wt, pool, &mut a)
5614                    });
5615                    *results[i].lock().unwrap() = a;
5616                }
5617            }
5618        }
5619        // Reduce in each token's routing order, exactly like
5620        // dsv4_chain1_layer. The jobs vector was built in that order.
5621        for (i, &(bi, _, _, _)) in jobs.iter().enumerate() {
5622            let a = results[i].lock().unwrap();
5623            for (o, &v) in out[bi * dim..(bi + 1) * dim].iter_mut().zip(a.iter()) {
5624                *o += v;
5625            }
5626        }
5627        return;
5628    }
5629    let mut active: Vec<(usize, Vec<(usize, usize, f32)>)> = Vec::new();
5630    for ei in 0..l.experts.len() {
5631        let mut jobs = Vec::new();
5632        for bi in 0..b {
5633            for (slot, &(picked, wt)) in cold[bi].iter().enumerate() {
5634                if picked == ei {
5635                    jobs.push((bi, slot, wt));
5636                }
5637            }
5638        }
5639        if !jobs.is_empty() {
5640            active.push((ei, jobs));
5641        }
5642    }
5643    let expert_fwd =
5644        |ei: usize, jobs: &[(usize, usize, f32)], inner: Option<&crate::pool::Pool>| -> Vec<f32> {
5645            let e = &l.experts[ei];
5646            let n = jobs.len();
5647            let mut xj = vec![0.0f32; n * dim];
5648            for (j, &(bi, _, _)) in jobs.iter().enumerate() {
5649                xj[j * dim..(j + 1) * dim].copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
5650            }
5651            let mut gate = vec![0.0f32; n * inter];
5652            let mut up = vec![0.0f32; n * inter];
5653            e.w1.matmat(&xj, n, &mut gate, inner);
5654            e.w3.matmat(&xj, n, &mut up, inner);
5655            for (j, &(_, _, wt)) in jobs.iter().enumerate() {
5656                let (gj, uj) = (
5657                    &mut gate[j * inter..(j + 1) * inter],
5658                    &mut up[j * inter..(j + 1) * inter],
5659                );
5660                if cfg.swiglu_limit > 0.0 {
5661                    for u in uj.iter_mut() {
5662                        *u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
5663                    }
5664                    for g in gj.iter_mut() {
5665                        *g = g.min(cfg.swiglu_limit);
5666                    }
5667                }
5668                for (g, &u) in gj.iter_mut().zip(uj.iter()) {
5669                    *g = (*g / (1.0 + (-*g).exp())) * u * wt;
5670                }
5671            }
5672            let mut down = vec![0.0f32; n * dim];
5673            e.w2.matmat(&gate, n, &mut down, inner);
5674            down
5675        };
5676    let mut routed = vec![0.0f32; b * slots * dim];
5677    match pool {
5678        Some(p) if active.len() > 1 => {
5679            let results: Vec<std::sync::Mutex<Vec<f32>>> = active
5680                .iter()
5681                .map(|_| std::sync::Mutex::new(Vec::new()))
5682                .collect();
5683            let active_ref = &active;
5684            let results_ref = &results;
5685            let fwd = &expert_fwd;
5686            p.run_rows(active.len(), &move |s, e| {
5687                for i in s..e {
5688                    let (ei, jobs) = &active_ref[i];
5689                    let d = crate::gpu::cpu_scope(|| fwd(*ei, jobs, None));
5690                    *results_ref[i].lock().unwrap() = d;
5691                }
5692            });
5693            for (i, (_, jobs)) in active.iter().enumerate() {
5694                let down = results[i].lock().unwrap();
5695                for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5696                    routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5697                        .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5698                }
5699            }
5700        }
5701        _ => {
5702            for (ei, jobs) in &active {
5703                let down = expert_fwd(*ei, jobs, pool);
5704                for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
5705                    routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim]
5706                        .copy_from_slice(&down[j * dim..(j + 1) * dim]);
5707                }
5708            }
5709        }
5710    }
5711    for bi in 0..b {
5712        let dst = &mut out[bi * dim..(bi + 1) * dim];
5713        for slot in 0..cold[bi].len() {
5714            let src = &routed[(bi * slots + slot) * dim..(bi * slots + slot + 1) * dim];
5715            for (o, &v) in dst.iter_mut().zip(src) {
5716                *o += v;
5717            }
5718        }
5719    }
5720}
5721
5722/// Feed the exact route of the speculative batch's guaranteed-accepted first
5723/// token back into the same FreeToken-style live slots ordinary decode uses.
5724/// Rejected draft suffixes must not train or pollute the LRU, so the caller
5725/// deliberately passes only row zero.
5726#[cfg(feature = "gpu")]
5727fn refill_route_slots(l: &Dsv4Layer, cfg: &Dsv4Cfg, pk: &Pack, picks: &[usize]) {
5728    if picks.is_empty() || pk.route_complete() {
5729        return;
5730    }
5731    let quota = {
5732        static Q: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
5733        *Q.get_or_init(|| {
5734            std::env::var("CMF_DSV4_FETCH_MAX")
5735                .ok()
5736                .and_then(|v| v.parse().ok())
5737                .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().0)
5738        })
5739    };
5740    if quota == 0 {
5741        return;
5742    }
5743    let min_seen = {
5744        static M: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
5745        *M.get_or_init(|| {
5746            std::env::var("CMF_DSV4_FETCH_MIN_SEEN")
5747                .ok()
5748                .and_then(|v| v.parse().ok())
5749                .unwrap_or_else(|| crate::gpu_wgpu::dsv4_fetch_defaults().1)
5750        })
5751    };
5752    let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
5753        return;
5754    };
5755    let mut dynv = pk.dynslots.lock().unwrap();
5756    if dynv.owner.is_empty() {
5757        return;
5758    }
5759    dynv.clock += 1;
5760    let clock = dynv.clock;
5761    if clock % 64 == 0 {
5762        for seen in &mut dynv.seen {
5763            *seen >>= 1;
5764        }
5765    }
5766    for &pick in picks {
5767        if pick >= dynv.seen.len() {
5768            continue;
5769        }
5770        dynv.seen[pick] = dynv.seen[pick].saturating_add(1);
5771        let slot = dynv.remap[pick];
5772        if slot != u32::MAX {
5773            dynv.last[slot as usize] = clock;
5774        }
5775    }
5776    let pack_first = pk.tensors.first().map(|t| t.0).unwrap_or(usize::MAX);
5777    let mut fetched = 0usize;
5778    for &pick in picks {
5779        if fetched >= quota || pick >= dynv.remap.len() {
5780            break;
5781        }
5782        if dynv.remap[pick] != u32::MAX || dynv.seen[pick] < min_seen {
5783            continue;
5784        }
5785        let victim = (0..dynv.owner.len())
5786            .filter(|&slot| dynv.last[slot] != clock)
5787            .min_by_key(|&slot| dynv.last[slot]);
5788        let Some(victim) = victim else { break };
5789        let Some(exp) = l.experts.get(pick) else {
5790            continue;
5791        };
5792        let tensors = (|| {
5793            Some((
5794                exp.w1.model_idx()?,
5795                exp.w3.model_idx()?,
5796                exp.w2.model_idx()?,
5797            ))
5798        })();
5799        let Some(tensors) = tensors else { continue };
5800        let gu_q2 = exp.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
5801        if !crate::gpu_wgpu::dsv4_slot_fill(
5802            &model,
5803            pack_first,
5804            victim,
5805            pick,
5806            tensors,
5807            cfg.moe_inter,
5808            cfg.dim,
5809            gu_q2,
5810        ) {
5811            break;
5812        }
5813        let old = dynv.owner[victim] as usize;
5814        if old < dynv.remap.len() {
5815            dynv.remap[old] = u32::MAX;
5816        }
5817        dynv.remap[pick] = victim as u32;
5818        dynv.owner[victim] = pick as u32;
5819        dynv.last[victim] = clock;
5820        dynv.mutated = true;
5821        fetched += 1;
5822    }
5823}
5824
5825/// Grouped output projection for a block. `wo_a` cannot use a plain matmat
5826/// because each group sees a different attention slice; reading a quantized
5827/// row once and applying it to every block position gives the same dot order
5828/// without rereading/dequantizing that row B times.
5829fn o_project_block(
5830    attn: &[f32],
5831    b: usize,
5832    wo_a: &crate::qtensor::QTensor,
5833    wo_b: &crate::qtensor::QTensor,
5834    groups: usize,
5835    lora: usize,
5836    pool: Option<&crate::pool::Pool>,
5837    out: &mut [f32],
5838) {
5839    let attn_len = attn.len() / b;
5840    let per_group = attn_len / groups;
5841    let rows = groups * lora;
5842    let mut mid = vec![0.0f32; b * rows];
5843    let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
5844    let run = |start: usize, end: usize| {
5845        let mut wr = vec![0.0f32; wo_a.cols()];
5846        for r in start..end {
5847            wo_a.row_f32(r, &mut wr);
5848            let group = r / lora;
5849            for bi in 0..b {
5850                let x = &attn
5851                    [bi * attn_len + group * per_group..bi * attn_len + (group + 1) * per_group];
5852                let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
5853                unsafe { *mid_addr.at(bi * rows + r) = v };
5854            }
5855        }
5856    };
5857    match pool {
5858        Some(p) if rows >= 256 => p.run_rows(rows, &run),
5859        _ => run(0, rows),
5860    }
5861    wo_b.matmat(&mid, b, out, pool);
5862}
5863
5864/// `CMF_DSV4_TRACE=1` prints the hidden state's RMS after each half-block and
5865/// the logits' shape at the end. A 300B model that decodes nonsense gives no
5866/// other handle: this says whether the state grew, collapsed or went
5867/// non-finite, and at which layer — before anyone reaches for a debugger on a
5868/// hundred-gigabyte file.
5869fn no_compressed() -> bool {
5870    static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5871    *OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
5872}
5873
5874fn trace_on() -> bool {
5875    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5876    *ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
5877}
5878
5879fn rms_of(v: &[f32]) -> f32 {
5880    if v.is_empty() {
5881        return 0.0;
5882    }
5883    (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
5884}
5885
5886#[cfg(feature = "gpu")]
5887fn verify_fp_on(pos: usize) -> bool {
5888    static POS: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
5889    let want = *POS.get_or_init(|| {
5890        std::env::var("CMF_DSV4_FP_POS")
5891            .ok()
5892            .and_then(|v| v.parse().ok())
5893    });
5894    want == Some(pos)
5895}
5896
5897#[cfg(feature = "gpu")]
5898fn verify_fp(tag: &str, pos: usize, li: usize, state: &[f32]) {
5899    if !verify_fp_on(pos) {
5900        return;
5901    }
5902    let mut fp = 0xcbf29ce484222325u64;
5903    for &x in state {
5904        fp ^= x.to_bits() as u64;
5905        fp = fp.wrapping_mul(0x100000001b3);
5906    }
5907    eprintln!(
5908        "[dsv4-fp] {tag} pos={pos} li={li} fp={fp:016x} rms={:.7} head={:?}",
5909        rms_of(state),
5910        &state[..4.min(state.len())]
5911    );
5912}
5913
5914/// `CMF_DSV4_DUMP=<path>` appends one JSON line per token: the embedding, the
5915/// hyper-connection state after every layer, the folded-and-normed head input
5916/// and the logits. It exists to be diffed against the reference forward on
5917/// the same weights — the numerical parity this port has never had, which at
5918/// toy scale is a few thousand floats and entirely tractable.
5919thread_local! {
5920    /// The attention body's input and output per layer, interleaved.
5921    static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
5922    /// Experts chosen per layer for the token being decoded — the dump needs
5923    /// them, because two implementations that pick DIFFERENT experts diverge
5924    /// hugely for a reason that is not a bug in either.
5925    static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
5926        const { std::cell::RefCell::new(Vec::new()) };
5927    /// (layer, chosen experts) in call order, when armed.
5928    static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
5929        const { std::cell::RefCell::new(None) };
5930}
5931
5932/// Start recording expert picks. Idempotent; the previous tally is dropped.
5933pub fn pick_tally_arm() {
5934    PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
5935}
5936
5937/// Take what was recorded and stop recording.
5938pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
5939    PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
5940}
5941
5942/// How many distinct experts a set of per-token pick lists reaches, and how
5943/// many picks it makes. The ratio is what a batched MoE can hope to save.
5944pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
5945    // Keyed by (layer, expert). Expert 17 of layer 3 and expert 17 of layer 4
5946    // are different weights, and counting them as one understated the traffic
5947    // a batch has to read — badly for the draft, whose three stages each have
5948    // their own 256.
5949    let mut seen = std::collections::HashSet::new();
5950    let mut total = 0;
5951    for (li, v) in picks {
5952        total += v.len();
5953        for &e in v {
5954            seen.insert((*li, e));
5955        }
5956    }
5957    (seen.len(), total)
5958}
5959
5960fn dump_path() -> Option<&'static str> {
5961    static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
5962    P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
5963        .as_deref()
5964}
5965
5966fn dump_line(json: &str) {
5967    if let Some(p) = dump_path() {
5968        use std::io::Write as _;
5969        if let Ok(mut f) = std::fs::OpenOptions::new()
5970            .create(true)
5971            .append(true)
5972            .open(p)
5973        {
5974            let _ = writeln!(f, "{json}");
5975        }
5976    }
5977}
5978
5979fn vec_json(v: &[f32]) -> String {
5980    let mut s = String::with_capacity(v.len() * 9);
5981    s.push('[');
5982    for (i, x) in v.iter().enumerate() {
5983        if i > 0 {
5984            s.push(',');
5985        }
5986        s.push_str(&format!("{x:.6e}"));
5987    }
5988    s.push(']');
5989    s
5990}
5991
5992/// One token through the whole stack.
5993///
5994/// The hidden state is `hc_mult` copies of a `dim`-vector from the very
5995/// first line to the very last: the embedding is replicated, every layer
5996/// folds/expands around its two halves, and only `hc_head_fold` collapses
5997/// it before the output norm and the head. There is no point in this
5998/// function where an ordinary residual would fit.
5999#[allow(clippy::too_many_arguments)]
6000/// A chunk of prompt tokens. Stage one of the batched prefill (see
6001/// docs/DSV4_PREFILL.md): the walk itself, with the head skipped for every
6002/// token but the last.
6003///
6004/// Prefill costs `len × per-token` today, and on a 2500-token prompt that is
6005/// a minute and a half before the first word. The stages that follow batch
6006/// the weight reads — which is where the nine-fold gap to the bandwidth
6007/// floor lives — but this one is the scaffolding they hang on, and it
6008/// already stops computing 129 280 logits for tokens nobody asks about.
6009#[allow(clippy::too_many_arguments)]
6010/// `CMF_DSV4_BATCH=N` — how many prompt tokens go through the card in one
6011/// submission. 1 keeps the walk. The chunk still bounds it: a batch never
6012/// spans two chunks, so cancellation stays as responsive as it was.
6013fn batch_prefill() -> usize {
6014    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
6015    *N.get_or_init(|| {
6016        std::env::var("CMF_DSV4_BATCH")
6017            .ok()
6018            .and_then(|v| v.parse::<usize>().ok())
6019            .filter(|&n| (1..=32).contains(&n))
6020            // A partial expert pack used to make the device-chain batch
6021            // decline, so the conservative default was one.  The exact host
6022            // batch below now handles that geometry: routing still spans all
6023            // experts and every cold winner is computed from the mmap-backed
6024            // checkpoint.  Eight amortises expert reads without making a long
6025            // prompt's cancellation granularity coarse.
6026            .unwrap_or(8)
6027    })
6028}
6029
6030/// The prompt as batches instead of a walk, when every layer will take one.
6031///
6032/// Refuses before touching any state, never half way: the caller's fallback
6033/// is the per-token walk, and a batch that advanced the caches and then gave
6034/// up would have them advanced twice. So everything that can decline is asked
6035/// first, and after the first dispatch the only outcomes are success and a
6036/// hard failure.
6037///
6038/// Hash layers are the one shape it cannot take: their expert list is forced
6039/// by the TOKEN's id and the layer description carries one list, not one per
6040/// token. The release has three of them (0, 1, 2); a file without them
6041/// batches the whole stack.
6042#[allow(clippy::too_many_arguments)]
6043fn forward_chunk_batched(
6044    g: &Dsv4Globals,
6045    layers: &[Dsv4Layer],
6046    cfg: &Dsv4Cfg,
6047    st: &mut Dsv4State,
6048    ids: &[u32],
6049    pos0: usize,
6050    inv_freq: &[f32],
6051    pool: Option<&crate::pool::Pool>,
6052    logits: &mut Vec<f32>,
6053    want_logits: bool,
6054) -> bool {
6055    #[cfg(not(feature = "gpu"))]
6056    {
6057        let _ = (
6058            g,
6059            layers,
6060            cfg,
6061            st,
6062            ids,
6063            pos0,
6064            inv_freq,
6065            pool,
6066            logits,
6067            want_logits,
6068        );
6069        false
6070    }
6071    #[cfg(feature = "gpu")]
6072    {
6073        let b = ids.len();
6074        // Complete packs form the fused device prefix.  Partial packs belong
6075        // to the exact causal tail: that tail routes over all experts and
6076        // completes cold winners, so gpu_end == 0 is a useful (and common on
6077        // smaller cards) batch rather than a reason to walk token by token.
6078        let gpu_end = st
6079            .dev_set
6080            .iter()
6081            .enumerate()
6082            .position(|(li, &on)| {
6083                !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6084            })
6085            .unwrap_or(st.dev_set.len());
6086        let why = if b < 2 {
6087            "fewer than two tokens"
6088        } else if !chain_enabled() {
6089            "the chain is off"
6090        } else if !st.dev_owned {
6091            "the card does not own the state yet"
6092        } else if st.dev_set.len() != layers.len() {
6093            "the layer set is not fixed yet"
6094        } else if st.dev_set[gpu_end.min(st.dev_set.len())..]
6095            .iter()
6096            .enumerate()
6097            .any(|(i, &on)| on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false))
6098        {
6099            "the layers on the card do not form a prefix"
6100        } else {
6101            ""
6102        };
6103        if !why.is_empty() {
6104            // This is an expected preflight refusal: the first prompt chunk
6105            // establishes device ownership and a one-token tail is too small
6106            // to batch.  The caller immediately takes the exact walk, so a
6107            // warning here looked like a generation failure when nothing had
6108            // failed. Keep the reason available only to the diagnostic gate.
6109            tracing::debug!("dsv4: batch preflight — {why}");
6110            if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
6111                eprintln!("dsv4: batch preflight — {why}");
6112            }
6113            return false;
6114        }
6115        let (hc, dim) = (cfg.hc_mult, cfg.dim);
6116        let mut emb = vec![0.0f32; dim];
6117        let mut states = vec![0.0f32; b * hc * dim];
6118        for (t, &id) in ids.iter().enumerate() {
6119            let mut state = vec![0.0f32; hc * dim];
6120            g.embed.row_f32(id as usize, &mut emb);
6121            for j in 0..hc {
6122                state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6123            }
6124            let (folded, post0, comb0) = hc_fold_norm(
6125                &state,
6126                &layers[0].hc_attn_fn,
6127                &layers[0].hc_attn_scale,
6128                &layers[0].hc_attn_base,
6129                &layers[0].attn_norm,
6130                cfg,
6131                pool,
6132            );
6133            let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6134            layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6135            rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6136            if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6137                || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6138                || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6139                || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6140            {
6141                return false;
6142            }
6143            states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6144        }
6145        let run: Vec<usize> = (0..gpu_end).collect();
6146        let mut folded = Vec::new();
6147        st.pos = pos0;
6148        if gpu_end > 0 {
6149            if !dsv4_chain_run(
6150                layers,
6151                &run,
6152                cfg,
6153                g,
6154                st,
6155                *ids.last().unwrap(),
6156                &mut folded,
6157                Some(&mut states),
6158                b,
6159                ids,
6160                true,
6161                pool,
6162            ) {
6163                return false;
6164            }
6165        }
6166        // Finish the trailing host layers in causal token order. Their KV
6167        // caches are host-owned, while the device prefix advanced its own
6168        // caches inside the one submission above. On the release this loop
6169        // is exactly layer 42; keeping it general makes smaller VRAM budgets
6170        // correct as long as the resident layers remain one prefix.
6171        let mut scratch = HcScratch::new(cfg);
6172        host_tail_walk_batch(
6173            g,
6174            layers,
6175            cfg,
6176            st,
6177            gpu_end,
6178            &mut states,
6179            ids,
6180            pos0,
6181            b,
6182            inv_freq,
6183            &mut scratch,
6184            pool,
6185            None,
6186        );
6187        st.pos = pos0 + b;
6188        // Said once. A gate that compares a batched prompt against a walked
6189        // one proves nothing if the batch quietly declined — the numbers match
6190        // because the same code produced both. This line is what tells the
6191        // two apart.
6192        {
6193            static SAID: std::sync::Once = std::sync::Once::new();
6194            SAID.call_once(|| tracing::warn!("dsv4: prefill in batches of {b}"));
6195        }
6196        // Only the last token's logits are read; the rest of the chunk exists
6197        // to fill the caches. The head consumes the hyper-connection state,
6198        // not the chain's intermediate fold — skipping this final learned
6199        // fold used to make a full-device batch fast and wrong.
6200        if want_logits {
6201            let last = &states[(b - 1) * hc * dim..b * hc * dim];
6202            let mut h = vec![0.0f32; dim];
6203            hc_head_fold(
6204                last,
6205                &g.hc_head_fn,
6206                g.hc_head_scale,
6207                &g.hc_head_base,
6208                cfg,
6209                pool,
6210                &mut h,
6211            );
6212            rms_weighted(&mut h, &g.norm, cfg.norm_eps);
6213            logits.resize(cfg.vocab, 0.0);
6214            g.head.matvec(&h, logits, pool);
6215        } else {
6216            logits.clear();
6217        }
6218        true
6219    }
6220}
6221
6222/// Everything a speculative verify must be able to put back.
6223///
6224/// Device caches roll back by restore-then-replay: the shadow puts the
6225/// window rings and compressor streams where they were BEFORE the pass, and
6226/// the replay re-appends the accepted tokens' state from the hidden inputs
6227/// the pass retained. Append-only regions roll back by count. Host-owned
6228/// tail layers roll back by clone-and-rewalk.
6229#[cfg(feature = "gpu")]
6230pub struct Dsv4SpecTxn {
6231    pos0: usize,
6232    batch: usize,
6233    pub(crate) gpu_end: usize,
6234    dev_filled: Vec<usize>,
6235    dev_n_comp: Vec<usize>,
6236    dev_n_ix: Vec<usize>,
6237    host: Vec<(usize, HostLayerSnap)>,
6238    /// Per host layer, per verified token: the layer's state right after
6239    /// that token's attention — what a rollback restores INSTEAD of
6240    /// re-walking the tail it already walked (the values are identical;
6241    /// only the side effects were ever needed).
6242    host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
6243    /// Every token's hyper-connection state as it left the device prefix,
6244    /// BEFORE the host tail walked (and mutated) anything: the rewalk's
6245    /// input, and the head's.
6246    pub states: Vec<f32>,
6247    shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
6248}
6249
6250#[cfg(feature = "gpu")]
6251struct HostLayerSnap {
6252    window: Vec<f32>,
6253    compressed: Vec<f32>,
6254    index_kv: Vec<f32>,
6255    pending_kv: Vec<f32>,
6256    pending_score: Vec<f32>,
6257    prev_kv: Vec<f32>,
6258    prev_score: Vec<f32>,
6259    pending_ix_kv: Vec<f32>,
6260    pending_ix_score: Vec<f32>,
6261    prev_ix_kv: Vec<f32>,
6262    prev_ix_score: Vec<f32>,
6263}
6264
6265#[cfg(feature = "gpu")]
6266fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
6267    HostLayerSnap {
6268        window: st.window[li].clone(),
6269        compressed: st.compressed[li].clone(),
6270        index_kv: st.index_kv[li].clone(),
6271        pending_kv: st.pending_kv[li].clone(),
6272        pending_score: st.pending_score[li].clone(),
6273        prev_kv: st.prev_kv[li].clone(),
6274        prev_score: st.prev_score[li].clone(),
6275        pending_ix_kv: st.pending_ix_kv[li].clone(),
6276        pending_ix_score: st.pending_ix_score[li].clone(),
6277        prev_ix_kv: st.prev_ix_kv[li].clone(),
6278        prev_ix_score: st.prev_ix_score[li].clone(),
6279    }
6280}
6281
6282#[cfg(feature = "gpu")]
6283fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
6284    st.window[li] = s.window.clone();
6285    st.compressed[li] = s.compressed.clone();
6286    st.index_kv[li] = s.index_kv.clone();
6287    st.pending_kv[li] = s.pending_kv.clone();
6288    st.pending_score[li] = s.pending_score.clone();
6289    st.prev_kv[li] = s.prev_kv.clone();
6290    st.prev_score[li] = s.prev_score.clone();
6291    st.pending_ix_kv[li] = s.pending_ix_kv.clone();
6292    st.pending_ix_score[li] = s.pending_ix_score.clone();
6293    st.prev_ix_kv[li] = s.prev_ix_kv.clone();
6294    st.prev_ix_score[li] = s.prev_ix_score.clone();
6295}
6296
6297/// One host-tail walk of token `t`'s state through layers `gpu_end..`,
6298/// mutating `state` in place and the layers' host caches. Exactly the loop
6299/// the batch runs, factored so the verify can re-run it for accepted tokens.
6300#[cfg(feature = "gpu")]
6301#[allow(clippy::too_many_arguments)]
6302fn host_tail_walk(
6303    g: &Dsv4Globals,
6304    layers: &[Dsv4Layer],
6305    cfg: &Dsv4Cfg,
6306    st: &mut Dsv4State,
6307    gpu_end: usize,
6308    state: &mut [f32],
6309    token_id: u32,
6310    pos: usize,
6311    inv_freq: &[f32],
6312    scratch: &mut HcScratch,
6313    pool: Option<&crate::pool::Pool>,
6314) {
6315    st.pos = pos;
6316    for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6317        let freqs = if l.compressor.is_some() {
6318            &g.inv_freq_compress
6319        } else {
6320            &g.inv_freq_window
6321        };
6322        let freqs = if freqs.is_empty() {
6323            inv_freq
6324        } else {
6325            freqs.as_slice()
6326        };
6327        hc_block(
6328            state,
6329            &l.hc_attn_fn,
6330            &l.hc_attn_scale,
6331            &l.hc_attn_base,
6332            &l.attn_norm,
6333            cfg,
6334            scratch,
6335            pool,
6336            |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6337        );
6338        hc_block(
6339            state,
6340            &l.hc_ffn_fn,
6341            &l.hc_ffn_scale,
6342            &l.hc_ffn_base,
6343            &l.ffn_norm,
6344            cfg,
6345            scratch,
6346            pool,
6347            |f, o| {
6348                if host_cpu_moe() {
6349                    crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
6350                } else {
6351                    moe_step(f, l, cfg, token_id, li, pool, o)
6352                }
6353            },
6354        );
6355        dspark_note(li, state, cfg);
6356    }
6357}
6358
6359/// The host tail for a whole batch: attention stays causal per token (its
6360/// window mutates), the MoE half runs through the block-grouped path — the
6361/// same accumulation order as the position walk, which the block tests pin
6362/// bit for bit. This is the verify's tail; the single-token paths keep
6363/// `hc_block`.
6364#[cfg(feature = "gpu")]
6365#[allow(clippy::too_many_arguments)]
6366fn host_tail_walk_batch(
6367    g: &Dsv4Globals,
6368    layers: &[Dsv4Layer],
6369    cfg: &Dsv4Cfg,
6370    st: &mut Dsv4State,
6371    gpu_end: usize,
6372    states: &mut [f32],
6373    ids: &[u32],
6374    pos0: usize,
6375    b: usize,
6376    inv_freq: &[f32],
6377    scratch: &mut HcScratch,
6378    pool: Option<&crate::pool::Pool>,
6379    mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
6380) {
6381    let (hc, dim) = (cfg.hc_mult, cfg.dim);
6382    let mix_hc = (2 + hc) * hc;
6383    let mut folds = vec![0.0f32; b * dim];
6384    let mut mo = vec![0.0f32; b * dim];
6385    let mut posts = vec![0.0f32; b * hc];
6386    let mut combs = vec![0.0f32; b * hc * hc];
6387    let mut resid = vec![0.0f32; b * hc * dim];
6388    let spec_time = {
6389        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6390        *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
6391    };
6392    for (li, l) in layers.iter().enumerate().skip(gpu_end) {
6393        let t_attn = std::time::Instant::now();
6394        let freqs = if l.compressor.is_some() {
6395            &g.inv_freq_compress
6396        } else {
6397            &g.inv_freq_window
6398        };
6399        let freqs = if freqs.is_empty() {
6400            inv_freq
6401        } else {
6402            freqs.as_slice()
6403        };
6404        for t in 0..b {
6405            st.pos = pos0 + t;
6406            let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6407            hc_block(
6408                state,
6409                &l.hc_attn_fn,
6410                &l.hc_attn_scale,
6411                &l.hc_attn_base,
6412                &l.attn_norm,
6413                cfg,
6414                scratch,
6415                pool,
6416                |f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
6417            );
6418            if let Some(steps) = steps.as_mut() {
6419                match steps.iter_mut().find(|(l, _)| *l == li) {
6420                    Some((_, v)) => v.push(host_snap(st, li)),
6421                    None => steps.push((li, vec![host_snap(st, li)])),
6422                }
6423            }
6424        }
6425        let t_glue = std::time::Instant::now();
6426        for t in 0..b {
6427            let state = &states[t * hc * dim..(t + 1) * hc * dim];
6428            hc_mixes(
6429                state,
6430                &l.hc_ffn_fn,
6431                mix_hc,
6432                cfg.norm_eps,
6433                pool,
6434                &mut scratch.mixes,
6435            );
6436            hc_split_sinkhorn(
6437                &scratch.mixes,
6438                &l.hc_ffn_scale,
6439                &l.hc_ffn_base,
6440                hc,
6441                cfg.hc_sinkhorn_iters,
6442                cfg.hc_eps,
6443                &mut scratch.pre,
6444                &mut posts[t * hc..(t + 1) * hc],
6445                &mut combs[t * hc * hc..(t + 1) * hc * hc],
6446            );
6447            let fold = &mut folds[t * dim..(t + 1) * dim];
6448            hc_fold(state, &scratch.pre, hc, dim, fold);
6449            let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
6450            let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
6451            for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
6452                *v = *v * inv * w;
6453            }
6454            resid[t * hc * dim..(t + 1) * hc * dim]
6455                .copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
6456        }
6457        let t_moe = std::time::Instant::now();
6458        // A tail layer with a device expert pack (partial or full) runs its
6459        // hot winners on the card per token and completes the cold ones on
6460        // the host — the same exact split the partial walk uses. Default on
6461        // (measured: the tail fell 27.4 → 18.2 ms of the verify round);
6462        // `CMF_DSV4_TAIL_PACK=0` restores the batched host block.
6463        let tail_pack = {
6464            static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6465            *ON.get_or_init(|| {
6466                std::env::var("CMF_DSV4_TAIL_PACK")
6467                    .map(|v| v != "0")
6468                    .unwrap_or(true)
6469            })
6470        };
6471        let mut packed_done = false;
6472        if tail_pack && pack_for(l, cfg, li).is_some() {
6473            packed_done = true;
6474            for t in 0..b {
6475                let f = &folds[t * dim..(t + 1) * dim];
6476                let forced = l.tid2eid.as_ref().map(|tbl| {
6477                    hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
6478                });
6479                let o = &mut mo[t * dim..(t + 1) * dim];
6480                match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
6481                    Some((cold_sum, n)) => {
6482                        if n > 0 {
6483                            for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
6484                                *od += cd;
6485                            }
6486                        }
6487                    }
6488                    None => {
6489                        packed_done = false;
6490                        break;
6491                    }
6492                }
6493            }
6494        }
6495        if !packed_done {
6496            if host_cpu_moe() {
6497                crate::gpu::cpu_scope(|| moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo));
6498            } else {
6499                moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
6500            }
6501        }
6502        let t_exp = std::time::Instant::now();
6503        for t in 0..b {
6504            let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
6505            hc_expand(
6506                &mo[t * dim..(t + 1) * dim],
6507                &resid[t * hc * dim..(t + 1) * hc * dim],
6508                &posts[t * hc..(t + 1) * hc],
6509                &combs[t * hc * hc..(t + 1) * hc * hc],
6510                hc,
6511                dim,
6512                state,
6513            );
6514            dspark_note(li, state, cfg);
6515        }
6516        if spec_time {
6517            eprintln!(
6518                "layer {li} tail: attn {:.1} ms, glue {:.1}, moe {:.1}, expand {:.1}",
6519                (t_glue - t_attn).as_secs_f64() * 1e3,
6520                (t_moe - t_glue).as_secs_f64() * 1e3,
6521                (t_exp - t_moe).as_secs_f64() * 1e3,
6522                t_exp.elapsed().as_secs_f64() * 1e3,
6523            );
6524        }
6525    }
6526}
6527
6528/// One exact token-axis layer over a partial expert pack.  The device runs
6529/// attention, routing over all experts, the resident MoE rows and the
6530/// hyper-connection join once for the whole batch.  Cold winners are grouped
6531/// by expert on the host, corrected into the returned state, and only then is
6532/// the next dependent layer allowed to start.
6533#[cfg(feature = "gpu")]
6534#[allow(clippy::too_many_arguments)]
6535fn partial_layer_batch(
6536    g: &Dsv4Globals,
6537    layers: &[Dsv4Layer],
6538    cfg: &Dsv4Cfg,
6539    st: &mut Dsv4State,
6540    li: usize,
6541    states: &mut [f32],
6542    ids: &[u32],
6543    pos0: usize,
6544    b: usize,
6545    pool: Option<&crate::pool::Pool>,
6546) -> bool {
6547    let l = &layers[li];
6548    let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6549    if states.len() < b * hc * dim || ids.len() < b {
6550        return false;
6551    }
6552    let Some(pk) = pack_for(l, cfg, li) else {
6553        return false;
6554    };
6555    if pk.route_complete() {
6556        return false;
6557    }
6558    let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
6559        return false;
6560    };
6561    let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
6562        l.wq_a.model_idx(),
6563        l.wq_b.model_idx(),
6564        l.wo_a.model_idx(),
6565        l.wo_b.model_idx(),
6566        l.wkv.model_idx(),
6567    ) else {
6568        return false;
6569    };
6570    let comp = match &l.compressor {
6571        None => None,
6572        Some(cp) => {
6573            let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
6574                return false;
6575            };
6576            Some((
6577                crate::gpu_wgpu::Dsv4CompW {
6578                    wkv: a,
6579                    wgate: bx,
6580                    norm: &cp.norm,
6581                    ape: &cp.ape,
6582                },
6583                crate::gpu_wgpu::Dsv4CompGeom {
6584                    width: cp.wkv.rows(),
6585                    hidden: dim,
6586                    ratio: cp.ratio,
6587                    overlap: cp.overlap,
6588                    rope_dim: cfg.rope_head_dim,
6589                    eps: cfg.norm_eps,
6590                },
6591            ))
6592        }
6593    };
6594    let ix = match &l.indexer {
6595        None => None,
6596        Some(ixr) => {
6597            let cp = &ixr.compressor;
6598            let (Some(a), Some(bx), Some(qb), Some(wp)) = (
6599                cp.wkv.model_idx(),
6600                cp.wgate.model_idx(),
6601                ixr.wq_b.model_idx(),
6602                ixr.weights_proj.model_idx(),
6603            ) else {
6604                return false;
6605            };
6606            let ih = ixr.weights_proj.rows();
6607            Some((
6608                crate::gpu_wgpu::Dsv4CompW {
6609                    wkv: a,
6610                    wgate: bx,
6611                    norm: &cp.norm,
6612                    ape: &cp.ape,
6613                },
6614                crate::gpu_wgpu::Dsv4CompGeom {
6615                    width: cp.wkv.rows(),
6616                    hidden: dim,
6617                    ratio: cp.ratio,
6618                    overlap: cp.overlap,
6619                    rope_dim: cfg.rope_head_dim,
6620                    eps: cfg.norm_eps,
6621                },
6622                crate::gpu_wgpu::Dsv4IxW {
6623                    wq_b: qb,
6624                    weights_proj: wp,
6625                },
6626                crate::gpu_wgpu::Dsv4IxGeom {
6627                    ih,
6628                    idim: ixr.wq_b.rows() / ih.max(1),
6629                    q_lora: cfg.q_lora_rank,
6630                    hidden: dim,
6631                    rope_dim: cfg.rope_head_dim,
6632                    eps: cfg.norm_eps,
6633                    top_k: cfg.index_topk,
6634                    window: cfg.window,
6635                },
6636            ))
6637        }
6638    };
6639    let ew_c = comp.as_ref().map_or(
6640        0,
6641        |(_, cg)| {
6642            if cg.overlap { cg.width / 2 } else { cg.width }
6643        },
6644    );
6645    let ew_i = ix.as_ref().map_or(
6646        0,
6647        |(_, cg, _, _)| {
6648            if cg.overlap { cg.width / 2 } else { cg.width }
6649        },
6650    );
6651    let comp_extra = comp
6652        .as_ref()
6653        .map_or(0, |(_, cg)| b.div_ceil(cg.ratio.max(1)));
6654    let need = cfg.window * hd + (st.dev_n_comp[li] + comp_extra + 1) * ew_c.max(1) + (b + 1) * hd;
6655    if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
6656        return false;
6657    }
6658    let base = crate::gpu_wgpu::Dsv4Prep {
6659        wkv,
6660        kv_norm: &l.kv_norm,
6661        comp,
6662        ix,
6663        filled: st.dev_filled[li],
6664        window: cfg.window,
6665        n_comp: st.dev_n_comp[li],
6666        n_ix: st.dev_n_ix[li],
6667        comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
6668        ix_dst_off: st.dev_n_ix[li] * ew_i,
6669        idx_cap: cfg.window
6670            + if l.indexer.is_some() {
6671                cfg.index_topk
6672            } else {
6673                st.dev_n_comp[li] + comp_extra + 1
6674            },
6675    };
6676    let mut preps = Vec::with_capacity(b);
6677    for t in 0..b {
6678        let mut p = base.clone();
6679        p.filled = (base.filled + t).min(base.window);
6680        let advanced = |ratio: usize| -> usize {
6681            if ratio == 0 {
6682                0
6683            } else {
6684                (0..t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
6685            }
6686        };
6687        if let Some((_, cg)) = base.comp.as_ref() {
6688            p.n_comp = base.n_comp + advanced(cg.ratio);
6689            p.comp_dst_off = base.comp_dst_off + (p.n_comp - base.n_comp) * ew_c;
6690        }
6691        if let Some((_, cg, _, _)) = base.ix.as_ref() {
6692            p.n_ix = base.n_ix + advanced(cg.ratio);
6693            p.ix_dst_off = base.ix_dst_off + (p.n_ix - base.n_ix) * ew_i;
6694        }
6695        preps.push(p);
6696    }
6697
6698    // Every row enters with an exact host state because the previous partial
6699    // layer was corrected before returning.  Seed the token-axis slots and
6700    // the q-LoRA vector the batched attention consumes.
6701    for t in 0..b {
6702        let state = &states[t * hc * dim..(t + 1) * hc * dim];
6703        let (fold, post, comb) = hc_fold_norm(
6704            state,
6705            &l.hc_attn_fn,
6706            &l.hc_attn_scale,
6707            &l.hc_attn_base,
6708            &l.attn_norm,
6709            cfg,
6710            pool,
6711        );
6712        let mut qn = vec![0.0f32; cfg.q_lora_rank];
6713        l.wq_a.matvec(&fold, &mut qn, pool);
6714        rms_weighted(&mut qn, &l.q_norm, cfg.norm_eps);
6715        if !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, state, &post, &comb, &fold, &qn) {
6716            return false;
6717        }
6718    }
6719    let forced_rows: Vec<Option<Vec<usize>>> = ids
6720        .iter()
6721        .take(b)
6722        .map(|&id| {
6723            l.tid2eid
6724                .as_ref()
6725                .map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, id))
6726        })
6727        .collect();
6728    let dynv = pk.dynslots.lock().unwrap();
6729    let w = crate::gpu_wgpu::Dsv4LayerW {
6730        attn: crate::gpu_wgpu::Dsv4AttnW {
6731            wq_a,
6732            wq_b,
6733            wo_a,
6734            wo_b,
6735            q_norm: &l.q_norm,
6736            sink: &l.attn_sink,
6737        },
6738        moe: crate::gpu_wgpu::Dsv4MoeW {
6739            router: &[],
6740            experts: &pk.tensors,
6741            logits: &[],
6742            bias: pk.bias.as_deref(),
6743            mask: pk.mask.as_deref(),
6744            forced: None,
6745            remap: Some(&dynv.remap),
6746            global: None,
6747            has_shared: true,
6748            shared_weight: 1.0,
6749            preweighted: false,
6750            qwen_softmax: false,
6751        },
6752        hc_ffn_fn: &l.hc_ffn_fn,
6753        hc_ffn_scale: &l.hc_ffn_scale,
6754        hc_ffn_base: &l.hc_ffn_base,
6755        // Stop after this layer's state.  The next-layer fold must see the
6756        // cold-corrected state, not the resident-only state on the card.
6757        hc_next_fn: None,
6758        hc_next_scale: &l.hc_attn_scale,
6759        hc_next_base: &l.hc_attn_base,
6760        ffn_norm: &l.ffn_norm,
6761        next_norm: &l.attn_norm,
6762        next_q_norm: &l.q_norm,
6763        next_wq_a: None,
6764        router: &pk.router,
6765    };
6766    let geom = crate::gpu_wgpu::Dsv4LayerGeom {
6767        attn: crate::gpu_wgpu::Dsv4AttnGeom {
6768            dim,
6769            nh: cfg.n_heads,
6770            hd,
6771            rd: cfg.rope_head_dim,
6772            q_lora: cfg.q_lora_rank,
6773            o_lora: cfg.o_lora_rank,
6774            o_groups: cfg.o_groups,
6775            eps: cfg.norm_eps,
6776            scale: (hd as f32).powf(-0.5),
6777            bf16: false,
6778            q_rms: true,
6779            o_fp8: false,
6780        },
6781        moe: crate::gpu_wgpu::Dsv4MoeGeom {
6782            hidden: dim,
6783            inter: cfg.moe_inter,
6784            top_k: cfg.top_k,
6785            route_scale: cfg.route_scale,
6786            swiglu_limit: cfg.swiglu_limit,
6787            gu_q2: l
6788                .experts
6789                .first()
6790                .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
6791            bf16: false,
6792        },
6793        hc,
6794        hc_eps: cfg.hc_eps,
6795        sinkhorn_iters: cfg.hc_sinkhorn_iters,
6796    };
6797    let freqs = if l.compressor.is_some() {
6798        g.inv_freq_compress.as_slice()
6799    } else {
6800        g.inv_freq_window.as_slice()
6801    };
6802    let Some(mut got) = crate::gpu_wgpu::dsv4_layer_batch_partial(
6803        &model,
6804        &w,
6805        geom,
6806        st.kv_id,
6807        li,
6808        b,
6809        &preps,
6810        Some(&forced_rows),
6811        freqs,
6812        pos0,
6813    ) else {
6814        return false;
6815    };
6816    drop(dynv);
6817    let mut cold_sum = vec![0.0f32; b * dim];
6818    cold_step_block(&got.cold_x, b, l, cfg, &got.cold, pool, &mut cold_sum);
6819    for t in 0..b {
6820        let state = &mut got.states[t * hc * dim..(t + 1) * hc * dim];
6821        let post = &got.posts[t * hc..(t + 1) * hc];
6822        let cold = &cold_sum[t * dim..(t + 1) * dim];
6823        for j in 0..hc {
6824            for d in 0..dim {
6825                state[j * dim + d] += post[j] * cold[d];
6826            }
6827        }
6828    }
6829    states[..b * hc * dim].copy_from_slice(&got.states);
6830    if !crate::gpu_wgpu::dsv4_spec_cap_write_host(li, b, hc * dim, &got.states) {
6831        return false;
6832    }
6833    if let Some(first_route) = got.routed.first() {
6834        refill_route_slots(l, cfg, &pk, first_route);
6835    }
6836    for t in 0..b {
6837        let pos = pos0 + t;
6838        st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
6839        if let Some((_, cg, ..)) = base.ix.as_ref() {
6840            if (pos + 1) % cg.ratio == 0 {
6841                st.dev_n_ix[li] += 1;
6842            }
6843        }
6844        if let Some((_, cg)) = base.comp.as_ref() {
6845            if (pos + 1) % cg.ratio == 0 {
6846                st.dev_n_comp[li] += 1;
6847                note_compressed(st.kv_id, li, st.dev_n_comp[li]);
6848            }
6849        }
6850    }
6851    true
6852}
6853
6854/// A speculative verify pass: run `ids` (the committed next token followed
6855/// by draft proposals) at positions `pos0..pos0+B` through the trunk in one
6856/// batched submission, WITHOUT giving up the ability to roll back, and
6857/// return every position's greedy answer. The caller decides the accepted
6858/// prefix and calls [`dsv4_spec_finish`], which either keeps everything
6859/// (`accepted == B`) or restores-and-replays to the accepted length.
6860///
6861/// `logits_out` takes B rows of vocab logits, `argmax_out` their argmaxes.
6862#[cfg(feature = "gpu")]
6863#[allow(clippy::too_many_arguments)]
6864pub fn dsv4_verify_chunk(
6865    g: &Dsv4Globals,
6866    layers: &[Dsv4Layer],
6867    cfg: &Dsv4Cfg,
6868    st: &mut Dsv4State,
6869    ids: &[u32],
6870    pos0: usize,
6871    inv_freq: &[f32],
6872    pool: Option<&crate::pool::Pool>,
6873    cap_targets: &[usize],
6874    argmax_out: &mut Vec<u32>,
6875    logits_out: &mut Vec<f32>,
6876    walked_out: &mut Vec<f32>,
6877) -> Option<Dsv4SpecTxn> {
6878    let b = ids.len();
6879    // The complete prefix still runs as one fused chain.  Immediately after
6880    // it, contiguous PARTIAL packs can now stay on the device too: each one
6881    // is corrected with its cold experts before the next layer is seeded.
6882    let complete_end = st
6883        .dev_set
6884        .iter()
6885        .enumerate()
6886        .position(|(li, &on)| {
6887            !on || pack_for(&layers[li], cfg, li).is_none_or(|p| !p.route_complete())
6888        })
6889        .unwrap_or(st.dev_set.len());
6890    fn partial_batch_on() -> bool {
6891        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6892        *ON.get_or_init(|| {
6893            std::env::var("CMF_DSV4_PARTIAL_BATCH")
6894                .map(|v| v != "0")
6895                .unwrap_or(true)
6896        })
6897    }
6898    let device_end = if partial_batch_on() {
6899        (complete_end..layers.len())
6900            .take_while(|&li| {
6901                st.dev_set.get(li).copied().unwrap_or(false)
6902                    && st.partial_set.get(li).copied().unwrap_or(false)
6903                    && pack_for(&layers[li], cfg, li).is_some_and(|p| !p.route_complete())
6904            })
6905            .last()
6906            .map_or(complete_end, |li| li + 1)
6907    } else {
6908        complete_end
6909    };
6910    // A PARTIAL layer after a host gap is allowed to walk in the host tail.
6911    // A FULL device layer there would violate the contiguous-prefix contract.
6912    let full_beyond = st.dev_set[device_end.min(st.dev_set.len())..]
6913        .iter()
6914        .enumerate()
6915        .any(|(i, &on)| on && !st.partial_set.get(device_end + i).copied().unwrap_or(false));
6916    // `CMF_DSV4_HOST_VERIFY=1` lets the verify run with NO device prefix:
6917    // every layer walks in the host tail, batched — which is where a
6918    // many-core host amortises the weight read and the unpack across the
6919    // draft (the whole point of a batched verify). Off, a partial layer 0
6920    // (dynamic-slot packs) silently priced the entire speculation at zero:
6921    // 625 drafted, 0 verified, all cost and no candidate.
6922    fn host_verify_on() -> bool {
6923        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
6924        *ON.get_or_init(|| {
6925            std::env::var("CMF_DSV4_HOST_VERIFY")
6926                .map(|v| v != "0")
6927                // On a small card every layer can have a useful partial pack
6928                // while no layer has a complete one.  The exact batched tail
6929                // is specifically built for that shape; silently pricing
6930                // speculation at zero here defeated the automatic fast path.
6931                .unwrap_or(true)
6932        })
6933    }
6934    if b < 2
6935        || !chain_enabled()
6936        || !st.dev_owned
6937        || st.dev_set.len() != layers.len()
6938        || (device_end == 0 && !host_verify_on())
6939        || full_beyond
6940    {
6941        return None;
6942    }
6943    let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
6944    // ── the transaction ──
6945    let metas: Vec<(usize, usize, usize, usize)> = (0..device_end)
6946        .map(|li| (li, hd, cfg.window, st.dev_filled[li]))
6947        .collect();
6948    let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
6949    let mut txn = Dsv4SpecTxn {
6950        pos0,
6951        batch: b,
6952        gpu_end: device_end,
6953        dev_filled: st.dev_filled.clone(),
6954        dev_n_comp: st.dev_n_comp.clone(),
6955        dev_n_ix: st.dev_n_ix.clone(),
6956        host: (device_end..layers.len())
6957            .map(|li| (li, host_snap(st, li)))
6958            .collect(),
6959        states: Vec::new(),
6960        host_steps: Vec::new(),
6961        shadow: Some(shadow),
6962    };
6963    // The capture targets that live on the device: photograph their states.
6964    let dev_caps: Vec<usize> = cap_targets
6965        .iter()
6966        .copied()
6967        .filter(|&t| t < device_end)
6968        .collect();
6969    crate::gpu_wgpu::dsv4_spec_retain_arm(device_end, &dev_caps);
6970
6971    // ── seed and run the batch (the prefill batch's own shape) ──
6972    let mut emb = vec![0.0f32; dim];
6973    let mut states = vec![0.0f32; b * hc * dim];
6974    for (t, &id) in ids.iter().enumerate() {
6975        let mut state = vec![0.0f32; hc * dim];
6976        g.embed.row_f32(id as usize, &mut emb);
6977        for j in 0..hc {
6978            state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
6979        }
6980        states[t * hc * dim..(t + 1) * hc * dim].copy_from_slice(&state);
6981        let (folded, post0, comb0) = hc_fold_norm(
6982            &state,
6983            &layers[0].hc_attn_fn,
6984            &layers[0].hc_attn_scale,
6985            &layers[0].hc_attn_base,
6986            &layers[0].attn_norm,
6987            cfg,
6988            pool,
6989        );
6990        let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
6991        layers[0].wq_a.matvec(&folded, &mut qn0, pool);
6992        rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
6993        if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
6994            || !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
6995            || !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
6996            || !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
6997        {
6998            crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
6999            return None;
7000        }
7001    }
7002    let spec_time = {
7003        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7004        *ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
7005    };
7006    let t0 = std::time::Instant::now();
7007    let mut folded = Vec::new();
7008    st.pos = pos0;
7009    // Diagnostic split: the fused prefix normally has one fence.  Splitting
7010    // only while fingerprinting tells us which complete layer first departs
7011    // from scalar decode; it must never become a user-facing tuning flag.
7012    let fp_split = verify_fp_on(pos0) && std::env::var("CMF_DSV4_FP_SPLIT").is_ok_and(|v| v != "0");
7013    let mut ok = true;
7014    if fp_split {
7015        for li in 0..complete_end {
7016            let one = [li];
7017            ok = dsv4_chain_run(
7018                layers,
7019                &one,
7020                cfg,
7021                g,
7022                st,
7023                *ids.last().unwrap(),
7024                &mut folded,
7025                Some(&mut states),
7026                b,
7027                ids,
7028                li == 0,
7029                pool,
7030            );
7031            if !ok {
7032                break;
7033            }
7034            verify_fp("verify", pos0, li, &states[..hc * dim]);
7035        }
7036    } else if complete_end > 0 {
7037        let run: Vec<usize> = (0..complete_end).collect();
7038        ok = dsv4_chain_run(
7039            layers,
7040            &run,
7041            cfg,
7042            g,
7043            st,
7044            *ids.last().unwrap(),
7045            &mut folded,
7046            Some(&mut states),
7047            b,
7048            ids,
7049            true,
7050            pool,
7051        );
7052        if ok {
7053            verify_fp("verify", pos0, complete_end - 1, &states[..hc * dim]);
7054        }
7055    }
7056    if ok {
7057        for li in complete_end..device_end {
7058            if !partial_layer_batch(g, layers, cfg, st, li, &mut states, ids, pos0, b, pool) {
7059                ok = false;
7060                break;
7061            }
7062            verify_fp("verify", pos0, li, &states[..hc * dim]);
7063        }
7064    }
7065    crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
7066    if !ok {
7067        // Nothing committed on the host; the device may hold half-appended
7068        // state, so put the snapshot back before declining.
7069        if let Some(sh) = txn.shadow.take() {
7070            let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
7071        }
7072        st.dev_filled = txn.dev_filled;
7073        st.dev_n_comp = txn.dev_n_comp;
7074        st.dev_n_ix = txn.dev_n_ix;
7075        st.pos = pos0;
7076        return None;
7077    }
7078    txn.states = states.clone();
7079    let t_chain = t0.elapsed();
7080    if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
7081        // Any indexer fold this window landed: read the entry back and
7082        // print a fingerprint, so the fused and per-token folds can be
7083        // held against each other on the release shapes.
7084        for li in 0..device_end {
7085            let Some(ixr) = &layers[li].indexer else {
7086                continue;
7087            };
7088            let ratio = ixr.compressor.ratio;
7089            for t in 0..b {
7090                if (pos0 + t + 1) % ratio == 0 {
7091                    let ew = {
7092                        let w = ixr.compressor.wkv.rows();
7093                        if ixr.compressor.overlap { w / 2 } else { w }
7094                    };
7095                    let idx_new = txn.dev_n_ix[li]
7096                        + (0..=t).filter(|k| (pos0 + k + 1) % ratio == 0).count()
7097                        - 1;
7098                    if let Some(v) =
7099                        crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
7100                    {
7101                        let sum: f32 = v.iter().sum();
7102                        eprintln!(
7103                            "[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
7104                            pos0 + t,
7105                            &v[..4.min(v.len())]
7106                        );
7107                    }
7108                }
7109            }
7110        }
7111    }
7112
7113    // ── host tail + every position's head ──
7114    let mut scratch = HcScratch::new(cfg);
7115    argmax_out.clear();
7116    logits_out.clear();
7117    logits_out.resize(b * cfg.vocab, 0.0);
7118    let mut head_in = vec![0.0f32; b * dim];
7119    let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
7120    host_tail_walk_batch(
7121        g,
7122        layers,
7123        cfg,
7124        st,
7125        device_end,
7126        &mut states,
7127        ids,
7128        pos0,
7129        b,
7130        inv_freq,
7131        &mut scratch,
7132        pool,
7133        Some(&mut host_steps),
7134    );
7135    txn.host_steps = host_steps;
7136    for t in 0..b {
7137        let state = &states[t * hc * dim..(t + 1) * hc * dim];
7138        let h = &mut head_in[t * dim..(t + 1) * dim];
7139        hc_head_fold(
7140            state,
7141            &g.hc_head_fn,
7142            g.hc_head_scale,
7143            &g.hc_head_base,
7144            cfg,
7145            pool,
7146            h,
7147        );
7148        rms_weighted(h, &g.norm, cfg.norm_eps);
7149    }
7150    // The experimental B-wide head uses a different reduction kernel from
7151    // ordinary decode.  On the release q4tp it changed row-zero argmax under
7152    // a force-reject transaction, so it is diagnostic-only until parity is
7153    // proven; speculative execution must inherit the canonical head exactly.
7154    let batch_head = std::env::var("CMF_DSV4_SPEC_BATCH_HEAD").is_ok_and(|v| v != "0");
7155    let head_gpu = batch_head
7156        && g.head.model_idx().is_some_and(|hi| {
7157            let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
7158            model.is_some_and(|m| {
7159                crate::gpu_wgpu::q4tp_matvec_batch_for_test(
7160                    &m, hi, &head_in, b, cfg.vocab, dim, logits_out,
7161                )
7162            })
7163        });
7164    for t in 0..b {
7165        if !head_gpu {
7166            let h = &head_in[t * dim..(t + 1) * dim];
7167            g.head
7168                .matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
7169        }
7170        let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
7171        let mut best = 0usize;
7172        for v in 1..cfg.vocab {
7173            if row[v] > row[best] {
7174                best = v;
7175            }
7176        }
7177        argmax_out.push(best as u32);
7178    }
7179    walked_out.clear();
7180    walked_out.extend_from_slice(&states);
7181    st.pos = pos0 + b;
7182    if spec_time {
7183        eprintln!(
7184            "verify: shadow+seed+chain {:.1} ms, tail+head {:.1} ms",
7185            t_chain.as_secs_f64() * 1e3,
7186            (t0.elapsed() - t_chain).as_secs_f64() * 1e3,
7187        );
7188    }
7189    Some(txn)
7190}
7191
7192/// Keep the accepted prefix of a verify pass and put everything else back.
7193///
7194/// `accepted` counts the FED tokens whose state stays (at least 1 — the
7195/// first fed token was already committed by the caller). With
7196/// `accepted == batch` this is free; otherwise the device restores its
7197/// snapshot and replays the accepted tokens' state appends, and the host
7198/// tail re-walks them.
7199#[cfg(feature = "gpu")]
7200pub fn dsv4_spec_finish(
7201    g: &Dsv4Globals,
7202    layers: &[Dsv4Layer],
7203    cfg: &Dsv4Cfg,
7204    st: &mut Dsv4State,
7205    mut txn: Dsv4SpecTxn,
7206    accepted: usize,
7207    ids: &[u32],
7208    inv_freq: &[f32],
7209    pool: Option<&crate::pool::Pool>,
7210) -> bool {
7211    macro_rules! sfail {
7212        ($($t:tt)*) => {{
7213            if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
7214                eprintln!("spec_finish: {}", format_args!($($t)*));
7215            }
7216            return false;
7217        }};
7218    }
7219    let b = txn.batch;
7220    let k = accepted.min(b);
7221    let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
7222    // The staged batch never slid the windows; land the accepted prefix now,
7223    // whatever k is.
7224    let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
7225        .map(|li| (li, txn.dev_filled[li], cfg.window, hd))
7226        .collect();
7227    if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
7228        sfail!("window commit");
7229    }
7230    if k == b {
7231        // Every stream mutation was the walk's own kernels in walk order —
7232        // nothing to put back.
7233        return true;
7234    }
7235    // ── device: restore to the snapshot, then replay the accepted tokens ──
7236    let Some(sh) = txn.shadow.take() else {
7237        sfail!("no shadow")
7238    };
7239    if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
7240        sfail!("restore");
7241    }
7242    let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
7243        sfail!("no model");
7244    };
7245    let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
7246    let mut freqs_own: Vec<&[f32]> = Vec::new();
7247    for li in 0..txn.gpu_end {
7248        let l = &layers[li];
7249        let Some(wkv) = l.wkv.model_idx() else {
7250            sfail!("wkv of layer {li}")
7251        };
7252        let comp = match &l.compressor {
7253            None => None,
7254            Some(cp) => {
7255                let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
7256                    sfail!("compressor of layer {li}");
7257                };
7258                Some((
7259                    crate::gpu_wgpu::Dsv4CompW {
7260                        wkv: a,
7261                        wgate: bx,
7262                        norm: &cp.norm,
7263                        ape: &cp.ape,
7264                    },
7265                    crate::gpu_wgpu::Dsv4CompGeom {
7266                        width: cp.wkv.rows(),
7267                        hidden: dim,
7268                        ratio: cp.ratio,
7269                        overlap: cp.overlap,
7270                        rope_dim: cfg.rope_head_dim,
7271                        eps: cfg.norm_eps,
7272                    },
7273                ))
7274            }
7275        };
7276        let ix = match &l.indexer {
7277            None => None,
7278            Some(ixr) => {
7279                let cp = &ixr.compressor;
7280                let (Some(a), Some(bx), Some(qb), Some(wp)) = (
7281                    cp.wkv.model_idx(),
7282                    cp.wgate.model_idx(),
7283                    ixr.wq_b.model_idx(),
7284                    ixr.weights_proj.model_idx(),
7285                ) else {
7286                    sfail!("indexer of layer {li}");
7287                };
7288                let ih = ixr.weights_proj.rows();
7289                Some((
7290                    crate::gpu_wgpu::Dsv4CompW {
7291                        wkv: a,
7292                        wgate: bx,
7293                        norm: &cp.norm,
7294                        ape: &cp.ape,
7295                    },
7296                    crate::gpu_wgpu::Dsv4CompGeom {
7297                        width: cp.wkv.rows(),
7298                        hidden: dim,
7299                        ratio: cp.ratio,
7300                        overlap: cp.overlap,
7301                        rope_dim: cfg.rope_head_dim,
7302                        eps: cfg.norm_eps,
7303                    },
7304                    crate::gpu_wgpu::Dsv4IxW {
7305                        wq_b: qb,
7306                        weights_proj: wp,
7307                    },
7308                    crate::gpu_wgpu::Dsv4IxGeom {
7309                        ih,
7310                        idim: ixr.wq_b.rows() / ih.max(1),
7311                        q_lora: cfg.q_lora_rank,
7312                        hidden: dim,
7313                        rope_dim: cfg.rope_head_dim,
7314                        eps: cfg.norm_eps,
7315                        top_k: cfg.index_topk,
7316                        window: cfg.window,
7317                    },
7318                ))
7319            }
7320        };
7321        let ew_c = comp.as_ref().map_or(
7322            0,
7323            |(_, cg)| {
7324                if cg.overlap { cg.width / 2 } else { cg.width }
7325            },
7326        );
7327        let ew_i = ix.as_ref().map_or(
7328            0,
7329            |(_, cg, _, _)| {
7330                if cg.overlap { cg.width / 2 } else { cg.width }
7331            },
7332        );
7333        let prep = crate::gpu_wgpu::Dsv4Prep {
7334            wkv,
7335            kv_norm: &l.kv_norm,
7336            comp,
7337            ix,
7338            filled: txn.dev_filled[li],
7339            window: cfg.window,
7340            n_comp: txn.dev_n_comp[li],
7341            n_ix: txn.dev_n_ix[li],
7342            comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
7343            ix_dst_off: txn.dev_n_ix[li] * ew_i,
7344            idx_cap: cfg.window
7345                + if l.indexer.is_some() {
7346                    cfg.index_topk
7347                } else {
7348                    0
7349                },
7350        };
7351        let fr = if l.compressor.is_some() {
7352            g.inv_freq_compress.as_slice()
7353        } else {
7354            g.inv_freq_window.as_slice()
7355        };
7356        freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
7357        plan.push((li, prep));
7358    }
7359    if !crate::gpu_wgpu::dsv4_spec_replay(
7360        &model,
7361        &plan,
7362        st.kv_id,
7363        txn.pos0,
7364        b,
7365        k,
7366        &freqs_own,
7367        hd,
7368        dim,
7369        cfg.rope_head_dim,
7370        cfg.norm_eps,
7371        true,
7372    ) {
7373        sfail!("replay k={k}");
7374    }
7375    // ── host counts: the snapshot advanced by k tokens ──
7376    let advanced = |ratio: usize| -> usize {
7377        if ratio == 0 {
7378            return 0;
7379        }
7380        (0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
7381    };
7382    for li in 0..txn.gpu_end {
7383        let l = &layers[li];
7384        st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
7385        let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
7386        let ai = l
7387            .indexer
7388            .as_ref()
7389            .map_or(0, |ix| advanced(ix.compressor.ratio));
7390        st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
7391        st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
7392        note_compressed(st.kv_id, li, st.dev_n_comp[li]);
7393    }
7394    // ── host tail: the verify pass already walked these tokens; restore
7395    //    the per-token snapshot it took instead of walking them again. ──
7396    if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty() {
7397        for (li, v) in &txn.host_steps {
7398            host_restore(st, *li, &v[k - 1]);
7399        }
7400    } else {
7401        for (li, snap) in &txn.host {
7402            host_restore(st, *li, snap);
7403        }
7404        let mut scratch = HcScratch::new(cfg);
7405        let mut states = txn.states.clone();
7406        host_tail_walk_batch(
7407            g,
7408            layers,
7409            cfg,
7410            st,
7411            txn.gpu_end,
7412            &mut states[..k * hc * dim],
7413            ids,
7414            txn.pos0,
7415            k,
7416            inv_freq,
7417            &mut scratch,
7418            pool,
7419            None,
7420        );
7421    }
7422    st.pos = txn.pos0 + k;
7423    true
7424}
7425
7426pub fn forward_chunk(
7427    g: &Dsv4Globals,
7428    layers: &[Dsv4Layer],
7429    cfg: &Dsv4Cfg,
7430    st: &mut Dsv4State,
7431    ids: &[u32],
7432    pos0: usize,
7433    inv_freq: &[f32],
7434    pool: Option<&crate::pool::Pool>,
7435    logits: &mut Vec<f32>,
7436    want_logits: bool,
7437) {
7438    let bs = batch_prefill();
7439    if bs > 1 {
7440        // The first token walks, always. The batch will only run where every
7441        // layer has already proved it takes the card, and that proof is a
7442        // completed single-token run — with the whole prompt arriving as one
7443        // chunk there is otherwise no first run to give it, and the batch
7444        // declines for the entire prompt while a gate comparing it against
7445        // the walk reports agreement it never tested.
7446        let mut i = 0;
7447        if !st.dev_owned && !ids.is_empty() {
7448            st.pos = pos0;
7449            forward_token_inner(
7450                g,
7451                layers,
7452                cfg,
7453                st,
7454                ids[0],
7455                inv_freq,
7456                pool,
7457                logits,
7458                ids.len() == 1,
7459            );
7460            i = 1;
7461        }
7462        while i < ids.len() {
7463            let end = (i + bs).min(ids.len());
7464            st.pos = pos0 + i;
7465            if !forward_chunk_batched(
7466                g,
7467                layers,
7468                cfg,
7469                st,
7470                &ids[i..end],
7471                pos0 + i,
7472                inv_freq,
7473                pool,
7474                logits,
7475                want_logits && end == ids.len(),
7476            ) {
7477                break;
7478            }
7479            i = end;
7480        }
7481        if i == ids.len() {
7482            return;
7483        }
7484        // Refused before touching anything; the walk starts where it left off.
7485        for (k, &id) in ids.iter().enumerate().skip(i) {
7486            st.pos = pos0 + k;
7487            let last = want_logits && k + 1 == ids.len();
7488            forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7489        }
7490        return;
7491    }
7492    for (i, &id) in ids.iter().enumerate() {
7493        st.pos = pos0 + i;
7494        let last = want_logits && i + 1 == ids.len();
7495        forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
7496    }
7497}
7498
7499pub fn forward_token(
7500    g: &Dsv4Globals,
7501    layers: &[Dsv4Layer],
7502    cfg: &Dsv4Cfg,
7503    st: &mut Dsv4State,
7504    token_id: u32,
7505    inv_freq: &[f32],
7506    pool: Option<&crate::pool::Pool>,
7507    logits: &mut Vec<f32>,
7508) {
7509    forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
7510}
7511
7512#[allow(clippy::too_many_arguments)]
7513fn forward_token_inner(
7514    g: &Dsv4Globals,
7515    layers: &[Dsv4Layer],
7516    cfg: &Dsv4Cfg,
7517    st: &mut Dsv4State,
7518    token_id: u32,
7519    inv_freq: &[f32],
7520    pool: Option<&crate::pool::Pool>,
7521    logits: &mut Vec<f32>,
7522    // Prompt tokens other than the last one have their logits thrown away.
7523    want_logits: bool,
7524) {
7525    let _t_all = prof::on().then(std::time::Instant::now);
7526    let _all_guard = Charge(_t_all, &prof::ALL_NS);
7527    let (hc, dim) = (cfg.hc_mult, cfg.dim);
7528
7529    // Embedding, replicated into the copies.
7530    let mut emb = vec![0.0f32; dim];
7531    g.embed.row_f32(token_id as usize, &mut emb);
7532    let mut state = vec![0.0f32; hc * dim];
7533    for j in 0..hc {
7534        state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
7535    }
7536
7537    let mut scratch = HcScratch::new(cfg);
7538    let mut dump: Vec<String> = Vec::new();
7539    if dump_path().is_some() {
7540        dump.push(format!("\"embed\":{}", vec_json(&emb)));
7541        PICKED.with(|p| p.borrow_mut().clear());
7542        BODY.with(|b| b.borrow_mut().clear());
7543        dump.push(",\"layers\":[".into());
7544    }
7545    if trace_on() {
7546        eprintln!(
7547            "[dsv4] tok={token_id} pos={} embed rms={:.5}",
7548            st.pos,
7549            rms_of(&emb)
7550        );
7551    }
7552    // ── one submission per layer, when the device will take it ──
7553    #[cfg(feature = "gpu")]
7554    let layer_frames = gpu_layer_enabled()
7555        && dsv4_layer_loop(
7556            &mut state,
7557            layers,
7558            g,
7559            cfg,
7560            st,
7561            token_id,
7562            inv_freq,
7563            pool,
7564            &mut scratch,
7565        );
7566    #[cfg(not(feature = "gpu"))]
7567    let layer_frames = false;
7568
7569    // ── the fast two-frame path: hyper-connections on the card ──
7570    // Measured on the release, the fold, the Sinkhorn and the norms cost 19
7571    // ms of a 57 ms token on the host and hundredths of one on the device.
7572    // With both frames doing their own, the host carries nothing between a
7573    // layer's halves and the MoE half's input never leaves the card — one
7574    // readback a layer instead of two.
7575    #[cfg(feature = "gpu")]
7576    let hc_dev = hc_on_device()
7577        && !layer_frames
7578        && gpu_attn_enabled()
7579        && gpu_moe2_enabled()
7580        && dump_path().is_none();
7581    #[cfg(not(feature = "gpu"))]
7582    let hc_dev = false;
7583    // The device loop's verdict as a VALUE, not as a cfg-gated `if`. It used
7584    // to be the latter, with the CPU loop in the `else` arm — so a build
7585    // without the gpu feature compiled no layer loop at all and every token
7586    // passed through untouched. The window test said so ("sliding window
7587    // never filled") and only in the CPU-only build, which is the one
7588    // configuration the gate was not running.
7589    #[cfg(feature = "gpu")]
7590    let two_frame_done = hc_dev
7591        && dsv4_two_frame_loop(
7592            &mut state,
7593            layers,
7594            g,
7595            cfg,
7596            st,
7597            token_id,
7598            inv_freq,
7599            pool,
7600            &mut scratch,
7601        );
7602    #[cfg(not(feature = "gpu"))]
7603    let two_frame_done = false;
7604    if !two_frame_done {
7605        for (li, l) in layers.iter().enumerate() {
7606            if layer_frames {
7607                break;
7608            }
7609            // attention half
7610            hc_block(
7611                &mut state,
7612                &l.hc_attn_fn,
7613                &l.hc_attn_scale,
7614                &l.hc_attn_base,
7615                &l.attn_norm,
7616                cfg,
7617                &mut scratch,
7618                pool,
7619                |folded, out| {
7620                    if dump_path().is_some() {
7621                        // The body's own input and output, so the reference can be
7622                        // fed the port's input: then only the body can differ.
7623                        BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
7624                    }
7625                    // The layer's kind decides its frequencies, not the model's.
7626                    let freqs = if l.compressor.is_some() {
7627                        &g.inv_freq_compress
7628                    } else {
7629                        &g.inv_freq_window
7630                    };
7631                    let freqs = if freqs.is_empty() {
7632                        inv_freq
7633                    } else {
7634                        freqs.as_slice()
7635                    };
7636                    attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
7637                    if dump_path().is_some() {
7638                        BODY.with(|b| b.borrow_mut().push(vec_json(out)));
7639                    }
7640                },
7641            );
7642            if dump_path().is_some() {
7643                // After the attention half only — this is what separates an
7644                // attention discrepancy from an expert one.
7645                dump.push(format!(
7646                    "{}{}",
7647                    if li == 0 { "" } else { "," },
7648                    vec_json(&state)
7649                ));
7650            }
7651            // FFN half
7652            let _t_hc2 = prof::on().then(std::time::Instant::now);
7653            hc_block(
7654                &mut state,
7655                &l.hc_ffn_fn,
7656                &l.hc_ffn_scale,
7657                &l.hc_ffn_base,
7658                &l.ffn_norm,
7659                cfg,
7660                &mut scratch,
7661                pool,
7662                |folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
7663            );
7664            if let Some(t) = _t_hc2 {
7665                // The block's own time minus the expert step inside it — what the
7666                // fold, the norm and the expand cost on their own.
7667                prof::HC_NS.fetch_add(
7668                    t.elapsed().as_nanos() as u64,
7669                    std::sync::atomic::Ordering::Relaxed,
7670                );
7671            }
7672            if dump_path().is_some() {
7673                dump.push(format!(",{}", vec_json(&state)));
7674            }
7675            if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
7676                eprintln!(
7677                    "[dsv4]  caches of layer {li}: window={} compressed={} index={} (ratio={:?})",
7678                    st.window[li].len() / cfg.head_dim.max(1),
7679                    st.compressed[li].len() / cfg.head_dim.max(1),
7680                    st.index_kv[li].len().max(1) / 128,
7681                    l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
7682                );
7683            }
7684            if trace_on() {
7685                let bad = state.iter().filter(|v| !v.is_finite()).count();
7686                eprintln!(
7687                    "[dsv4]  layer {li:>2}: rms={:.5}{}",
7688                    rms_of(&state),
7689                    if bad > 0 {
7690                        format!("  NON-FINITE x{bad}")
7691                    } else {
7692                        String::new()
7693                    }
7694                );
7695            }
7696            dspark_note(li, &state, cfg);
7697        }
7698    }
7699    st.pos += 1;
7700
7701    // Collapse the copies, normalize, project to the vocabulary.
7702    let mut h = vec![0.0f32; dim];
7703    hc_head_fold(
7704        &state,
7705        &g.hc_head_fn,
7706        g.hc_head_scale,
7707        &g.hc_head_base,
7708        cfg,
7709        pool,
7710        &mut h,
7711    );
7712    if !want_logits {
7713        logits.clear();
7714        return;
7715    }
7716    let _t_head = prof::on().then(std::time::Instant::now);
7717    rms_weighted(&mut h, &g.norm, cfg.norm_eps);
7718    logits.clear();
7719    logits.resize(g.head.rows(), 0.0);
7720    g.head.matvec(&h, logits, pool);
7721    if let Some(t) = _t_head {
7722        prof::HEAD_NS.fetch_add(
7723            t.elapsed().as_nanos() as u64,
7724            std::sync::atomic::Ordering::Relaxed,
7725        );
7726    }
7727    if dump_path().is_some() {
7728        dump.push("]".into());
7729        let picked = PICKED.with(|p| {
7730            p.borrow()
7731                .iter()
7732                .map(|v| {
7733                    format!(
7734                        "[{}]",
7735                        v.iter()
7736                            .map(|e| e.to_string())
7737                            .collect::<Vec<_>>()
7738                            .join(",")
7739                    )
7740                })
7741                .collect::<Vec<_>>()
7742                .join(",")
7743        });
7744        dump.push(format!(",\"experts\":[{picked}]"));
7745        let body = BODY.with(|b| b.borrow().join(","));
7746        dump.push(format!(",\"attn_io\":[{body}]"));
7747        dump_line(&format!(
7748            "{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
7749            st.pos - 1,
7750            dump.join(""),
7751            vec_json(&h),
7752            vec_json(logits)
7753        ));
7754    }
7755    if trace_on() {
7756        let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
7757        for (i, &v) in logits.iter().enumerate() {
7758            if v > best {
7759                best = v;
7760                top = i;
7761            }
7762        }
7763        let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
7764        eprintln!(
7765            "[dsv4]  head: rms={:.5} logits[{}..{:.3}] argmax={top}",
7766            rms_of(&h),
7767            format_args!("{lo:.3}"),
7768            best
7769        );
7770    }
7771}
7772
7773/// Build the runtime weights from a converted `.cmf`.
7774///
7775/// Names are the converter's output (see `canon_name`'s deepseek_v4 arm):
7776/// attention keeps DeepSeek's own spelling under `self_attn.`, the MoE is
7777/// rewritten into the layout every other MoE here uses, and the hyper-
7778/// connection tensors ride under the layer prefix.
7779pub fn load(
7780    model: &std::sync::Arc<cortiq_core::CmfModel>,
7781    cfg: &Dsv4Cfg,
7782    n_layers: usize,
7783) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
7784    let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7785        crate::qtensor::QTensor::from_model(model, name)
7786    };
7787    // The small pieces — norms, the sink, ape, the hyper-connection
7788    // projections — are read as plain f32. They are not all 2-D (a norm is a
7789    // vector), so this cannot go through QTensor, which requires a matrix.
7790    let f = |name: &str| -> Result<Vec<f32>, String> {
7791        crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7792    };
7793
7794    // Two frequency tables, chosen per layer by whether it compresses. The
7795    // release's compress_rope_theta (160 000) is not in config.json — it
7796    // lives in inference/config.json — so it is pinned here with the other
7797    // constants the header cannot carry.
7798    let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
7799        if yarn {
7800            crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
7801        } else {
7802            crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
7803        }
7804    };
7805    let globals = Dsv4Globals {
7806        inv_freq_compress: rope_of(160_000.0, true),
7807        inv_freq_window: rope_of(10_000.0, false),
7808        embed: q("model.embed_tokens.weight")?,
7809        norm: f("model.norm.weight")?,
7810        head: q("lm_head.weight")?,
7811        hc_head_fn: f("model.hc_head_fn")?,
7812        hc_head_base: f("model.hc_head_base")?,
7813        hc_head_scale: *f("model.hc_head_scale")?
7814            .first()
7815            .ok_or("dsv4: empty hc_head_scale")?,
7816    };
7817
7818    let mut layers = Vec::with_capacity(n_layers);
7819    for li in 0..n_layers {
7820        layers.push(load_layer(
7821            model,
7822            cfg,
7823            &format!("model.layers.{li}"),
7824            Scheme::Main,
7825        )?);
7826    }
7827    // The projection this loader exists to serve: with a RAM tier configured,
7828    // pin the MASKED expert set with one sequential sweep of the file at
7829    // streaming rate, before decode discovers it miss by miss in random
7830    // order. Experts outside a layer's mask are skipped; a layer without a
7831    // mask keeps all of its experts (the budget caps the sweep).
7832    #[cfg(feature = "gpu")]
7833    if crate::gpu_wgpu::host_banks_on() {
7834        // Host banks: one background sweep, layer by layer, oldest first.
7835        let sets: Vec<(usize, Vec<(usize, usize, usize)>, bool)> = layers
7836            .iter()
7837            .filter_map(|l| {
7838                let idx3 = |e: &Dsv4Expert| {
7839                    Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
7840                };
7841                let mut v: Vec<_> = l.experts.iter().filter_map(idx3).collect();
7842                let first = v.first()?.0;
7843                v.push(idx3(&l.shared)?);
7844                let gu_q2 =
7845                    l.experts.first()?.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP);
7846                Some((first, v, gu_q2))
7847            })
7848            .collect();
7849        let m2 = model.clone();
7850        let (inter, dim) = (cfg.moe_inter, cfg.dim);
7851        std::thread::spawn(move || {
7852            for (first, v, gu_q2) in sets {
7853                crate::gpu_wgpu::dsv4_host_bank_build(&m2, first, &v, inter, dim, gu_q2);
7854            }
7855            tracing::info!("host banks: built");
7856        });
7857    }
7858    #[cfg(feature = "gpu")]
7859    {
7860        let masks: Vec<Option<Vec<bool>>> = layers.iter().map(|l| l.mask.clone()).collect();
7861        crate::gpu_wgpu::prefetch_tier(model, &|name: &str| {
7862            let Some(i) = name.find(".experts.") else {
7863                return false;
7864            };
7865            let rest = &name[i + 9..];
7866            let e: usize = match rest[..rest.find('.').unwrap_or(rest.len())].parse() {
7867                Ok(v) => v,
7868                Err(_) => return false,
7869            };
7870            let li: usize = {
7871                let Some(j) = name.find("layers.") else {
7872                    return false;
7873                };
7874                let r = &name[j + 7..];
7875                match r[..r.find('.').unwrap_or(r.len())].parse() {
7876                    Ok(v) => v,
7877                    Err(_) => return false,
7878                }
7879            };
7880            match masks.get(li).and_then(|m| m.as_ref()) {
7881                Some(m) => m.get(e).copied().unwrap_or(false),
7882                None => true,
7883            }
7884        });
7885    }
7886    Ok((globals, layers))
7887}
7888
7889/// Where a layer's tensors live in the file.
7890///
7891/// The MTP modules are the same layer as any other — attention, a
7892/// hyper-connection pair, a gated MoE over 256 experts — but the converter
7893/// wrote them under DeepSeek's internal names rather than the HF ones it used
7894/// for the trunk. Two schemes, one loader: a second copy would drift.
7895#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7896pub enum Scheme {
7897    Main,
7898    Mtp,
7899}
7900
7901impl Scheme {
7902    fn attn(self) -> &'static str {
7903        match self {
7904            Scheme::Main => "self_attn",
7905            Scheme::Mtp => "attn",
7906        }
7907    }
7908    fn attn_norm(self) -> &'static str {
7909        match self {
7910            Scheme::Main => "input_layernorm.weight",
7911            Scheme::Mtp => "attn_norm.weight",
7912        }
7913    }
7914    fn ffn_norm(self) -> &'static str {
7915        match self {
7916            Scheme::Main => "post_attention_layernorm.weight",
7917            Scheme::Mtp => "ffn_norm.weight",
7918        }
7919    }
7920    fn mlp(self) -> &'static str {
7921        match self {
7922            Scheme::Main => "mlp",
7923            Scheme::Mtp => "ffn",
7924        }
7925    }
7926    /// The router's per-expert bias. Absent on the trunk's hash layers, which
7927    /// is how they are recognised; always present on an MTP module.
7928    fn gate_bias(self) -> &'static str {
7929        match self {
7930            Scheme::Main => "expert_bias",
7931            Scheme::Mtp => "gate.bias",
7932        }
7933    }
7934    fn shared(self) -> &'static str {
7935        match self {
7936            Scheme::Main => "shared_expert",
7937            Scheme::Mtp => "shared_experts",
7938        }
7939    }
7940    /// gate, down, up — in that order, which is w1/w2/w3 upstream.
7941    fn w(self, i: u8) -> &'static str {
7942        match (self, i) {
7943            (Scheme::Main, 1) => "gate_proj.weight",
7944            (Scheme::Main, 2) => "down_proj.weight",
7945            (Scheme::Main, _) => "up_proj.weight",
7946            (Scheme::Mtp, 1) => "w1.weight",
7947            (Scheme::Mtp, 2) => "w2.weight",
7948            (Scheme::Mtp, _) => "w3.weight",
7949        }
7950    }
7951}
7952
7953/// One layer, wherever it lives in the file.
7954pub fn load_layer(
7955    model: &std::sync::Arc<cortiq_core::CmfModel>,
7956    cfg: &Dsv4Cfg,
7957    p: &str,
7958    s: Scheme,
7959) -> Result<Dsv4Layer, String> {
7960    let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
7961        crate::qtensor::QTensor::from_model(model, name)
7962    };
7963    let f = |name: &str| -> Result<Vec<f32>, String> {
7964        crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
7965    };
7966    let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
7967    let at = s.attn();
7968    let ml = s.mlp();
7969    {
7970        let scale3 = |name: &str| -> Result<[f32; 3], String> {
7971            let v = f(name)?;
7972            if v.len() < 3 {
7973                return Err(format!("{name}: expected 3 scales, got {}", v.len()));
7974            }
7975            Ok([v[0], v[1], v[2]])
7976        };
7977        // The compressor exists on every layer whose ratio is non-zero;
7978        // its presence in the file is the only signal we need.
7979        let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
7980            Ok(wkv) => {
7981                let ape = f(&format!("{p}.{at}.compressor.ape"))?;
7982                // ape is [ratio, coff*head_dim]; coff is 2 when the windows
7983                // overlap, which the release does at ratio 4.
7984                let width = wkv.rows();
7985                let ratio = (ape.len() / width.max(1)).max(1);
7986                Some(Dsv4Compressor {
7987                    wkv,
7988                    wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
7989                    norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
7990                    ape,
7991                    ratio,
7992                    overlap: ratio == 4,
7993                })
7994            }
7995            Err(_) => None,
7996        };
7997        let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
7998            Ok(wq_b) => {
7999                let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
8000                let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
8001                let width = cwkv.rows();
8002                let ratio = (ape.len() / width.max(1)).max(1);
8003                Some(Dsv4Indexer {
8004                    wq_b,
8005                    weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
8006                    compressor: Dsv4Compressor {
8007                        wkv: cwkv,
8008                        wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
8009                        norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
8010                        ape,
8011                        ratio,
8012                        overlap: ratio == 4,
8013                    },
8014                })
8015            }
8016            Err(_) => None,
8017        };
8018
8019        let mut experts = Vec::with_capacity(cfg.n_routed_experts);
8020        for e in 0..cfg.n_routed_experts {
8021            let ep = format!("{p}.{ml}.experts.{e}");
8022            experts.push(Dsv4Expert {
8023                w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
8024                w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
8025                w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
8026            });
8027        }
8028
8029        Ok(Dsv4Layer {
8030            attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
8031            ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
8032            wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
8033            q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
8034            wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
8035            wkv: q(&format!("{p}.{at}.wkv.weight"))?,
8036            kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
8037            wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
8038            wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
8039            attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
8040            compressor,
8041            indexer,
8042            hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
8043            hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
8044            hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
8045            hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
8046            hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
8047            hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
8048            gate: q(&format!("{p}.{ml}.gate.weight"))?,
8049            // The bias is absent exactly on the hash layers, and the table
8050            // is present exactly there — the file itself says which is which.
8051            gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
8052            tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
8053            experts,
8054            mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
8055                None
8056            } else {
8057                crate::loader::moe_task_mask(model, &format!("{p}."), cfg.n_routed_experts)
8058            },
8059            shared: Dsv4Expert {
8060                w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
8061                w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
8062                w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
8063            },
8064        })
8065    }
8066}
8067
8068/// One module of the speculation stack.
8069///
8070/// The release carries three, so the draft is three deep, and the last one
8071/// also holds a confidence head — the model scores its own proposals rather
8072/// than leaving acceptance to a threshold we would have to invent. Each
8073/// module is a full layer with its own 256 experts; what makes it an MTP
8074/// module rather than a 44th layer is `main_proj`, which folds the previous
8075/// hidden state into the next embedding before the layer runs.
8076pub struct Dsv4Mtp {
8077    pub layer: Dsv4Layer,
8078    /// Stage 0 only: the projection that turns the trunk's captured hidden
8079    /// states into the block's input. Later stages take the block from the
8080    /// stage before them, so they carry none.
8081    pub main_proj: Option<crate::qtensor::QTensor>,
8082    pub main_norm: Option<Vec<f32>>,
8083    /// Last module only: what turns a draft hidden state into logits.
8084    pub norm: Option<Vec<f32>>,
8085    pub hc_head_fn: Option<Vec<f32>>,
8086    pub hc_head_base: Option<Vec<f32>>,
8087    pub hc_head_scale: Option<f32>,
8088    pub confidence: Option<crate::qtensor::QTensor>,
8089    /// Last stage only: a rank-256 bigram table that biases the draft's
8090    /// logits, and whose embedding also feeds the confidence head. Cheap
8091    /// enough that the draft samples through it position by position while
8092    /// the network itself runs the whole block at once.
8093    pub markov_w1: Option<crate::qtensor::QTensor>,
8094    pub markov_w2: Option<crate::qtensor::QTensor>,
8095}
8096
8097/// Load as much of the speculation stack as the file carries, up to
8098/// `max_depth`. Missing is not an error: a checkpoint without MTP simply
8099/// yields an empty stack, and the caller falls back to plain decoding.
8100pub fn load_mtp(
8101    model: &std::sync::Arc<cortiq_core::CmfModel>,
8102    cfg: &Dsv4Cfg,
8103    max_depth: usize,
8104) -> Vec<Dsv4Mtp> {
8105    let f = |name: &str| -> Option<Vec<f32>> {
8106        crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
8107    };
8108    let mut out = Vec::new();
8109    for d in 0..max_depth {
8110        let p = format!("model.mtp.{d}");
8111        // A stage is recognised by its attention, not by `main_proj`: only
8112        // stage 0 has that, and only the last has the head. Keying on either
8113        // end found one module of three.
8114        if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
8115            break;
8116        }
8117        let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
8118            Ok(l) => l,
8119            Err(e) => {
8120                eprintln!("MTP {d}: skipped, {e}");
8121                break;
8122            }
8123        };
8124        out.push(Dsv4Mtp {
8125            layer,
8126            main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
8127                .ok(),
8128            main_norm: f(&format!("{p}.main_norm.weight")),
8129            norm: f(&format!("{p}.norm.weight")),
8130            hc_head_fn: f(&format!("{p}.hc_head_fn")),
8131            hc_head_base: f(&format!("{p}.hc_head_base")),
8132            hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
8133            confidence: crate::qtensor::QTensor::from_model(
8134                model,
8135                &format!("{p}.confidence_head.proj.weight"),
8136            )
8137            .ok(),
8138            markov_w1: crate::qtensor::QTensor::from_model(
8139                model,
8140                &format!("{p}.markov_head.markov_w1.weight"),
8141            )
8142            .ok(),
8143            markov_w2: crate::qtensor::QTensor::from_model(
8144                model,
8145                &format!("{p}.markov_head.markov_w2.weight"),
8146            )
8147            .ok(),
8148        });
8149    }
8150    dspark_apply_mask(&mut out);
8151    if !out.is_empty() {
8152        let mp = out
8153            .iter()
8154            .find_map(|m| m.main_proj.as_ref())
8155            .map(|t| format!("[{}, {}]", t.rows(), t.cols()))
8156            .unwrap_or_else(|| "no".into());
8157        eprintln!(
8158            "MTP: {} stage(s), main_proj {mp}, experts {}, \
8159             confidence head {}, markov {}",
8160            out.len(),
8161            out[0].layer.experts.len(),
8162            if out.iter().any(|m| m.confidence.is_some()) {
8163                "yes"
8164            } else {
8165                "no"
8166            },
8167            if out.iter().any(|m| m.markov_w1.is_some()) {
8168                "yes"
8169            } else {
8170                "no"
8171            },
8172        );
8173    }
8174    out
8175}
8176
8177#[cfg(test)]
8178mod tests {
8179    use super::*;
8180
8181    // A whole model, small enough to reason about: 2 layers, 4 heads, 8
8182    // experts. Weights are deterministic and tiny, which is the point —
8183    // this test is about shapes, indexing and cache bookkeeping, the things
8184    // that a 138 GB file would surface only after an hour of loading.
8185    fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
8186        use crate::qtensor::QTensor;
8187        let cfg = Dsv4Cfg {
8188            dim: 32,
8189            n_heads: 4,
8190            head_dim: 8,
8191            rope_head_dim: 4,
8192            q_lora_rank: 16,
8193            o_lora_rank: 16,
8194            o_groups: 2,
8195            hc_mult: 4,
8196            hc_sinkhorn_iters: 20,
8197            hc_eps: 1e-6,
8198            norm_eps: 1e-6,
8199            n_routed_experts: 8,
8200            top_k: 2,
8201            moe_inter: 16,
8202            route_scale: 1.0,
8203            swiglu_limit: 10.0,
8204            window: 6,
8205            index_topk: 8,
8206            vocab: 24,
8207        };
8208        // Deterministic pseudo-random in a narrow band: big enough to move
8209        // the state, small enough that nothing saturates.
8210        let w = |n: usize, seed: usize| -> Vec<f32> {
8211            (0..n)
8212                .map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
8213                .collect()
8214        };
8215        let t = |rows: usize, cols: usize, seed: usize| {
8216            QTensor::from_f32(w(rows * cols, seed), rows, cols)
8217        };
8218        let ones = |n: usize| vec![1.0f32; n];
8219
8220        let (dim, hc) = (cfg.dim, cfg.hc_mult);
8221        // q is n_heads*head_dim wide and kv is one head wide; rope rides the
8222        // tail of each rather than widening anything.
8223        let q_width = cfg.n_heads * cfg.head_dim;
8224        let kv_width = cfg.head_dim;
8225        let o_per_group = q_width / cfg.o_groups;
8226        let mut layers = Vec::new();
8227        for li in 0..2 {
8228            let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
8229                .map(|e| Dsv4Expert {
8230                    w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
8231                    w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
8232                    w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
8233                })
8234                .collect();
8235            // Layer 0 is a hash layer (table-routed); layer 1 routes normally
8236            // and carries the compressor — both paths get exercised.
8237            layers.push(Dsv4Layer {
8238                attn_norm: ones(dim),
8239                ffn_norm: ones(dim),
8240                wq_a: t(cfg.q_lora_rank, dim, 1 + li),
8241                q_norm: ones(cfg.q_lora_rank),
8242                wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
8243                wkv: t(kv_width, dim, 5 + li),
8244                kv_norm: ones(kv_width),
8245                wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
8246                wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
8247                attn_sink: vec![0.1; cfg.n_heads],
8248                // Layer 1 carries the OVERLAPPING compressor, as the release
8249                // does at ratio 4: the projection is twice the entry width.
8250                compressor: if li == 1 {
8251                    Some(Dsv4Compressor {
8252                        wkv: t(2 * kv_width, dim, 11),
8253                        wgate: t(2 * kv_width, dim, 13),
8254                        norm: ones(kv_width),
8255                        ape: vec![0.01; 4 * 2 * kv_width],
8256                        ratio: 4,
8257                        overlap: true,
8258                    })
8259                } else {
8260                    None
8261                },
8262                indexer: if li == 1 {
8263                    Some(Dsv4Indexer {
8264                        wq_b: t(2 * 16, cfg.q_lora_rank, 41),
8265                        weights_proj: t(2, dim, 43),
8266                        compressor: Dsv4Compressor {
8267                            wkv: t(2 * 16, dim, 45),
8268                            wgate: t(2 * 16, dim, 47),
8269                            norm: ones(16),
8270                            ape: vec![0.01; 4 * 2 * 16],
8271                            ratio: 4,
8272                            overlap: true,
8273                        },
8274                    })
8275                } else {
8276                    None
8277                },
8278                hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
8279                hc_attn_base: w((2 + hc) * hc, 17 + li),
8280                hc_attn_scale: [1.0, 1.0, 1.0],
8281                hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
8282                hc_ffn_base: w((2 + hc) * hc, 21 + li),
8283                hc_ffn_scale: [1.0, 1.0, 1.0],
8284                gate: t(cfg.n_routed_experts, dim, 23 + li),
8285                gate_bias: if li == 1 {
8286                    Some(vec![0.0; cfg.n_routed_experts])
8287                } else {
8288                    None
8289                },
8290                tid2eid: if li == 0 {
8291                    Some(
8292                        (0..cfg.vocab * cfg.top_k)
8293                            .map(|i| (i % cfg.n_routed_experts) as f32)
8294                            .collect(),
8295                    )
8296                } else {
8297                    None
8298                },
8299                experts,
8300                mask: None,
8301                shared: Dsv4Expert {
8302                    w1: t(cfg.moe_inter, dim, 25 + li),
8303                    w2: t(dim, cfg.moe_inter, 27 + li),
8304                    w3: t(cfg.moe_inter, dim, 29 + li),
8305                },
8306            });
8307        }
8308        let inv = |base: f32| -> Vec<f32> {
8309            (0..cfg.rope_head_dim / 2)
8310                .map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8311                .collect()
8312        };
8313        let g = Dsv4Globals {
8314            inv_freq_compress: inv(160000.0),
8315            inv_freq_window: inv(10000.0),
8316            embed: t(cfg.vocab, dim, 31),
8317            norm: ones(dim),
8318            head: t(cfg.vocab, dim, 33),
8319            hc_head_fn: w(hc * hc * dim, 35),
8320            hc_head_base: w(hc, 37),
8321            hc_head_scale: 1.0,
8322        };
8323        (g, layers, cfg)
8324    }
8325
8326    /// The whole stack, decoding a sequence. Every block is on the path:
8327    /// hyper-connections, the double-LoRA attention with its sink, the KV
8328    /// compressor firing on its ratio boundary, hash routing on one layer
8329    /// and score routing on the other.
8330    #[test]
8331    fn forward_token_decodes_a_sequence_without_falling_over() {
8332        let (g, layers, cfg) = toy();
8333        let mut st = Dsv4State::new(layers.len());
8334        let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
8335            .map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
8336            .collect();
8337        let mut logits = Vec::new();
8338
8339        // Ten tokens: more than twice the compressor's ratio, so the
8340        // compressed cache is written on a boundary and read afterwards.
8341        let mut first: Option<Vec<f32>> = None;
8342        for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
8343            forward_token(
8344                &g,
8345                &layers,
8346                &cfg,
8347                &mut st,
8348                tok,
8349                &inv_freq,
8350                None,
8351                &mut logits,
8352            );
8353            assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
8354            assert!(
8355                logits.iter().all(|v| v.is_finite()),
8356                "step {step}: non-finite logit — {logits:?}"
8357            );
8358            // A model that has collapsed returns the same distribution
8359            // regardless of input; that is the failure this catches.
8360            let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
8361                - logits.iter().cloned().fold(f32::MAX, f32::min);
8362            assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
8363            if step == 0 {
8364                first = Some(logits.clone());
8365            }
8366            assert_eq!(st.pos, step + 1, "position bookkeeping");
8367        }
8368
8369        // The cache has to have grown, and the compressor layer must have
8370        // emitted compressed entries (10 tokens / ratio 4 = 2 windows).
8371        assert!(!st.window[0].is_empty(), "sliding window never filled");
8372        // Ten tokens through a window of six: it must have slid, not grown.
8373        for (li, w) in st.window.iter().enumerate() {
8374            assert!(
8375                w.len() / cfg.head_dim <= cfg.window,
8376                "layer {li}: window holds {} positions, cap is {}",
8377                w.len() / cfg.head_dim,
8378                cfg.window
8379            );
8380        }
8381        assert!(
8382            !st.compressed[1].is_empty(),
8383            "compressor layer produced no compressed KV in 10 tokens"
8384        );
8385        // Ten tokens at ratio 4 fold twice, and the entries must be one head
8386        // wide — the overlapping projection is 2x that, so a width mistake
8387        // shows up here rather than as quiet nonsense.
8388        assert_eq!(
8389            st.compressed[1].len() / cfg.head_dim,
8390            2,
8391            "expected two folds in ten tokens at ratio 4"
8392        );
8393        assert!(
8394            !st.prev_kv[1].is_empty(),
8395            "the overlapping compressor never kept a previous window"
8396        );
8397        // Every layer that HAS an indexer must have filled the indexer's own
8398        // cache: it is what decides which compressed positions attention
8399        // reads, and an empty one silently discards the whole long-range
8400        // memory rather than failing.
8401        for (li, l) in layers.iter().enumerate() {
8402            if l.indexer.is_some() {
8403                assert!(
8404                    !st.index_kv[li].is_empty(),
8405                    "layer {li} has an indexer but its cache stayed empty"
8406                );
8407            }
8408        }
8409
8410        // Context must matter: the same token at position 0 of a fresh state
8411        // and at the end of a filled one cannot give identical logits.
8412        let mut fresh = Dsv4State::new(layers.len());
8413        let mut relogits = Vec::new();
8414        forward_token(
8415            &g,
8416            &layers,
8417            &cfg,
8418            &mut fresh,
8419            3,
8420            &inv_freq,
8421            None,
8422            &mut relogits,
8423        );
8424        assert_eq!(
8425            relogits,
8426            first.unwrap(),
8427            "the same token from a fresh state must reproduce exactly"
8428        );
8429    }
8430
8431    /// The reference clamps `up` on both sides but `gate` only from above.
8432    /// Getting that symmetric would quietly change every expert's output on
8433    /// the tokens that saturate, which is the hardest kind of bug to see.
8434    #[test]
8435    fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
8436        let inter = 4;
8437        // gate = [-50, 50, 1, -1], up = [50, -50, 1, -1]
8438        let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
8439        let up_src = [50.0f32, -50.0, 1.0, -1.0];
8440        let limit = 10.0f32;
8441        let mut got = vec![0.0f32; inter];
8442        expert_swiglu(
8443            &[0.0],
8444            &|_, d| d.copy_from_slice(&gate_src),
8445            &|_, d| d.copy_from_slice(&up_src),
8446            &|src, d| d.copy_from_slice(src),
8447            inter,
8448            1.0,
8449            limit,
8450            &mut got,
8451        );
8452        let silu = |g: f32| g / (1.0 + (-g).exp());
8453        // gate: only the +50 is cut, the -50 rides through silu untouched.
8454        let want = [
8455            silu(-50.0) * limit,
8456            silu(limit) * -limit,
8457            silu(1.0) * 1.0,
8458            -silu(-1.0),
8459        ];
8460        for (i, w) in want.iter().enumerate() {
8461            assert!(
8462                (got[i] - w).abs() < 1e-5,
8463                "lane {i}: got {} want {w}",
8464                got[i]
8465            );
8466        }
8467        // And with the clamp off nothing is touched.
8468        let mut raw = vec![0.0f32; inter];
8469        expert_swiglu(
8470            &[0.0],
8471            &|_, d| d.copy_from_slice(&gate_src),
8472            &|_, d| d.copy_from_slice(&up_src),
8473            &|src, d| d.copy_from_slice(src),
8474            inter,
8475            1.0,
8476            0.0,
8477            &mut raw,
8478        );
8479        assert!(
8480            (raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
8481            "limit 0 must not clamp"
8482        );
8483    }
8484
8485    /// The grouped projection writes its intermediate from several threads
8486    /// at once. Disjoint indices are the whole argument for that being safe,
8487    /// so the pooled result has to equal the serial one exactly — a race
8488    /// here would show up as occasional wrong tokens, not as a crash.
8489    #[test]
8490    fn grouped_projection_is_identical_with_and_without_a_pool() {
8491        let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
8492        let attn: Vec<f32> = (0..groups * per_group)
8493            .map(|i| ((i * 13) as f32 * 0.021).sin())
8494            .collect();
8495        let wo_a: Vec<f32> = (0..groups * lora * per_group)
8496            .map(|i| ((i * 7) as f32 * 0.011).cos())
8497            .collect();
8498        let wo_b: Vec<f32> = (0..dim * groups * lora)
8499            .map(|i| ((i * 5) as f32 * 0.009).sin())
8500            .collect();
8501        let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
8502            wo_a[r * per_group..(r + 1) * per_group]
8503                .iter()
8504                .zip(x)
8505                .map(|(a, b)| a * b)
8506                .sum()
8507        };
8508        let project = |mid: &[f32], dst: &mut [f32]| {
8509            for (d, o) in dst.iter_mut().enumerate() {
8510                *o = wo_b[d * mid.len()..(d + 1) * mid.len()]
8511                    .iter()
8512                    .zip(mid)
8513                    .map(|(a, b)| a * b)
8514                    .sum();
8515            }
8516        };
8517
8518        let mut serial = vec![0.0f32; dim];
8519        o_project(
8520            &attn,
8521            &row,
8522            per_group,
8523            &project,
8524            groups,
8525            lora,
8526            None,
8527            &mut serial,
8528        );
8529
8530        let pool = crate::pool::Pool::new(4);
8531        let mut pooled = vec![0.0f32; dim];
8532        o_project(
8533            &attn,
8534            &row,
8535            per_group,
8536            &project,
8537            groups,
8538            lora,
8539            Some(&pool),
8540            &mut pooled,
8541        );
8542        assert_eq!(serial, pooled, "the pooled projection diverged");
8543        assert!(
8544            serial.iter().any(|v| v.abs() > 1e-6),
8545            "test data is degenerate"
8546        );
8547    }
8548
8549    #[test]
8550    fn block_grouped_projection_matches_position_walk() {
8551        let (_g, layers, cfg) = toy();
8552        let l = &layers[1];
8553        let b = 5;
8554        let attn_len = cfg.n_heads * cfg.head_dim;
8555        let attn: Vec<f32> = (0..b * attn_len)
8556            .map(|i| ((i * 17) as f32 * 0.013).sin())
8557            .collect();
8558        let mut walked = vec![0.0f32; b * cfg.dim];
8559        for bi in 0..b {
8560            o_project(
8561                &attn[bi * attn_len..(bi + 1) * attn_len],
8562                &|r, x, sc| l.wo_a.row_dot(r, x, sc),
8563                l.wo_a.cols(),
8564                &|mid, dst| l.wo_b.matvec(mid, dst, None),
8565                cfg.o_groups,
8566                cfg.o_lora_rank,
8567                None,
8568                &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8569            );
8570        }
8571        let mut batched = vec![0.0f32; b * cfg.dim];
8572        o_project_block(
8573            &attn,
8574            b,
8575            &l.wo_a,
8576            &l.wo_b,
8577            cfg.o_groups,
8578            cfg.o_lora_rank,
8579            None,
8580            &mut batched,
8581        );
8582        assert_eq!(batched, walked);
8583    }
8584
8585    #[test]
8586    fn block_moe_matches_position_walk_in_route_order() {
8587        let (_g, layers, cfg) = toy();
8588        // The scored layer exercises repeated and distinct experts without
8589        // tying the result to a token-id table.
8590        let l = &layers[1];
8591        let b = 5;
8592        let xs: Vec<f32> = (0..b * cfg.dim)
8593            .map(|i| ((i * 11) as f32 * 0.019).cos())
8594            .collect();
8595        let ids = [1u32, 2, 3, 4, 5];
8596        let mut walked = vec![0.0f32; b * cfg.dim];
8597        for bi in 0..b {
8598            moe_step(
8599                &xs[bi * cfg.dim..(bi + 1) * cfg.dim],
8600                l,
8601                &cfg,
8602                ids[bi],
8603                1,
8604                None,
8605                &mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
8606            );
8607        }
8608        let mut batched = vec![0.0f32; b * cfg.dim];
8609        moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
8610        assert_eq!(batched, walked);
8611    }
8612
8613    /// The overlapping compressor folds 2*ratio slots, not ratio: the
8614    /// previous window contributes its first half of dimensions and the
8615    /// current one its second half. Treating it as a plain compressor makes
8616    /// the entry twice as wide as the cache expects, which lands the whole
8617    /// thing in the wrong store rather than raising anything.
8618    #[test]
8619    fn overlapping_compressor_folds_both_windows() {
8620        let (ratio, d) = (2usize, 3usize);
8621        // Current window: two tokens, 2*d wide each. Second half is what the
8622        // current window contributes.
8623        let cur_kv: Vec<f32> = vec![
8624            1.0, 1.0, 1.0, /*|*/ 10.0, 20.0, 30.0, // token 0
8625            2.0, 2.0, 2.0, /*|*/ 40.0, 50.0, 60.0, // token 1
8626        ];
8627        // Make the current window's second-half scores dominate everywhere.
8628        let cur_sc: Vec<f32> = vec![
8629            0.0, 0.0, 0.0, /*|*/ 0.0, 0.0, 100.0, //
8630            0.0, 0.0, 0.0, /*|*/ 100.0, 100.0, 0.0,
8631        ];
8632        // Previous window: its FIRST half is what it contributes.
8633        let prev_kv: Vec<f32> = vec![
8634            7.0, 8.0, 9.0, /*|*/ 0.0, 0.0, 0.0, //
8635            5.0, 6.0, 7.0, /*|*/ 0.0, 0.0, 0.0,
8636        ];
8637        let prev_sc = vec![0.0f32; ratio * 2 * d];
8638
8639        let mut out = vec![0.0f32; d];
8640        compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
8641        // dim 0 and 1: token 1's second half wins (score 100)
8642        assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
8643        assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
8644        // dim 2: token 0's second half wins
8645        assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
8646
8647        // With no previous window the fold still works and uses only the
8648        // current one — this is the very first window of a generation.
8649        let mut first = vec![0.0f32; d];
8650        compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
8651        assert!(
8652            first.iter().all(|v| v.is_finite()),
8653            "first window: {first:?}"
8654        );
8655        assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
8656
8657        // And a previous window with real scores does pull the result.
8658        let mut both = vec![0.0f32; d];
8659        let strong_prev = vec![100.0f32; ratio * 2 * d];
8660        compress_window_overlap(
8661            &prev_kv,
8662            &strong_prev,
8663            &cur_kv,
8664            &cur_sc,
8665            ratio,
8666            d,
8667            &mut both,
8668        );
8669        assert!(
8670            (both[0] - 40.0).abs() > 1.0,
8671            "a scored previous window must move the fold, got {}",
8672            both[0]
8673        );
8674    }
8675
8676    /// Numerical parity with the reference. The vectors below come from
8677    /// running `kernel.py::hc_split_sinkhorn`'s own formula on a fixed
8678    /// input; matching them pins the exponent order, the eps placement and
8679    /// the off-by-one in the iteration count all at once — a property test
8680    /// alone would pass with any of those wrong.
8681    #[test]
8682    fn sinkhorn_matches_the_reference_numbers() {
8683        let hc = 4;
8684        let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8685        let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
8686        let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8687        hc_split_sinkhorn(
8688            &mixes,
8689            &[1.0, 1.0, 1.0],
8690            &base,
8691            hc,
8692            20,
8693            1e-6,
8694            &mut pre,
8695            &mut post,
8696            &mut comb,
8697        );
8698        let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
8699        let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
8700        let want_comb = [
8701            0.5996052,
8702            0.282_535_9,
8703            0.09218107,
8704            0.025676856,
8705            0.17564717,
8706            0.22228767,
8707            0.271_745_4,
8708            0.330_318_8,
8709            0.029528176,
8710            0.12206022,
8711            0.32619134,
8712            0.5222193,
8713            0.19521846,
8714            0.37311527,
8715            0.30988118,
8716            0.12178412,
8717        ];
8718        for (i, w) in want_pre.iter().enumerate() {
8719            assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
8720        }
8721        for (i, w) in want_post.iter().enumerate() {
8722            assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
8723        }
8724        for (i, w) in want_comb.iter().enumerate() {
8725            assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
8726        }
8727    }
8728
8729    /// Sinkhorn's whole point is a doubly stochastic matrix: every row and
8730    /// every column sums to one. If the alternating normalization is wrong
8731    /// (or the loop count is off by one) the sums drift, and the residual
8732    /// mixing quietly gains or loses mass on every layer.
8733    #[test]
8734    fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
8735        let hc = 4;
8736        let mix_hc = (2 + hc) * hc;
8737        // a deliberately lopsided projection
8738        let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
8739        let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
8740        let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
8741        hc_split_sinkhorn(
8742            &mixes,
8743            &[1.0, 1.0, 1.0],
8744            &base,
8745            hc,
8746            20,
8747            1e-6,
8748            &mut pre,
8749            &mut post,
8750            &mut comb,
8751        );
8752        for j in 0..hc {
8753            let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
8754            assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
8755            let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
8756            assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
8757        }
8758        // pre is a gate in (eps, 1+eps); post carries the factor 2
8759        assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
8760        assert!(post.iter().all(|&v| (0.0..=2.0).contains(&v)));
8761    }
8762
8763    /// Folding four copies and expanding them back must preserve a constant
8764    /// state exactly when the block contributes nothing: with post = 0 the
8765    /// expansion is a doubly stochastic mix of identical copies, i.e. itself.
8766    #[test]
8767    fn expand_of_identical_copies_is_a_fixed_point() {
8768        let (hc, dim) = (4usize, 3usize);
8769        let residual: Vec<f32> = std::iter::repeat_n([1.5f32, -2.0, 0.25], hc)
8770            .flatten()
8771            .collect();
8772        let comb = {
8773            // exactly doubly stochastic: uniform
8774            vec![0.25f32; hc * hc]
8775        };
8776        let post = vec![0.0f32; hc];
8777        let mut out = vec![0.0f32; hc * dim];
8778        hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
8779        for (o, r) in out.iter().zip(&residual) {
8780            assert!((o - r).abs() < 1e-6, "{o} vs {r}");
8781        }
8782    }
8783
8784    /// The bias must move the SELECTION without touching the weights: with a
8785    /// large bias on a low-scoring expert it gets picked, but its weight is
8786    /// still its own (small) score, renormalized.
8787    #[test]
8788    fn selection_bias_steers_the_choice_but_not_the_weights() {
8789        let scores = [3.0f32, 0.1, 2.0, 0.05];
8790        let bias = [0.0f32, 10.0, 0.0, 0.0];
8791        let (mut idx, mut w) = (Vec::new(), Vec::new());
8792        route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
8793        assert_eq!(idx[0], 1, "the biased expert must win selection");
8794        assert_eq!(idx[1], 0);
8795        // weights come from sqrt(softplus(score)) BEFORE the bias, so the
8796        // biased expert's share must be the smaller of the two
8797        assert!(w[0] < w[1], "biased expert kept its own (small) weight");
8798        let sum: f32 = w.iter().sum();
8799        assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
8800    }
8801
8802    /// The sink is an extra logit with no value: it must lower every
8803    /// weight without adding output. With a huge sink the head should
8804    /// attend to almost nothing.
8805    #[test]
8806    fn attention_sink_drains_weight_without_contributing_output() {
8807        let hd = 2;
8808        let q = [1.0f32, 0.0];
8809        let kv = [1.0f32, 0.0, 0.0, 1.0];
8810        let mut out = vec![0.0f32; hd];
8811        sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
8812        let plain = out.clone();
8813        assert!(plain[0] > plain[1], "the aligned key must dominate");
8814        sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
8815        assert!(
8816            out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
8817            "a large sink must drain nearly all the mass: {out:?}"
8818        );
8819    }
8820
8821    /// A masked slot must be ignored entirely — not folded in as a zero
8822    /// key, which would still add exp(0) to the denominator.
8823    #[test]
8824    fn masked_positions_leave_the_denominator_alone() {
8825        let hd = 2;
8826        let q = [1.0f32, 0.0];
8827        let kv = [1.0f32, 0.0, 0.0, 1.0];
8828        let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
8829        sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
8830        sparse_attend(
8831            &q,
8832            &kv,
8833            &[0, usize::MAX],
8834            f32::NEG_INFINITY,
8835            1.0,
8836            hd,
8837            &mut b,
8838        );
8839        for (x, y) in a.iter().zip(&b) {
8840            assert!((x - y).abs() < 1e-6, "{x} vs {y}");
8841        }
8842    }
8843
8844    /// Forward then inverse rotation is the identity — the property the
8845    /// output path depends on.
8846    #[test]
8847    fn rope_tail_inverts_itself() {
8848        let inv_freq = [1.0f32, 0.5];
8849        let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
8850        let mut v = orig;
8851        rope_tail(&mut v, &inv_freq, 7, 4, false);
8852        assert!(v[..2] == orig[..2], "the non-rope head must not move");
8853        assert!(v[2..] != orig[2..], "the tail must actually rotate");
8854        rope_tail(&mut v, &inv_freq, 7, 4, true);
8855        for (a, b) in v.iter().zip(&orig) {
8856            assert!((a - b).abs() < 1e-5, "{a} vs {b}");
8857        }
8858    }
8859
8860    /// The window pooling is a softmax per DIMENSION over the ratio, with
8861    /// the position bias inside the exponent.
8862    #[test]
8863    fn compressor_pools_the_window_per_dimension() {
8864        let (ratio, width) = (2usize, 2usize);
8865        let kv = [1.0f32, 10.0, 3.0, 20.0];
8866        // dim 0: equal scores → mean; dim 1: second token wins by a mile
8867        let score = [0.0f32, 0.0, 0.0, 50.0];
8868        let ape = vec![0.0f32; ratio * width];
8869        let mut out = vec![0.0f32; width];
8870        compress_window(&kv, &score, &ape, ratio, width, &mut out);
8871        assert!(
8872            (out[0] - 2.0).abs() < 1e-5,
8873            "equal scores average: {}",
8874            out[0]
8875        );
8876        assert!(
8877            (out[1] - 20.0).abs() < 1e-3,
8878            "a dominant score wins: {}",
8879            out[1]
8880        );
8881    }
8882
8883    /// A negative dot product must not drag a position down: the relu
8884    /// means heads abstain rather than veto.
8885    #[test]
8886    fn index_scores_relu_before_weighting() {
8887        let (nh, hd) = (2usize, 2usize);
8888        // head 0 aligns with position 0, head 1 anti-aligns with it
8889        let q = [1.0f32, 0.0, -1.0, 0.0];
8890        let kv = [1.0f32, 0.0, 0.0, 1.0];
8891        let w = [1.0f32, 1.0];
8892        let mut sc = Vec::new();
8893        index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
8894        // without the relu the anti-aligned head would cancel head 0 to zero
8895        assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
8896    }
8897
8898    #[test]
8899    fn index_scores_mask_the_future() {
8900        let (nh, hd) = (1usize, 2usize);
8901        let q = [1.0f32, 0.0];
8902        let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
8903        let w = [1.0f32];
8904        let mut sc = Vec::new();
8905        index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
8906        assert!(sc[0].is_finite() && sc[1].is_finite());
8907        assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
8908        let mut idx = Vec::new();
8909        top_k_positions(&sc, 3, &mut idx);
8910        assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
8911    }
8912
8913    #[test]
8914    fn top_k_is_deterministic_on_ties() {
8915        let sc = [1.0f32, 1.0, 1.0, 0.0];
8916        let mut idx = Vec::new();
8917        top_k_positions(&sc, 2, &mut idx);
8918        assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
8919    }
8920
8921    /// The block cycle must leave the state's SHAPE intact (hc copies in,
8922    /// hc copies out) and must actually route the block's output back in:
8923    /// a block that writes a constant has to move every copy.
8924    #[test]
8925    fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
8926        let cfg = Dsv4Cfg {
8927            dim: 4,
8928            n_heads: 1,
8929            head_dim: 4,
8930            rope_head_dim: 2,
8931            q_lora_rank: 4,
8932            o_lora_rank: 2,
8933            o_groups: 1,
8934            hc_mult: 4,
8935            hc_sinkhorn_iters: 20,
8936            hc_eps: 1e-6,
8937            norm_eps: 1e-6,
8938            n_routed_experts: 2,
8939            top_k: 1,
8940            moe_inter: 4,
8941            route_scale: 1.0,
8942            swiglu_limit: 10.0,
8943            window: 128,
8944            index_topk: 4,
8945            vocab: 8,
8946        };
8947        let (hc, dim) = (cfg.hc_mult, cfg.dim);
8948        let mix_hc = (2 + hc) * hc;
8949        let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
8950            .map(|i| ((i % 13) as f32 - 6.0) * 0.05)
8951            .collect();
8952        let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
8953        let norm_w = vec![1.0f32; dim];
8954        let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
8955        let before = state.clone();
8956        let mut scratch = HcScratch::new(&cfg);
8957        hc_block(
8958            &mut state,
8959            &hc_fn,
8960            &[1.0, 1.0, 1.0],
8961            &hc_base,
8962            &norm_w,
8963            &cfg,
8964            &mut scratch,
8965            None,
8966            |_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
8967        );
8968        assert_eq!(state.len(), before.len(), "copy structure must survive");
8969        assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
8970        assert!(
8971            state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
8972            "the block's output has to reach the state"
8973        );
8974    }
8975
8976    #[test]
8977    fn hash_route_reads_the_table_row() {
8978        // vocab 3, top_k 2
8979        let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
8980        assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
8981        assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
8982        // out-of-range ids clamp instead of panicking
8983        assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
8984    }
8985
8986    /// A task mask restricts SELECTION and nothing else: the weights still
8987    /// come from the pre-bias scores and still renormalize, now over what
8988    /// survives. Masking must never reroute — an expert the mask forbids has
8989    /// to be absent, not replaced by a neighbour with the wrong weight.
8990    #[test]
8991    fn a_task_mask_restricts_selection_and_renormalizes() {
8992        // Expert 3 scores highest, then 1, then 2, then 0.
8993        let scores = [0.1f32, 4.0, 1.0, 9.0];
8994        let (mut idx, mut w) = (Vec::new(), Vec::new());
8995        route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
8996        assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
8997        let sum: f32 = w.iter().sum();
8998        assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
8999
9000        // Forbid the winner: the next two take its place and the weights
9001        // renormalize over them.
9002        let mask = [true, false, true, true];
9003        let (mut i2, mut w2) = (Vec::new(), Vec::new());
9004        route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
9005        assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
9006        let sum2: f32 = w2.iter().sum();
9007        assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
9008
9009        // A mask leaving fewer than top_k experts yields fewer, not garbage.
9010        let tight = [false, false, false, true];
9011        let (mut i3, mut w3) = (Vec::new(), Vec::new());
9012        route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
9013        assert_eq!(i3, vec![3]);
9014        assert_eq!(w3.len(), 1);
9015    }
9016
9017    /// On a hash layer the reference gathers the scores AT THE TABLE's
9018    /// experts. Choosing top-k first and swapping the indices afterwards
9019    /// leaves every weight attached to a different expert than the one it
9020    /// scales — silently, since both lists are the right length.
9021    #[test]
9022    fn hash_layers_weight_the_experts_the_table_names() {
9023        // Expert 3 scores highest, expert 0 lowest; the table names 0 and 1.
9024        let scores = [0.1f32, 0.4, 0.2, 5.0];
9025        let table = vec![0.0f32, 1.0];
9026        let idx_forced = hash_route(&table, 1, 2, 0);
9027        assert_eq!(idx_forced, vec![0, 1]);
9028
9029        let (mut idx, mut w) = (Vec::new(), Vec::new());
9030        route(
9031            &scores,
9032            None,
9033            2,
9034            1.0,
9035            Some(&idx_forced),
9036            None,
9037            &mut idx,
9038            &mut w,
9039        );
9040        assert_eq!(idx, vec![0, 1], "the table must decide the experts");
9041
9042        // The weights must be the table experts' own scores, normalized.
9043        let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
9044        let (s0, s1) = (sp(scores[0]), sp(scores[1]));
9045        let tot = s0 + s1;
9046        assert!(
9047            (w[0] - s0 / tot).abs() < 1e-6,
9048            "w[0]={} want {}",
9049            w[0],
9050            s0 / tot
9051        );
9052        assert!(
9053            (w[1] - s1 / tot).abs() < 1e-6,
9054            "w[1]={} want {}",
9055            w[1],
9056            s1 / tot
9057        );
9058
9059        // And the top-k path is untouched: expert 3 still wins there.
9060        let (mut idx2, mut w2) = (Vec::new(), Vec::new());
9061        route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
9062        assert_eq!(idx2[0], 3, "without a table the highest score still wins");
9063    }
9064}
9065
9066// ══ DSpark: the block-parallel draft ══════════════════════════════════
9067//
9068// Not a classic MTP chain. One pass through the three stages produces the
9069// WHOLE block of `block_size` positions at once: position 0 carries the token
9070// the trunk just emitted, the rest carry a noise token, and every position
9071// attends to every other one — which is why the block cannot be measured a
9072// position at a time and pretend to be faithful. Depth comes from the block,
9073// not from the stage count.
9074//
9075// The stages' KV cache is built from the trunk's hidden state, not from the
9076// draft's own tokens: one entry per real position, `kv_norm(wkv(main_x))`,
9077// in a ring of `window`. The block's own keys and values are appended for
9078// the duration of the block and then discarded.
9079
9080/// The noise token the block's unknown positions carry
9081/// (`dspark_noise_token_id`).
9082pub const DSPARK_NOISE_TOKEN: u32 = 128799;
9083/// `dspark_block_size` — the width of the draft block, and NOT a tuning knob.
9084///
9085/// All five positions attend to each other and the model was trained with
9086/// exactly four noise slots behind the real token, so a narrower block is a
9087/// different draft model, not a cheaper one. What the survival curve argues
9088/// for is verifying fewer of the five — see `dspark_verify_k` — which costs
9089/// less without changing what the draft computes.
9090pub fn dspark_block() -> usize {
9091    5
9092}
9093
9094/// How many of the block's proposals the trunk actually checks.
9095///
9096/// Survival is [0.67, 0.50, 0.29, 0.08, 0.04]: positions four and five are
9097/// paid for on every verify and delivered on a twelfth of them. Three yields
9098/// 2.46 tokens a cycle against five's 2.58, for three fifths of the verify.
9099/// `CMF_DSPARK_VERIFY_K=N` sets it.
9100#[cfg(feature = "gpu")]
9101fn dspark_native_on() -> bool {
9102    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9103    *ON.get_or_init(|| {
9104        std::env::var("CMF_DSPARK_NATIVE")
9105            .map(|v| v != "0")
9106            // The q4 checkpoint's native draft accepts far more proposals
9107            // than its upload-time q4→q2 recode. A q2 file stays q2 below.
9108            .unwrap_or(true)
9109    })
9110}
9111
9112pub fn dspark_verify_k() -> usize {
9113    static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
9114    *K.get_or_init(|| {
9115        std::env::var("CMF_DSPARK_VERIFY_K")
9116            .ok()
9117            .and_then(|v| v.parse::<usize>().ok())
9118            .filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
9119            .unwrap_or(DSPARK_BLOCK_MAX)
9120    })
9121}
9122
9123/// The trained block width.
9124pub const DSPARK_BLOCK_MAX: usize = 5;
9125
9126/// Per-sequence state of the draft: one KV ring per stage, and the trunk
9127/// hidden states the block's input is projected from.
9128pub struct DsparkState {
9129    /// `[stage][window * kv_width]`, written at `pos % window`.
9130    pub win: Vec<Vec<f32>>,
9131    /// How many real positions each ring holds, capped at `window`.
9132    pub filled: Vec<usize>,
9133    /// The trunk's captured hidden, `dim * n_targets`, refreshed every token.
9134    pub main_hidden: Vec<f32>,
9135    /// True once `main_hidden` holds this position's capture.
9136    pub have_hidden: bool,
9137}
9138
9139impl DsparkState {
9140    pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
9141        Self {
9142            win: vec![Vec::new(); stages],
9143            filled: vec![0; stages],
9144            main_hidden: vec![0.0; cfg.dim * targets],
9145            have_hidden: false,
9146        }
9147    }
9148}
9149
9150/// Which trunk layers the draft reads. Upstream names them explicitly
9151/// (`dspark_target_layer_ids`); the file says the same thing less directly —
9152/// `main_proj` has one `dim`-wide input block per captured layer — and the
9153/// release captures the last three. Deriving it from the weight keeps the
9154/// two from disagreeing.
9155pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
9156    let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
9157        return Vec::new();
9158    };
9159    let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
9160    (n_layers - n..n_layers).collect()
9161}
9162
9163thread_local! {
9164    /// The armed capture: which layers to take, and the buffer they fill.
9165    /// A thread-local rather than a parameter because the capture has to
9166    /// reach into the middle of a layer loop that eight call sites share,
9167    /// and threading an optional buffer through all of them to serve one
9168    /// diagnostic is a worse trade than this.
9169    static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
9170        const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
9171}
9172
9173/// Arm the capture for the layers `targets`, in order.
9174pub fn dspark_arm(targets: &[usize], dim: usize) {
9175    DSPARK_CAP.with(|c| {
9176        let mut c = c.borrow_mut();
9177        c.0 = targets.to_vec();
9178        c.1 = vec![0.0; dim * targets.len()];
9179        c.2 = 0;
9180    });
9181}
9182
9183/// Whether the armed MTP capture needs the state immediately after `li`.
9184/// The normal decode path keeps a full run in one submission; DSpark is the
9185/// only caller that needs an intermediate state to cross the device boundary.
9186fn dspark_wants(li: usize) -> bool {
9187    DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
9188}
9189
9190/// Called after every host layer. Free when nothing is armed.
9191pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
9192    DSPARK_CAP.with(|c| {
9193        let mut c = c.borrow_mut();
9194        if c.0.is_empty() {
9195            return;
9196        }
9197        if let Some(slot) = c.0.iter().position(|&t| t == li) {
9198            let (_, buf, seen) = &mut *c;
9199            dspark_capture(state, cfg, slot, buf);
9200            // Counted, not "was the last one" — under the device chain only
9201            // the layers left on the host call this, and taking the last
9202            // target as the signal would hand the draft a buffer whose other
9203            // slots still hold the previous token, or nothing at all.
9204            *seen = if slot == 0 { 1 } else { *seen + 1 };
9205            if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9206                eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
9207            }
9208        }
9209    });
9210}
9211
9212/// Read one slot of the armed capture buffer as-is, complete or not. The
9213/// speculative verify fills the DEVICE targets from its own photographs and
9214/// only needs the host layers' slots from here — `dspark_take`'s
9215/// completeness contract would never be met on that path.
9216pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
9217    DSPARK_CAP.with(|c| {
9218        let c = c.borrow();
9219        let lo = slot * dim;
9220        if c.1.len() < lo + dim {
9221            return false;
9222        }
9223        out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
9224        true
9225    })
9226}
9227
9228/// Move the capture out, if this token produced a complete one.
9229pub fn dspark_take(out: &mut Vec<f32>) -> bool {
9230    DSPARK_CAP.with(|c| {
9231        let mut c = c.borrow_mut();
9232        if c.0.is_empty() || c.2 != c.0.len() {
9233            if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
9234                eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
9235            }
9236            return false;
9237        }
9238        out.clear();
9239        out.extend_from_slice(&c.1);
9240        c.2 = 0;
9241        true
9242    })
9243}
9244
9245/// The trunk's contribution: the mean over the hyper-connection copies,
9246/// appended in target order. Costs one pass over `hc * dim` per captured
9247/// layer and nothing else.
9248pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
9249    let (hc, dim) = (cfg.hc_mult, cfg.dim);
9250    let dst = &mut out[slot * dim..(slot + 1) * dim];
9251    let inv = 1.0 / hc as f32;
9252    for d in 0..dim {
9253        let mut s = 0.0;
9254        for j in 0..hc {
9255            s += state[j * dim + d];
9256        }
9257        dst[d] = s * inv;
9258    }
9259}
9260
9261/// `CMF_DSPARK_PICK_DUMP=path` — accumulate the draft's expert picks per
9262/// stage and periodically rewrite `path` with `stage<TAB>expert<TAB>count`
9263/// lines. Rewritten every 32 blocks rather than at exit, so a run that is
9264/// killed still leaves the tallies on disk.
9265pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
9266    static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9267        std::sync::Mutex::new(None);
9268    let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
9269        return;
9270    };
9271    let mut g = FREQ.lock().unwrap();
9272    let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9273    for (stage, idx) in picks {
9274        for &e in idx {
9275            *map.entry((*stage, e)).or_insert(0) += 1;
9276        }
9277    }
9278    *blocks += 1;
9279    if *blocks % 32 == 0 {
9280        let mut lines: Vec<_> = map.iter().collect();
9281        lines.sort();
9282        let body: String = lines
9283            .iter()
9284            .map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
9285            .collect();
9286        let _ = std::fs::write(&path, body);
9287    }
9288}
9289
9290/// `CMF_DSV4_TRUNK_PICK_DUMP=path` — the same tally for the TRUNK's layers:
9291/// `layer<TAB>expert<TAB>count`, rewritten every 32 tokens. The pick lists
9292/// come from the probe's own tally window, so only layers that route on the
9293/// host are counted — which is exactly the population a partial pack serves.
9294pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
9295    static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
9296        std::sync::Mutex::new(None);
9297    let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
9298        return;
9299    };
9300    let mut g = FREQ.lock().unwrap();
9301    let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
9302    for (li, idx) in picks {
9303        for &e in idx {
9304            *map.entry((*li, e)).or_insert(0) += 1;
9305        }
9306    }
9307    *blocks += 1;
9308    if *blocks % 32 == 0 {
9309        let mut lines: Vec<_> = map.iter().collect();
9310        lines.sort();
9311        let body: String = lines
9312            .iter()
9313            .map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
9314            .collect();
9315        let _ = std::fs::write(&path, body);
9316    }
9317}
9318
9319/// `CMF_DSPARK_MASK=path` — restrict the draft's routed experts to an
9320/// explicit per-stage keep-set: line `d` of the file lists the expert ids
9321/// stage `d` may route to, comma-separated. Weights renormalize over what
9322/// remains (the `Dsv4Layer::mask` contract). The draft only proposes — the
9323/// trunk still verifies every token — so a thinner draft costs acceptance,
9324/// never correctness. This is the offline dial for sizing a resident
9325/// device pack before one exists.
9326fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
9327    let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
9328        return;
9329    };
9330    let Ok(text) = std::fs::read_to_string(&path) else {
9331        eprintln!("DSpark: CMF_DSPARK_MASK={path} is unreadable — no mask applied");
9332        return;
9333    };
9334    for (d, line) in text.lines().enumerate() {
9335        let Some(m) = out.get_mut(d) else { break };
9336        let n = m.layer.experts.len();
9337        let mut mask = vec![false; n];
9338        let mut kept = 0usize;
9339        for tok in line.split(',') {
9340            if let Ok(e) = tok.trim().parse::<usize>() {
9341                if e < n && !mask[e] {
9342                    mask[e] = true;
9343                    kept += 1;
9344                }
9345            }
9346        }
9347        if kept == 0 {
9348            continue;
9349        }
9350        eprintln!("DSpark: stage {d} limited to {kept}/{n} experts");
9351        m.layer.mask = Some(mask);
9352    }
9353}
9354
9355/// The draft's device residency: which experts of each stage live on the
9356/// card, and how the device router reaches them.
9357///
9358/// The draft only proposes — the trunk verifies every token — so the pack
9359/// is free to keep a SUBSET of each stage's experts and mask the routing to
9360/// it: acceptance pays, correctness never does. The subset is chosen by
9361/// measured routing frequency (`CMF_DSPARK_PACK` names the tally file that
9362/// `CMF_DSPARK_PICK_DUMP` wrote; `CMF_DSPARK_RESIDENT` caps experts per
9363/// stage, default 48).
9364#[cfg(feature = "gpu")]
9365pub struct DsparkPack {
9366    pub stages: Vec<DsparkStagePack>,
9367    /// Gate/up requantized to q2tp at upload (the binary registered an
9368    /// encoder); the graph then dispatches the q2tp kernels.
9369    pub gu_q2: bool,
9370    /// The down planes too (native in the file, never requantized at
9371    /// upload); the graph dispatches the 2-bit down kernel.
9372    pub dn_q2: bool,
9373    /// Dequantized router and bias per stage, f32 — address-stable for the
9374    /// life of the pack, which is what the device's const cache needs.
9375    pub routers: Vec<Vec<f32>>,
9376    pub biases: Vec<Option<Vec<f32>>>,
9377}
9378
9379#[cfg(feature = "gpu")]
9380pub struct DsparkStagePack {
9381    /// Selectable experts (true = resident).
9382    pub mask: Vec<bool>,
9383    /// Global expert id → pack slot; usize::MAX where cold.
9384    pub to_slot: Vec<usize>,
9385    /// The same two as the device consumes them — u32, address-stable for
9386    /// the pack's lifetime (the const cache keys on the pointer).
9387    pub mask_u32: Vec<u32>,
9388    pub map_u32: Vec<u32>,
9389    /// (gate, up, down) directory indices, pack order, shared LAST.
9390    pub tensors: Vec<(usize, usize, usize)>,
9391    pub n_resident: usize,
9392    /// A protected subset inside the trunk's model-wide physical banks.
9393    /// None keeps the legacy independent local pack for adapters without
9394    /// descriptor-indexed storage arrays and for q2tp.
9395    global: Option<GlobalPack>,
9396}
9397
9398/// The q2tp encoder, registered by the binary that has one (the CLI's
9399/// converter owns the rung-search implementation and the engine must not
9400/// depend on the CLI). When present, the draft's gate/up experts are
9401/// requantized q4tp → q2tp AT UPLOAD — half the VRAM and the same kernels
9402/// the trunk's q2tp experts already use. Draft-only fidelity: acceptance
9403/// pays, correctness never does.
9404pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
9405    std::sync::OnceLock::new();
9406
9407/// `CMF_DSPARK_GPU=1` — the probe (and later the speculative loop) drafts
9408/// on the card instead of the CPU/disk tier.
9409#[cfg(feature = "gpu")]
9410pub fn dspark_gpu_on() -> bool {
9411    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9412    *ON.get_or_init(|| {
9413        std::env::var("CMF_DSPARK_GPU")
9414            .map(|v| v != "0")
9415            .unwrap_or(true)
9416    })
9417}
9418
9419/// The pack, built once per process (the stand runs one model).
9420#[cfg(feature = "gpu")]
9421pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
9422    static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
9423    P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new))
9424        .as_deref()
9425}
9426
9427/// Build and upload the draft's pack. Returns `None` when the stack is
9428/// absent, the budget refuses, or a stage's weights are not where the
9429/// device path needs them — the caller falls back to the CPU draft.
9430/// Reserve the VRAM the speculative draft's device pack will take, so the
9431/// trunk's greedy packing leaves it room. Called at load, before any trunk
9432/// pack is built; a no-op when there is no MTP stack or speculation is off.
9433/// The estimate uses the draft's native dtypes — an upload-time re-encode
9434/// only shrinks it, which errs on the safe side of the physical ceiling.
9435///
9436/// A budget that cannot pack the trunk to the draft's capture layers gets NO
9437/// reservation.  Host-batch verify is exact there, but the measured A40 q4tp
9438/// result is 0.63 tok/s versus the faster ordinary exact walk: reserving the
9439/// draft shrinks every trunk layer and makes speculation a net loss.  The
9440/// threshold is geometric (nine tenths of the trunk's own expert bytes plus
9441/// the draft), never a card name.
9442#[cfg(feature = "gpu")]
9443pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
9444    if mtp.is_empty() || std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0") || !dspark_gpu_on()
9445    {
9446        return;
9447    }
9448    // `=1` is the diagnostic force path used to measure a configuration the
9449    // zero-knob geometric gate would reject.  Production auto-selection keeps
9450    // the gate below; forcing must reserve before the trunk is packed or the
9451    // late draft upload simply OOMs.
9452    let forced = std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "1");
9453    let dt = |q2: bool| {
9454        if q2 {
9455            cortiq_core::TensorDtype::Q2TiledP
9456        } else {
9457            cortiq_core::TensorDtype::Q4TiledP
9458        }
9459    };
9460    let gu_q2 = mtp[0]
9461        .layer
9462        .experts
9463        .first()
9464        .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9465    let dn_q2 = mtp[0]
9466        .layer
9467        .experts
9468        .first()
9469        .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9470    // A native-q4 draft can share the descriptor-indexed trunk arena and
9471    // therefore needs no second expert reservation.  Do not arm automatic
9472    // speculation here yet: a frequency pack says which experts are common,
9473    // not that the draft's proposals pay for a five-token exact verify.  The
9474    // explicit diagnostic gate can exercise the shared path without OOM;
9475    // zero-knob production remains the measured faster exact walk until an
9476    // acceptance profile has passed its own quality/speed gate.
9477    if !gu_q2
9478        && !dn_q2
9479        && std::env::var("CMF_MOE_MASK").is_err()
9480        && crate::gpu_wgpu::dsv4_global_moe_supported()
9481    {
9482        crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9483        crate::gpu_wgpu::DRAFT_RESERVE.store(0, std::sync::atomic::Ordering::Relaxed);
9484        return;
9485    }
9486    let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim]).unwrap_or(0);
9487    let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter]).unwrap_or(0);
9488    let per = (2 * gu + dn) as u64;
9489    let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9490        .ok()
9491        .and_then(|v| v.parse().ok())
9492        // The default matches the measured acceptance plateau's low edge:
9493        // residency below it costs acceptance, above it only costs VRAM.
9494        .unwrap_or(40);
9495    // Routed residents per stage, plus each stage's shared expert.
9496    let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
9497    // The trunk's own expert bytes, from the route it will actually serve.
9498    // A task-specialist mask changes the physical working set: counting all
9499    // 256 rows here made a compact, fully resident masked trunk look like the
9500    // 158 GB general model, so the zero-knob gate silently disabled the draft
9501    // that is responsible for the second half of its speedup.  Hash layers
9502    // deliberately have no mask and still count every checkpoint-named row.
9503    // Keep the production gate used by the zero-knob path: if nearly all of
9504    // the effective trunk plus the draft cannot fit, spend the whole budget
9505    // on trunk slots.
9506    let trunk: u64 = layers
9507        .iter()
9508        .map(|l| {
9509            let Some(e) = l.experts.first() else {
9510                return 0;
9511            };
9512            let gu = cortiq_core::quant::expected_nbytes(
9513                dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9514                &[cfg.moe_inter, cfg.dim],
9515            )
9516            .unwrap_or(0);
9517            let dn = cortiq_core::quant::expected_nbytes(
9518                dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
9519                &[cfg.dim, cfg.moe_inter],
9520            )
9521            .unwrap_or(0);
9522            let routed = l
9523                .mask
9524                .as_deref()
9525                .map_or(l.experts.len(), |m| m.iter().filter(|&&open| open).count());
9526            ((2 * gu + dn) * (routed + 1)) as u64
9527        })
9528        .sum();
9529    if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
9530        if !forced && budget < trunk / 10 * 9 + bytes {
9531            return;
9532        }
9533    }
9534    // The pack is not the draft's whole physical footprint.  Its three
9535    // attention skeletons, block-axis activations, captures and retained
9536    // verify states are ordinary wgpu allocations and therefore do not
9537    // appear in the resident-weight ledger.  Keeping only `bytes` here made
9538    // q4tp fit on paper and then panic the A40 driver while building DSpark.
9539    // A geometry-scaled workspace (bounded to 512..1024 MiB) is separate from
9540    // the expert reservation: dsv4_draft_fit must hand back only PACK bytes,
9541    // never turn scratch headroom into more resident experts.
9542    let mib = 1024 * 1024u64;
9543    let workspace = match crate::gpu_wgpu::dsv4_vram_budget() {
9544        // Smaller discrete heaps have less slack between the reported weight
9545        // ceiling and the driver's physical allocation ceiling.  One GiB is
9546        // still only ~2% of an A40 and is cheaper than an OOM/restart.
9547        Some(b) if b <= 64 * 1024 * mib => 1024 * mib,
9548        _ => {
9549            ((cfg.dim * cfg.hc_mult * DSPARK_BLOCK_MAX * 4096) as u64).clamp(512 * mib, 1024 * mib)
9550        }
9551    };
9552    crate::gpu_wgpu::DRAFT_PACK_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
9553    crate::gpu_wgpu::DRAFT_RESERVE.store(
9554        bytes.saturating_add(workspace),
9555        std::sync::atomic::Ordering::Relaxed,
9556    );
9557}
9558
9559#[cfg(not(feature = "gpu"))]
9560pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
9561
9562#[cfg(feature = "gpu")]
9563pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
9564    if mtp.is_empty() {
9565        return None;
9566    }
9567    let model = mtp[0]
9568        .layer
9569        .experts
9570        .first()
9571        .and_then(|e| e.w1.model_arc())?;
9572    let native_q2 = mtp[0]
9573        .layer
9574        .experts
9575        .first()
9576        .is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9577    let dn_native = mtp[0]
9578        .layer
9579        .experts
9580        .first()
9581        .is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
9582    let global_share = !native_q2
9583        && !dn_native
9584        && std::env::var("CMF_MOE_MASK").is_err()
9585        && crate::gpu_wgpu::dsv4_global_moe_supported()
9586        && crate::gpu_wgpu::dsv4_global_moe_ready(&model);
9587    let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
9588        .ok()
9589        .and_then(|v| v.parse().ok())
9590        .unwrap_or_else(|| {
9591            if global_share {
9592                // The measured acceptance plateau starts here. These
9593                // rows occupy existing arena slots rather than asking
9594                // the weight allocator for another multi-GiB pack.
9595                return 40;
9596            }
9597            // No knob: take what the card actually has left, whatever the
9598            // card is. The stages split the fit evenly after their shared
9599            // experts. Forty is the measured acceptance plateau: larger
9600            // packs still make the router and upload more rows without a
9601            // useful increase in accepted tokens (64 was slower on A40).
9602            let gu_q2 = native_q2 || (!dspark_native_on() && DSPARK_Q2TP_ENCODE.get().is_some());
9603            let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_native);
9604            (room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 40)
9605        });
9606    // Frequency tallies: lines of `stage<TAB>expert<TAB>count`. Named by
9607    // `CMF_DSPARK_PACK`, or found as `<model>.dspark.tsv` beside the model
9608    // file — ship the tally next to the checkpoint and no knob is needed.
9609    let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
9610    let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
9611        let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
9612        let mut s = m.path.as_os_str().to_os_string();
9613        s.push(".dspark.tsv");
9614        let p = std::path::PathBuf::from(s);
9615        p.exists().then(|| p.to_string_lossy().into_owned())
9616    });
9617    if let Some(path) = pack_path {
9618        if let Ok(text) = std::fs::read_to_string(&path) {
9619            for line in text.lines() {
9620                let mut it = line.split_whitespace();
9621                if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
9622                    if let (Ok(s), Ok(e), Ok(n)) =
9623                        (s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
9624                    {
9625                        if s < freq.len() {
9626                            freq[s].push((n, e));
9627                        }
9628                    }
9629                }
9630            }
9631        }
9632    }
9633    let mut stages = Vec::with_capacity(mtp.len());
9634    let mut routers = Vec::with_capacity(mtp.len());
9635    let mut biases = Vec::with_capacity(mtp.len());
9636    for (si, m) in mtp.iter().enumerate() {
9637        let l = &m.layer;
9638        let n = l.experts.len();
9639        // Frequency order, then the untallied ids — a cold start still
9640        // packs SOMETHING deterministic.
9641        let mut order: Vec<usize> = {
9642            let mut f = freq[si].clone();
9643            f.sort_by(|a, b| b.0.cmp(&a.0));
9644            let mut seen = vec![false; n];
9645            let mut o: Vec<usize> = f
9646                .into_iter()
9647                .map(|(_, e)| e)
9648                .filter(|&e| {
9649                    if e < n && !seen[e] {
9650                        seen[e] = true;
9651                        true
9652                    } else {
9653                        false
9654                    }
9655                })
9656                .collect();
9657            o.extend((0..n).filter(|&e| !seen[e]));
9658            o
9659        };
9660        order.truncate(n_res.min(n));
9661        let (global, mask, to_slot, tensors) = if global_share {
9662            let pk = pack_for(l, cfg, model.header.arch.num_layers + si)?;
9663            let gl = pk.global.clone()?;
9664            let remap = gl.pool.pin_picks(&model, gl.layer, &order, &l.experts);
9665            let mut mask = vec![false; n];
9666            let mut to_slot = vec![usize::MAX; n];
9667            for e in 0..n {
9668                if remap.get(e).copied().unwrap_or(u32::MAX) != u32::MAX {
9669                    mask[e] = true;
9670                    to_slot[e] = remap[e] as usize;
9671                }
9672            }
9673            (Some(gl), mask, to_slot, Vec::new())
9674        } else {
9675            let mut mask = vec![false; n];
9676            let mut to_slot = vec![usize::MAX; n];
9677            let mut tensors = Vec::with_capacity(order.len() + 1);
9678            for (slot, &e) in order.iter().enumerate() {
9679                let ex = &l.experts[e];
9680                let (Some(w1), Some(w3), Some(w2)) =
9681                    (ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
9682                else {
9683                    return None;
9684                };
9685                mask[e] = true;
9686                to_slot[e] = slot;
9687                tensors.push((w1, w3, w2));
9688            }
9689            let (Some(s1), Some(s3), Some(s2)) = (
9690                l.shared.w1.model_idx(),
9691                l.shared.w3.model_idx(),
9692                l.shared.w2.model_idx(),
9693            ) else {
9694                return None;
9695            };
9696            tensors.push((s1, s3, s2));
9697            (None, mask, to_slot, tensors)
9698        };
9699        // The router and bias, dequantized once.
9700        let mut router = vec![0.0f32; n * cfg.dim];
9701        for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
9702            l.gate.row_f32(r, row);
9703        }
9704        routers.push(router);
9705        biases.push(l.gate_bias.clone());
9706        let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
9707        let map_u32: Vec<u32> = to_slot
9708            .iter()
9709            .map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
9710            .collect();
9711        stages.push(DsparkStagePack {
9712            mask,
9713            to_slot,
9714            mask_u32,
9715            map_u32,
9716            tensors,
9717            n_resident: order.len(),
9718            global,
9719        });
9720    }
9721    // ── upload: the small skeleton FIRST, the expert stacks after — the
9722    //    documented admission order (experts fill the card and the skeleton
9723    //    then misses). ──
9724    let mut skeleton = Vec::new();
9725    for m in mtp {
9726        let l = &m.layer;
9727        for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
9728            skeleton.push(t.model_idx()?);
9729        }
9730    }
9731    if let Some(mp) = mtp[0].main_proj.as_ref() {
9732        skeleton.push(mp.model_idx()?);
9733    }
9734    for &idx in &skeleton {
9735        if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
9736            eprintln!("DSpark: the draft skeleton does not fit in VRAM — the GPU draft is off");
9737            return None;
9738        }
9739    }
9740    // The dtype in the FILE decides: a properly converted CMF stores the
9741    // draft's gate/up as q2tp and uploads through the same path as the
9742    // trunk's 2-bit experts. The at-upload requant is only the fallback for
9743    // files published before the converter's q2tp profile covered the MTP
9744    // stack (and only when the binary registered an encoder).
9745    let gu_q2 = native_q2
9746        || (!crate::dsv4::dspark_native_on() && crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some());
9747    for (si, sp) in stages.iter().enumerate() {
9748        let ok = if sp.global.is_some() {
9749            true
9750        } else if native_q2 {
9751            crate::gpu_wgpu::dsv4_experts_ready(
9752                &model,
9753                &sp.tensors,
9754                cfg.moe_inter,
9755                cfg.dim,
9756                true,
9757                dn_native,
9758            )
9759        } else if gu_q2 {
9760            crate::gpu_wgpu::moe_expert_bufs_requant_gu(&model, &sp.tensors, cfg.moe_inter, cfg.dim)
9761                .is_some()
9762        } else {
9763            crate::gpu_wgpu::dsv4_experts_ready(
9764                &model,
9765                &sp.tensors,
9766                cfg.moe_inter,
9767                cfg.dim,
9768                false,
9769                false,
9770            )
9771        };
9772        if !ok {
9773            eprintln!(
9774                "DSpark: the experts of stage {si} ({} + shared) do not fit in VRAM — the GPU draft is off",
9775                sp.n_resident
9776            );
9777            return None;
9778        }
9779    }
9780    let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
9781    eprintln!(
9782        "DSpark: {} — {} stages of {} experts + shared",
9783        if global_share {
9784            "a reserved share of the unified pool"
9785        } else {
9786            "a separate draft pack on the card"
9787        },
9788        stages.len(),
9789        stages
9790            .iter()
9791            .map(|s| s.n_resident.to_string())
9792            .collect::<Vec<_>>()
9793            .join("/")
9794    );
9795    Some(DsparkPack {
9796        stages,
9797        gu_q2: if global_share { false } else { gu_q2 },
9798        dn_q2: dn_native,
9799        routers,
9800        biases,
9801    })
9802}
9803
9804/// Append one real position's entry to every stage's KV ring, from the
9805/// trunk captures in `ds.main_hidden`. The draft does this for the position
9806/// it drafts at; a speculative decode also owes an entry for every accepted
9807/// position it never drafted from — a hole in the ring silently starves
9808/// later blocks of context, which reads as "acceptance decayed" and not as
9809/// a bug.
9810pub fn dspark_ring_append(
9811    g: &Dsv4Globals,
9812    mtp: &[Dsv4Mtp],
9813    cfg: &Dsv4Cfg,
9814    ds: &mut DsparkState,
9815    pos: usize,
9816    pool: Option<&crate::pool::Pool>,
9817) {
9818    let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
9819    let inv_freq = &g.inv_freq_window;
9820    let Some(stage0) = mtp.first() else { return };
9821    let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
9822        return;
9823    };
9824    let mut main_x = vec![0.0f32; dim];
9825    mp.matvec(&ds.main_hidden, &mut main_x, pool);
9826    rms_weighted(&mut main_x, mn, cfg.norm_eps);
9827    for (si, m) in mtp.iter().enumerate() {
9828        let kvw = m.layer.wkv.rows();
9829        if ds.win[si].len() < cfg.window * kvw {
9830            ds.win[si].resize(cfg.window * kvw, 0.0);
9831        }
9832        let mut kv = vec![0.0f32; kvw];
9833        m.layer.wkv.matvec(&main_x, &mut kv, pool);
9834        rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
9835        rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
9836        let slot = pos % cfg.window;
9837        ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
9838        ds.filled[si] = (pos + 1).min(cfg.window);
9839    }
9840}
9841
9842/// The draft block on the card: one submission for all three stages and
9843/// five positions, states home in one fence, the head on the host. The
9844/// markov bias is skipped (its per-position chain through the previous
9845/// PROPOSAL is the one part a single graph cannot batch) — compare against
9846/// the CPU draft under `CMF_DSPARK_NO_MARKOV=1`.
9847#[cfg(feature = "gpu")]
9848#[allow(clippy::too_many_arguments)]
9849pub fn dspark_draft_gpu(
9850    g: &Dsv4Globals,
9851    mtp: &[Dsv4Mtp],
9852    cfg: &Dsv4Cfg,
9853    ds: &mut DsparkState,
9854    pack: &DsparkPack,
9855    kv_id: u64,
9856    last_token: u32,
9857    pos: usize,
9858    pool: Option<&crate::pool::Pool>,
9859    out_conf: &mut Vec<f32>,
9860) -> Vec<u32> {
9861    let (hc, dim) = (cfg.hc_mult, cfg.dim);
9862    let block = dspark_block();
9863    let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
9864        return Vec::new();
9865    };
9866    let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
9867        return Vec::new();
9868    };
9869    let Some(mp_idx) = mp.model_idx() else {
9870        return Vec::new();
9871    };
9872    let mut stages = Vec::with_capacity(mtp.len());
9873    for (si, m) in mtp.iter().enumerate() {
9874        let l = &m.layer;
9875        let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
9876            l.wq_a.model_idx(),
9877            l.wq_b.model_idx(),
9878            l.wo_a.model_idx(),
9879            l.wo_b.model_idx(),
9880            l.wkv.model_idx(),
9881        ) else {
9882            return Vec::new();
9883        };
9884        let sp = &pack.stages[si];
9885        stages.push(crate::gpu_wgpu::DsparkStageW {
9886            wq_a,
9887            wq_b,
9888            wo_a,
9889            wo_b,
9890            wkv,
9891            q_norm: &l.q_norm,
9892            kv_norm: &l.kv_norm,
9893            attn_norm: &l.attn_norm,
9894            ffn_norm: &l.ffn_norm,
9895            sink: &l.attn_sink,
9896            hc_attn_fn: &l.hc_attn_fn,
9897            hc_attn_scale: &l.hc_attn_scale,
9898            hc_attn_base: &l.hc_attn_base,
9899            hc_ffn_fn: &l.hc_ffn_fn,
9900            hc_ffn_scale: &l.hc_ffn_scale,
9901            hc_ffn_base: &l.hc_ffn_base,
9902            router: &pack.routers[si],
9903            bias: pack.biases[si].as_deref(),
9904            experts: &sp.tensors,
9905            mask_u32: &sp.mask_u32,
9906            map_u32: &sp.map_u32,
9907            global: sp.global.as_ref().map(|gl| crate::gpu_wgpu::Dsv4GlobalMoe {
9908                pool_uid: gl.pool.uid,
9909                shared_slot: gl.shared_slot,
9910                segment_slots: gl.pool.segment_slots as u32,
9911            }),
9912        });
9913    }
9914    let geom = crate::gpu_wgpu::DsparkGeom {
9915        dim,
9916        hc,
9917        nh: cfg.n_heads,
9918        hd: cfg.head_dim,
9919        rd: cfg.rope_head_dim,
9920        q_lora: cfg.q_lora_rank,
9921        o_lora: cfg.o_lora_rank,
9922        o_groups: cfg.o_groups,
9923        inter: cfg.moe_inter,
9924        n_experts: cfg.n_routed_experts,
9925        top_k: cfg.top_k,
9926        window: cfg.window,
9927        eps: cfg.norm_eps,
9928        hc_eps: cfg.hc_eps,
9929        sinkhorn_iters: cfg.hc_sinkhorn_iters,
9930        route_scale: cfg.route_scale,
9931        swiglu_limit: cfg.swiglu_limit,
9932        scale: (cfg.head_dim as f32).powf(-0.5),
9933        gu_q2: pack.gu_q2,
9934        dn_q2: pack.dn_q2,
9935    };
9936    // ── seed states: the real token, then noise, replicated over copies ──
9937    let ids: Vec<u32> = (0..block)
9938        .map(|i| {
9939            if i == 0 {
9940                last_token
9941            } else {
9942                DSPARK_NOISE_TOKEN
9943            }
9944        })
9945        .collect();
9946    let mut states0 = vec![0.0f32; block * hc * dim];
9947    let mut emb = vec![0.0f32; dim];
9948    for (i, &id) in ids.iter().enumerate() {
9949        g.embed.row_f32(id as usize, &mut emb);
9950        for j in 0..hc {
9951            states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
9952        }
9953    }
9954    let dspark_time = {
9955        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9956        *ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
9957    };
9958    let t0 = std::time::Instant::now();
9959    let filled = (pos + 1).min(cfg.window);
9960    let mut states = vec![0.0f32; block * hc * dim];
9961    if !crate::gpu_wgpu::dspark_graph(
9962        &model,
9963        &stages,
9964        geom,
9965        kv_id,
9966        mp_idx,
9967        mn,
9968        &ds.main_hidden,
9969        &states0,
9970        pos,
9971        filled,
9972        &g.inv_freq_window,
9973        block,
9974        &mut states,
9975    ) {
9976        return Vec::new();
9977    }
9978    for si in 0..mtp.len() {
9979        ds.filled[si] = filled;
9980    }
9981    let t_graph = t0.elapsed();
9982
9983    // ── head, on the host: fold, norm, one B-wide matmat, argmax ──
9984    let last = &mtp[mtp.len() - 1];
9985    let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
9986        last.hc_head_fn.as_ref(),
9987        last.hc_head_base.as_ref(),
9988        last.hc_head_scale,
9989        last.norm.as_ref(),
9990    ) else {
9991        return Vec::new();
9992    };
9993    let mut head_in = vec![0.0f32; block * dim];
9994    let mut pre_norms = vec![vec![0.0f32; dim]; block];
9995    for i in 0..block {
9996        hc_head_fold(
9997            &states[i * hc * dim..(i + 1) * hc * dim],
9998            hfn,
9999            hscale,
10000            hbase,
10001            cfg,
10002            pool,
10003            &mut head_in[i * dim..(i + 1) * dim],
10004        );
10005        pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10006        rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10007    }
10008    let t_fold = t0.elapsed();
10009    let mut logits = vec![0.0f32; block * cfg.vocab];
10010    // The B-axis q4tp kernel, one submission: `matmat` at B=5 falls to the
10011    // CPU tile path and measured 46 ms of a 60 ms draft.
10012    let head_gpu = g.head.model_idx().is_some_and(|hi| {
10013        crate::gpu_wgpu::q4tp_matvec_batch_for_test(
10014            &model,
10015            hi,
10016            &head_in,
10017            block,
10018            cfg.vocab,
10019            dim,
10020            &mut logits,
10021        )
10022    });
10023    if !head_gpu {
10024        g.head.matmat(&head_in, block, &mut logits, pool);
10025    }
10026    let t_head = t0.elapsed();
10027    // The markov bigram is not optional: without it acceptance fell 1.02 →
10028    // 0.42 on natural text. Its chain runs through the previous PROPOSAL,
10029    // so it stays position-by-position; the w2 matvec is big enough that
10030    // the QTensor route puts it on the card by itself.
10031    let mut proposals = Vec::with_capacity(block);
10032    out_conf.clear();
10033    let mut prev = last_token;
10034    let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10035    let mut bias = vec![0.0f32; cfg.vocab];
10036    for i in 0..block {
10037        let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10038        if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10039            w1.row_f32(prev as usize, &mut mk_embed);
10040            w2.matvec(&mk_embed, &mut bias, pool);
10041            for (a, b) in row.iter_mut().zip(&bias) {
10042                *a += *b;
10043            }
10044        }
10045        let mut best = 0usize;
10046        for v in 1..row.len() {
10047            if row[v] > row[best] {
10048                best = v;
10049            }
10050        }
10051        if let Some(cf) = last.confidence.as_ref() {
10052            let mut cat = pre_norms[i].clone();
10053            cat.extend_from_slice(&mk_embed);
10054            let mut sc = [0.0f32; 1];
10055            if cat.len() == cf.cols() {
10056                cf.matvec(&cat, &mut sc, pool);
10057            }
10058            out_conf.push(sc[0]);
10059        }
10060        proposals.push(best as u32);
10061        prev = best as u32;
10062    }
10063    if dspark_time {
10064        eprintln!(
10065            "DSpark GPU: graph {:.1} ms, folds {:.1}, head {:.1}, markov+argmax {:.1}",
10066            t_graph.as_secs_f64() * 1e3,
10067            (t_fold - t_graph).as_secs_f64() * 1e3,
10068            (t_head - t_fold).as_secs_f64() * 1e3,
10069            (t0.elapsed() - t_head).as_secs_f64() * 1e3,
10070        );
10071    }
10072    proposals
10073}
10074
10075/// One draft: `DSPARK_BLOCK` proposed tokens and a confidence per position.
10076///
10077/// `pos` is the position of `last_token` — the block predicts `pos+1 ..
10078/// pos+BLOCK`. Returns the proposals in order; `out_conf` takes the
10079/// confidence head's score where the last stage carries one.
10080#[allow(clippy::too_many_arguments)]
10081pub fn dspark_draft(
10082    g: &Dsv4Globals,
10083    mtp: &[Dsv4Mtp],
10084    cfg: &Dsv4Cfg,
10085    ds: &mut DsparkState,
10086    last_token: u32,
10087    pos: usize,
10088    pool: Option<&crate::pool::Pool>,
10089    out_conf: &mut Vec<f32>,
10090) -> Vec<u32> {
10091    let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
10092    let block = dspark_block();
10093    let inv_freq = &g.inv_freq_window;
10094
10095    // ── the block's input: main_norm(main_proj(captured hiddens)) ──
10096    let Some(stage0) = mtp.first() else {
10097        return Vec::new();
10098    };
10099    let (Some(_mp), Some(_mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
10100        return Vec::new();
10101    };
10102    dspark_ring_append(g, mtp, cfg, ds, pos, pool);
10103
10104    // ── the block: the real token, then noise ──
10105    let ids: Vec<u32> = (0..block)
10106        .map(|i| {
10107            if i == 0 {
10108                last_token
10109            } else {
10110                DSPARK_NOISE_TOKEN
10111            }
10112        })
10113        .collect();
10114    let mut states = vec![vec![0.0f32; hc * dim]; block];
10115    let mut emb = vec![0.0f32; dim];
10116    for (i, &id) in ids.iter().enumerate() {
10117        g.embed.row_f32(id as usize, &mut emb);
10118        for j in 0..hc {
10119            states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
10120        }
10121    }
10122
10123    let mut scratch = HcScratch::new(cfg);
10124    for (si, m) in mtp.iter().enumerate() {
10125        let l = &m.layer;
10126        let kvw = l.wkv.rows();
10127        // ── attention half: fold every position first, because each one's
10128        //    keys are visible to all the others. ──
10129        let mut post = vec![vec![0.0f32; hc]; block];
10130        let mut comb = vec![vec![0.0f32; hc * hc]; block];
10131        let mut resid = vec![vec![0.0f32; hc * dim]; block];
10132        let mut folded = vec![vec![0.0f32; dim]; block];
10133        let mix_hc = (2 + hc) * hc;
10134        for i in 0..block {
10135            hc_mixes(
10136                &states[i],
10137                &l.hc_attn_fn,
10138                mix_hc,
10139                cfg.norm_eps,
10140                pool,
10141                &mut scratch.mixes,
10142            );
10143            hc_split_sinkhorn(
10144                &scratch.mixes,
10145                &l.hc_attn_scale,
10146                &l.hc_attn_base,
10147                hc,
10148                cfg.hc_sinkhorn_iters,
10149                cfg.hc_eps,
10150                &mut scratch.pre,
10151                &mut post[i],
10152                &mut comb[i],
10153            );
10154            hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
10155            rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
10156            resid[i].copy_from_slice(&states[i]);
10157        }
10158        // Keys and values of the block itself — kept for this block only.
10159        let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
10160        let mut blk_kv = vec![0.0f32; block * kvw];
10161        l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
10162        for i in 0..block {
10163            let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
10164            rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
10165            rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
10166        }
10167        // The attended set: every cached real position, then the whole block.
10168        let win_len = ds.filled[si];
10169        let mut cache = Vec::with_capacity((win_len + block) * hd);
10170        for p in 0..win_len {
10171            let e = &ds.win[si][p * kvw..(p + 1) * kvw];
10172            cache.extend_from_slice(&e[kvw - hd..]);
10173        }
10174        for i in 0..block {
10175            let e = &blk_kv[i * kvw..(i + 1) * kvw];
10176            cache.extend_from_slice(&e[kvw - hd..]);
10177        }
10178        let idxs: Vec<usize> = (0..win_len + block).collect();
10179        let scale = (hd as f32).powf(-0.5);
10180        let qrank = l.wq_a.rows();
10181        let qdim = cfg.n_heads * hd;
10182        let mut qr = vec![0.0f32; block * qrank];
10183        l.wq_a.matmat(&folded_all, block, &mut qr, pool);
10184        for i in 0..block {
10185            rms_weighted(&mut qr[i * qrank..(i + 1) * qrank], &l.q_norm, cfg.norm_eps);
10186        }
10187        let mut q = vec![0.0f32; block * qdim];
10188        l.wq_b.matmat(&qr, block, &mut q, pool);
10189        let mut attn = vec![0.0f32; block * qdim];
10190        for i in 0..block {
10191            let qi = &mut q[i * qdim..(i + 1) * qdim];
10192            let ai = &mut attn[i * qdim..(i + 1) * qdim];
10193            let qpos = pos + 1 + i;
10194            for h in 0..cfg.n_heads {
10195                let head = &mut qi[h * hd..(h + 1) * hd];
10196                rms_inplace(head, cfg.norm_eps);
10197                rope_tail(head, inv_freq, qpos, rd, false);
10198            }
10199            for h in 0..cfg.n_heads {
10200                let qh = &qi[h * hd..(h + 1) * hd];
10201                let oh = &mut ai[h * hd..(h + 1) * hd];
10202                sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
10203                rope_tail(oh, inv_freq, qpos, rd, true);
10204            }
10205        }
10206        let mut blk_out = vec![0.0f32; block * dim];
10207        o_project_block(
10208            &attn,
10209            block,
10210            &l.wo_a,
10211            &l.wo_b,
10212            cfg.o_groups,
10213            cfg.o_lora_rank,
10214            pool,
10215            &mut blk_out,
10216        );
10217        for i in 0..block {
10218            let mut next = vec![0.0f32; hc * dim];
10219            hc_expand(
10220                &blk_out[i * dim..(i + 1) * dim],
10221                &resid[i],
10222                &post[i],
10223                &comb[i],
10224                hc,
10225                dim,
10226                &mut next,
10227            );
10228            states[i] = next;
10229        }
10230        // ── MoE: fold every position, group equal experts, then expand in
10231        //    the original per-position route order. ──
10232        let mut ffn_fold = vec![0.0f32; block * dim];
10233        let mut ffn_post = vec![vec![0.0f32; hc]; block];
10234        let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
10235        let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
10236        for i in 0..block {
10237            hc_mixes(
10238                &states[i],
10239                &l.hc_ffn_fn,
10240                mix_hc,
10241                cfg.norm_eps,
10242                pool,
10243                &mut scratch.mixes,
10244            );
10245            hc_split_sinkhorn(
10246                &scratch.mixes,
10247                &l.hc_ffn_scale,
10248                &l.hc_ffn_base,
10249                hc,
10250                cfg.hc_sinkhorn_iters,
10251                cfg.hc_eps,
10252                &mut scratch.pre,
10253                &mut ffn_post[i],
10254                &mut ffn_comb[i],
10255            );
10256            hc_fold(
10257                &states[i],
10258                &scratch.pre,
10259                hc,
10260                dim,
10261                &mut ffn_fold[i * dim..(i + 1) * dim],
10262            );
10263            rms_weighted(
10264                &mut ffn_fold[i * dim..(i + 1) * dim],
10265                &l.ffn_norm,
10266                cfg.norm_eps,
10267            );
10268            ffn_resid[i].copy_from_slice(&states[i]);
10269        }
10270        let mut moe_out = vec![0.0f32; block * dim];
10271        moe_step_block(&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out);
10272        for i in 0..block {
10273            let mut next = vec![0.0f32; hc * dim];
10274            hc_expand(
10275                &moe_out[i * dim..(i + 1) * dim],
10276                &ffn_resid[i],
10277                &ffn_post[i],
10278                &ffn_comb[i],
10279                hc,
10280                dim,
10281                &mut next,
10282            );
10283            states[i] = next;
10284        }
10285    }
10286
10287    // ── head: the last stage's fold, the trunk's own head ──
10288    let last = &mtp[mtp.len() - 1];
10289    let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
10290        last.hc_head_fn.as_ref(),
10291        last.hc_head_base.as_ref(),
10292        last.hc_head_scale,
10293        last.norm.as_ref(),
10294    ) else {
10295        return Vec::new();
10296    };
10297    let mut proposals = Vec::with_capacity(block);
10298    out_conf.clear();
10299    let mut prev = last_token;
10300    let mut head_in = vec![0.0f32; block * dim];
10301    let mut pre_norms = vec![vec![0.0f32; dim]; block];
10302    for i in 0..block {
10303        hc_head_fold(
10304            &states[i],
10305            hfn,
10306            hscale,
10307            hbase,
10308            cfg,
10309            pool,
10310            &mut head_in[i * dim..(i + 1) * dim],
10311        );
10312        pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
10313        rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
10314    }
10315    let mut logits = vec![0.0f32; block * cfg.vocab];
10316    g.head.matmat(&head_in, block, &mut logits, pool);
10317    let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
10318    for i in 0..block {
10319        let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
10320        // The markov head biases the logits from the PREVIOUS token — a
10321        // rank-256 bigram the draft samples through position by position,
10322        // while the network itself ran the whole block at once.
10323        // `CMF_DSPARK_NO_MARKOV=1` drops it: the bias is sequential through
10324        // the block (each position needs the previous PROPOSAL), which is
10325        // the one part of the draft a single device graph cannot batch — so
10326        // its acceptance value has to be known before it earns that
10327        // complexity.
10328        let no_markov = {
10329            static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10330            *ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
10331        };
10332        if no_markov {
10333            // Still feed the confidence head's embedding slot below.
10334            if let Some(w1) = last.markov_w1.as_ref() {
10335                w1.row_f32(prev as usize, &mut mk_embed);
10336            }
10337        } else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
10338            w1.row_f32(prev as usize, &mut mk_embed);
10339            let mut bias = vec![0.0f32; cfg.vocab];
10340            w2.matvec(&mk_embed, &mut bias, pool);
10341            for (a, b) in logits_i.iter_mut().zip(&bias) {
10342                *a += *b;
10343            }
10344        }
10345        let mut best = 0usize;
10346        for v in 1..logits_i.len() {
10347            if logits_i[v] > logits_i[best] {
10348                best = v;
10349            }
10350        }
10351        if let Some(cf) = last.confidence.as_ref() {
10352            let mut cat = pre_norms[i].clone();
10353            cat.extend_from_slice(&mk_embed);
10354            let mut s = [0.0f32; 1];
10355            if cat.len() == cf.cols() {
10356                cf.matvec(&cat, &mut s, pool);
10357            }
10358            out_conf.push(s[0]);
10359        }
10360        proposals.push(best as u32);
10361        prev = best as u32;
10362    }
10363    proposals
10364}