Skip to main content

memra_engine/
dsv4_gpu.rs

1//! DeepSeek-V4-Flash GPU trunk forward (lane 4): 2-card layer-split placement,
2//! correctness bring-up gated against the lane-2/3 CPU oracle fixtures.
3//!
4//! Plan of record: wt-dsv4-loader research/dsv4-flash-loader-20260818/RECEIPTS.md
5//! "Lane 4" (placement math, quant rungs, threshold derivation — banked BEFORE this
6//! module was written). Semantic law: darklanes SEMANTICS.md; arithmetic contract: the
7//! lane-3 CPU oracle (memra_gguf::dsv4_forward), whose host-side pieces
8//! (hc_split_sinkhorn, rope tables, index builders, routing math) are REUSED here
9//! verbatim so the CPU/GPU forks share one implementation of every host-side rule.
10//!
11//! Rungs (explicit): trunk routed experts stay AS-STORED NVFP4 on GPU and are
12//! dequantized per activated expert into a reused bf16 scratch (exact in bf16), all
13//! other quantized linears are host-dequantized (lane-1 proven decoders) to bf16 at
14//! load with a bit-level exactness refusal; f32 islands (SEMANTICS §7.2) run in
15//! dedicated f32/f64 kernels or on the host. bf16 enters ONLY at the activation inputs
16//! of the non-island GEMMs (cuBLASLt bf16, f32 accumulate).
17//!
18//! Multi-GPU: PP layer split (the engine's only executing multi-GPU idiom, pp.rs /
19//! Step-3.7-Flash precedent), split point derived from per-layer byte math, ONE hc-state
20//! boundary copy per forward via host bounce (peer copy is a perf-lane step).
21//!
22//! NOT a serving path: prefill-only, greedy continuation by re-prefill per step (the
23//! accepted O(n²) bring-up rung). Decode KV caching, CUDA-graph, batched serving and any
24//! perf claims belong to later lanes.
25
26use std::collections::BTreeMap;
27use std::os::raw::c_void;
28use std::path::Path;
29
30use cudarc::driver::{CudaSlice, CudaStream, DevicePtr, DevicePtrMut};
31use memra_gguf::dsv4_forward::{
32    ActQuantVariant, Dsv4Model, FreqsCis, compress_topk_idxs, hc_split_sinkhorn,
33    precompute_freqs_cis, window_topk_idxs,
34};
35
36use crate::dsv4_ffi as k;
37use crate::dsv4_ffi::ck;
38
39type Res<T> = Result<T, String>;
40
41fn e<E: std::fmt::Display>(what: &str) -> impl FnOnce(E) -> String + '_ {
42    move |err| format!("{what}: {err}")
43}
44
45// ---------------------------------------------------------------- host math (oracle twins)
46
47#[inline]
48fn sigmoid_f32(x: f32) -> f32 {
49    1.0 / (1.0 + (-x).exp())
50}
51
52/// torch softplus (beta=1, threshold=20) — same as the oracle's private softplus_f32.
53#[inline]
54fn softplus_f32(x: f32) -> f32 {
55    if x > 20.0 { x } else { x.exp().ln_1p() }
56}
57
58// ---------------------------------------------------------------- device buffers
59
60/// One stage = one GPU: its runtime handle plus the resident weights of its layer range.
61pub struct Stage {
62    pub dev: usize,
63    pub gpu: memra_runtime::Gpu,
64    pub layers: Vec<LayerDev>,
65    pub embed: Option<CudaSlice<u8>>, // bf16 raw [vocab, hidden] (stage 0)
66    pub head: Option<CudaSlice<u8>>,  // bf16 raw [vocab, hidden] (last stage)
67    pub trunk_norm: Option<CudaSlice<f32>>,
68    pub hc_head_fn: Option<CudaSlice<f32>>, // [hc, hc*hidden]
69    pub fc_yarn: CudaSlice<f32>,            // rope table, compressor layers [max_seq, rd]
70    pub fc_plain: CudaSlice<f32>,           // rope table, ratio-0 layers    [max_seq, rd]
71    pub ws: CudaSlice<u8>,                  // cuBLASLt workspace
72    pub deq: [CudaSlice<u8>; 3],            // expert dequant scratch, bf16 [inter*hidden] each
73    pub loaded_bytes: u64,                  // resident weight bytes uploaded to this device
74    // lane 8: device twins of the trunk hc_head gate constants (last stage)
75    pub hc_head_base_dev: Option<CudaSlice<f32>>,
76    pub hc_head_scale_dev: Option<CudaSlice<f32>>,
77}
78
79pub struct CmpDev {
80    pub ratio: usize,
81    pub d: usize,
82    pub latent: usize,
83    pub overlap: bool,
84    pub rotate: bool,
85    pub wkv: CudaSlice<f32>,   // f32 island
86    pub wgate: CudaSlice<f32>, // f32 island
87    pub norm: CudaSlice<f32>,
88    pub ape: CudaSlice<f32>, // [ratio, latent]
89}
90
91pub struct IdxDev {
92    pub wq_b: DenseBf16,         // bf16 [heads*hd, q_lora]
93    pub weights_proj: DenseBf16, // bf16 [heads, hidden]
94    pub wq_b_fp8: Option<Fp8Dense>,
95    pub weights_proj_fp8: Option<Fp8Dense>,
96    pub cmp: CmpDev,
97    pub heads: usize,
98    pub hd: usize,
99    pub topk: usize,
100}
101
102/// Iteration-5 FP8 dense arm (`MEMRA_DSV4_DENSE_ARM=fp8`): an FP8-blk linear held
103/// AS-STORED — e4m3 codes `[rows, cols]` plus the 128x128 block-scale grid decoded to
104/// f32 on the host (exact: every e8m0 code is a pow2; 0xFF refused at load). The device
105/// GEMV twins decode `e4m3(code) * scale` in-register — the SAME f32 value the bf16
106/// dequant slab holds (the loader's `f32_to_bf16_exact` refusal proves exactness), with
107/// the accumulation order VERBATIM — so the arm is bit-identical to the bf16 arm by
108/// construction and its gate is a no-regression proof. It5 ledger item 3: when this
109/// pair exists, the bf16 twin is NOT device-resident — it drops to [`DenseBf16::Host`]
110/// staged residency (the dual-residency +~2.7 GiB/card is gone).
111pub struct Fp8Dense {
112    pub codes: CudaSlice<u8>,   // e4m3, [rows, cols] row-major as stored
113    pub scales: CudaSlice<f32>, // [ceil(rows/128), sc_cols] host-decoded e8m0
114    pub sc_cols: usize,         // ceil(cols/128)
115    pub rows: usize,
116    pub cols: usize,
117}
118
119/// It5 ledger item 3 — residency of a dense bf16 slab. `Dev` = device-resident, today's
120/// exact bytes: the only realization when the dense arm is bf16, and always the
121/// realization for the drafter/MTP blocks (no fp8 twins this rung). `Host` = the fp8
122/// dense arm's STAGED residency: the same host-dequantized bf16 bytes the loader would
123/// have uploaded, kept host-side; the fp8 pair owns every device decode/verify read
124/// (via [`dwsel`]) and the prefill pass stages these bytes H2D per consuming call,
125/// the transient copy freed stream-ordered when the [`DenseView`] drops. This is the
126/// engine's existing staged-residency idiom (hybrid EDGE-1 `HostExps` / the moe-cache
127/// host-resident expert staging) translated to dsv4; dsv4 has no CUDA-graph capture,
128/// so the "release after capture" boundary degenerates to "never resident outside a
129/// prefill pass". Prefill's bf16 path is byte-identical by construction: the staged
130/// upload is the SAME `f32_to_bf16_exact` byte vector the resident slab held.
131pub enum DenseBf16 {
132    Dev(CudaSlice<u8>),
133    Host(Vec<u8>),
134}
135
136impl DenseBf16 {
137    /// The device-resident slab. Host residency here is an ENGINE bug, never an env
138    /// error: `Host` exists only when the fp8 arm is on, and every path that reaches
139    /// this accessor under fp8 (legacy decode combos, bf16-slab probes) is already a
140    /// boot refusal (hermes a4e3d9a8eab4cf17) or dwsel-routed to the fp8 twin.
141    pub fn dev(&self) -> &CudaSlice<u8> {
142        match self {
143            DenseBf16::Dev(d) => d,
144            DenseBf16::Host(_) => unreachable!(
145                "bf16 dense slab is host-staged (fp8 dense arm): this consumer must \
146                 ride the fp8 twins (dwsel) or the staged prefill view"
147            ),
148        }
149    }
150
151    /// Prefill-class access (block_forward / shared-expert finish): borrow the
152    /// resident slab, or stage the host bytes into a transient device copy freed
153    /// (stream-ordered, after the enqueued consumers) when the view drops.
154    fn staged(&self, stream: &std::sync::Arc<CudaStream>) -> Res<DenseView<'_>> {
155        Ok(match self {
156            DenseBf16::Dev(d) => DenseView::Res(d),
157            DenseBf16::Host(b) => DenseView::Tmp(upload_u8(stream, b)?),
158        })
159    }
160}
161
162/// A borrowed resident slab or a staged transient copy — see [`DenseBf16::staged`].
163pub enum DenseView<'a> {
164    Res(&'a CudaSlice<u8>),
165    Tmp(CudaSlice<u8>),
166}
167
168impl DenseView<'_> {
169    fn slab(&self) -> &CudaSlice<u8> {
170        match self {
171            DenseView::Res(d) => d,
172            DenseView::Tmp(d) => d,
173        }
174    }
175}
176
177/// Dense-weight pointer for the device-path GEMV wrappers: the bf16 dequant slab, or
178/// the as-stored FP8 pair when the dense arm is on. Copy of raw pointers only — built
179/// per call from the owning slabs via [`dwsel`].
180#[derive(Clone, Copy)]
181pub enum DW {
182    Bf16(*const c_void),
183    Fp8 {
184        codes: *const c_void,
185        scales: *const f32,
186        sc_cols: i32,
187    },
188}
189
190impl DW {
191    /// Row-offset view (the grouped wo_a slices): `rows_off` rows into the weight, row
192    /// width `cols`. The fp8 arm requires the offset to land on a scale-grid row
193    /// boundary (o_lora = 1024 = 8x128 — asserted, never assumed).
194    fn offset_rows(self, rows_off: usize, cols: usize) -> DW {
195        match self {
196            DW::Bf16(p) => DW::Bf16((p as usize + rows_off * cols * 2) as *const c_void),
197            DW::Fp8 {
198                codes,
199                scales,
200                sc_cols,
201            } => {
202                assert_eq!(
203                    rows_off % 128,
204                    0,
205                    "fp8 dense arm: grouped row offset {rows_off} not on the 128-row \
206                     scale-grid boundary"
207                );
208                DW::Fp8 {
209                    codes: (codes as usize + rows_off * cols) as *const c_void,
210                    scales: unsafe { scales.add((rows_off / 128) * sc_cols as usize) },
211                    sc_cols,
212                }
213            }
214        }
215    }
216}
217
218/// Select the weight realization for a device-path GEMV: the fp8 pair when the dense
219/// arm is on AND this tensor is FP8-blk stored, else the bf16 slab. `active` is
220/// `self.dense_fp8` — passed explicitly because the wrappers are associated fns.
221fn dwsel(
222    active: bool,
223    stream: &cudarc::driver::CudaStream,
224    w_bf16: &DenseBf16,
225    fp8: &Option<Fp8Dense>,
226) -> DW {
227    match fp8 {
228        Some(f) if active => DW::Fp8 {
229            codes: f.codes.device_ptr(stream).0 as *const c_void,
230            scales: f.scales.device_ptr(stream).0 as *const f32,
231            sc_cols: f.sc_cols as i32,
232        },
233        // item 3: reached only when the fp8 twin is absent or the arm is off, i.e.
234        // exactly when the bf16 slab IS device-resident — .dev() is the invariant.
235        _ => DW::Bf16(w_bf16.dev().device_ptr(stream).0 as *const c_void),
236    }
237}
238
239/// Routed-expert quantization recipe of a layer (lane-1 census: trunk = modelopt NVFP4,
240/// MTP = OCP MXFP4). Never inferred from ancestry — detected from the stored dtypes and
241/// sibling names, refused on any surprise.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum ExpertKind {
244    Nvfp4,
245    Mxfp4,
246}
247
248/// Lane 7: which expert-GEMM realization runs. `Bf16Dequant` = the lane-4 gated rung
249/// (on-the-fly exact dequant + cuBLASLt bf16, the fallback and A/B reference).
250/// `Native` = the reference-law quantized GEMMs (act_quant per-128 FP8 codes ×
251/// as-stored NVFP4/MXFP4 slabs, kernel.py fp4_gemm arithmetic — RECEIPTS.md "Lane 7").
252/// Selected by `MEMRA_DSV4_EXPERT_ARM=native` via [`memra_gguf::dsv4_forward::
253/// expert_arm_native`] — the SAME seam the CPU oracle reads, so one invocation can
254/// never mix numeric classes.
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub enum ExpertArm {
257    Bf16Dequant,
258    Native,
259}
260
261/// Lane 8: which decode-step realization runs (RECEIPTS.md "Lane 8"). `Legacy` = the
262/// lane-6/7 gated host-driven loop, byte-stable. `Device` = the device-resident step:
263/// preallocated workspace arena, device index build / fine top-k / router / Sinkhorn /
264/// head gate, one-launch-per-projection indirect expert dispatch, peer-copy PP
265/// boundary. `host_math: true` (seam `device-hostmath`) keeps Sinkhorn + router +
266/// fine-top-k + head-gate math on the HOST — the byte-identity instrument for the
267/// mechanical rungs; `false` (seam `device`) runs them as kernels (expf/log1pf
268/// realization fork, gated at class bounds per the lane-6/7 doctrine). Selected by
269/// MEMRA_DSV4_DECODE_PATH — read once at load and printed; one binary carries both
270/// arms for the interleaved A/B law.
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum DecodePath {
273    Legacy,
274    Device { host_math: bool },
275}
276
277pub struct LayerDev {
278    pub il: u32,
279    pub ratio: usize,
280    pub expert_kind: ExpertKind,
281    // attention (bf16 unless island; staged host residency under the fp8 dense arm)
282    pub wq_a: DenseBf16,
283    pub wq_b: DenseBf16,
284    pub wkv: DenseBf16,
285    pub wo_a: DenseBf16, // [o_groups*o_lora, hidden-group-width] grouped rows
286    pub wo_b: DenseBf16,
287    pub q_norm: CudaSlice<f32>,
288    pub kv_norm: CudaSlice<f32>,
289    pub attn_norm: CudaSlice<f32>,
290    pub ffn_norm: CudaSlice<f32>,
291    pub sink: CudaSlice<f32>,
292    pub cmp: Option<CmpDev>,
293    pub idx: Option<IdxDev>,
294    // hyper-connections (f32 island; base/scale live host-side)
295    pub hc_attn_fn: CudaSlice<f32>,
296    pub hc_ffn_fn: CudaSlice<f32>,
297    pub hc_attn_base: Vec<f32>,
298    pub hc_attn_scale: Vec<f32>,
299    pub hc_ffn_base: Vec<f32>,
300    pub hc_ffn_scale: Vec<f32>,
301    // lane 8: device twins of the host-side routing/hc constants (tiny; loaded always)
302    pub hc_attn_base_dev: CudaSlice<f32>,
303    pub hc_attn_scale_dev: CudaSlice<f32>,
304    pub hc_ffn_base_dev: CudaSlice<f32>,
305    pub hc_ffn_scale_dev: CudaSlice<f32>,
306    pub gate_bias_dev: Option<CudaSlice<f32>>,
307    /// i32 cast of tid2eid, range- and distinctness-validated at LOAD (the legacy path
308    /// asserts per token at route time; the device route kernel cannot).
309    pub tid2eid_dev: Option<CudaSlice<i32>>,
310    pub experts_s2_dev: CudaSlice<f32>,
311    // MoE
312    pub gate_w: CudaSlice<f32>, // f32 island [ne, hidden]
313    pub gate_bias: Option<Vec<f32>>,
314    pub tid2eid: Option<Vec<i64>>, // host routing table (hash layers)
315    pub experts_w: CudaSlice<u8>,  // expert slab: per (e, proj) nibble-pair bytes
316    pub experts_sc: CudaSlice<u8>, // expert slab: per (e, proj) scales (e4m3/16 or e8m0/32)
317    pub experts_s2: Vec<f32>,      // host [ne*3] scale_2 (NVFP4 only, asserted pow2)
318    pub shared_w: [DenseBf16; 3],  // bf16 shared expert w1/w2/w3
319    // iteration-5 FP8 dense arm: as-stored twins of the FP8-blk linears, Some only when
320    // MEMRA_DSV4_DENSE_ARM=fp8 AND the tensor is F8_E4M3-stored (trunk layers only —
321    // the drafter/MTP blocks ride the prefill helpers and keep bf16 this rung).
322    pub wq_a_fp8: Option<Fp8Dense>,
323    pub wq_b_fp8: Option<Fp8Dense>,
324    pub wkv_fp8: Option<Fp8Dense>,
325    pub wo_a_fp8: Option<Fp8Dense>,
326    pub wo_b_fp8: Option<Fp8Dense>,
327    pub shared_fp8: [Option<Fp8Dense>; 3],
328}
329
330/// Fixture-array capture (GPU twin of the oracle's BlockCapture, gathered host-side).
331#[derive(Default)]
332pub struct GpuCapture {
333    pub embed_out: Option<Vec<f32>>,
334    pub layer_out: BTreeMap<u32, Vec<f32>>,
335    pub attn_out: BTreeMap<u32, Vec<f32>>,
336    /// diagnostic (lane-6 bisect probe): post-attn-norm x, post-rope q, post-QAT kv,
337    /// post-derotation o — full [s, ...] arrays
338    pub x_dbg: BTreeMap<u32, Vec<f32>>,
339    pub q_dbg: BTreeMap<u32, Vec<f32>>,
340    pub kv_dbg: BTreeMap<u32, Vec<f32>>,
341    pub o_dbg: BTreeMap<u32, Vec<f32>>,
342    pub compressor_kv: BTreeMap<u32, (Vec<f32>, usize)>,
343    pub indexer_kv: BTreeMap<u32, (Vec<f32>, usize)>,
344    pub index_score: BTreeMap<u32, (Vec<f32>, usize)>,
345    /// lane 7: post-ffn-norm MoE input rows [s, hidden] (the real activation vectors
346    /// the native-GEMM kernel gate feeds to sampled experts)
347    pub moe_x: BTreeMap<u32, Vec<f32>>,
348    pub want: std::collections::BTreeSet<u32>,
349}
350
351/// MTP (NextN) block on the LAST stage (pp idiom: MTP -> last stage). Shares the trunk
352/// embed (host-gathered) and head; own norms/projections/block/hc_head (SEMANTICS §5).
353pub struct MtpDev {
354    pub layer: LayerDev, // layer id = n_trunk: ratio 0, score-routed, MXFP4 experts
355    pub enorm: CudaSlice<f32>,
356    pub hnorm: CudaSlice<f32>,
357    pub norm: CudaSlice<f32>,
358    pub e_proj: CudaSlice<u8>, // bf16
359    pub h_proj: CudaSlice<u8>, // bf16
360    pub hc_head_fn: CudaSlice<f32>,
361    pub hc_head_base: Vec<f32>,
362    pub hc_head_scale: Vec<f32>,
363}
364
365/// DSpark drafter on the LAST stage (iteration 3; semantics DSPARK-SEMANTICS.md,
366/// numeric truth = the lane-10 CPU oracle `memra_gguf::dsv4_dspark`). Loaded only
367/// under MEMRA_DSV4_DRAFTER=dspark (≈10.7 GiB resident on dev1 — VRAM plan in the
368/// iteration-3 receipts); config census pins ride the oracle's own
369/// `DsparkConfig::load` (refuse-on-drift, NextN refusal included).
370pub struct DsparkDev {
371    /// mtp.0..2 — layer ids n_trunk+k, ratio 0 (window-only), score-routed MXFP4.
372    pub blocks: Vec<LayerDev>,
373    pub main_proj: CudaSlice<u8>, // bf16 [hidden, n_targets*hidden]
374    pub main_norm: CudaSlice<f32>,
375    pub norm: CudaSlice<f32>, // mtp.2.norm (exit head)
376    /// markov factors held f32 at runtime (M:795-804 reference convention); the
377    /// bias GEMV runs the f32-island dots kernel (f64 accumulation, oracle class).
378    pub markov_w1: CudaSlice<f32>, // [vocab, rank]
379    pub markov_w2: CudaSlice<f32>, // [vocab, rank]
380    pub markov_w1_host: Vec<f32>, // host copy (row gather per chained id)
381    pub conf_w: CudaSlice<f32>, // f32 [hidden + rank] (fp32 head, M:810)
382    pub hc_head_fn: CudaSlice<f32>, // mtp.2 hc_head trio
383    pub hc_head_base: Vec<f32>,
384    pub hc_head_scale: Vec<f32>,
385    pub block_size: usize,
386    pub noise_token: u32,
387    pub targets: Vec<usize>, // [40, 41, 42]
388    pub rank: usize,
389    pub vocab: usize,
390}
391
392/// DSpark decode-side state: the 3 per-block main_kv rings, each allocated
393/// [win + block_size, hd] — rows [0, win) are the persistent ring (slot = pos % win,
394/// M:783), rows [win, win+block) hold the CURRENT round's transient draft kv (the
395/// M:784 cat([kv_cache, draft_kv]) gather realized in one buffer; rewritten every
396/// propose, never read as ring). Rings advance ONLY for committed positions
397/// (`dspark_write_rings`) — the §3.1 drafter rule.
398pub struct DsparkState {
399    pub rings: Vec<CudaSlice<f32>>,
400    /// tap rows [t_max, n_targets*hidden] on the last stage: the hc-mean concat of
401    /// layers 40/41/42, written by the decode step (and consumed by write_rings /
402    /// forward_spec).
403    pub taps: CudaSlice<f32>,
404}
405
406/// One drafter proposal (host view). `out_ids[0]` is the input token; margins/top1
407/// are adjudication instruments (populated only under `capture`).
408pub struct DsparkProposal {
409    pub out_ids: Vec<u32>,
410    pub confidence: Vec<f32>,
411    pub margins: Vec<f32>,
412    pub top1_logits: Vec<f32>,
413    /// captured component arrays for the gate (dtoh): main_x, per-block outs,
414    /// x_collapsed (pre-norm), post-markov logits rows, markov_embed.
415    pub capture: Option<DsparkCaptureOut>,
416}
417
418pub struct DsparkCaptureOut {
419    /// the trunk tap row itself (hc-mean concat of layers 40/41/42) — the CPU gate's
420    /// `pos{p}_main_hidden` array; captured here so the GPU gate compares the SAME
421    /// seven arrays the lane-10 CPU components gate does.
422    pub main_hidden: Vec<f32>,
423    pub main_x: Vec<f32>,
424    pub block_outs: Vec<Vec<f32>>,
425    pub x_collapsed: Vec<f32>,
426    /// shared-trunk-head logits BEFORE any markov bias add (`pos{p}_logits_pre`).
427    pub logits_pre: Vec<f32>,
428    pub logits_post: Vec<f32>,
429    pub markov_embed: Vec<f32>,
430}
431
432pub struct Dsv4Gpu {
433    pub model: Dsv4Model,
434    pub stages: Vec<Stage>,
435    pub layer_stage: Vec<usize>, // trunk layer -> stage idx
436    pub split_at: u32,           // first layer of stage 1
437    pub max_seq: usize,
438    pub variant: ActQuantVariant,
439    pub fc_yarn_host: FreqsCis,
440    pub fc_plain_host: FreqsCis,
441    pub mtp: Option<MtpDev>,
442    /// iteration 3: the DSpark drafter (0731 lineage), loaded under
443    /// MEMRA_DSV4_DRAFTER=dspark; None = today's exact behavior everywhere.
444    pub dspark: Option<DsparkDev>,
445    pub expert_arm: ExpertArm,
446    pub decode_path: DecodePath,
447    /// lane 9 (owner ruling 2026-08-19): island dots on the DEVICE decode path run the
448    /// f32-accumulation serving arm when true (fork-gated); false = the f64
449    /// oracle-truth arm (MEMRA_DSV4_DOTS_ARM=f64). Legacy path and prefill NEVER
450    /// consult this (they stay the pinned reference realizations).
451    pub dots_f32: bool,
452    /// 0731 re-gate extension rung — RATIFIED by the owner 2026-08-19 and now the
453    /// DEFAULT (unset env == f32x): the remaining f64 dependency chains on the DEVICE
454    /// decode path (sink scores/soft/out, rmsnorm, headrms, rowsq_scale,
455    /// indexer_score) run f32-accumulation twins when true. false = those chains keep
456    /// the f64 kernels (MEMRA_DSV4_DOTS_ARM=f64|f32 — oracle/debug arms, bytes
457    /// untouched). hc_sinkhorn is NOT in f32x (never authorized). Legacy path and
458    /// prefill NEVER consult this.
459    pub chains_f32: bool,
460    /// iteration-3 rung 4c MEASURED FORK (`MEMRA_DSV4_DSPARK_HEAD_ARM=f32x`, default
461    /// f64 = the lane-10-gated bytes): the DSpark drafter's shared-trunk-head projection
462    /// over block_size rows uses the f32-accumulation hoisted kernel instead of the f64
463    /// one. Affects WHICH tokens are drafted, never the emitted stream (verification
464    /// always emits the trunk's own argmax — the greedy identity law).
465    pub dspark_head_f32: bool,
466    /// iteration-5 FP8 dense arm (`MEMRA_DSV4_DENSE_ARM`; DEFAULT fp8 on the device
467    /// decode path since the 2026-08-20 ratification, bf16 selectable and the legacy
468    /// default): the DEVICE decode/verify paths read the FP8-blk linears as-stored
469    /// (e4m3 + f32 block scales) through the bit-identical GEMV twins, halving the
470    /// dense weight traffic (79.9% of a step's bytes). It5 ledger item 3: the trunk
471    /// bf16 slabs are NOT device-resident under this arm — they hold [`DenseBf16::Host`]
472    /// staged residency (same bytes, staged H2D per prefill pass); the legacy path is a
473    /// boot refusal and the drafter's cuBLASLt linears keep resident bf16 (no twins).
474    pub dense_fp8: bool,
475    /// lane 8: cross-stage boundary events (peer transport), one per boundary,
476    /// created in the TX stage's context (cuEventRecord requires event ctx == stream ctx).
477    boundary_ev: Vec<cudarc::driver::CudaEvent>,
478    hc_head_base: Vec<f32>,
479    hc_head_scale: Vec<f32>,
480}
481
482/// A full-trunk forward's outputs: last-position logits + the final hc state (resident
483/// on the LAST stage — the MTP drafter's input).
484pub struct ForwardOut {
485    pub logits: Vec<f32>,
486    pub h_last: CudaSlice<f32>,
487}
488
489/// Lane-6 decode cache for ONE trunk layer, on the layer's owning stage. Layout mirrors
490/// the reference (model.py:473-474, :491): `kvc` = [win + cap_blocks, hd] f32 with the
491/// 128-slot window ring at rows [0, win) (slot = pos % win, M:530) and compressed block
492/// j at row win + j (decode index offset = win, M:509). Pending state = RAW wkv/wgate
493/// rows (ape added at pool time — see the lane-6 receipts): fine [2·ratio, latent] with
494/// rows [0, ratio) = previous block / [ratio, 2·ratio) = current (M:344-370 state
495/// machine); coarse [ratio, latent]. `pend_score` is initialized to −inf so a block
496/// with no predecessor reproduces the reference j==0 masking bit-exactly.
497pub struct LayerCache {
498    pub kvc: CudaSlice<f32>,
499    pub n_blocks: usize,
500    pub pend_kv: Option<CudaSlice<f32>>,
501    pub pend_score: Option<CudaSlice<f32>>,
502    /// indexer compressed-kv store [cap_blocks, index_head_dim] (FP4-QAT'd values) +
503    /// its own pending pair — fine layers only.
504    pub ikvc: Option<CudaSlice<f32>>,
505    pub i_blocks: usize,
506    pub ipend_kv: Option<CudaSlice<f32>>,
507    pub ipend_score: Option<CudaSlice<f32>>,
508}
509
510/// Lane-8 per-stage decode workspace: every per-step buffer preallocated ONCE (the
511/// legacy path issues ~3,086 allocAsync+memset+free triplets per step — rung-0
512/// profile). Every buffer is fully rewritten before it is read within a step; the
513/// consumers (sink_attn via idx pads, combine via order, top-k via exact nb) read
514/// exactly the regions written this step, so no per-step zeroing exists at all.
515pub struct StepWs {
516    pub h_a: CudaSlice<f32>, // [hc*hidden] layer io (in h_a -> h2 in h_b -> h3 in h_a)
517    pub h_b: CudaSlice<f32>, // [hc*hidden]
518    pub h_rx: CudaSlice<f32>, // [hc*hidden] boundary RX slot (peer TX writes here)
519    pub emb: CudaSlice<f32>, // [hidden]
520    pub mixes: CudaSlice<f32>, // [(2+hc)*hc]
521    pub pre: CudaSlice<f32>, // [hc]
522    pub post: CudaSlice<f32>, // [hc]
523    pub comb: CudaSlice<f32>, // [hc*hc]
524    pub y_hc: CudaSlice<f32>, // [hidden] hc_pre collapse out
525    pub x: CudaSlice<f32>,   // [hidden] post-attn-norm
526    pub xf: CudaSlice<f32>,  // [hidden] post-ffn-norm
527    pub qr: CudaSlice<f32>,  // [q_lora]
528    pub qr_b: CudaSlice<u8>, // [q_lora*2]
529    pub q: CudaSlice<f32>,   // [heads*hd]
530    pub kv: CudaSlice<f32>,  // [hd]
531    pub qi: CudaSlice<f32>,  // [iheads*ihd]
532    pub wproj: CudaSlice<f32>, // [iheads]
533    pub score: CudaSlice<f32>, // [max_seq/ratio_min]
534    pub idx: CudaSlice<i32>, // [win + max(topk, max_seq/128)]
535    pub o: CudaSlice<f32>,   // [heads*hd]
536    pub o_b: CudaSlice<u8>,  // [heads*hd*2] (bf16 cvt of o, once — grouped wo reads slices)
537    pub og: CudaSlice<f32>,  // [o_groups*o_lora]
538    pub attn_out: CudaSlice<f32>, // [hidden]
539    pub gemm_xb: CudaSlice<u8>, // [max_gemm_k*2] per-call cvt scratch
540    // MoE
541    pub raw: CudaSlice<f32>,     // [ne]
542    pub sel: CudaSlice<i32>,     // [topk]
543    pub selw: CudaSlice<f32>,    // [topk]
544    pub order: CudaSlice<i32>,   // [topk]
545    pub xq: CudaSlice<u8>,       // [hidden]
546    pub xs: CudaSlice<f32>,      // [hidden/128]
547    pub g1: CudaSlice<f32>,      // [topk*inter]
548    pub g3: CudaSlice<f32>,      // [topk*inter]
549    pub hbuf: CudaSlice<f32>,    // [topk*inter]
550    pub hq: CudaSlice<u8>,       // [topk*inter]
551    pub hs: CudaSlice<f32>,      // [topk*inter/128]
552    pub contrib: CudaSlice<f32>, // [topk*hidden]
553    pub y: CudaSlice<f32>,       // [hidden]
554    pub xb: CudaSlice<u8>,       // [hidden*2] shared-expert input (bf16 cvt of xf)
555    pub sg1: CudaSlice<f32>,     // [sh_inter]
556    pub sg3: CudaSlice<f32>,
557    pub shbuf: CudaSlice<f32>,
558    pub shb16: CudaSlice<u8>,   // [sh_inter*2]
559    pub sh_out: CudaSlice<f32>, // [hidden]
560    // compressor scratch (max class dims across attn fine/coarse + indexer)
561    pub cmp_kv_row: CudaSlice<f32>, // [max latent]
562    pub cmp_sc_row: CudaSlice<f32>, // [max latent]
563    pub cmp_emit: CudaSlice<f32>,   // [2*max d]
564    pub cmp_shift: CudaSlice<f32>,  // [max overlap ratio*latent]
565    // sink attention (three-kernel split): scores/evals [heads, win+idx_tail], f64 den
566    pub sink_scores: CudaSlice<f32>,
567    pub sink_evals: CudaSlice<f32>,
568    pub sink_den: CudaSlice<f64>,
569    // head (allocated on every stage; consumed on the last)
570    pub head_mixes: CudaSlice<f32>, // [hc]
571    pub head_pre: CudaSlice<f32>,   // [hc]
572    pub collapsed: CudaSlice<f32>,  // [hidden]
573    pub logits: CudaSlice<f32>,     // [vocab]
574    pub argmax: CudaSlice<i32>,     // [1]
575    pub tok: CudaSlice<i32>,        // [1]
576}
577
578/// Whole-trunk decode state: one [`LayerCache`] per trunk layer + the stream position.
579/// `pos` = tokens consumed so far (the next decode_step processes position `pos`).
580pub struct DecodeState {
581    pub caches: Vec<LayerCache>,
582    pub pos: usize,
583    /// allocated cache bytes per device index (gate (e): measured vs design math)
584    pub cache_bytes: Vec<u64>,
585    /// lane 8: per-stage step workspace (Some iff the load-time decode path is Device)
586    pub ws: Option<Vec<StepWs>>,
587}
588
589// ---------------------------------------------------------------- small launch helpers
590
591fn sp(stream: &CudaStream) -> *mut c_void {
592    stream.cu_stream() as *mut c_void
593}
594
595fn upload_f32(stream: &std::sync::Arc<CudaStream>, v: &[f32]) -> Res<CudaSlice<f32>> {
596    let mut d = stream.alloc_zeros::<f32>(v.len()).map_err(e("alloc f32"))?;
597    stream.memcpy_htod(v, &mut d).map_err(e("htod f32"))?;
598    Ok(d)
599}
600
601fn upload_i32(stream: &std::sync::Arc<CudaStream>, v: &[i32]) -> Res<CudaSlice<i32>> {
602    let mut d = stream.alloc_zeros::<i32>(v.len()).map_err(e("alloc i32"))?;
603    stream.memcpy_htod(v, &mut d).map_err(e("htod i32"))?;
604    Ok(d)
605}
606
607fn upload_u8(stream: &std::sync::Arc<CudaStream>, v: &[u8]) -> Res<CudaSlice<u8>> {
608    let mut d = stream.alloc_zeros::<u8>(v.len()).map_err(e("alloc u8"))?;
609    stream.memcpy_htod(v, &mut d).map_err(e("htod u8"))?;
610    Ok(d)
611}
612
613fn dtoh_f32(stream: &std::sync::Arc<CudaStream>, d: &CudaSlice<f32>) -> Res<Vec<f32>> {
614    let mut v = vec![0f32; d.len()];
615    stream.memcpy_dtoh(d, &mut v[..]).map_err(e("dtoh"))?;
616    stream.synchronize().map_err(e("sync dtoh"))?;
617    Ok(v)
618}
619
620// -------------------------------------------------- lane 8: peer byte-integrity probe
621//
622// (lane/hermes-perf-fixes, 2026-08-23 — the "DSv4 device-path PP copies hidden state with no
623// peer byte probe" finding.) The lane-8 setup used to cuCtxEnablePeerAccess +
624// cuMemPoolSetAccess and eprintln success; the pp.rs boot probe exists precisely because a
625// fabric can grant peer access and still corrupt bytes in flight (Pod B: official simpleP2P
626// reproduced it while bandwidth-test returned rc=0). The probe here runs the PRODUCTION
627// program — stream-ordered pool allocations moved by the exact `memcpy_peer_async`-on-the-
628// TX-stream call shape the boundary copy uses (the cx-peerprobe lesson: probing legacy
629// cuMemAlloc buffers validates a different allocation class) — over every cross-device
630// boundary, both directions, on a byte ladder up to the prefill hidden-state payload class.
631// FAIL-CLOSED: dsv4's device PP path has no host-bounce twin, so a mismatch refuses at load.
632
633/// Deterministic per-(bytes, boundary, src, dst) xorshift pattern (pp.rs idiom): a stuck or
634/// crossed lane cannot alias another probe's expected bytes.
635fn dsv4_peer_probe_pattern(
636    bytes: usize,
637    boundary: usize,
638    src_dev: usize,
639    dst_dev: usize,
640) -> Vec<u8> {
641    let mut state = 0xD1B5_4A32_D192_ED03u64
642        ^ (bytes as u64).rotate_left(7)
643        ^ (boundary as u64).rotate_left(19)
644        ^ (src_dev as u64).rotate_left(31)
645        ^ (dst_dev as u64).rotate_left(43);
646    (0..bytes)
647        .map(|_| {
648            state ^= state << 13;
649            state ^= state >> 7;
650            state ^= state << 17;
651            state as u8
652        })
653        .collect()
654}
655
656fn dsv4_peer_probe_mismatches(expected: &[u8], readback: &[u8]) -> usize {
657    expected
658        .iter()
659        .zip(readback)
660        .filter(|(a, b)| a != b)
661        .count()
662        + expected.len().abs_diff(readback.len())
663}
664
665fn dsv4_peer_probe_ladder(hidden: usize, hc: usize) -> Vec<usize> {
666    let hc_state = hidden * hc * std::mem::size_of::<f32>();
667    let mut ladder = vec![
668        16 << 10,
669        hc_state,
670        8 * hc_state,
671        1 << 20,
672        (4096usize * hidden * std::mem::size_of::<f32>()).min(64 << 20),
673    ];
674    ladder.sort_unstable();
675    ladder.dedup();
676    ladder
677}
678
679/// One probed copy src->dst at `bytes`. Destination is poisoned with the inverted pattern
680/// first, so a silently dropped copy reads back as full-length corruption, never as PASS.
681fn dsv4_peer_probe_copy(src: &Stage, dst: &Stage, boundary: usize, bytes: usize) -> Res<()> {
682    let expected = dsv4_peer_probe_pattern(bytes, boundary, src.dev, dst.dev);
683    src.gpu.ctx.bind_to_thread().map_err(e("probe bind src"))?;
684    let src_stream = src.gpu.stream();
685    let src_buf = upload_u8(&src_stream, &expected)?;
686    src_stream.synchronize().map_err(e("probe sync src htod"))?;
687
688    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind dst"))?;
689    let dst_stream = dst.gpu.stream();
690    let poison: Vec<u8> = expected.iter().map(|b| !b).collect();
691    let mut dst_buf = upload_u8(&dst_stream, &poison)?;
692    dst_stream.synchronize().map_err(e("probe sync poison"))?;
693
694    // the production call shape: peer copy issued on the TX (source) stream.
695    src.gpu.ctx.bind_to_thread().map_err(e("probe bind tx"))?;
696    {
697        let (sp, _g0) = src_buf.device_ptr(&src_stream);
698        let (dp, _g1) = dst_buf.device_ptr_mut(&src_stream);
699        unsafe {
700            cudarc::driver::result::memcpy_peer_async(
701                dst.gpu.ctx.cu_ctx(),
702                dp,
703                src.gpu.ctx.cu_ctx(),
704                sp,
705                bytes,
706                src_stream.cu_stream(),
707            )
708            .map_err(e("probe peer copy"))?;
709        }
710    }
711    src_stream.synchronize().map_err(e("probe sync copy"))?;
712
713    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind rx"))?;
714    let mut readback = vec![0u8; bytes];
715    dst_stream
716        .memcpy_dtoh(&dst_buf, &mut readback[..])
717        .map_err(e("probe readback"))?;
718    dst_stream.synchronize().map_err(e("probe sync readback"))?;
719    // TEETH DOOR (diagnostics only, never a tuning knob): MEMRA_DSV4_PEER_PROBE_POISON=1
720    // flips one readback byte so the refusal arm can be proven live on a healthy fabric —
721    // a probe that can only be observed passing proves nothing (serve-stress-gate law).
722    if std::env::var("MEMRA_DSV4_PEER_PROBE_POISON").as_deref() == Ok("1") && !readback.is_empty() {
723        readback[0] ^= 1;
724    }
725    let mismatches = dsv4_peer_probe_mismatches(&expected, &readback);
726    if mismatches == 0 {
727        Ok(())
728    } else {
729        Err(format!("{mismatches} mismatched byte(s) of {bytes}"))
730    }
731}
732
733// ================================================== iteration-5: drafted-round phase instruments
734//
735// WHY: iteration 4 measured `cost(T) = F + 0.272*T` plain steps with F = 1.057 plain steps on
736// the f32x exit head, proved the marginal term is ~65-70% irreducible expert-union traffic, and
737// showed the ENTIRE drafted gap to the bar is F. F cannot be attacked until it is itemised into
738// named components with sizes, which is what these two instruments produce. Both are OFF by
739// default and their env knobs are read ONCE through a `OnceLock` (never per round), so the
740// shipping path is untouched: with both unset `Dsv4Phase::new` returns `None` before any work.
741//
742//   MEMRA_DSV4_ROUND_PROFILE=1 -- sync-bracketed host timers. Every phase boundary
743//       synchronizes the head stage's stream, so per-phase wall times SUM to the round's wall
744//       time and can be quoted in F's own unit (plain steps). It PERTURBS: the added syncs
745//       expose latency a queued round would have overlapped, so the report always prints the
746//       bracketed round total for comparison against the unbracketed A/B baseline. A
747//       sync-bracketed run is a rung-0 instrument, NEVER an A/B observation.
748//
749//   MEMRA_DSV4_NVTX=1 -- NVTX push/pop only, no added syncs, so the round is undisturbed.
750//       `nsys profile -t cuda,nvtx` then gives `nvtx_gpu_proj_sum` (GPU-busy attributed to the
751//       range that launched each op) and `nvtx_sum` (host wall per range). GPU-busy is the real
752//       kernel work; wall minus GPU-busy inside a sync-terminated phase is the exposed stall.
753//
754// The accumulator is thread-local and the phase stack makes nesting exact: each row keeps
755// INCLUSIVE time plus the time its direct children consumed, so `self = inclusive - children`
756// is a true exclusive cost and the leaves partition the round.
757#[derive(Default, Clone)]
758struct Dsv4PhaseAcc {
759    /// (label, inclusive_us, direct_child_us, calls)
760    rows: Vec<(&'static str, u64, u64, u64)>,
761    /// (row index, direct-child us accumulated for the open range)
762    stack: Vec<(usize, u64)>,
763}
764
765thread_local! {
766    static DSV4_PHASES: std::cell::RefCell<Dsv4PhaseAcc> =
767        std::cell::RefCell::new(Dsv4PhaseAcc::default());
768}
769
770fn dsv4_prof_sync() -> bool {
771    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
772    *V.get_or_init(|| std::env::var("MEMRA_DSV4_ROUND_PROFILE").as_deref() == Ok("1"))
773}
774
775fn dsv4_prof_nvtx() -> bool {
776    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
777    *V.get_or_init(|| std::env::var("MEMRA_DSV4_NVTX").as_deref() == Ok("1"))
778}
779
780/// True when either phase instrument is armed. Checked first in `Dsv4Phase::new` so an
781/// unprofiled build pays one relaxed load per bracket and nothing else.
782pub fn dsv4_prof_on() -> bool {
783    dsv4_prof_sync() || dsv4_prof_nvtx()
784}
785
786/// A named, nestable phase bracket. Constructed through the `phase!` macro, which supplies a
787/// NUL-terminated literal so the NVTX push needs no allocation.
788pub struct Dsv4Phase<'a> {
789    stream: Option<&'a std::sync::Arc<CudaStream>>,
790    t0: std::time::Instant,
791    nvtx: bool,
792}
793
794impl<'a> Dsv4Phase<'a> {
795    /// `name` MUST end in `\0` (use the `phase!` macro). `stream` is the stream whose queue
796    /// this phase's work rides; it is synchronized on drop under `MEMRA_DSV4_ROUND_PROFILE=1`
797    /// and ignored otherwise.
798    pub fn new(name: &'static str, stream: Option<&'a std::sync::Arc<CudaStream>>) -> Option<Self> {
799        if !dsv4_prof_on() {
800            return None;
801        }
802        let nvtx = dsv4_prof_nvtx();
803        if nvtx {
804            unsafe {
805                k::memra_dsv4_nvtx_push(name.as_ptr() as *const std::os::raw::c_char);
806            }
807        }
808        let label = &name[..name.len() - 1];
809        DSV4_PHASES.with(|p| {
810            let mut p = p.borrow_mut();
811            let idx = match p.rows.iter().position(|r| r.0 == label) {
812                Some(i) => i,
813                None => {
814                    p.rows.push((label, 0, 0, 0));
815                    p.rows.len() - 1
816                }
817            };
818            p.stack.push((idx, 0));
819        });
820        Some(Dsv4Phase {
821            stream: if dsv4_prof_sync() { stream } else { None },
822            t0: std::time::Instant::now(),
823            nvtx,
824        })
825    }
826}
827
828impl Drop for Dsv4Phase<'_> {
829    fn drop(&mut self) {
830        // sync BEFORE stopping the clock: under the sync-bracketed instrument the phase's cost
831        // includes the GPU work it queued, which is the only way the rows can sum to the round.
832        if let Some(s) = self.stream {
833            let _ = s.synchronize();
834        }
835        let us = self.t0.elapsed().as_micros() as u64;
836        if self.nvtx {
837            unsafe {
838                k::memra_dsv4_nvtx_pop();
839            }
840        }
841        DSV4_PHASES.with(|p| {
842            let mut p = p.borrow_mut();
843            if let Some((idx, child)) = p.stack.pop() {
844                let r = &mut p.rows[idx];
845                r.1 += us;
846                r.2 += child;
847                r.3 += 1;
848                if let Some(top) = p.stack.last_mut() {
849                    top.1 += us;
850                }
851            }
852        });
853    }
854}
855
856/// Bracket a phase. `phase!("name", stream_opt)` -> `Option<Dsv4Phase>`; bind it to a `_p`
857/// local so it drops at the end of the scope.
858macro_rules! phase {
859    ($name:literal, $stream:expr) => {
860        crate::dsv4_gpu::Dsv4Phase::new(concat!($name, "\0"), $stream)
861    };
862}
863
864/// Print the accumulated itemisation. `plain_us` is the measured PLAIN step wall time so each
865/// row can be quoted in plain steps, which is the unit `F` is expressed in; pass 0.0 to omit.
866pub fn dsv4_phase_report(tag: &str, rounds: u64, plain_us: f64) {
867    DSV4_PHASES.with(|p| {
868        let p = p.borrow();
869        if p.rows.is_empty() {
870            return;
871        }
872        let mode = if dsv4_prof_sync() {
873            "sync-bracketed (PERTURBS: compare the round total against the unbracketed A/B)"
874        } else {
875            "nvtx-only (host wall; GPU-busy comes from nsys nvtx_gpu_proj_sum)"
876        };
877        println!("\n[phase] === F ITEMISATION: {tag} ===");
878        println!("[phase] rounds={rounds}  plain step={plain_us:.1} us  mode={mode}");
879        println!(
880            "[phase] {:<26} {:>11} {:>11} {:>9} {:>12} {:>12}",
881            "phase", "incl_us/rd", "self_us/rd", "calls/rd", "self_plainstp", "incl_plainstp"
882        );
883        let mut rows = p.rows.clone();
884        rows.sort_by_key(|r| std::cmp::Reverse(r.1.saturating_sub(r.2)));
885        let r = rounds.max(1) as f64;
886        let mut leaf_sum = 0f64;
887        for (name, incl, child, calls) in rows {
888            let selfus = incl.saturating_sub(child) as f64 / r;
889            let inclus = incl as f64 / r;
890            leaf_sum += selfus;
891            let (sp, ip) = if plain_us > 0.0 {
892                (selfus / plain_us, inclus / plain_us)
893            } else {
894                (0.0, 0.0)
895            };
896            println!(
897                "[phase] {name:<26} {inclus:>11.1} {selfus:>11.1} {:>9.2} {sp:>12.4} {ip:>12.4}",
898                calls as f64 / r
899            );
900        }
901        println!(
902            "[phase] {:<26} {:>11} {:>11.1} {:>9} {:>12.4}",
903            "SUM of self",
904            "",
905            leaf_sum,
906            "",
907            if plain_us > 0.0 {
908                leaf_sum / plain_us
909            } else {
910                0.0
911            }
912        );
913    });
914}
915
916/// `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the DSpark markov chain resident on the device (see
917/// `dspark_forward_spec`). Default (`host`, or unset) reproduces the pre-iteration-5 transport
918/// exactly, including its ten per-round stream drains, so the shipped arm is unchanged until an
919/// A/B and the gate battery say otherwise.
920fn dsv4_dspark_chain_device() -> bool {
921    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
922    *V.get_or_init(|| {
923        let on = std::env::var("MEMRA_DSV4_DSPARK_CHAIN").as_deref() == Ok("device");
924        if on {
925            println!(
926                "[spec] DSpark markov chain RESIDENT ON DEVICE (MEMRA_DSV4_DSPARK_CHAIN=device): \
927                 one D2H per round instead of 2 x block_size"
928            );
929        }
930        on
931    })
932}
933
934/// `MEMRA_DSV4_DSPARK_MARKOV=rowblk` runs the DSpark markov bias GEMV through the row-blocked
935/// twin of the f64 island dots. Bit-identical output (same accumulation order and reduction tree,
936/// only R rows share a block), so this is a pure geometry change; the default `base` keeps the
937/// shipped kernel. Measured defect it addresses: 5 x 318 us/round at 416 GB/s = 26% of roofline,
938/// latency-bound on one 7-level reduction tree per 1 KB of weights read.
939fn dsv4_dspark_markov_rowblk() -> bool {
940    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
941    *V.get_or_init(|| {
942        let on = std::env::var("MEMRA_DSV4_DSPARK_MARKOV").as_deref() == Ok("rowblk");
943        if on {
944            println!(
945                "[spec] DSpark markov bias GEMV on the ROW-BLOCKED dots twin \
946                 (MEMRA_DSV4_DSPARK_MARKOV=rowblk; bit-identical, geometry only)"
947            );
948        }
949        on
950    })
951}
952
953/// Drop everything accumulated so far (used to keep the plain arm's brackets out of the
954/// drafted arm's table).
955pub fn dsv4_phase_reset() {
956    DSV4_PHASES.with(|p| *p.borrow_mut() = Dsv4PhaseAcc::default());
957}
958
959macro_rules! dp {
960    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const c_void }};
961}
962macro_rules! dpf {
963    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const f32 }};
964}
965macro_rules! dpm {
966    ($slice:expr, $stream:expr) => {{ $slice.device_ptr_mut($stream).0 as *mut f32 }};
967}
968
969// ---------------------------------------------------------------- loading
970
971/// f32 (already NaN-checked by tensor_f32) -> bf16 with a bit-level exactness REFUSAL:
972/// every value in the lane-4 rungs is exactly representable (see receipts); a non-zero
973/// low half means the exactness proof broke and the load must stop, not round.
974fn f32_to_bf16_exact(name: &str, v: &[f32]) -> Vec<u8> {
975    let mut out = Vec::with_capacity(v.len() * 2);
976    for (i, x) in v.iter().enumerate() {
977        let bits = x.to_bits();
978        assert!(
979            bits & 0xFFFF == 0,
980            "{name}: element {i} = {x} not exactly representable in bf16 — lane-4 rung \
981             exactness violated"
982        );
983        out.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
984    }
985    out
986}
987
988impl Dsv4Gpu {
989    /// Upload a tensor as bf16: BF16-stored tensors ride raw bytes; FP8-blk tensors are
990    /// host-dequantized (lane-1 decoder) and cast with the exactness refusal.
991    fn tensor_bf16(&mut self, stage: usize, name: &str) -> Res<CudaSlice<u8>> {
992        let raw_name = format!("{name}.weight");
993        let is_bf16_raw = self
994            .model
995            .st
996            .raw(&raw_name)
997            .map(|(i, _)| i.dtype == "BF16")
998            .unwrap_or(false)
999            || self
1000                .model
1001                .st
1002                .raw(name)
1003                .map(|(i, _)| i.dtype == "BF16")
1004                .unwrap_or(false);
1005        let stream = self.stages[stage].gpu.stream();
1006        let bytes: u64;
1007        let out = if is_bf16_raw {
1008            let (_, raw) = self
1009                .model
1010                .st
1011                .raw(&raw_name)
1012                .or_else(|| self.model.st.raw(name))
1013                .unwrap();
1014            bytes = raw.len() as u64;
1015            upload_u8(&stream, raw)?
1016        } else {
1017            let (_, v) = self.model.tensor_f32(name);
1018            let b = f32_to_bf16_exact(name, &v);
1019            bytes = b.len() as u64;
1020            upload_u8(&stream, &b)?
1021        };
1022        self.stages[stage].loaded_bytes += bytes;
1023        Ok(out)
1024    }
1025
1026    /// Iteration-5 FP8 dense arm loader. bf16 arm (or no fp8 twin): the device-resident
1027    /// bf16 dequant slab, today's exact bytes. fp8 arm on an F8_E4M3-stored `fp8_ok`
1028    /// tensor (trunk layers only this rung): the as-stored codes + host-decoded f32
1029    /// scale grid go to the device, and the bf16 slab drops to STAGED residency
1030    /// ([`DenseBf16::Host`], it5 ledger item 3) — the fp8 twins own every device
1031    /// decode/verify read and prefill stages the same bytes per pass, so the
1032    /// +~2.7 GiB/card dual residency is gone. Load-time refusals: missing/mis-shaped
1033    /// scale grid, e8m0 NaN code, cols not a multiple of 8 (the uint2 chunk contract),
1034    /// and a 1,024-element stride-sampled BIT check
1035    /// `e4m3(code[r,c]) * sc[r/128, c/128] == host_dequant[r,c]` — the layout/indexing
1036    /// proof, in the load-refusal tradition of the bf16 slab's own exactness check.
1037    fn tensor_dense(
1038        &mut self,
1039        stage: usize,
1040        name: &str,
1041        fp8_ok: bool,
1042    ) -> Res<(DenseBf16, Option<Fp8Dense>)> {
1043        let raw_name = format!("{name}.weight");
1044        let is_bf16_raw = self
1045            .model
1046            .st
1047            .raw(&raw_name)
1048            .map(|(i, _)| i.dtype == "BF16")
1049            .unwrap_or(false)
1050            || self
1051                .model
1052                .st
1053                .raw(name)
1054                .map(|(i, _)| i.dtype == "BF16")
1055                .unwrap_or(false);
1056        if is_bf16_raw || !fp8_ok || !self.dense_fp8 {
1057            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1058        }
1059        // FP8-blk path: resolve the weight raw + its scale sibling.
1060        let (wi, wraw, stem) = if let Some((i, r)) = self.model.st.raw(&raw_name) {
1061            (i.clone(), r.to_vec(), name.to_string())
1062        } else {
1063            let (i, r) = self
1064                .model
1065                .st
1066                .raw(name)
1067                .unwrap_or_else(|| panic!("missing dense tensor {name}"));
1068            let stem = name.strip_suffix(".weight").unwrap_or(name).to_string();
1069            (i.clone(), r.to_vec(), stem)
1070        };
1071        if wi.dtype != "F8_E4M3" {
1072            // not the FP8-blk class (e.g. a BF16-raw special) — bf16 slab only.
1073            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1074        }
1075        assert_eq!(wi.shape.len(), 2, "{name}: fp8 dense tensor must be 2-D");
1076        let rows = wi.shape[0] as usize;
1077        let cols = wi.shape[1] as usize;
1078        assert_eq!(cols % 8, 0, "{name}: fp8 dense cols {cols} % 8 != 0");
1079        assert_eq!(wraw.len(), rows * cols, "{name}: fp8 byte count");
1080        let scale_name = format!("{stem}.scale");
1081        let (si, sraw) = self
1082            .model
1083            .st
1084            .raw(&scale_name)
1085            .unwrap_or_else(|| panic!("{name}: F8_E4M3 weight without {scale_name}"));
1086        assert_eq!(si.dtype, "F8_E8M0", "{scale_name}: dtype");
1087        let sc_rows = rows.div_ceil(128);
1088        let sc_cols = cols.div_ceil(128);
1089        assert_eq!(
1090            (si.shape[0] as usize, si.shape[1] as usize),
1091            (sc_rows, sc_cols),
1092            "{scale_name}: scale grid shape vs [ceil({rows}/128), ceil({cols}/128)]"
1093        );
1094        let sc_f32: Vec<f32> = sraw
1095            .iter()
1096            .map(|&b| {
1097                assert_ne!(b, 0xFF, "{scale_name}: e8m0 NaN code");
1098                memra_gguf::dsv4::e8m0_to_f32(b)
1099            })
1100            .collect();
1101        // host dequant (the bf16 slab's own source) + the sampled layout bit-check.
1102        let (_, v) = self.model.tensor_f32(name);
1103        assert_eq!(v.len(), rows * cols, "{name}: dequant len");
1104        let step = (v.len() / 1024).max(1);
1105        for idx in (0..v.len()).step_by(step) {
1106            let (r, c) = (idx / cols, idx % cols);
1107            let got = memra_gguf::nvfp4_repack::fp8_e4m3_to_f32(wraw[idx])
1108                * sc_f32[(r / 128) * sc_cols + c / 128];
1109            assert_eq!(
1110                got.to_bits(),
1111                v[idx].to_bits(),
1112                "{name}: fp8 arm layout check failed at [{r},{c}] ({got} vs {})",
1113                v[idx]
1114            );
1115        }
1116        let b = f32_to_bf16_exact(name, &v);
1117        let stream = self.stages[stage].gpu.stream();
1118        // item 3: the bf16 slab is NOT uploaded — the fp8 pair owns every device
1119        // decode/verify read (dwsel) and prefill stages `b` per pass. loaded_bytes
1120        // counts DEVICE bytes only, so vram_report stays honest.
1121        let codes = upload_u8(&stream, &wraw)?;
1122        let scales = upload_f32(&stream, &sc_f32)?;
1123        self.stages[stage].loaded_bytes += (wraw.len() + sc_f32.len() * 4) as u64;
1124        Ok((
1125            DenseBf16::Host(b),
1126            Some(Fp8Dense {
1127                codes,
1128                scales,
1129                sc_cols,
1130                rows,
1131                cols,
1132            }),
1133        ))
1134    }
1135
1136    /// Upload a tensor as f32 (islands): any storage dtype goes through the proven
1137    /// tensor_f32 decode.
1138    fn tensor_f32_dev(&mut self, stage: usize, name: &str) -> Res<CudaSlice<f32>> {
1139        let (_, v) = self.model.tensor_f32(name);
1140        let stream = self.stages[stage].gpu.stream();
1141        self.stages[stage].loaded_bytes += (v.len() * 4) as u64;
1142        upload_f32(&stream, &v)
1143    }
1144
1145    fn load_cmp(
1146        &mut self,
1147        stage: usize,
1148        prefix: &str,
1149        ratio: usize,
1150        d: usize,
1151        rotate: bool,
1152    ) -> Res<CmpDev> {
1153        let (wkv_shape, _) = self.model.tensor_f32(&format!("{prefix}.wkv.weight"));
1154        let latent = wkv_shape[0];
1155        let overlap = ratio == 4;
1156        assert_eq!(latent, if overlap { 2 * d } else { d }, "{prefix} latent");
1157        Ok(CmpDev {
1158            ratio,
1159            d,
1160            latent,
1161            overlap,
1162            rotate,
1163            wkv: self.tensor_f32_dev(stage, &format!("{prefix}.wkv.weight"))?,
1164            wgate: self.tensor_f32_dev(stage, &format!("{prefix}.wgate.weight"))?,
1165            norm: self.tensor_f32_dev(stage, &format!("{prefix}.norm.weight"))?,
1166            ape: self.tensor_f32_dev(stage, &format!("{prefix}.ape"))?,
1167        })
1168    }
1169
1170    /// Load one block's device weights. `prefix` is "layers.N" for trunk, "mtp.0" for
1171    /// the MTP block (whose layer id is n_trunk — ratio 0, score-routed, MXFP4 experts).
1172    fn load_layer(&mut self, stage: usize, il: u32, prefix: &str) -> Res<LayerDev> {
1173        let d = self.model.cfg().clone();
1174        let moe = self.model.mc.moe.clone().expect("moe block");
1175        let ratio = d.compress_ratio(il) as usize;
1176        let hd = d.head_dim as usize;
1177        let p = prefix.to_string();
1178        let hash = d.is_hash_layer(il);
1179        let ne = moe.expert_count as usize;
1180        let inter = moe.expert_ff_length as usize;
1181        let hidden = self.model.mc.n_embd as usize;
1182
1183        // hc host params
1184        let hc_load = |m: &Dsv4Model, fam: &str| -> (Vec<f32>, Vec<f32>, Vec<f32>) {
1185            let fn_w = m.tensor_f32(&format!("{p}.hc_{fam}_fn")).1;
1186            let base = m.tensor_f32(&format!("{p}.hc_{fam}_base")).1;
1187            let scale = m.tensor_f32(&format!("{p}.hc_{fam}_scale")).1;
1188            (fn_w, base, scale)
1189        };
1190        let (attn_fn, attn_base, attn_scale) = hc_load(&self.model, "attn");
1191        let (ffn_fn, ffn_base, ffn_scale) = hc_load(&self.model, "ffn");
1192        let stream = self.stages[stage].gpu.stream();
1193        let hc_attn_fn = upload_f32(&stream, &attn_fn)?;
1194        let hc_ffn_fn = upload_f32(&stream, &ffn_fn)?;
1195        self.stages[stage].loaded_bytes += ((attn_fn.len() + ffn_fn.len()) * 4) as u64;
1196
1197        // expert slab (as-stored quant bytes) — geometry derived from config; the recipe
1198        // is DETECTED from the stored dtype (U8+weight_scale+weight_scale_2 = modelopt
1199        // NVFP4; I8+scale = OCP MXFP4, the MTP experts) and refused on any surprise.
1200        let (wi0, _) = self
1201            .model
1202            .st
1203            .raw(&format!("{p}.ffn.experts.0.w1.weight"))
1204            .unwrap_or_else(|| panic!("missing {p}.ffn.experts.0.w1.weight"));
1205        let expert_kind = match wi0.dtype.as_str() {
1206            "U8" => ExpertKind::Nvfp4,
1207            "I8" => ExpertKind::Mxfp4,
1208            other => panic!("{p}: unexpected expert weight dtype {other}"),
1209        };
1210        let wbytes = inter * hidden / 2; // same for w1/w2/w3 (transposed dims)
1211        let sbytes = match expert_kind {
1212            ExpertKind::Nvfp4 => inter * hidden / 16,
1213            ExpertKind::Mxfp4 => inter * hidden / 32,
1214        };
1215        let mut experts_w = stream
1216            .alloc_zeros::<u8>(ne * 3 * wbytes)
1217            .map_err(e("alloc expert slab"))?;
1218        let mut experts_sc = stream
1219            .alloc_zeros::<u8>(ne * 3 * sbytes)
1220            .map_err(e("alloc expert scale slab"))?;
1221        let mut experts_s2 = Vec::with_capacity(ne * 3);
1222        for ex in 0..ne {
1223            for (pi, pname) in ["w1", "w2", "w3"].iter().enumerate() {
1224                let base = format!("{p}.ffn.experts.{ex}.{pname}");
1225                let (wi, wb) = self
1226                    .model
1227                    .st
1228                    .raw(&format!("{base}.weight"))
1229                    .unwrap_or_else(|| panic!("missing {base}.weight"));
1230                assert_eq!(wb.len(), wbytes, "{base}: weight bytes");
1231                let sb = match expert_kind {
1232                    ExpertKind::Nvfp4 => {
1233                        assert_eq!(wi.dtype, "U8", "{base}: expected NVFP4 U8 weight");
1234                        let (_, sb) = self
1235                            .model
1236                            .st
1237                            .raw(&format!("{base}.weight_scale"))
1238                            .unwrap_or_else(|| panic!("missing {base}.weight_scale"));
1239                        let (_, s2b) = self
1240                            .model
1241                            .st
1242                            .raw(&format!("{base}.weight_scale_2"))
1243                            .unwrap_or_else(|| panic!("missing {base}.weight_scale_2"));
1244                        let s2 = f32::from_le_bytes(s2b.try_into().expect("scale_2 4B"));
1245                        // pow2 refusal: the bf16-exactness proof of the on-the-fly dequant
1246                        // rung requires a pow2 scale_2 (receipts, "Quant rungs" §1).
1247                        assert!(
1248                            s2 > 0.0 && s2.to_bits() & 0x007F_FFFF == 0,
1249                            "{base}: scale_2 {s2} not a power of two — rung exactness violated"
1250                        );
1251                        experts_s2.push(s2);
1252                        sb
1253                    }
1254                    ExpertKind::Mxfp4 => {
1255                        assert_eq!(wi.dtype, "I8", "{base}: expected MXFP4 I8 weight");
1256                        let (si, sb) = self
1257                            .model
1258                            .st
1259                            .raw(&format!("{base}.scale"))
1260                            .unwrap_or_else(|| panic!("missing {base}.scale"));
1261                        assert_eq!(si.dtype, "F8_E8M0", "{base}: expected E8M0 scale");
1262                        // e8m0 0xFF is the NaN code — refuse at load, never zero a scale
1263                        assert!(
1264                            !sb.contains(&0xFFu8),
1265                            "{base}: E8M0 NaN scale code — refusing"
1266                        );
1267                        experts_s2.push(1.0);
1268                        sb
1269                    }
1270                };
1271                assert_eq!(sb.len(), sbytes, "{base}: scale bytes");
1272                let off = (ex * 3 + pi) * wbytes;
1273                let mut view = experts_w.slice_mut(off..off + wbytes);
1274                stream
1275                    .memcpy_htod(wb, &mut view)
1276                    .map_err(e("htod expert w"))?;
1277                let soff = (ex * 3 + pi) * sbytes;
1278                let mut sview = experts_sc.slice_mut(soff..soff + sbytes);
1279                stream
1280                    .memcpy_htod(sb, &mut sview)
1281                    .map_err(e("htod expert sc"))?;
1282            }
1283        }
1284        self.stages[stage].loaded_bytes +=
1285            (ne * 3 * (wbytes + sbytes)) as u64 + (ne * 3 * 4) as u64;
1286
1287        let cmp = if ratio != 0 {
1288            Some(self.load_cmp(stage, &format!("{p}.attn.compressor"), ratio, hd, false)?)
1289        } else {
1290            None
1291        };
1292        // iteration-5 FP8 dense arm: trunk layers only this rung (the drafter/MTP
1293        // blocks ride the prefill helpers, which consume the bf16 slabs).
1294        let fp8_ok = p.starts_with("layers.");
1295        let idx = if d.has_indexer(il) {
1296            let heads = d.index_n_heads as usize;
1297            let ihd = d.index_head_dim as usize;
1298            let (iwq_b, iwq_b_fp8) =
1299                self.tensor_dense(stage, &format!("{p}.attn.indexer.wq_b"), fp8_ok)?;
1300            let (iwp, iwp_fp8) = self.tensor_dense(
1301                stage,
1302                &format!("{p}.attn.indexer.weights_proj.weight"),
1303                fp8_ok,
1304            )?;
1305            Some(IdxDev {
1306                wq_b: iwq_b,
1307                weights_proj: iwp,
1308                wq_b_fp8: iwq_b_fp8,
1309                weights_proj_fp8: iwp_fp8,
1310                cmp: self.load_cmp(
1311                    stage,
1312                    &format!("{p}.attn.indexer.compressor"),
1313                    ratio,
1314                    ihd,
1315                    true,
1316                )?,
1317                heads,
1318                hd: ihd,
1319                topk: d.index_topk as usize,
1320            })
1321        } else {
1322            None
1323        };
1324
1325        // lane 8: device twins of the host routing/hc constants. tid2eid is validated
1326        // here ONCE (range + per-row distinctness — the checks the legacy route_host
1327        // asserts per token) because the device route kernel cannot refuse.
1328        let stream = self.stages[stage].gpu.stream();
1329        let hc_attn_base_dev = upload_f32(&stream, &attn_base)?;
1330        let hc_attn_scale_dev = upload_f32(&stream, &attn_scale)?;
1331        let hc_ffn_base_dev = upload_f32(&stream, &ffn_base)?;
1332        let hc_ffn_scale_dev = upload_f32(&stream, &ffn_scale)?;
1333        let gate_bias_host: Option<Vec<f32>> = if hash {
1334            None
1335        } else {
1336            Some(self.model.tensor_f32(&format!("{p}.ffn.gate.bias")).1)
1337        };
1338        let gate_bias_dev = match &gate_bias_host {
1339            Some(b) => Some(upload_f32(&stream, b)?),
1340            None => None,
1341        };
1342        let tid2eid_host: Option<Vec<i64>> = if hash {
1343            Some(self.model.tensor_i64(&format!("{p}.ffn.gate.tid2eid")).1)
1344        } else {
1345            None
1346        };
1347        let tid2eid_dev = match &tid2eid_host {
1348            Some(t) => {
1349                let topk = moe.expert_used_count as usize;
1350                assert_eq!(t.len() % topk, 0, "{p}: tid2eid rows");
1351                let mut t32 = Vec::with_capacity(t.len());
1352                for row in t.chunks(topk) {
1353                    let mut seen = std::collections::BTreeSet::new();
1354                    for &ex in row {
1355                        assert!(
1356                            (0..ne as i64).contains(&ex),
1357                            "{p}: tid2eid out of range at load"
1358                        );
1359                        assert!(seen.insert(ex), "{p}: duplicate expert id in tid2eid row");
1360                        t32.push(ex as i32);
1361                    }
1362                }
1363                Some(upload_i32(&stream, &t32)?)
1364            }
1365            None => None,
1366        };
1367        let experts_s2_dev = upload_f32(&stream, &experts_s2)?;
1368
1369        let (wq_a, wq_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_a"), fp8_ok)?;
1370        let (wq_b, wq_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_b"), fp8_ok)?;
1371        let (wkv, wkv_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wkv"), fp8_ok)?;
1372        let (wo_a, wo_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_a"), fp8_ok)?;
1373        let (wo_b, wo_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_b"), fp8_ok)?;
1374        let (sw1, sw1_fp8) =
1375            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w1"), fp8_ok)?;
1376        let (sw2, sw2_fp8) =
1377            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w2"), fp8_ok)?;
1378        let (sw3, sw3_fp8) =
1379            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w3"), fp8_ok)?;
1380
1381        Ok(LayerDev {
1382            il,
1383            ratio,
1384            expert_kind,
1385            hc_attn_base_dev,
1386            hc_attn_scale_dev,
1387            hc_ffn_base_dev,
1388            hc_ffn_scale_dev,
1389            gate_bias_dev,
1390            tid2eid_dev,
1391            experts_s2_dev,
1392            wq_a,
1393            wq_b,
1394            wkv,
1395            wo_a,
1396            wo_b,
1397            wq_a_fp8,
1398            wq_b_fp8,
1399            wkv_fp8,
1400            wo_a_fp8,
1401            wo_b_fp8,
1402            q_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.q_norm.weight"))?,
1403            kv_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.kv_norm.weight"))?,
1404            attn_norm: self.tensor_f32_dev(stage, &format!("{p}.attn_norm.weight"))?,
1405            ffn_norm: self.tensor_f32_dev(stage, &format!("{p}.ffn_norm.weight"))?,
1406            sink: self.tensor_f32_dev(stage, &format!("{p}.attn.attn_sink"))?,
1407            cmp,
1408            idx,
1409            hc_attn_fn,
1410            hc_ffn_fn,
1411            hc_attn_base: attn_base,
1412            hc_attn_scale: attn_scale,
1413            hc_ffn_base: ffn_base,
1414            hc_ffn_scale: ffn_scale,
1415            gate_w: self.tensor_f32_dev(stage, &format!("{p}.ffn.gate.weight"))?,
1416            gate_bias: gate_bias_host,
1417            tid2eid: tid2eid_host,
1418            experts_w,
1419            experts_sc,
1420            experts_s2,
1421            shared_w: [sw1, sw2, sw3],
1422            shared_fp8: [sw1_fp8, sw2_fp8, sw3_fp8],
1423        })
1424    }
1425
1426    /// Open the artifact and place the trunk across `devices`. `split_at` = first layer
1427    /// of stage 1, derived from per-layer byte math unless overridden.
1428    pub fn load(
1429        dir: &Path,
1430        devices: &[usize],
1431        variant: ActQuantVariant,
1432        max_seq: usize,
1433    ) -> Res<Self> {
1434        assert_eq!(devices.len(), 2, "lane 4 placement is a 2-card layer split");
1435        let model = Dsv4Model::open(dir)?;
1436        let d = model.cfg().clone();
1437        let mc = model.mc.clone();
1438        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
1439        let rd = d.qk_rope_head_dim as usize;
1440
1441        // split point: balance per-layer resident bytes (experts uniform; fine layers
1442        // carry the indexer). Computed from config, not hardcoded.
1443        let layer_bytes = |il: u32| -> u64 {
1444            let ratio = d.compress_ratio(il);
1445            let base = 3_875_000_000u64; // experts slab + attn bf16 (measured class math)
1446            match ratio {
1447                4 => base + 66_000_000,
1448                _ => base,
1449            }
1450        };
1451        let total: u64 = (0..n_trunk).map(layer_bytes).sum();
1452        let mut acc = 0u64;
1453        let mut split_at = n_trunk / 2;
1454        for il in 0..n_trunk {
1455            acc += layer_bytes(il);
1456            if acc * 2 >= total {
1457                split_at = il + 1;
1458                break;
1459            }
1460        }
1461
1462        let fc_yarn_host = precompute_freqs_cis(
1463            rd,
1464            max_seq,
1465            d.rope_yarn_orig_ctx,
1466            d.compress_rope_theta,
1467            d.rope_yarn_factor,
1468            d.rope_yarn_beta_fast,
1469            d.rope_yarn_beta_slow,
1470        );
1471        let fc_plain_host = precompute_freqs_cis(
1472            rd,
1473            max_seq,
1474            0,
1475            mc.rope_freq_base,
1476            d.rope_yarn_factor,
1477            d.rope_yarn_beta_fast,
1478            d.rope_yarn_beta_slow,
1479        );
1480        let flat =
1481            |fc: &FreqsCis| -> Vec<f32> { fc.cs.iter().flat_map(|&(c, s)| [c, s]).collect() };
1482
1483        let inter = mc.moe.as_ref().expect("moe").expert_ff_length as usize;
1484        let hidden = mc.n_embd as usize;
1485        let mut stages = Vec::new();
1486        for &dev in devices {
1487            let gpu = memra_runtime::Gpu::new(dev).map_err(e("Gpu::new"))?;
1488            // Engine::new idiom (lib.rs:1172): single stream per stage, explicit syncs at
1489            // the boundary — cudarc per-arg event tracking off.
1490            unsafe { gpu.ctx.disable_event_tracking() };
1491            let stream = gpu.stream();
1492            let fc_yarn = upload_f32(&stream, &flat(&fc_yarn_host))?;
1493            let fc_plain = upload_f32(&stream, &flat(&fc_plain_host))?;
1494            let ws = stream.alloc_zeros::<u8>(64 << 20).map_err(e("ws alloc"))?;
1495            let deq = [
1496                stream
1497                    .alloc_zeros::<u8>(inter * hidden * 2)
1498                    .map_err(e("deq"))?,
1499                stream
1500                    .alloc_zeros::<u8>(inter * hidden * 2)
1501                    .map_err(e("deq"))?,
1502                stream
1503                    .alloc_zeros::<u8>(inter * hidden * 2)
1504                    .map_err(e("deq"))?,
1505            ];
1506            stages.push(Stage {
1507                dev,
1508                gpu,
1509                layers: Vec::new(),
1510                embed: None,
1511                head: None,
1512                trunk_norm: None,
1513                hc_head_fn: None,
1514                fc_yarn,
1515                fc_plain,
1516                ws,
1517                deq,
1518                loaded_bytes: 0,
1519                hc_head_base_dev: None,
1520                hc_head_scale_dev: None,
1521            });
1522        }
1523
1524        // lane 8: decode-path seam (read once, printed; one binary carries both arms)
1525        let decode_path = match std::env::var("MEMRA_DSV4_DECODE_PATH").as_deref() {
1526            Err(_) | Ok("") | Ok("legacy") => DecodePath::Legacy,
1527            Ok("device-hostmath") => DecodePath::Device { host_math: true },
1528            Ok("device") => DecodePath::Device { host_math: false },
1529            Ok(other) => {
1530                return Err(format!(
1531                    "MEMRA_DSV4_DECODE_PATH '{other}' unknown (legacy | device | device-hostmath)"
1532                ));
1533            }
1534        };
1535        // lane 9: island-dots arm seam (owner-gated fork; f64 = the oracle-truth arm).
1536        // 0731 re-gate extension rung: `f32x` = the f32 dots arm PLUS f32-accumulation
1537        // twins for the remaining device-path f64 chains (owner-authorized fork).
1538        // OWNER RATIFICATION 2026-08-19: f32x is the DEFAULT device-decode dots arm
1539        // (quality-stays condition met at the owner bar — 0731 re-gate Task B gates:
1540        // decode 52/52, CPU teacher-forcing 257/260 all-in-band, tf-gate 158/160,
1541        // determinism ×2). f64 stays the selectable oracle-truth arm; hc_sinkhorn is
1542        // NOT part of f32x (never authorized). Legacy path and prefill are untouched.
1543        // The unset default is DEVICE-decode-scoped by the ratification's own words:
1544        // the legacy path never consults the flag, so on Legacy an UNSET env resolves
1545        // to the f64 oracle bytes rather than tripping the f32-requires-device refusal
1546        // (box4 find, 2026-08-20: dsv4-gpu-gate under the flipped default panicked at
1547        // load on the legacy path — the refusal is for EXPLICIT f32/f32x only).
1548        // Illegal combos are BOOT REFUSALS (Err), never post-build aborts — hermes
1549        // review fingerprint a4e3d9a8eab4cf17: an assert! after Dsv4Gpu is built dies
1550        // as a process ABORT, which a serving watchdog restarts in a crash loop; the
1551        // unknown-enum arms already refuse at parse, so the combo checks live here too.
1552        let on_device = matches!(decode_path, DecodePath::Device { .. });
1553        let (dots_f32, chains_f32) = match std::env::var("MEMRA_DSV4_DOTS_ARM").as_deref() {
1554            Err(_) | Ok("") => {
1555                // ratified default, DEVICE-decode-scoped: legacy resolves f64.
1556                if on_device {
1557                    (true, true)
1558                } else {
1559                    (false, false)
1560                }
1561            }
1562            Ok(explicit @ ("f32x" | "f32")) if !on_device => {
1563                return Err(format!(
1564                    "MEMRA_DSV4_DOTS_ARM={explicit} requires MEMRA_DSV4_DECODE_PATH=device \
1565                     (the f32 dots arms exist on the device decode path only)"
1566                ));
1567            }
1568            Ok("f32x") => (true, true),
1569            Ok("f64") => (false, false),
1570            Ok("f32") => (true, false),
1571            Ok(other) => {
1572                return Err(format!(
1573                    "MEMRA_DSV4_DOTS_ARM '{other}' unknown (f64 | f32 | f32x)"
1574                ));
1575            }
1576        };
1577
1578        // Iteration-3 rung 4c, MEASURED FORK (nsys, drafted rounds [4,12)): the DRAFTER's
1579        // shared-trunk-head projection runs `dsv4_dots_f32` — the f64 kernel — over
1580        // block_size rows, and it measured **16.3 ms of a 78 ms drafted round (21%)**, one
1581        // instance at 13-14.7 ms. The trunk's OWN head already runs the ratified f32x arm
1582        // on the SAME weights; the drafter's copy only picks DRAFTS (verification always
1583        // emits the trunk's argmax, so output identity cannot depend on it). This arm makes
1584        // the drafter's exit head follow the ratified class. Default is f64 — today's gated
1585        // bytes, untouched — because the lane-10 components gate ran the drafter at f64 and
1586        // a gated component does not change default without its gate; `f32x` is the
1587        // measured arm offered for owner ratification with the acceptance delta reported.
1588        // OWNER RATIFICATION 2026-08-19 (relayed to the box4 lane 2026-08-20): f32x is
1589        // the DEFAULT drafter exit-head arm — the fork was measured quality-INERT
1590        // (acceptance digest byte-identical across arms on the gate fixture AND 3,321
1591        // corpora rounds, iteration-3 rung 4c) and it only picks DRAFTS (the greedy
1592        // identity law keeps the emitted stream the trunk's own argmax either way).
1593        // f64 stays selectable as the lane-10 oracle-truth arm. hc_sinkhorn remains f64
1594        // in every arm — never authorized.
1595        let dspark_head_f32 = match std::env::var("MEMRA_DSV4_DSPARK_HEAD_ARM").as_deref() {
1596            Err(_) | Ok("") | Ok("f32x") => true,
1597            Ok("f64") => false,
1598            Ok(other) => {
1599                return Err(format!(
1600                    "MEMRA_DSV4_DSPARK_HEAD_ARM '{other}' unknown (f64 | f32x)"
1601                ));
1602            }
1603        };
1604
1605        // iteration-5 FP8 dense arm seam — OWNER RATIFICATION 2026-08-20 (the ratified
1606        // bundle, executed in the v0.98 train once the it5 item-3 cells went green on
1607        // box7): **fp8 is the DEFAULT DEVICE-DECODE dense arm.** Receipts: bit-identical
1608        // to bf16 on four boxes / five binaries (dsgate accept shas
1609        // 150342bae32b38b5/85603e87fadf7876 one bit pattern, tf-gate 158/160 with the
1610        // banked near-ties at steps 22+134), completed interleaved x5 A/B plain
1611        // 41.06 -> 47.19 median (+14.9%, box5), and the item-3 staged residency turns
1612        // the arm's +2.7 GiB/card dual-residency cost into a saving (box7: -5.56/-5.34
1613        // GiB/card vs the dual-resident builds, every item-3 bit-gate green).
1614        // DEVICE-scoped exactly like the ratified dots default (82a754fbec): unset on
1615        // the LEGACY path resolves bf16 (legacy has no fp8 twins and must keep
1616        // booting); explicit fp8 on legacy still refuses; bf16 stays selectable
1617        // everywhere. Resolution is the pure `resolve_dense_arm` so the flip is
1618        // toothed-testable; the `[load] dense arm:` line below is the boot receipt.
1619        let dense_fp8 = resolve_dense_arm(
1620            std::env::var("MEMRA_DSV4_DENSE_ARM").ok().as_deref(),
1621            on_device,
1622        )?;
1623
1624        let mut me = Dsv4Gpu {
1625            model,
1626            stages,
1627            layer_stage: (0..n_trunk).map(|il| usize::from(il >= split_at)).collect(),
1628            split_at,
1629            max_seq,
1630            variant,
1631            fc_yarn_host,
1632            fc_plain_host,
1633            mtp: None,
1634            expert_arm: if memra_gguf::dsv4_forward::expert_arm_native() {
1635                ExpertArm::Native
1636            } else {
1637                ExpertArm::Bf16Dequant
1638            },
1639            decode_path,
1640            dots_f32,
1641            chains_f32,
1642            dspark_head_f32,
1643            dense_fp8,
1644            dspark: None,
1645            boundary_ev: Vec::new(),
1646            hc_head_base: Vec::new(),
1647            hc_head_scale: Vec::new(),
1648        };
1649        eprintln!(
1650            "[load] expert arm: {:?} | decode path: {:?} | dots arm: {}",
1651            me.expert_arm,
1652            me.decode_path,
1653            if me.chains_f32 {
1654                "f32x (dots + sink/norm/indexer chains)"
1655            } else if me.dots_f32 {
1656                "f32"
1657            } else {
1658                "f64"
1659            }
1660        );
1661        eprintln!(
1662            "[load] dspark exit-head dots arm: {} (rung-4c fork; drafts only, never the \
1663             emitted stream)",
1664            if me.dspark_head_f32 { "f32x" } else { "f64" }
1665        );
1666        eprintln!(
1667            "[load] dense arm: {} (iteration-5; fp8 = FP8-blk linears as-stored on the \
1668             device decode/verify paths, bit-identical twins)",
1669            if me.dense_fp8 { "fp8" } else { "bf16" }
1670        );
1671        if matches!(me.decode_path, DecodePath::Device { .. }) && me.expert_arm != ExpertArm::Native
1672        {
1673            // the indirect fused dispatch is an fp4-slab program — the bf16-dequant arm
1674            // has no device-indirect twin. Boot refusal, not a post-build abort
1675            // (hermes fingerprint a4e3d9a8eab4cf17); the dots/dense combos refuse at
1676            // env-parse above for the same reason.
1677            return Err(
1678                "MEMRA_DSV4_DECODE_PATH=device requires MEMRA_DSV4_EXPERT_ARM=native".to_string(),
1679            );
1680        }
1681
1682        // lane 8: peer transport for the PP boundary (pp.rs idiom: cuCtxEnablePeerAccess
1683        // both directions + default-mempool access grants — cudarc buffers are
1684        // stream-ordered-pool allocations, unmapped by EnablePeerAccess alone).
1685        if matches!(me.decode_path, DecodePath::Device { .. }) && me.stages.len() > 1 {
1686            use cudarc::driver::sys as cus;
1687            for a in 0..me.stages.len() {
1688                for b in 0..me.stages.len() {
1689                    if a == b || me.stages[a].dev == me.stages[b].dev {
1690                        continue;
1691                    }
1692                    me.stages[a]
1693                        .gpu
1694                        .ctx
1695                        .bind_to_thread()
1696                        .map_err(e("peer bind"))?;
1697                    let rc =
1698                        unsafe { cus::cuCtxEnablePeerAccess(me.stages[b].gpu.ctx.cu_ctx(), 0) };
1699                    if rc != cus::cudaError_enum::CUDA_SUCCESS
1700                        && rc != cus::cudaError_enum::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED
1701                    {
1702                        return Err(format!(
1703                            "cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
1704                            me.stages[a].dev, me.stages[b].dev
1705                        ));
1706                    }
1707                    let dev = cudarc::driver::result::device::get(me.stages[a].dev as i32)
1708                        .map_err(e("device get"))?;
1709                    let mut pool: cus::CUmemoryPool = std::ptr::null_mut();
1710                    unsafe {
1711                        cus::cuDeviceGetDefaultMemPool(&mut pool, dev)
1712                            .result()
1713                            .map_err(e("default pool"))?;
1714                    }
1715                    let desc = cus::CUmemAccessDesc {
1716                        location: cus::CUmemLocation {
1717                            type_: cus::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
1718                            id: me.stages[b].dev as i32,
1719                        },
1720                        flags: cus::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
1721                    };
1722                    let rc = unsafe { cus::cuMemPoolSetAccess(pool, &desc, 1) };
1723                    if rc != cus::cudaError_enum::CUDA_SUCCESS {
1724                        return Err(format!(
1725                            "cuMemPoolSetAccess(dev{} pool -> dev{}) failed: {rc:?}",
1726                            me.stages[a].dev, me.stages[b].dev
1727                        ));
1728                    }
1729                }
1730            }
1731            for bnd in 0..me.stages.len() - 1 {
1732                let ev = me.stages[bnd]
1733                    .gpu
1734                    .ctx
1735                    .new_event(None)
1736                    .map_err(e("boundary event"))?;
1737                me.boundary_ev.push(ev);
1738            }
1739            // PEER BYTE-INTEGRITY PROBE (lane/hermes-perf-fixes, 2026-08-23): enable +
1740            // pool grants alone prove ADDRESSABILITY, not integrity — see the probe
1741            // helpers' header for the Pod B receipt. Ladder up to the prefill
1742            // hidden-state payload class; both directions per cross-device boundary;
1743            // FAIL-CLOSED at load (the device PP path has no host-bounce twin).
1744            {
1745                let hidden = me.model.mc.n_embd as usize;
1746                let hc = me.model.cfg().hc_mult as usize;
1747                // Include the exact persistent decode payload (hc*hidden f32) and the
1748                // maximum served verify-width payload, not only nearby powers of two. Peer
1749                // corruption on the affected drivers is size-class-sensitive (Hermes
1750                // `58843bb6b924125b`).
1751                let ladder = dsv4_peer_probe_ladder(hidden, hc);
1752                let probe_t0 = std::time::Instant::now();
1753                let mut copies = 0usize;
1754                for bnd in 0..me.stages.len() - 1 {
1755                    if me.stages[bnd].dev == me.stages[bnd + 1].dev {
1756                        continue;
1757                    }
1758                    for (s, d) in [(bnd, bnd + 1), (bnd + 1, bnd)] {
1759                        for &bytes in &ladder {
1760                            dsv4_peer_probe_copy(&me.stages[s], &me.stages[d], bnd, bytes)
1761                                .map_err(|err| {
1762                                    format!(
1763                                        "dsv4 PP peer byte-integrity probe FAILED: \
1764                                         boundary={bnd} dev{}->dev{} bytes={bytes}: {err}; \
1765                                         refusing the device PP path (silent hidden-state \
1766                                         corruption class — fix the P2P fabric or serve a \
1767                                         non-device MEMRA_DSV4_DECODE_PATH)",
1768                                        me.stages[s].dev, me.stages[d].dev,
1769                                    )
1770                                })?;
1771                            copies += 1;
1772                        }
1773                    }
1774                }
1775                eprintln!(
1776                    "[load] lane-8 peer byte-integrity probe PASS: {} boundaries, \
1777                     {copies} copies, ladder {ladder:?} bytes, {:.1}ms",
1778                    me.boundary_ev.len(),
1779                    probe_t0.elapsed().as_secs_f64() * 1e3,
1780                );
1781            }
1782            eprintln!(
1783                "[load] lane-8 peer transport enabled ({} boundaries)",
1784                me.boundary_ev.len()
1785            );
1786        }
1787
1788        // stage 0: embed; last stage: head + trunk hc_head/norm
1789        me.stages[0].embed = Some({
1790            let (_, raw) = me.model.st.raw("embed.weight").expect("embed.weight");
1791            let stream = me.stages[0].gpu.stream();
1792            me.stages[0].loaded_bytes += raw.len() as u64;
1793            upload_u8(&stream, raw)?
1794        });
1795        let last = me.stages.len() - 1;
1796        me.stages[last].head = Some({
1797            let (_, raw) = me.model.st.raw("head.weight").expect("head.weight");
1798            let stream = me.stages[last].gpu.stream();
1799            me.stages[last].loaded_bytes += raw.len() as u64;
1800            upload_u8(&stream, raw)?
1801        });
1802        me.stages[last].trunk_norm = Some(me.tensor_f32_dev(last, "norm.weight")?);
1803        me.stages[last].hc_head_fn = Some(me.tensor_f32_dev(last, "hc_head_fn")?);
1804        me.hc_head_base = me.model.tensor_f32("hc_head_base").1;
1805        me.hc_head_scale = me.model.tensor_f32("hc_head_scale").1;
1806        {
1807            let stream = me.stages[last].gpu.stream();
1808            let base_dev = upload_f32(&stream, &me.hc_head_base)?;
1809            let scale_dev = upload_f32(&stream, &me.hc_head_scale)?;
1810            me.stages[last].hc_head_base_dev = Some(base_dev);
1811            me.stages[last].hc_head_scale_dev = Some(scale_dev);
1812        }
1813
1814        let t0 = std::time::Instant::now();
1815        for il in 0..n_trunk {
1816            let stage = me.layer_stage[il as usize];
1817            let l = me.load_layer(stage, il, &format!("layers.{il}"))?;
1818            me.stages[stage].layers.push(l);
1819            if il % 4 == 3 || il + 1 == n_trunk {
1820                eprintln!(
1821                    "[load] layer {il} -> dev{} done t={:.0}s",
1822                    me.stages[stage].dev,
1823                    t0.elapsed().as_secs_f64()
1824                );
1825            }
1826        }
1827        // MTP (NextN) block on the last stage — optional path taken because the trunk
1828        // landed with box time to spare (lane brief); layer id = n_trunk from config.
1829        // 0731 lineage: the `mtp.*` namespace is the DSPARK drafter (3 window-only
1830        // blocks; census per the mint receipts: mtp.0 main_proj/main_norm, mtp.2
1831        // markov_w1/w2 + confidence_head — no e_proj/enorm), NOT a NextN head. Its GPU
1832        // path is a separate lane; the trunk forward never consumes it. Discriminate on
1833        // the artifact's own stored structure (lane-1 law: stored tensor names are the
1834        // recipe truth): a NextN block carries `mtp.0.e_proj.weight` (RAW safetensors
1835        // name — measured on both artifacts: preview has e_proj.weight+.scale, 0731 has
1836        // no e_proj keys; the stem alone misses because `has` is raw-exact).
1837        let nextn = me.model.mc.nextn_predict_layers;
1838        if nextn > 0 && me.model.has("mtp.0.e_proj.weight") {
1839            assert_eq!(
1840                nextn, 1,
1841                "multi-NextN chains not wired (single MTP layer expected)"
1842            );
1843            let p = "mtp.0";
1844            let layer = me.load_layer(last, n_trunk, p)?;
1845            assert_eq!(
1846                layer.expert_kind,
1847                ExpertKind::Mxfp4,
1848                "MTP experts must be MXFP4"
1849            );
1850            let mtp = MtpDev {
1851                layer,
1852                enorm: me.tensor_f32_dev(last, &format!("{p}.enorm.weight"))?,
1853                hnorm: me.tensor_f32_dev(last, &format!("{p}.hnorm.weight"))?,
1854                norm: me.tensor_f32_dev(last, &format!("{p}.norm.weight"))?,
1855                e_proj: me.tensor_bf16(last, &format!("{p}.e_proj"))?,
1856                h_proj: me.tensor_bf16(last, &format!("{p}.h_proj"))?,
1857                hc_head_fn: me.tensor_f32_dev(last, &format!("{p}.hc_head_fn"))?,
1858                hc_head_base: me.model.tensor_f32(&format!("{p}.hc_head_base")).1,
1859                hc_head_scale: me.model.tensor_f32(&format!("{p}.hc_head_scale")).1,
1860            };
1861            me.mtp = Some(mtp);
1862        } else if nextn > 0 {
1863            if std::env::var("MEMRA_DSV4_DRAFTER").as_deref() == Ok("dspark") {
1864                // iteration 3: the DSpark drafter, whole module on the LAST stage
1865                // (tap layers 40/41/42 + shared head locality — VRAM plan in the
1866                // iteration-3 receipts). Census pins + NextN refusal ride the CPU
1867                // oracle's own config loader (one refusal program, two realizations).
1868                let cfg = memra_gguf::dsv4_dspark::DsparkConfig::load(dir, &me.model);
1869                let hidden = me.model.mc.n_embd as usize;
1870                let mut blocks = Vec::with_capacity(cfg.n_blocks);
1871                for k in 0..cfg.n_blocks {
1872                    let layer = me.load_layer(last, n_trunk + k as u32, &format!("mtp.{k}"))?;
1873                    assert_eq!(layer.ratio, 0, "dspark block mtp.{k} must be ratio 0");
1874                    assert_eq!(
1875                        layer.expert_kind,
1876                        ExpertKind::Mxfp4,
1877                        "dspark experts must be MXFP4"
1878                    );
1879                    blocks.push(layer);
1880                }
1881                let last_p = format!("mtp.{}", cfg.n_blocks - 1);
1882                let (mp_shape, _) = me.model.tensor_f32("mtp.0.main_proj");
1883                assert_eq!(
1884                    mp_shape,
1885                    vec![hidden, cfg.target_layer_ids.len() * hidden],
1886                    "main_proj shape"
1887                );
1888                let (w1_shape, w1) = me
1889                    .model
1890                    .tensor_f32(&format!("{last_p}.markov_head.markov_w1.weight"));
1891                let (w2_shape, w2) = me
1892                    .model
1893                    .tensor_f32(&format!("{last_p}.markov_head.markov_w2.weight"));
1894                let vocab = w1_shape[0];
1895                assert_eq!(w1_shape[1], cfg.markov_rank, "markov_w1 rank");
1896                assert_eq!(w2_shape, vec![vocab, cfg.markov_rank], "markov_w2 shape");
1897                let (cf_shape, _) = me
1898                    .model
1899                    .tensor_f32(&format!("{last_p}.confidence_head.proj.weight"));
1900                assert_eq!(
1901                    cf_shape,
1902                    vec![1, hidden + cfg.markov_rank],
1903                    "confidence proj shape"
1904                );
1905                let st_stream = me.stages[last].gpu.stream();
1906                let markov_w1 = upload_f32(&st_stream, &w1)?;
1907                let markov_w2 = upload_f32(&st_stream, &w2)?;
1908                let dspark = DsparkDev {
1909                    blocks,
1910                    main_proj: me.tensor_bf16(last, "mtp.0.main_proj")?,
1911                    main_norm: me.tensor_f32_dev(last, "mtp.0.main_norm.weight")?,
1912                    norm: me.tensor_f32_dev(last, &format!("{last_p}.norm.weight"))?,
1913                    markov_w1,
1914                    markov_w2,
1915                    markov_w1_host: w1,
1916                    conf_w: me
1917                        .tensor_f32_dev(last, &format!("{last_p}.confidence_head.proj.weight"))?,
1918                    hc_head_fn: me.tensor_f32_dev(last, &format!("{last_p}.hc_head_fn"))?,
1919                    hc_head_base: me.model.tensor_f32(&format!("{last_p}.hc_head_base")).1,
1920                    hc_head_scale: me.model.tensor_f32(&format!("{last_p}.hc_head_scale")).1,
1921                    block_size: cfg.block_size,
1922                    noise_token: cfg.noise_token_id,
1923                    targets: cfg.target_layer_ids.clone(),
1924                    rank: cfg.markov_rank,
1925                    vocab,
1926                };
1927                eprintln!(
1928                    "[load] drafter: DSpark ({} blocks, block_size {}, targets {:?}) \
1929                     resident on stage {last}",
1930                    cfg.n_blocks, cfg.block_size, cfg.target_layer_ids
1931                );
1932                me.dspark = Some(dspark);
1933            } else {
1934                eprintln!(
1935                    "[load] drafter: {nextn} DSpark block(s) (mtp.0.e_proj absent) — GPU \
1936                     drafter path off (set MEMRA_DSV4_DRAFTER=dspark); trunk-only"
1937                );
1938            }
1939        }
1940        for st in &me.stages {
1941            st.gpu.stream().synchronize().map_err(e("load sync"))?;
1942        }
1943        Ok(me)
1944    }
1945
1946    /// (free, total, resident-by-loader) bytes per device — the placement table source.
1947    pub fn vram_report(&self) -> Res<Vec<(usize, u64, u64, u64)>> {
1948        let mut out = Vec::new();
1949        for st in &self.stages {
1950            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
1951            let (free, total) = st.gpu.ctx.mem_get_info().map_err(e("mem_get_info"))?;
1952            out.push((st.dev, free as u64, total as u64, st.loaded_bytes));
1953        }
1954        Ok(out)
1955    }
1956
1957    // ---------------------------------------------------------------- forward pieces
1958
1959    /// bf16 GEMM y[mxn] f32 = x[mxk] (f32, cast here) @ w[nxk]ᵀ (bf16 resident).
1960    /// `w_off_elems` slices the weight (grouped wo_a).
1961    #[allow(clippy::too_many_arguments)]
1962    fn gemm(
1963        st: &Stage,
1964        x_f32: &CudaSlice<f32>,
1965        w_bf16: &CudaSlice<u8>,
1966        w_off_elems: usize,
1967        m: usize,
1968        n: usize,
1969        kdim: usize,
1970        y: &mut CudaSlice<f32>,
1971    ) -> Res<()> {
1972        let stream = st.gpu.stream();
1973        let mut xb = stream
1974            .alloc_zeros::<u8>(m * kdim * 2)
1975            .map_err(e("alloc xb"))?;
1976        unsafe {
1977            ck(
1978                "cvt_bf16",
1979                k::memra_dsv4_cvt_bf16(
1980                    dpf!(x_f32, &stream),
1981                    xb.device_ptr_mut(&stream).0 as *mut c_void,
1982                    (m * kdim) as i64,
1983                    sp(&stream),
1984                ),
1985            )?;
1986            ck(
1987                "gemm_bf16",
1988                k::memra_dsv4_gemm_bf16(
1989                    (w_bf16.device_ptr(&stream).0 as usize + w_off_elems * 2) as *const c_void,
1990                    dp!(xb, &stream),
1991                    dpm!(y, &stream),
1992                    m as i32,
1993                    n as i32,
1994                    kdim as i32,
1995                    st.dev as i32,
1996                    st.ws.device_ptr(&stream).0 as *mut c_void,
1997                    st.ws.len(),
1998                    sp(&stream),
1999                ),
2000            )?;
2001        }
2002        Ok(())
2003    }
2004
2005    /// bf16 GEMM from an ALREADY-bf16 activation buffer.
2006    #[allow(clippy::too_many_arguments)]
2007    fn gemm_pre(
2008        st: &Stage,
2009        xb: &CudaSlice<u8>,
2010        w_bf16_ptr: *const c_void,
2011        m: usize,
2012        n: usize,
2013        kdim: usize,
2014        y: &mut CudaSlice<f32>,
2015    ) -> Res<()> {
2016        let stream = st.gpu.stream();
2017        unsafe {
2018            ck(
2019                "gemm_bf16",
2020                k::memra_dsv4_gemm_bf16(
2021                    w_bf16_ptr,
2022                    dp!(xb, &stream),
2023                    dpm!(y, &stream),
2024                    m as i32,
2025                    n as i32,
2026                    kdim as i32,
2027                    st.dev as i32,
2028                    st.ws.device_ptr(&stream).0 as *mut c_void,
2029                    st.ws.len(),
2030                    sp(&stream),
2031                ),
2032            )?;
2033        }
2034        Ok(())
2035    }
2036
2037    /// f32-island GEMM (f64-accumulated dots kernel).
2038    fn dots(
2039        st: &Stage,
2040        x: &CudaSlice<f32>,
2041        w_f32: &CudaSlice<f32>,
2042        s: usize,
2043        kdim: usize,
2044        n: usize,
2045        y: &mut CudaSlice<f32>,
2046    ) -> Res<()> {
2047        let stream = st.gpu.stream();
2048        unsafe {
2049            ck(
2050                "dots_f32",
2051                k::memra_dsv4_dots_f32(
2052                    dpf!(x, &stream),
2053                    dp!(w_f32, &stream),
2054                    0,
2055                    dpm!(y, &stream),
2056                    s as i32,
2057                    kdim as i32,
2058                    n as i32,
2059                    sp(&stream),
2060                ),
2061            )?;
2062        }
2063        Ok(())
2064    }
2065
2066    /// Island dots on the DEVICE decode path (lane 9): routes to the f64 oracle-truth
2067    /// arm (default — byte-identical to `Self::dots`) or the owner-gated
2068    /// f32-accumulation serving arm (MEMRA_DSV4_DOTS_ARM=f32; fork gated by
2069    /// decode-gate + oracle teacher-forcing, RECEIPTS.md "Lane 9").
2070    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
2071    fn dots_dev(
2072        &self,
2073        st: &Stage,
2074        x: &CudaSlice<f32>,
2075        w_f32: &CudaSlice<f32>,
2076        s: usize,
2077        kdim: usize,
2078        n: usize,
2079        y: &mut CudaSlice<f32>,
2080    ) -> Res<()> {
2081        if !self.dots_f32 {
2082            return Self::dots(st, x, w_f32, s, kdim, n, y);
2083        }
2084        let stream = st.gpu.stream();
2085        unsafe {
2086            ck(
2087                "dots_f32acc",
2088                k::memra_dsv4_dots_f32acc(
2089                    dpf!(x, &stream),
2090                    dp!(w_f32, &stream),
2091                    0,
2092                    dpm!(y, &stream),
2093                    s as i32,
2094                    kdim as i32,
2095                    n as i32,
2096                    sp(&stream),
2097                ),
2098            )?;
2099        }
2100        Ok(())
2101    }
2102
2103    /// Compressor forward (f32 island end-to-end). Returns (Some((ckv [nb, d], nb)) or
2104    /// None when no complete block, kv_raw [s, latent], score_raw [s, latent]).
2105    /// The raw GEMM outputs are ALWAYS computed (the reference does too, M:330-331) —
2106    /// lane 6 seeds the decode pending state from their trailing rows.
2107    #[allow(clippy::too_many_arguments)]
2108    #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
2109    fn compressor(
2110        &self,
2111        st: &Stage,
2112        cmp: &CmpDev,
2113        x: &CudaSlice<f32>, // [s, hidden] post-attn-norm
2114        s: usize,
2115        hidden: usize,
2116        fc_dev: &CudaSlice<f32>,
2117        rd: usize,
2118        eps: f32,
2119    ) -> Res<(
2120        Option<(CudaSlice<f32>, usize)>,
2121        CudaSlice<f32>,
2122        CudaSlice<f32>,
2123    )> {
2124        let stream = st.gpu.stream();
2125        let mut kv = stream
2126            .alloc_zeros::<f32>(s * cmp.latent)
2127            .map_err(e("cmp kv"))?;
2128        let mut score = stream
2129            .alloc_zeros::<f32>(s * cmp.latent)
2130            .map_err(e("cmp score"))?;
2131        Self::dots(st, x, &cmp.wkv, s, hidden, cmp.latent, &mut kv)?;
2132        Self::dots(st, x, &cmp.wgate, s, hidden, cmp.latent, &mut score)?;
2133        if s < cmp.ratio {
2134            return Ok((None, kv, score));
2135        }
2136        let cutoff = s - s % cmp.ratio;
2137        let nb = cutoff / cmp.ratio;
2138        let mut pooled = stream
2139            .alloc_zeros::<f32>(nb * cmp.d)
2140            .map_err(e("cmp out"))?;
2141        unsafe {
2142            ck(
2143                "compressor_pool",
2144                k::memra_dsv4_compressor_pool(
2145                    dpf!(kv, &stream),
2146                    dpf!(score, &stream),
2147                    dpf!(cmp.ape, &stream),
2148                    dpm!(pooled, &stream),
2149                    nb as i32,
2150                    cmp.ratio as i32,
2151                    cmp.d as i32,
2152                    cmp.latent as i32,
2153                    cmp.overlap as i32,
2154                    sp(&stream),
2155                ),
2156            )?;
2157            ck(
2158                "rmsnorm cmp",
2159                k::memra_dsv4_rmsnorm(
2160                    dpf!(pooled, &stream),
2161                    dpf!(cmp.norm, &stream),
2162                    dpm!(pooled, &stream),
2163                    nb as i32,
2164                    cmp.d as i32,
2165                    eps,
2166                    sp(&stream),
2167                ),
2168            )?;
2169            let positions: Vec<i32> = (0..nb).map(|j| (j * cmp.ratio) as i32).collect();
2170            let pos_dev = upload_i32(&stream, &positions)?;
2171            ck(
2172                "rope cmp",
2173                k::memra_dsv4_rope(
2174                    dpm!(pooled, &stream),
2175                    nb as i32,
2176                    1,
2177                    cmp.d as i32,
2178                    rd as i32,
2179                    dpf!(fc_dev, &stream),
2180                    pos_dev.device_ptr(&stream).0 as *const i32,
2181                    0,
2182                    sp(&stream),
2183                ),
2184            )?;
2185            if cmp.rotate {
2186                // oracle hadamard scale: (d as f32).powf(-0.5)
2187                let scale = (cmp.d as f32).powf(-0.5);
2188                ck(
2189                    "hadamard cmp",
2190                    k::memra_dsv4_hadamard(
2191                        dpm!(pooled, &stream),
2192                        nb as i32,
2193                        cmp.d as i32,
2194                        scale,
2195                        sp(&stream),
2196                    ),
2197                )?;
2198                ck(
2199                    "fp4 cmp",
2200                    k::memra_dsv4_fp4_act_quant(
2201                        dpm!(pooled, &stream),
2202                        nb as i32,
2203                        cmp.d as i64,
2204                        cmp.d as i32,
2205                        sp(&stream),
2206                    ),
2207                )?;
2208            } else {
2209                ck(
2210                    "act_quant cmp",
2211                    k::memra_dsv4_act_quant(
2212                        dpm!(pooled, &stream),
2213                        nb as i32,
2214                        cmp.d as i64,
2215                        (cmp.d - rd) as i32,
2216                        64,
2217                        (self.variant == ActQuantVariant::ClampOnly) as i32,
2218                        sp(&stream),
2219                    ),
2220                )?;
2221            }
2222        }
2223        Ok((Some((pooled, nb)), kv, score))
2224    }
2225
2226    /// Prefill→decode handoff for one compressor: copy the pooled blocks into the
2227    /// store rows [row0, row0+nb) and seed the pending state from the raw kv/score
2228    /// trailing rows (fine: last COMPLETE block → prev slots + remainder → cur slots,
2229    /// M:346-352; coarse: remainder → slots [0, rem)).
2230    #[allow(clippy::too_many_arguments)]
2231    fn populate_cmp_cache(
2232        stream: &std::sync::Arc<CudaStream>,
2233        s: usize,
2234        cmp_ratio: usize,
2235        latent: usize,
2236        d: usize,
2237        pooled: &Option<(CudaSlice<f32>, usize)>,
2238        kv_raw: &CudaSlice<f32>,
2239        score_raw: &CudaSlice<f32>,
2240        store: &mut CudaSlice<f32>,
2241        row0: usize,
2242        blocks: &mut usize,
2243        pend_kv: &mut CudaSlice<f32>,
2244        pend_score: &mut CudaSlice<f32>,
2245        overlap: bool,
2246    ) -> Res<()> {
2247        *blocks = 0;
2248        if let Some((buf, nb)) = pooled {
2249            let src = buf.slice(0..nb * d);
2250            let mut dst = store.slice_mut(row0 * d..(row0 + nb) * d);
2251            stream.memcpy_dtod(&src, &mut dst).map_err(e("cmp store"))?;
2252            *blocks = *nb;
2253        }
2254        let cutoff = s - s % cmp_ratio;
2255        let rem = s - cutoff;
2256        if overlap {
2257            if cutoff >= cmp_ratio {
2258                let a = (cutoff - cmp_ratio) * latent;
2259                let b = cutoff * latent;
2260                let src = kv_raw.slice(a..b);
2261                let mut dst = pend_kv.slice_mut(0..cmp_ratio * latent);
2262                stream
2263                    .memcpy_dtod(&src, &mut dst)
2264                    .map_err(e("pend kv prev"))?;
2265                let src = score_raw.slice(a..b);
2266                let mut dst = pend_score.slice_mut(0..cmp_ratio * latent);
2267                stream
2268                    .memcpy_dtod(&src, &mut dst)
2269                    .map_err(e("pend sc prev"))?;
2270            }
2271            if rem > 0 {
2272                let a = cutoff * latent;
2273                let src = kv_raw.slice(a..s * latent);
2274                let mut dst = pend_kv.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2275                stream
2276                    .memcpy_dtod(&src, &mut dst)
2277                    .map_err(e("pend kv cur"))?;
2278                let src = score_raw.slice(a..s * latent);
2279                let mut dst = pend_score.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2280                stream
2281                    .memcpy_dtod(&src, &mut dst)
2282                    .map_err(e("pend sc cur"))?;
2283            }
2284        } else if rem > 0 {
2285            let a = cutoff * latent;
2286            let src = kv_raw.slice(a..s * latent);
2287            let mut dst = pend_kv.slice_mut(0..rem * latent);
2288            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
2289            let src = score_raw.slice(a..s * latent);
2290            let mut dst = pend_score.slice_mut(0..rem * latent);
2291            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
2292        }
2293        Ok(())
2294    }
2295
2296    /// hc_pre: mixes GEMM (f32 island) + rowsq scale on GPU, Sinkhorn on HOST via the
2297    /// oracle's own hc_split_sinkhorn. Returns (y [s,hidden] dev, post dev, comb dev).
2298    #[allow(clippy::too_many_arguments)]
2299    fn hc_pre(
2300        st: &Stage,
2301        h: &CudaSlice<f32>, // [s, hc, hidden]
2302        fn_w: &CudaSlice<f32>,
2303        base: &[f32],
2304        scale: &[f32],
2305        s: usize,
2306        hc: usize,
2307        hidden: usize,
2308        iters: u32,
2309        hc_eps: f32,
2310    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)> {
2311        let stream = st.gpu.stream();
2312        let w = hc * hidden;
2313        let rows = (2 + hc) * hc;
2314        let mut mixes = stream.alloc_zeros::<f32>(s * rows).map_err(e("mixes"))?;
2315        Self::dots(st, h, fn_w, s, w, rows, &mut mixes)?;
2316        unsafe {
2317            ck(
2318                "rowsq_scale",
2319                k::memra_dsv4_rowsq_scale(
2320                    dpf!(h, &stream),
2321                    dpm!(mixes, &stream),
2322                    s as i32,
2323                    w as i32,
2324                    rows as i32,
2325                    hc_eps,
2326                    sp(&stream),
2327                ),
2328            )?;
2329        }
2330        let mixes_h = dtoh_f32(&stream, &mixes)?;
2331        let (pre, post, comb) = hc_split_sinkhorn(&mixes_h, s, hc, scale, base, iters, hc_eps);
2332        let pre_d = upload_f32(&stream, &pre)?;
2333        let post_d = upload_f32(&stream, &post)?;
2334        let comb_d = upload_f32(&stream, &comb)?;
2335        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("hc y"))?;
2336        unsafe {
2337            ck(
2338                "hc_collapse",
2339                k::memra_dsv4_hc_collapse(
2340                    dpf!(h, &stream),
2341                    dpf!(pre_d, &stream),
2342                    dpm!(y, &stream),
2343                    s as i32,
2344                    hc as i32,
2345                    hidden as i32,
2346                    sp(&stream),
2347                ),
2348            )?;
2349        }
2350        Ok((y, post_d, comb_d))
2351    }
2352
2353    /// Host routing — the oracle MoeW::forward selection/weight math verbatim.
2354    #[allow(clippy::too_many_arguments)]
2355    fn route_host(
2356        layer: &LayerDev,
2357        raw_scores: &[f32], // [s, ne] gate GEMM output (pre-softplus)
2358        ids: &[u32],
2359        s: usize,
2360        ne: usize,
2361        topk: usize,
2362        route_scale: f32,
2363    ) -> (Vec<usize>, Vec<f32>) {
2364        let mut scores = raw_scores.to_vec();
2365        for v in &mut scores {
2366            *v = softplus_f32(*v).sqrt();
2367        }
2368        let mut indices = vec![0usize; s * topk];
2369        if let Some(tid2eid) = &layer.tid2eid {
2370            for t in 0..s {
2371                let row = &tid2eid[ids[t] as usize * topk..(ids[t] as usize + 1) * topk];
2372                let mut seen = std::collections::BTreeSet::new();
2373                for (kk, &ex) in row.iter().enumerate() {
2374                    assert!(
2375                        (0..ne as i64).contains(&ex),
2376                        "layer {}: tid2eid out of range",
2377                        layer.il
2378                    );
2379                    assert!(
2380                        seen.insert(ex),
2381                        "layer {}: duplicate expert id in tid2eid row {}",
2382                        layer.il,
2383                        ids[t]
2384                    );
2385                    indices[t * topk + kk] = ex as usize;
2386                }
2387            }
2388        } else {
2389            let bias = layer.gate_bias.as_ref().expect("score layer needs bias");
2390            for t in 0..s {
2391                let biased: Vec<f32> = (0..ne).map(|ex| scores[t * ne + ex] + bias[ex]).collect();
2392                let mut order: Vec<usize> = (0..ne).collect();
2393                order.sort_by(|&a, &b| {
2394                    biased[b]
2395                        .partial_cmp(&biased[a])
2396                        .unwrap_or(std::cmp::Ordering::Equal)
2397                        .then(a.cmp(&b))
2398                });
2399                for kk in 0..topk {
2400                    indices[t * topk + kk] = order[kk];
2401                }
2402            }
2403        }
2404        let mut weights = vec![0f32; s * topk];
2405        for t in 0..s {
2406            let mut sum = 0f32;
2407            for kk in 0..topk {
2408                let w = scores[t * ne + indices[t * topk + kk]];
2409                weights[t * topk + kk] = w;
2410                sum += w;
2411            }
2412            for kk in 0..topk {
2413                weights[t * topk + kk] = weights[t * topk + kk] / sum * route_scale;
2414            }
2415        }
2416        (indices, weights)
2417    }
2418
2419    /// One trunk block on its stage. h is [s, hc, hidden] f32 on the stage device.
2420    /// `cache` (lane 6): populate this layer's decode cache while prefilling.
2421    #[allow(clippy::too_many_arguments)]
2422    fn block_forward(
2423        &self,
2424        st: &Stage,
2425        layer: &LayerDev,
2426        h: &CudaSlice<f32>,
2427        s: usize,
2428        ids: &[u32],
2429        mut capture: Option<&mut GpuCapture>,
2430        mut cache: Option<&mut LayerCache>,
2431    ) -> Res<CudaSlice<f32>> {
2432        let d = self.model.cfg();
2433        let mc = &self.model.mc;
2434        let hc = d.hc_mult as usize;
2435        let hidden = mc.n_embd as usize;
2436        let heads = mc.n_head as usize;
2437        let hd = d.head_dim as usize;
2438        let rd = d.qk_rope_head_dim as usize;
2439        let q_lora = d.q_lora_rank as usize;
2440        let win = d.sliding_window as usize;
2441        let o_groups = d.o_groups as usize;
2442        let o_lora = d.o_lora_rank as usize;
2443        let eps = mc.rms_eps;
2444        let iters = d.hc_sinkhorn_iters;
2445        let hc_eps = d.hc_eps;
2446        // Runtime-API kernel launches in the FFI TU need this stage's context current on
2447        // the calling thread (cudarc binds it inside its own ops, but the previous op may
2448        // have been another stage's).
2449        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
2450        let stream = st.gpu.stream();
2451        let fc_dev = if layer.ratio != 0 {
2452            &st.fc_yarn
2453        } else {
2454            &st.fc_plain
2455        };
2456        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
2457
2458        // ---- attention sub-block
2459        let (y, post, comb) = Self::hc_pre(
2460            st,
2461            h,
2462            &layer.hc_attn_fn,
2463            &layer.hc_attn_base,
2464            &layer.hc_attn_scale,
2465            s,
2466            hc,
2467            hidden,
2468            iters,
2469            hc_eps,
2470        )?;
2471        let mut x = stream.alloc_zeros::<f32>(s * hidden).map_err(e("x"))?;
2472        unsafe {
2473            ck(
2474                "rmsnorm attn",
2475                k::memra_dsv4_rmsnorm(
2476                    dpf!(y, &stream),
2477                    dpf!(layer.attn_norm, &stream),
2478                    dpm!(x, &stream),
2479                    s as i32,
2480                    hidden as i32,
2481                    eps,
2482                    sp(&stream),
2483                ),
2484            )?;
2485        }
2486
2487        // q path (item 3: under the fp8 dense arm the bf16 slabs are host-staged —
2488        // each `staged` view uploads a transient device copy freed, stream-ordered,
2489        // when the view drops at the end of this pass; on the bf16 arm it borrows
2490        // the resident slab and stages nothing)
2491        let wq_a_v = layer.wq_a.staged(&stream)?;
2492        let mut qr = stream.alloc_zeros::<f32>(s * q_lora).map_err(e("qr"))?;
2493        Self::gemm(st, &x, wq_a_v.slab(), 0, s, q_lora, hidden, &mut qr)?;
2494        unsafe {
2495            ck(
2496                "rmsnorm q",
2497                k::memra_dsv4_rmsnorm(
2498                    dpf!(qr, &stream),
2499                    dpf!(layer.q_norm, &stream),
2500                    dpm!(qr, &stream),
2501                    s as i32,
2502                    q_lora as i32,
2503                    eps,
2504                    sp(&stream),
2505                ),
2506            )?;
2507        }
2508        // qr as bf16 once (feeds wq_b and the indexer wq_b, oracle reuses qr the same way)
2509        let mut qr_b = stream
2510            .alloc_zeros::<u8>(s * q_lora * 2)
2511            .map_err(e("qr_b"))?;
2512        unsafe {
2513            ck(
2514                "cvt qr",
2515                k::memra_dsv4_cvt_bf16(
2516                    dpf!(qr, &stream),
2517                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
2518                    (s * q_lora) as i64,
2519                    sp(&stream),
2520                ),
2521            )?;
2522        }
2523        let wq_b_v = layer.wq_b.staged(&stream)?;
2524        let mut q = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("q"))?;
2525        Self::gemm_pre(
2526            st,
2527            &qr_b,
2528            wq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2529            s,
2530            heads * hd,
2531            q_lora,
2532            &mut q,
2533        )?;
2534        let positions: Vec<i32> = (0..s as i32).collect();
2535        let pos_dev = upload_i32(&stream, &positions)?;
2536        unsafe {
2537            ck(
2538                "headrms",
2539                k::memra_dsv4_headrms(
2540                    dpm!(q, &stream),
2541                    (s * heads) as i32,
2542                    hd as i32,
2543                    eps,
2544                    sp(&stream),
2545                ),
2546            )?;
2547            ck(
2548                "rope q",
2549                k::memra_dsv4_rope(
2550                    dpm!(q, &stream),
2551                    s as i32,
2552                    heads as i32,
2553                    hd as i32,
2554                    rd as i32,
2555                    dpf!(fc_dev, &stream),
2556                    pos_dev.device_ptr(&stream).0 as *const i32,
2557                    0,
2558                    sp(&stream),
2559                ),
2560            )?;
2561        }
2562
2563        // shared K==V latent + window QAT
2564        let wkv_v = layer.wkv.staged(&stream)?;
2565        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("kv"))?;
2566        Self::gemm(st, &x, wkv_v.slab(), 0, s, hd, hidden, &mut kv)?;
2567        unsafe {
2568            ck(
2569                "rmsnorm kv",
2570                k::memra_dsv4_rmsnorm(
2571                    dpf!(kv, &stream),
2572                    dpf!(layer.kv_norm, &stream),
2573                    dpm!(kv, &stream),
2574                    s as i32,
2575                    hd as i32,
2576                    eps,
2577                    sp(&stream),
2578                ),
2579            )?;
2580            ck(
2581                "rope kv",
2582                k::memra_dsv4_rope(
2583                    dpm!(kv, &stream),
2584                    s as i32,
2585                    1,
2586                    hd as i32,
2587                    rd as i32,
2588                    dpf!(fc_dev, &stream),
2589                    pos_dev.device_ptr(&stream).0 as *const i32,
2590                    0,
2591                    sp(&stream),
2592                ),
2593            )?;
2594            ck(
2595                "act_quant kv",
2596                k::memra_dsv4_act_quant(
2597                    dpm!(kv, &stream),
2598                    s as i32,
2599                    hd as i64,
2600                    (hd - rd) as i32,
2601                    64,
2602                    clamp_only,
2603                    sp(&stream),
2604                ),
2605            )?;
2606        }
2607        // lane 6: window ring handoff — last min(s, win) post-QAT rows at slot p % win
2608        // (M:524-527: prefill leaves the cache exactly as if the ring had been written
2609        // position by position).
2610        if let Some(c) = cache.as_deref_mut() {
2611            for p in s.saturating_sub(win)..s {
2612                let slot = p % win;
2613                let src = kv.slice(p * hd..(p + 1) * hd);
2614                let mut dst = c.kvc.slice_mut(slot * hd..(slot + 1) * hd);
2615                stream.memcpy_dtod(&src, &mut dst).map_err(e("ring copy"))?;
2616            }
2617        }
2618
2619        // index assembly (host, oracle builders) + compressed kv
2620        let (widx, wslots) = window_topk_idxs(win, s);
2621        let mut idxs: Vec<i64> = widx;
2622        let mut slots = wslots;
2623        let mut n_kv = s;
2624        let mut kv_full = kv;
2625        let mut cap_cmp: Option<(Vec<f32>, usize)> = None;
2626        let mut cap_ikv: Option<(Vec<f32>, usize)> = None;
2627        let mut cap_isc: Option<(Vec<f32>, usize)> = None;
2628        let want_cap = capture
2629            .as_ref()
2630            .map(|c| c.want.contains(&layer.il))
2631            .unwrap_or(false);
2632        if layer.ratio != 0 {
2633            let offset = s;
2634            let (cidx, cslots) = if let Some(ix) = &layer.idx {
2635                // indexer q
2636                let mut qi = stream
2637                    .alloc_zeros::<f32>(s * ix.heads * ix.hd)
2638                    .map_err(e("qi"))?;
2639                let iwq_b_v = ix.wq_b.staged(&stream)?;
2640                Self::gemm_pre(
2641                    st,
2642                    &qr_b,
2643                    iwq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2644                    s,
2645                    ix.heads * ix.hd,
2646                    q_lora,
2647                    &mut qi,
2648                )?;
2649                unsafe {
2650                    ck(
2651                        "rope qi",
2652                        k::memra_dsv4_rope(
2653                            dpm!(qi, &stream),
2654                            s as i32,
2655                            ix.heads as i32,
2656                            ix.hd as i32,
2657                            rd as i32,
2658                            dpf!(fc_dev, &stream),
2659                            pos_dev.device_ptr(&stream).0 as *const i32,
2660                            0,
2661                            sp(&stream),
2662                        ),
2663                    )?;
2664                    let scale = (ix.hd as f32).powf(-0.5);
2665                    ck(
2666                        "hadamard qi",
2667                        k::memra_dsv4_hadamard(
2668                            dpm!(qi, &stream),
2669                            (s * ix.heads) as i32,
2670                            ix.hd as i32,
2671                            scale,
2672                            sp(&stream),
2673                        ),
2674                    )?;
2675                    ck(
2676                        "fp4 qi",
2677                        k::memra_dsv4_fp4_act_quant(
2678                            dpm!(qi, &stream),
2679                            (s * ix.heads) as i32,
2680                            ix.hd as i64,
2681                            ix.hd as i32,
2682                            sp(&stream),
2683                        ),
2684                    )?;
2685                }
2686                // indexer compressed kv
2687                let (ckv_i, ikv_raw, isc_raw) =
2688                    self.compressor(st, &ix.cmp, &x, s, hidden, fc_dev, rd, eps)?;
2689                if want_cap && let Some((buf, nb)) = &ckv_i {
2690                    cap_ikv = Some((dtoh_f32(&stream, buf)?, *nb));
2691                }
2692                if let Some(c) = cache.as_deref_mut() {
2693                    let mut i_blocks = c.i_blocks;
2694                    Self::populate_cmp_cache(
2695                        &stream,
2696                        s,
2697                        ix.cmp.ratio,
2698                        ix.cmp.latent,
2699                        ix.cmp.d,
2700                        &ckv_i,
2701                        &ikv_raw,
2702                        &isc_raw,
2703                        c.ikvc.as_mut().expect("fine layer has indexer store"),
2704                        0,
2705                        &mut i_blocks,
2706                        c.ipend_kv.as_mut().expect("ipend"),
2707                        c.ipend_score.as_mut().expect("ipend"),
2708                        ix.cmp.overlap,
2709                    )?;
2710                    c.i_blocks = i_blocks;
2711                }
2712                // head weights (weights_proj is BF16 — lawful bf16 GEMM)
2713                let iwp_v = ix.weights_proj.staged(&stream)?;
2714                let mut wproj = stream.alloc_zeros::<f32>(s * ix.heads).map_err(e("wp"))?;
2715                Self::gemm(st, &x, iwp_v.slab(), 0, s, ix.heads, hidden, &mut wproj)?;
2716                if let Some((ckv, nb)) = &ckv_i {
2717                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
2718                    let mut score = stream.alloc_zeros::<f32>(s * nb).map_err(e("iscore"))?;
2719                    unsafe {
2720                        ck(
2721                            "indexer_score",
2722                            k::memra_dsv4_indexer_score(
2723                                dpf!(qi, &stream),
2724                                dpf!(ckv, &stream),
2725                                dpf!(wproj, &stream),
2726                                wscale,
2727                                dpm!(score, &stream),
2728                                s as i32,
2729                                ix.heads as i32,
2730                                ix.hd as i32,
2731                                *nb as i32,
2732                                layer.ratio as i32,
2733                                -1, // prefill law: lim = (t+1)/ratio with local t
2734                                sp(&stream),
2735                            ),
2736                        )?;
2737                    }
2738                    let score_h = dtoh_f32(&stream, &score)?;
2739                    if want_cap {
2740                        cap_isc = Some((score_h.clone(), *nb));
2741                    }
2742                    // host topk with the oracle's exact ordering + re-mask (model.py:508-510)
2743                    let kk = ix.topk.min(*nb);
2744                    let mut cidx = vec![-1i64; s * kk];
2745                    for t in 0..s {
2746                        let lim = (t + 1) / layer.ratio;
2747                        let mut order: Vec<usize> = (0..*nb).collect();
2748                        order.sort_by(|&a, &b| {
2749                            score_h[t * nb + b]
2750                                .partial_cmp(&score_h[t * nb + a])
2751                                .unwrap_or(std::cmp::Ordering::Equal)
2752                                .then(a.cmp(&b))
2753                        });
2754                        for (slot, &j) in order.iter().take(kk).enumerate() {
2755                            cidx[t * kk + slot] = if j >= lim { -1 } else { (j + offset) as i64 };
2756                        }
2757                    }
2758                    (cidx, kk)
2759                } else {
2760                    (Vec::new(), 0)
2761                }
2762            } else {
2763                compress_topk_idxs(layer.ratio, s, offset)
2764            };
2765            if cslots > 0 {
2766                let mut merged = vec![-1i64; s * (slots + cslots)];
2767                for t in 0..s {
2768                    merged[t * (slots + cslots)..t * (slots + cslots) + slots]
2769                        .copy_from_slice(&idxs[t * slots..(t + 1) * slots]);
2770                    merged[t * (slots + cslots) + slots..(t + 1) * (slots + cslots)]
2771                        .copy_from_slice(&cidx[t * cslots..(t + 1) * cslots]);
2772                }
2773                idxs = merged;
2774                slots += cslots;
2775            }
2776            // attention-side compressed kv appended to the kv stream
2777            let acmp = layer.cmp.as_ref().expect("ratio!=0 has compressor");
2778            let (ckv, akv_raw, asc_raw) =
2779                self.compressor(st, acmp, &x, s, hidden, fc_dev, rd, eps)?;
2780            if want_cap && let Some((buf, nb)) = &ckv {
2781                cap_cmp = Some((dtoh_f32(&stream, buf)?, *nb));
2782            }
2783            if let Some(c) = cache {
2784                let mut n_blocks = c.n_blocks;
2785                Self::populate_cmp_cache(
2786                    &stream,
2787                    s,
2788                    acmp.ratio,
2789                    acmp.latent,
2790                    acmp.d,
2791                    &ckv,
2792                    &akv_raw,
2793                    &asc_raw,
2794                    &mut c.kvc,
2795                    win,
2796                    &mut n_blocks,
2797                    c.pend_kv.as_mut().expect("pend"),
2798                    c.pend_score.as_mut().expect("pend"),
2799                    acmp.overlap,
2800                )?;
2801                c.n_blocks = n_blocks;
2802            }
2803            if let Some((ckv_buf, nb)) = ckv {
2804                let mut merged_kv = stream
2805                    .alloc_zeros::<f32>((s + nb) * hd)
2806                    .map_err(e("kv_full"))?;
2807                {
2808                    let mut head_view = merged_kv.slice_mut(0..s * hd);
2809                    stream
2810                        .memcpy_dtod(&kv_full.slice(0..s * hd), &mut head_view)
2811                        .map_err(e("kv copy"))?;
2812                }
2813                {
2814                    let mut tail = merged_kv.slice_mut(s * hd..(s + nb) * hd);
2815                    stream
2816                        .memcpy_dtod(&ckv_buf.slice(0..nb * hd), &mut tail)
2817                        .map_err(e("ckv copy"))?;
2818                }
2819                kv_full = merged_kv;
2820                n_kv += nb;
2821            }
2822        }
2823        let _ = n_kv;
2824        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
2825        let idx_dev = upload_i32(&stream, &idxs_i32)?;
2826
2827        // sparse sink attention + query-position de-rotation
2828        let mut o = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("o"))?;
2829        let scale = (hd as f64).powf(-0.5) as f32;
2830        unsafe {
2831            ck(
2832                "sink_attn",
2833                k::memra_dsv4_sink_attn(
2834                    dpf!(q, &stream),
2835                    dpf!(kv_full, &stream),
2836                    idx_dev.device_ptr(&stream).0 as *const i32,
2837                    dpf!(layer.sink, &stream),
2838                    dpm!(o, &stream),
2839                    s as i32,
2840                    heads as i32,
2841                    hd as i32,
2842                    slots as i32,
2843                    scale,
2844                    sp(&stream),
2845                ),
2846            )?;
2847            ck(
2848                "rope o inv",
2849                k::memra_dsv4_rope(
2850                    dpm!(o, &stream),
2851                    s as i32,
2852                    heads as i32,
2853                    hd as i32,
2854                    rd as i32,
2855                    dpf!(fc_dev, &stream),
2856                    pos_dev.device_ptr(&stream).0 as *const i32,
2857                    1,
2858                    sp(&stream),
2859                ),
2860            )?;
2861        }
2862
2863        // grouped wo: per group g, og[:, g*o_lora..] = o_g @ wo_a[g]ᵀ; then wo_b.
2864        let gw = heads / o_groups * hd;
2865        let mut og = stream
2866            .alloc_zeros::<f32>(s * o_groups * o_lora)
2867            .map_err(e("og"))?;
2868        let mut o_grp = stream.alloc_zeros::<f32>(s * gw).map_err(e("o_grp"))?;
2869        let mut y_grp = stream.alloc_zeros::<f32>(s * o_lora).map_err(e("y_grp"))?;
2870        let wo_a_v = layer.wo_a.staged(&stream)?; // once, outside the group loop
2871        for g in 0..o_groups {
2872            unsafe {
2873                ck(
2874                    "take_cols",
2875                    k::memra_dsv4_take_cols(
2876                        dpf!(o, &stream),
2877                        dpm!(o_grp, &stream),
2878                        s as i32,
2879                        gw as i32,
2880                        (heads * hd) as i64,
2881                        (g * gw) as i64,
2882                        sp(&stream),
2883                    ),
2884                )?;
2885            }
2886            Self::gemm(
2887                st,
2888                &o_grp,
2889                wo_a_v.slab(),
2890                g * o_lora * gw,
2891                s,
2892                o_lora,
2893                gw,
2894                &mut y_grp,
2895            )?;
2896            unsafe {
2897                ck(
2898                    "place_cols",
2899                    k::memra_dsv4_place_cols(
2900                        dpf!(y_grp, &stream),
2901                        dpm!(og, &stream),
2902                        s as i32,
2903                        o_lora as i32,
2904                        (o_groups * o_lora) as i64,
2905                        (g * o_lora) as i64,
2906                        sp(&stream),
2907                    ),
2908                )?;
2909            }
2910        }
2911        let wo_b_v = layer.wo_b.staged(&stream)?;
2912        let mut attn_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("ao"))?;
2913        Self::gemm(
2914            st,
2915            &og,
2916            wo_b_v.slab(),
2917            0,
2918            s,
2919            hidden,
2920            o_groups * o_lora,
2921            &mut attn_out,
2922        )?;
2923
2924        let mut cap_attn: Option<Vec<f32>> = None;
2925        if want_cap {
2926            cap_attn = Some(dtoh_f32(&stream, &attn_out)?);
2927        }
2928
2929        // hc_post (attention)
2930        let mut h2 = stream
2931            .alloc_zeros::<f32>(s * hc * hidden)
2932            .map_err(e("h2"))?;
2933        unsafe {
2934            ck(
2935                "hc_post attn",
2936                k::memra_dsv4_hc_post(
2937                    dpf!(attn_out, &stream),
2938                    dpf!(h, &stream),
2939                    dpf!(post, &stream),
2940                    dpf!(comb, &stream),
2941                    dpm!(h2, &stream),
2942                    s as i32,
2943                    hc as i32,
2944                    hidden as i32,
2945                    sp(&stream),
2946                ),
2947            )?;
2948        }
2949
2950        // ---- ffn sub-block
2951        let (y2, post2, comb2) = Self::hc_pre(
2952            st,
2953            &h2,
2954            &layer.hc_ffn_fn,
2955            &layer.hc_ffn_base,
2956            &layer.hc_ffn_scale,
2957            s,
2958            hc,
2959            hidden,
2960            iters,
2961            hc_eps,
2962        )?;
2963        let mut xf = stream.alloc_zeros::<f32>(s * hidden).map_err(e("xf"))?;
2964        unsafe {
2965            ck(
2966                "rmsnorm ffn",
2967                k::memra_dsv4_rmsnorm(
2968                    dpf!(y2, &stream),
2969                    dpf!(layer.ffn_norm, &stream),
2970                    dpm!(xf, &stream),
2971                    s as i32,
2972                    hidden as i32,
2973                    eps,
2974                    sp(&stream),
2975                ),
2976            )?;
2977        }
2978        if let Some(c) = capture.as_deref_mut()
2979            && c.want.contains(&layer.il)
2980        {
2981            c.moe_x.insert(layer.il, dtoh_f32(&stream, &xf)?);
2982        }
2983        let moe_out = self.moe_forward(st, layer, &xf, s, ids)?;
2984        let mut h3 = stream
2985            .alloc_zeros::<f32>(s * hc * hidden)
2986            .map_err(e("h3"))?;
2987        unsafe {
2988            ck(
2989                "hc_post ffn",
2990                k::memra_dsv4_hc_post(
2991                    dpf!(moe_out, &stream),
2992                    dpf!(h2, &stream),
2993                    dpf!(post2, &stream),
2994                    dpf!(comb2, &stream),
2995                    dpm!(h3, &stream),
2996                    s as i32,
2997                    hc as i32,
2998                    hidden as i32,
2999                    sp(&stream),
3000                ),
3001            )?;
3002        }
3003
3004        if let Some(c) = capture
3005            && c.want.contains(&layer.il)
3006        {
3007            c.layer_out.insert(layer.il, dtoh_f32(&stream, &h3)?);
3008            c.x_dbg.insert(layer.il, dtoh_f32(&stream, &x)?);
3009            c.q_dbg.insert(layer.il, dtoh_f32(&stream, &q)?);
3010            {
3011                let mut kvh = vec![0f32; s * hd];
3012                stream
3013                    .memcpy_dtoh(&kv_full.slice(0..s * hd), &mut kvh[..])
3014                    .map_err(e("dtoh kv_dbg"))?;
3015                stream.synchronize().map_err(e("sync kv_dbg"))?;
3016                c.kv_dbg.insert(layer.il, kvh);
3017            }
3018            c.o_dbg.insert(layer.il, dtoh_f32(&stream, &o)?);
3019            if let Some(a) = cap_attn {
3020                c.attn_out.insert(layer.il, a);
3021            }
3022            if let Some(v) = cap_cmp {
3023                c.compressor_kv.insert(layer.il, v);
3024            }
3025            if let Some(v) = cap_ikv {
3026                c.indexer_kv.insert(layer.il, v);
3027            }
3028            if let Some(v) = cap_isc {
3029                c.index_score.insert(layer.il, v);
3030            }
3031        }
3032        Ok(h3)
3033    }
3034
3035    /// MoE on GPU: gate GEMM f32 island -> host routing (oracle math) -> per-expert
3036    /// on-the-fly NVFP4 dequant + bf16 GEMMs (ascending expert order, oracle
3037    /// accumulation order) -> shared expert last.
3038    fn moe_forward(
3039        &self,
3040        st: &Stage,
3041        layer: &LayerDev,
3042        x: &CudaSlice<f32>, // [s, hidden] post-ffn-norm
3043        s: usize,
3044        ids: &[u32],
3045    ) -> Res<CudaSlice<f32>> {
3046        let mc = &self.model.mc;
3047        let d = self.model.cfg();
3048        let moe = mc.moe.as_ref().expect("moe");
3049        let hidden = mc.n_embd as usize;
3050        let ne = moe.expert_count as usize;
3051        let topk = moe.expert_used_count as usize;
3052        let inter = moe.expert_ff_length as usize;
3053        let limit = d.swiglu_limit;
3054        let stream = st.gpu.stream();
3055
3056        let mut raw = stream.alloc_zeros::<f32>(s * ne).map_err(e("gate raw"))?;
3057        Self::dots(st, x, &layer.gate_w, s, hidden, ne, &mut raw)?;
3058        let raw_h = dtoh_f32(&stream, &raw)?;
3059        let (indices, weights) =
3060            Self::route_host(layer, &raw_h, ids, s, ne, topk, d.routed_scaling_factor);
3061
3062        // x as bf16 once for all expert GEMMs
3063        let mut xb = stream
3064            .alloc_zeros::<u8>(s * hidden * 2)
3065            .map_err(e("xb moe"))?;
3066        unsafe {
3067            ck(
3068                "cvt moe x",
3069                k::memra_dsv4_cvt_bf16(
3070                    dpf!(x, &stream),
3071                    xb.device_ptr_mut(&stream).0 as *mut c_void,
3072                    (s * hidden) as i64,
3073                    sp(&stream),
3074                ),
3075            )?;
3076        }
3077        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("moe y"))?;
3078
3079        let wbytes = inter * hidden / 2;
3080        let sbytes = match layer.expert_kind {
3081            ExpertKind::Nvfp4 => inter * hidden / 16,
3082            ExpertKind::Mxfp4 => inter * hidden / 32,
3083        };
3084        let mut uniq: Vec<usize> = indices.clone();
3085        uniq.sort_unstable();
3086        uniq.dedup();
3087        if self.expert_arm == ExpertArm::Native {
3088            // lane 7: reference-law quantized expert GEMMs (RECEIPTS.md "Lane 7").
3089            // x quantized ONCE per-row-per-128 (model.py:113-115); code/scale rows
3090            // gathered per expert (row-local quant commutes with gathering exactly);
3091            // h re-quantized AFTER the routing-weight multiply (M:604-606) before w2.
3092            let kind = match layer.expert_kind {
3093                ExpertKind::Nvfp4 => 0i32,
3094                ExpertKind::Mxfp4 => 1i32,
3095            };
3096            let kq_x = hidden / 128;
3097            let kq_h = inter / 128;
3098            let mut xq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xq"))?;
3099            let mut xs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xs"))?;
3100            unsafe {
3101                ck(
3102                    "act_quant_fp8 x",
3103                    k::memra_dsv4_act_quant_fp8(
3104                        dpf!(x, &stream),
3105                        xq.device_ptr_mut(&stream).0 as *mut c_void,
3106                        dpm!(xs, &stream),
3107                        s as i32,
3108                        hidden as i32,
3109                        sp(&stream),
3110                    ),
3111                )?;
3112            }
3113            let mut xgq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xgq"))?;
3114            let mut xgs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xgs"))?;
3115            let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3116            let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3117            let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3118            let mut hq = stream.alloc_zeros::<u8>(s * inter).map_err(e("hq"))?;
3119            let mut hs = stream.alloc_zeros::<f32>(s * kq_h).map_err(e("hs"))?;
3120            let mut contrib = stream
3121                .alloc_zeros::<f32>(s * hidden)
3122                .map_err(e("contrib"))?;
3123            for &ex in &uniq {
3124                let toks: Vec<(usize, usize)> = (0..s * topk)
3125                    .filter(|i| indices[*i] == ex)
3126                    .map(|i| (i / topk, i % topk))
3127                    .collect();
3128                let g = toks.len();
3129                let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3130                let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3131                let rows_dev = upload_i32(&stream, &tok_rows)?;
3132                let wrow_dev = upload_f32(&stream, &wrow)?;
3133                unsafe {
3134                    ck(
3135                        "gather xq",
3136                        k::memra_dsv4_gather_rows_u8(
3137                            dp!(xq, &stream),
3138                            rows_dev.device_ptr(&stream).0 as *const i32,
3139                            xgq.device_ptr_mut(&stream).0 as *mut c_void,
3140                            g as i32,
3141                            hidden as i64,
3142                            sp(&stream),
3143                        ),
3144                    )?;
3145                    ck(
3146                        "gather xs",
3147                        k::memra_dsv4_gather_rows_u8(
3148                            xs.device_ptr(&stream).0 as *const c_void,
3149                            rows_dev.device_ptr(&stream).0 as *const i32,
3150                            xgs.device_ptr_mut(&stream).0 as *mut c_void,
3151                            g as i32,
3152                            (kq_x * 4) as i64,
3153                            sp(&stream),
3154                        ),
3155                    )?;
3156                    // w1 (out inter), w3 (out inter) from x codes; w2 (out hidden) from h codes
3157                    for (pi, dst) in [(0usize, &mut g1), (2usize, &mut g3)] {
3158                        let woff = (ex * 3 + pi) * wbytes;
3159                        let soff = (ex * 3 + pi) * sbytes;
3160                        ck(
3161                            "fp4_gemm w1/w3",
3162                            k::memra_dsv4_fp4_gemm(
3163                                dp!(xgq, &stream),
3164                                dpf!(xgs, &stream),
3165                                (layer.experts_w.device_ptr(&stream).0 as usize + woff)
3166                                    as *const c_void,
3167                                (layer.experts_sc.device_ptr(&stream).0 as usize + soff)
3168                                    as *const c_void,
3169                                layer.experts_s2[ex * 3 + pi],
3170                                kind,
3171                                dpm!(*dst, &stream),
3172                                g as i32,
3173                                inter as i32,
3174                                hidden as i32,
3175                                sp(&stream),
3176                            ),
3177                        )?;
3178                    }
3179                    ck(
3180                        "swiglu",
3181                        k::memra_dsv4_swiglu(
3182                            dpf!(g1, &stream),
3183                            dpf!(g3, &stream),
3184                            dpm!(hbuf, &stream),
3185                            g as i32,
3186                            inter as i32,
3187                            limit,
3188                            wrow_dev.device_ptr(&stream).0 as *const f32,
3189                            sp(&stream),
3190                        ),
3191                    )?;
3192                    ck(
3193                        "act_quant_fp8 h",
3194                        k::memra_dsv4_act_quant_fp8(
3195                            dpf!(hbuf, &stream),
3196                            hq.device_ptr_mut(&stream).0 as *mut c_void,
3197                            dpm!(hs, &stream),
3198                            g as i32,
3199                            inter as i32,
3200                            sp(&stream),
3201                        ),
3202                    )?;
3203                    let woff2 = (ex * 3 + 1) * wbytes;
3204                    let soff2 = (ex * 3 + 1) * sbytes;
3205                    ck(
3206                        "fp4_gemm w2",
3207                        k::memra_dsv4_fp4_gemm(
3208                            dp!(hq, &stream),
3209                            dpf!(hs, &stream),
3210                            (layer.experts_w.device_ptr(&stream).0 as usize + woff2)
3211                                as *const c_void,
3212                            (layer.experts_sc.device_ptr(&stream).0 as usize + soff2)
3213                                as *const c_void,
3214                            layer.experts_s2[ex * 3 + 1],
3215                            kind,
3216                            dpm!(contrib, &stream),
3217                            g as i32,
3218                            hidden as i32,
3219                            inter as i32,
3220                            sp(&stream),
3221                        ),
3222                    )?;
3223                    ck(
3224                        "scatter",
3225                        k::memra_dsv4_scatter_add(
3226                            dpm!(y, &stream),
3227                            dpf!(contrib, &stream),
3228                            rows_dev.device_ptr(&stream).0 as *const i32,
3229                            g as i32,
3230                            hidden as i32,
3231                            sp(&stream),
3232                        ),
3233                    )?;
3234                }
3235            }
3236            return self.moe_shared_and_finish(st, layer, &xb, s, y);
3237        }
3238        // reusable per-expert buffers sized for the worst case (all tokens on one expert)
3239        let mut xg = stream.alloc_zeros::<u8>(s * hidden * 2).map_err(e("xg"))?;
3240        let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3241        let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3242        let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3243        let mut hb = stream.alloc_zeros::<u8>(s * inter * 2).map_err(e("hb"))?;
3244        let mut contrib = stream
3245            .alloc_zeros::<f32>(s * hidden)
3246            .map_err(e("contrib"))?;
3247        for &ex in &uniq {
3248            let toks: Vec<(usize, usize)> = (0..s * topk)
3249                .filter(|i| indices[*i] == ex)
3250                .map(|i| (i / topk, i % topk))
3251                .collect();
3252            let g = toks.len();
3253            let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3254            let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3255            let rows_dev = upload_i32(&stream, &tok_rows)?;
3256            let wrow_dev = upload_f32(&stream, &wrow)?;
3257            unsafe {
3258                ck(
3259                    "gather",
3260                    k::memra_dsv4_gather_bf16(
3261                        dp!(xb, &stream),
3262                        rows_dev.device_ptr(&stream).0 as *const i32,
3263                        xg.device_ptr_mut(&stream).0 as *mut c_void,
3264                        g as i32,
3265                        hidden as i32,
3266                        sp(&stream),
3267                    ),
3268                )?;
3269                // dequant w1 (rows=inter, cols=hidden), w2 (rows=hidden, cols=inter), w3
3270                for (pi, (rows, cols)) in [(inter, hidden), (hidden, inter), (inter, hidden)]
3271                    .iter()
3272                    .enumerate()
3273                {
3274                    let woff = (ex * 3 + pi) * wbytes;
3275                    let soff = (ex * 3 + pi) * sbytes;
3276                    let wp =
3277                        (layer.experts_w.device_ptr(&stream).0 as usize + woff) as *const c_void;
3278                    let scp =
3279                        (layer.experts_sc.device_ptr(&stream).0 as usize + soff) as *const c_void;
3280                    let dst = st.deq[pi].device_ptr(&stream).0 as *mut c_void;
3281                    match layer.expert_kind {
3282                        ExpertKind::Nvfp4 => ck(
3283                            "nvfp4 deq",
3284                            k::memra_dsv4_nvfp4_deq_bf16(
3285                                wp,
3286                                scp,
3287                                layer.experts_s2[ex * 3 + pi],
3288                                *rows as i32,
3289                                *cols as i32,
3290                                dst,
3291                                sp(&stream),
3292                            ),
3293                        )?,
3294                        ExpertKind::Mxfp4 => ck(
3295                            "mxfp4 deq",
3296                            k::memra_dsv4_mxfp4_deq_bf16(
3297                                wp,
3298                                scp,
3299                                *rows as i32,
3300                                *cols as i32,
3301                                dst,
3302                                sp(&stream),
3303                            ),
3304                        )?,
3305                    }
3306                }
3307                ck(
3308                    "gemm w1",
3309                    k::memra_dsv4_gemm_bf16(
3310                        st.deq[0].device_ptr(&stream).0 as *const c_void,
3311                        dp!(xg, &stream),
3312                        dpm!(g1, &stream),
3313                        g as i32,
3314                        inter as i32,
3315                        hidden as i32,
3316                        st.dev as i32,
3317                        st.ws.device_ptr(&stream).0 as *mut c_void,
3318                        st.ws.len(),
3319                        sp(&stream),
3320                    ),
3321                )?;
3322                ck(
3323                    "gemm w3",
3324                    k::memra_dsv4_gemm_bf16(
3325                        st.deq[2].device_ptr(&stream).0 as *const c_void,
3326                        dp!(xg, &stream),
3327                        dpm!(g3, &stream),
3328                        g as i32,
3329                        inter as i32,
3330                        hidden as i32,
3331                        st.dev as i32,
3332                        st.ws.device_ptr(&stream).0 as *mut c_void,
3333                        st.ws.len(),
3334                        sp(&stream),
3335                    ),
3336                )?;
3337                ck(
3338                    "swiglu",
3339                    k::memra_dsv4_swiglu(
3340                        dpf!(g1, &stream),
3341                        dpf!(g3, &stream),
3342                        dpm!(hbuf, &stream),
3343                        g as i32,
3344                        inter as i32,
3345                        limit,
3346                        wrow_dev.device_ptr(&stream).0 as *const f32,
3347                        sp(&stream),
3348                    ),
3349                )?;
3350                ck(
3351                    "cvt h",
3352                    k::memra_dsv4_cvt_bf16(
3353                        dpf!(hbuf, &stream),
3354                        hb.device_ptr_mut(&stream).0 as *mut c_void,
3355                        (g * inter) as i64,
3356                        sp(&stream),
3357                    ),
3358                )?;
3359                ck(
3360                    "gemm w2",
3361                    k::memra_dsv4_gemm_bf16(
3362                        st.deq[1].device_ptr(&stream).0 as *const c_void,
3363                        dp!(hb, &stream),
3364                        dpm!(contrib, &stream),
3365                        g as i32,
3366                        hidden as i32,
3367                        inter as i32,
3368                        st.dev as i32,
3369                        st.ws.device_ptr(&stream).0 as *mut c_void,
3370                        st.ws.len(),
3371                        sp(&stream),
3372                    ),
3373                )?;
3374                ck(
3375                    "scatter",
3376                    k::memra_dsv4_scatter_add(
3377                        dpm!(y, &stream),
3378                        dpf!(contrib, &stream),
3379                        rows_dev.device_ptr(&stream).0 as *const i32,
3380                        g as i32,
3381                        hidden as i32,
3382                        sp(&stream),
3383                    ),
3384                )?;
3385            }
3386        }
3387        self.moe_shared_and_finish(st, layer, &xb, s, y)
3388    }
3389
3390    /// Shared expert (unweighted, added last — oracle order) + return. Stays on the
3391    /// lane-4 bf16 rung under BOTH expert arms (lane-7 banked deviation: shared experts
3392    /// are FP8-blk weights — the FP8-linear stay-bf16 decision).
3393    fn moe_shared_and_finish(
3394        &self,
3395        st: &Stage,
3396        layer: &LayerDev,
3397        xb: &CudaSlice<u8>,
3398        s: usize,
3399        mut y: CudaSlice<f32>,
3400    ) -> Res<CudaSlice<f32>> {
3401        let d = self.model.cfg();
3402        let hidden = self.model.mc.n_embd as usize;
3403        let limit = d.swiglu_limit;
3404        let stream = st.gpu.stream();
3405        let sh_inter = {
3406            // width derived from the tensor itself (n_shared_experts * inter)
3407            let (shape, _) = self
3408                .model
3409                .st
3410                .raw("layers.0.ffn.shared_experts.w1.weight")
3411                .map(|(i, _)| (i.shape.clone(), ()))
3412                .expect("shared w1");
3413            shape[0] as usize
3414        };
3415        let mut sg1 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg1"))?;
3416        let mut sg3 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg3"))?;
3417        let mut shbuf = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("shb"))?;
3418        let mut shb16 = stream
3419            .alloc_zeros::<u8>(s * sh_inter * 2)
3420            .map_err(e("shb16"))?;
3421        let mut sh_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("sh_out"))?;
3422        // item 3: staged views (transient upload under the fp8 arm, borrow otherwise)
3423        let sw = [
3424            layer.shared_w[0].staged(&stream)?,
3425            layer.shared_w[1].staged(&stream)?,
3426            layer.shared_w[2].staged(&stream)?,
3427        ];
3428        Self::gemm_pre(
3429            st,
3430            xb,
3431            sw[0].slab().device_ptr(&stream).0 as *const c_void,
3432            s,
3433            sh_inter,
3434            hidden,
3435            &mut sg1,
3436        )?;
3437        Self::gemm_pre(
3438            st,
3439            xb,
3440            sw[2].slab().device_ptr(&stream).0 as *const c_void,
3441            s,
3442            sh_inter,
3443            hidden,
3444            &mut sg3,
3445        )?;
3446        unsafe {
3447            ck(
3448                "swiglu sh",
3449                k::memra_dsv4_swiglu(
3450                    dpf!(sg1, &stream),
3451                    dpf!(sg3, &stream),
3452                    dpm!(shbuf, &stream),
3453                    s as i32,
3454                    sh_inter as i32,
3455                    limit,
3456                    std::ptr::null(),
3457                    sp(&stream),
3458                ),
3459            )?;
3460            ck(
3461                "cvt sh",
3462                k::memra_dsv4_cvt_bf16(
3463                    dpf!(shbuf, &stream),
3464                    shb16.device_ptr_mut(&stream).0 as *mut c_void,
3465                    (s * sh_inter) as i64,
3466                    sp(&stream),
3467                ),
3468            )?;
3469        }
3470        Self::gemm_pre(
3471            st,
3472            &shb16,
3473            sw[1].slab().device_ptr(&stream).0 as *const c_void,
3474            s,
3475            hidden,
3476            sh_inter,
3477            &mut sh_out,
3478        )?;
3479        unsafe {
3480            ck(
3481                "add shared",
3482                k::memra_dsv4_add_inplace(
3483                    dpm!(y, &stream),
3484                    dpf!(sh_out, &stream),
3485                    (s * hidden) as i64,
3486                    sp(&stream),
3487                ),
3488            )?;
3489        }
3490        Ok(y)
3491    }
3492
3493    /// Full trunk prefill. Returns last-position logits, or None on early exit.
3494    /// `early_exit_after` stops after that layer (fixture Input B replays layers 0..=3).
3495    pub fn forward(
3496        &self,
3497        ids: &[u32],
3498        capture: Option<&mut GpuCapture>,
3499        early_exit_after: Option<u32>,
3500    ) -> Res<Option<ForwardOut>> {
3501        self.forward_impl(ids, capture, early_exit_after, None)
3502    }
3503
3504    /// Lane 6: prefill the prompt with the lane-4 path while POPULATING the decode
3505    /// caches, so decode_step can continue incrementally from ids.len().
3506    pub fn prefill_with_cache(&self, ids: &[u32], state: &mut DecodeState) -> Res<ForwardOut> {
3507        assert_eq!(state.pos, 0, "prefill_with_cache needs a fresh DecodeState");
3508        assert!(!ids.is_empty(), "empty prompt");
3509        let out = self
3510            .forward_impl(ids, None, None, Some(state))?
3511            .expect("prefill logits");
3512        state.pos = ids.len();
3513        Ok(out)
3514    }
3515
3516    fn forward_impl(
3517        &self,
3518        ids: &[u32],
3519        mut capture: Option<&mut GpuCapture>,
3520        early_exit_after: Option<u32>,
3521        mut state: Option<&mut DecodeState>,
3522    ) -> Res<Option<ForwardOut>> {
3523        let mc = &self.model.mc;
3524        let d = self.model.cfg();
3525        let s = ids.len();
3526        assert!(s <= self.max_seq, "seq {s} > max_seq {}", self.max_seq);
3527        let hidden = mc.n_embd as usize;
3528        let hc = d.hc_mult as usize;
3529        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3530
3531        // stage 0: embed -> hc state
3532        let st0 = &self.stages[0];
3533        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
3534        let stream0 = st0.gpu.stream();
3535        let ids_i32: Vec<i32> = ids.iter().map(|&x| x as i32).collect();
3536        let ids_dev = upload_i32(&stream0, &ids_i32)?;
3537        let mut emb = stream0.alloc_zeros::<f32>(s * hidden).map_err(e("emb"))?;
3538        unsafe {
3539            ck(
3540                "embed_rows",
3541                k::memra_dsv4_embed_rows(
3542                    st0.embed
3543                        .as_ref()
3544                        .expect("embed on stage 0")
3545                        .device_ptr(&stream0)
3546                        .0 as *const c_void,
3547                    ids_dev.device_ptr(&stream0).0 as *const i32,
3548                    dpm!(emb, &stream0),
3549                    s as i32,
3550                    hidden as i32,
3551                    sp(&stream0),
3552                ),
3553            )?;
3554        }
3555        if let Some(c) = capture.as_deref_mut()
3556            && c.embed_out.is_none()
3557        {
3558            c.embed_out = Some(dtoh_f32(&stream0, &emb)?);
3559        }
3560        let mut h = stream0
3561            .alloc_zeros::<f32>(s * hc * hidden)
3562            .map_err(e("h0"))?;
3563        unsafe {
3564            ck(
3565                "repeat_hc",
3566                k::memra_dsv4_repeat_hc(
3567                    dpf!(emb, &stream0),
3568                    dpm!(h, &stream0),
3569                    s as i32,
3570                    hc as i32,
3571                    hidden as i32,
3572                    sp(&stream0),
3573                ),
3574            )?;
3575        }
3576
3577        // layers, stage by stage; ONE host-bounce boundary copy at the split
3578        let mut cur_stage = 0usize;
3579        for il in 0..n_trunk {
3580            let stage = self.layer_stage[il as usize];
3581            if stage != cur_stage {
3582                let src_stream = self.stages[cur_stage].gpu.stream();
3583                let host = dtoh_f32(&src_stream, &h)?;
3584                let dst_stream = self.stages[stage].gpu.stream();
3585                self.stages[stage]
3586                    .gpu
3587                    .ctx
3588                    .bind_to_thread()
3589                    .map_err(e("bind"))?;
3590                h = upload_f32(&dst_stream, &host)?;
3591                cur_stage = stage;
3592            }
3593            let st = &self.stages[stage];
3594            let lidx = st
3595                .layers
3596                .iter()
3597                .position(|l| l.il == il)
3598                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
3599            let layer_cache = state.as_deref_mut().map(|ds| &mut ds.caches[il as usize]);
3600            h = self.block_forward(
3601                st,
3602                &st.layers[lidx],
3603                &h,
3604                s,
3605                ids,
3606                capture.as_deref_mut(),
3607                layer_cache,
3608            )?;
3609            if early_exit_after == Some(il) {
3610                self.stages[cur_stage]
3611                    .gpu
3612                    .stream()
3613                    .synchronize()
3614                    .map_err(e("sync"))?;
3615                return Ok(None);
3616            }
3617        }
3618
3619        // head (last stage): hc_head collapse (host sigmoid gates) -> norm -> logits
3620        let last = self.stages.len() - 1;
3621        if cur_stage != last {
3622            let src_stream = self.stages[cur_stage].gpu.stream();
3623            let host = dtoh_f32(&src_stream, &h)?;
3624            let dst_stream = self.stages[last].gpu.stream();
3625            h = upload_f32(&dst_stream, &host)?;
3626        }
3627        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3628        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3629        let logits = self.head_logits_from(
3630            &h,
3631            s,
3632            hc_head_fn,
3633            &self.hc_head_base,
3634            &self.hc_head_scale,
3635            trunk_norm,
3636        )?;
3637        Ok(Some(ForwardOut { logits, h_last: h }))
3638    }
3639
3640    /// ParallelHead (model.py:713-735): hc_head collapse (mix GEMM f32 island + host
3641    /// sigmoid gates, the oracle's own arithmetic) -> final RMSNorm -> last-position
3642    /// logits over the SHARED bf16 head. Used by the trunk head and the MTP head.
3643    fn head_logits_from(
3644        &self,
3645        h: &CudaSlice<f32>,
3646        s: usize,
3647        fn_w: &CudaSlice<f32>,
3648        base: &[f32],
3649        scale: &[f32],
3650        norm: &CudaSlice<f32>,
3651    ) -> Res<Vec<f32>> {
3652        self.head_logits_row(h, s, s - 1, fn_w, base, scale, norm)
3653    }
3654
3655    /// Same head, logits at an arbitrary position row (lane-6 m-sensitivity probe:
3656    /// the reference's own realization noise is measured by comparing the SAME row
3657    /// under two prefill lengths).
3658    #[allow(clippy::too_many_arguments)]
3659    fn head_logits_row(
3660        &self,
3661        h: &CudaSlice<f32>,
3662        s: usize,
3663        row: usize,
3664        fn_w: &CudaSlice<f32>,
3665        base: &[f32],
3666        scale: &[f32],
3667        norm: &CudaSlice<f32>,
3668    ) -> Res<Vec<f32>> {
3669        let d = self.model.cfg();
3670        let mc = &self.model.mc;
3671        let hc = d.hc_mult as usize;
3672        let hidden = mc.n_embd as usize;
3673        let eps = mc.rms_eps;
3674        let last = self.stages.len() - 1;
3675        let st = &self.stages[last];
3676        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx head"))?;
3677        let stream = st.gpu.stream();
3678        let w = hc * hidden;
3679        let mut mixes = stream.alloc_zeros::<f32>(s * hc).map_err(e("hm"))?;
3680        Self::dots(st, h, fn_w, s, w, hc, &mut mixes)?;
3681        unsafe {
3682            ck(
3683                "rowsq head",
3684                k::memra_dsv4_rowsq_scale(
3685                    dpf!(h, &stream),
3686                    dpm!(mixes, &stream),
3687                    s as i32,
3688                    w as i32,
3689                    hc as i32,
3690                    eps,
3691                    sp(&stream),
3692                ),
3693            )?;
3694        }
3695        // oracle hc_head: pre = sigmoid(mix*scale + base) + hc_eps (note: RMS eps is the
3696        // model rms_eps inside the mean, hc_eps only in the gate — mirrored exactly)
3697        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
3698        for t in 0..s {
3699            for c in 0..hc {
3700                let m = mixes_h[t * hc + c];
3701                mixes_h[t * hc + c] = sigmoid_f32(m * scale[0] + base[c]) + d.hc_eps;
3702            }
3703        }
3704        let pre_d = upload_f32(&stream, &mixes_h)?;
3705        let mut collapsed = stream.alloc_zeros::<f32>(s * hidden).map_err(e("col"))?;
3706        unsafe {
3707            ck(
3708                "hc_collapse head",
3709                k::memra_dsv4_hc_collapse(
3710                    dpf!(h, &stream),
3711                    dpf!(pre_d, &stream),
3712                    dpm!(collapsed, &stream),
3713                    s as i32,
3714                    hc as i32,
3715                    hidden as i32,
3716                    sp(&stream),
3717                ),
3718            )?;
3719            ck(
3720                "rmsnorm head",
3721                k::memra_dsv4_rmsnorm(
3722                    dpf!(collapsed, &stream),
3723                    dpf!(norm, &stream),
3724                    dpm!(collapsed, &stream),
3725                    s as i32,
3726                    hidden as i32,
3727                    eps,
3728                    sp(&stream),
3729                ),
3730            )?;
3731        }
3732        // logits for the selected position (f32 island GEMM over bf16 head rows)
3733        assert!(row < s, "logits row {row} out of range (s = {s})");
3734        let vocab = {
3735            let (info, _) = self.model.st.raw("head.weight").expect("head");
3736            info.shape[0] as usize
3737        };
3738        let last_row = collapsed.slice(row * hidden..(row + 1) * hidden);
3739        let mut logits = stream.alloc_zeros::<f32>(vocab).map_err(e("logits"))?;
3740        unsafe {
3741            ck(
3742                "head dots",
3743                k::memra_dsv4_dots_f32(
3744                    last_row.device_ptr(&stream).0 as *const f32,
3745                    st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
3746                    1,
3747                    dpm!(logits, &stream),
3748                    1,
3749                    hidden as i32,
3750                    vocab as i32,
3751                    sp(&stream),
3752                ),
3753            )?;
3754        }
3755        dtoh_f32(&stream, &logits)
3756    }
3757
3758    /// MTP logits at the fixture call shape (model.py:826 — same ids to trunk and MTP;
3759    /// the V3 NextN drafter shift is the spec-decode lane's wiring, not claimed here).
3760    /// `h_trunk` = the trunk's final hc state on the LAST stage (ForwardOut::h_last).
3761    pub fn mtp_logits_last(&self, h_trunk: &CudaSlice<f32>, ids: &[u32]) -> Res<Vec<f32>> {
3762        self.mtp_logits_last_cap(h_trunk, ids, None)
3763    }
3764
3765    /// [`Self::mtp_logits_last`] with a capture pass-through (lane 7: the native-GEMM
3766    /// kernel gate captures the MTP block's moe_x under want = {n_trunk}).
3767    pub fn mtp_logits_last_cap(
3768        &self,
3769        h_trunk: &CudaSlice<f32>,
3770        ids: &[u32],
3771        capture: Option<&mut GpuCapture>,
3772    ) -> Res<Vec<f32>> {
3773        let mtp = self.mtp.as_ref().expect("MTP not loaded");
3774        let d = self.model.cfg();
3775        let mc = &self.model.mc;
3776        let hc = d.hc_mult as usize;
3777        let hidden = mc.n_embd as usize;
3778        let eps = mc.rms_eps;
3779        let s = ids.len();
3780        let last = self.stages.len() - 1;
3781        let st = &self.stages[last];
3782        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx mtp"))?;
3783        let stream = st.gpu.stream();
3784
3785        // e = rmsnorm(embed(ids), enorm): embed rows gathered HOST-side (bit-exact bf16
3786        // decode, same as the oracle's embed_rows) — the embed table lives on stage 0.
3787        let e_host = self.model.embed_rows(ids);
3788        let mut e_dev = upload_f32(&stream, &e_host)?;
3789        unsafe {
3790            ck(
3791                "rmsnorm enorm",
3792                k::memra_dsv4_rmsnorm(
3793                    dpf!(e_dev, &stream),
3794                    dpf!(mtp.enorm, &stream),
3795                    dpm!(e_dev, &stream),
3796                    s as i32,
3797                    hidden as i32,
3798                    eps,
3799                    sp(&stream),
3800                ),
3801            )?;
3802        }
3803        // x = hnorm(h_trunk) per hc copy
3804        let mut xh = stream
3805            .alloc_zeros::<f32>(s * hc * hidden)
3806            .map_err(e("mtp xh"))?;
3807        unsafe {
3808            ck(
3809                "rmsnorm hnorm",
3810                k::memra_dsv4_rmsnorm(
3811                    dpf!(h_trunk, &stream),
3812                    dpf!(mtp.hnorm, &stream),
3813                    dpm!(xh, &stream),
3814                    (s * hc) as i32,
3815                    hidden as i32,
3816                    eps,
3817                    sp(&stream),
3818                ),
3819            )?;
3820        }
3821        // ep = e_proj(e) [s, hidden]; hp = h_proj(xh) per copy [s*hc, hidden]
3822        let mut ep = stream.alloc_zeros::<f32>(s * hidden).map_err(e("mtp ep"))?;
3823        Self::gemm(st, &e_dev, &mtp.e_proj, 0, s, hidden, hidden, &mut ep)?;
3824        let mut hp = stream
3825            .alloc_zeros::<f32>(s * hc * hidden)
3826            .map_err(e("mtp hp"))?;
3827        Self::gemm(st, &xh, &mtp.h_proj, 0, s * hc, hidden, hidden, &mut hp)?;
3828        // xm[t, c, :] = ep[t, :] + hp[t, c, :]  (e broadcast over the hc copies)
3829        let mut xm = stream
3830            .alloc_zeros::<f32>(s * hc * hidden)
3831            .map_err(e("mtp xm"))?;
3832        unsafe {
3833            ck(
3834                "repeat ep",
3835                k::memra_dsv4_repeat_hc(
3836                    dpf!(ep, &stream),
3837                    dpm!(xm, &stream),
3838                    s as i32,
3839                    hc as i32,
3840                    hidden as i32,
3841                    sp(&stream),
3842                ),
3843            )?;
3844            ck(
3845                "add hp",
3846                k::memra_dsv4_add_inplace(
3847                    dpm!(xm, &stream),
3848                    dpf!(hp, &stream),
3849                    (s * hc * hidden) as i64,
3850                    sp(&stream),
3851                ),
3852            )?;
3853        }
3854        let xm = self.block_forward(st, &mtp.layer, &xm, s, ids, capture, None)?;
3855        self.head_logits_from(
3856            &xm,
3857            s,
3858            &mtp.hc_head_fn,
3859            &mtp.hc_head_base,
3860            &mtp.hc_head_scale,
3861            &mtp.norm,
3862        )
3863    }
3864
3865    /// Trunk-head logits at position `row` of a ForwardOut hc state (m-sensitivity probe).
3866    pub fn trunk_logits_row(&self, h: &CudaSlice<f32>, s: usize, row: usize) -> Res<Vec<f32>> {
3867        let last = self.stages.len() - 1;
3868        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3869        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3870        self.head_logits_row(
3871            h,
3872            s,
3873            row,
3874            hc_head_fn,
3875            &self.hc_head_base,
3876            &self.hc_head_scale,
3877            trunk_norm,
3878        )
3879    }
3880
3881    // ---------------------------------------------------------------- lane 6: decode
3882
3883    /// Allocate the per-layer decode caches (capacity = max_seq, the reference
3884    /// register_buffer shape) on each layer's owning stage. Returns a fresh state
3885    /// (pos = 0) ready for [`Self::prefill_with_cache`].
3886    pub fn alloc_decode_state(&self) -> Res<DecodeState> {
3887        let d = self.model.cfg();
3888        let mc = &self.model.mc;
3889        let win = d.sliding_window as usize;
3890        let hd = d.head_dim as usize;
3891        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3892        let mut caches = Vec::with_capacity(n_trunk as usize);
3893        let mut cache_bytes = vec![0u64; self.stages.len()];
3894        // iteration 3, rung 4: reserve T_max TRANSIENT window-kv rows per layer at
3895        // kvc rows [win + cap_blocks, win + cap_blocks + T_max) — where a batched verify
3896        // round's kv lands so the persistent ring stays read-only until commit (§3.1).
3897        // Zero rows when the drafter is not loaded: today's exact allocation, byte for byte.
3898        let trans_rows = self.verify_tmax();
3899        for il in 0..n_trunk {
3900            let stage_i = self.layer_stage[il as usize];
3901            let st = &self.stages[stage_i];
3902            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx cache"))?;
3903            let stream = st.gpu.stream();
3904            let lidx = st
3905                .layers
3906                .iter()
3907                .position(|l| l.il == il)
3908                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
3909            let layer = &st.layers[lidx];
3910            let ratio = layer.ratio;
3911            #[allow(clippy::manual_checked_ops)]
3912            // allow: the explicit zero guard names the degenerate-ratio case; checked ops would hide the sentinel
3913            let cap_blocks = if ratio != 0 { self.max_seq / ratio } else { 0 };
3914            let kvc_rows = win + cap_blocks + trans_rows;
3915            let mut bytes = (kvc_rows * hd * 4) as u64;
3916            let kvc = stream
3917                .alloc_zeros::<f32>(kvc_rows * hd)
3918                .map_err(e("kvc alloc"))?;
3919            // pending pair: kv zeros, score -inf (block-0-at-decode masking, receipts)
3920            let mk_pend = |latent: usize, slots: usize| -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
3921                let kv = stream
3922                    .alloc_zeros::<f32>(slots * latent)
3923                    .map_err(e("pend kv alloc"))?;
3924                let sc = upload_f32(&stream, &vec![f32::NEG_INFINITY; slots * latent])?;
3925                Ok((kv, sc))
3926            };
3927            let (pend_kv, pend_score) = if let Some(cmp) = &layer.cmp {
3928                let slots = if cmp.overlap {
3929                    2 * cmp.ratio
3930                } else {
3931                    cmp.ratio
3932                };
3933                bytes += (2 * slots * cmp.latent * 4) as u64;
3934                let (a, b) = mk_pend(cmp.latent, slots)?;
3935                (Some(a), Some(b))
3936            } else {
3937                (None, None)
3938            };
3939            let (ikvc, ipend_kv, ipend_score) = if let Some(ix) = &layer.idx {
3940                bytes += (cap_blocks * ix.cmp.d * 4) as u64;
3941                let store = stream
3942                    .alloc_zeros::<f32>(cap_blocks * ix.cmp.d)
3943                    .map_err(e("ikvc alloc"))?;
3944                let slots = if ix.cmp.overlap {
3945                    2 * ix.cmp.ratio
3946                } else {
3947                    ix.cmp.ratio
3948                };
3949                bytes += (2 * slots * ix.cmp.latent * 4) as u64;
3950                let (a, b) = mk_pend(ix.cmp.latent, slots)?;
3951                (Some(store), Some(a), Some(b))
3952            } else {
3953                (None, None, None)
3954            };
3955            cache_bytes[stage_i] += bytes;
3956            caches.push(LayerCache {
3957                kvc,
3958                n_blocks: 0,
3959                pend_kv,
3960                pend_score,
3961                ikvc,
3962                i_blocks: 0,
3963                ipend_kv,
3964                ipend_score,
3965            });
3966        }
3967        let ws = if matches!(self.decode_path, DecodePath::Device { .. }) {
3968            Some(self.alloc_step_ws()?)
3969        } else {
3970            None
3971        };
3972        for st in &self.stages {
3973            st.gpu.stream().synchronize().map_err(e("cache sync"))?;
3974        }
3975        Ok(DecodeState {
3976            caches,
3977            pos: 0,
3978            cache_bytes,
3979            ws,
3980        })
3981    }
3982
3983    /// Lane 8: allocate the per-stage step workspace (device decode path only).
3984    fn alloc_step_ws(&self) -> Res<Vec<StepWs>> {
3985        let d = self.model.cfg();
3986        let mc = &self.model.mc;
3987        let moe = mc.moe.as_ref().expect("moe");
3988        let hc = d.hc_mult as usize;
3989        let hidden = mc.n_embd as usize;
3990        let heads = mc.n_head as usize;
3991        let hd = d.head_dim as usize;
3992        let q_lora = d.q_lora_rank as usize;
3993        let win = d.sliding_window as usize;
3994        let o_groups = d.o_groups as usize;
3995        let o_lora = d.o_lora_rank as usize;
3996        let iheads = d.index_n_heads as usize;
3997        let ihd = d.index_head_dim as usize;
3998        let topk = moe.expert_used_count as usize;
3999        let ne = moe.expert_count as usize;
4000        let inter = moe.expert_ff_length as usize;
4001        let itopk = d.index_topk as usize;
4002        let vocab = {
4003            let (info, _) = self.model.st.raw("head.weight").expect("head");
4004            info.shape[0] as usize
4005        };
4006        let sh_inter = {
4007            let (info, _) = self
4008                .model
4009                .st
4010                .raw("layers.0.ffn.shared_experts.w1.weight")
4011                .expect("shared w1");
4012            info.shape[0] as usize
4013        };
4014        // fine ratio (indexer-carrying) and per-class compressor maxima, config-derived
4015        let mut max_latent = 0usize;
4016        let mut max_d = 0usize;
4017        let mut max_shift = 0usize;
4018        let mut min_ratio = usize::MAX;
4019        for st in &self.stages {
4020            for l in &st.layers {
4021                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
4022                    max_latent = max_latent.max(cmp.latent);
4023                    max_d = max_d.max(cmp.d);
4024                    if cmp.overlap {
4025                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
4026                    }
4027                    min_ratio = min_ratio.min(cmp.ratio);
4028                }
4029            }
4030        }
4031        assert!(min_ratio != usize::MAX, "no compressor layers?");
4032        let score_cap = self.max_seq / min_ratio + 1;
4033        let idx_tail = itopk.max(self.max_seq / 128 + 1);
4034        // the largest bf16 cvt any device-path gemm() performs (activation side, m=1):
4035        // wo_b consumes o_groups*o_lora, the o cvt covers heads*hd separately.
4036        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
4037        let mut out = Vec::with_capacity(self.stages.len());
4038        for st in &self.stages {
4039            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ws"))?;
4040            let s = st.gpu.stream();
4041            let f = |n: usize| s.alloc_zeros::<f32>(n).map_err(e("ws f32"));
4042            let b = |n: usize| s.alloc_zeros::<u8>(n).map_err(e("ws u8"));
4043            let i = |n: usize| s.alloc_zeros::<i32>(n).map_err(e("ws i32"));
4044            out.push(StepWs {
4045                h_a: f(hc * hidden)?,
4046                h_b: f(hc * hidden)?,
4047                h_rx: f(hc * hidden)?,
4048                emb: f(hidden)?,
4049                mixes: f((2 + hc) * hc)?,
4050                pre: f(hc)?,
4051                post: f(hc)?,
4052                comb: f(hc * hc)?,
4053                y_hc: f(hidden)?,
4054                x: f(hidden)?,
4055                xf: f(hidden)?,
4056                qr: f(q_lora)?,
4057                qr_b: b(q_lora * 2)?,
4058                q: f(heads * hd)?,
4059                kv: f(hd)?,
4060                qi: f(iheads * ihd)?,
4061                wproj: f(iheads)?,
4062                score: f(score_cap)?,
4063                idx: i(win + idx_tail)?,
4064                o: f(heads * hd)?,
4065                o_b: b(heads * hd * 2)?,
4066                og: f(o_groups * o_lora)?,
4067                attn_out: f(hidden)?,
4068                gemm_xb: b(max_gemm_k * 2)?,
4069                raw: f(ne)?,
4070                sel: i(topk)?,
4071                selw: f(topk)?,
4072                order: i(topk)?,
4073                xq: b(hidden)?,
4074                xs: f(hidden / 128)?,
4075                g1: f(topk * inter)?,
4076                g3: f(topk * inter)?,
4077                hbuf: f(topk * inter)?,
4078                hq: b(topk * inter)?,
4079                hs: f(topk * inter / 128)?,
4080                contrib: f(topk * hidden)?,
4081                y: f(hidden)?,
4082                xb: b(hidden * 2)?,
4083                sg1: f(sh_inter)?,
4084                sg3: f(sh_inter)?,
4085                shbuf: f(sh_inter)?,
4086                shb16: b(sh_inter * 2)?,
4087                sh_out: f(hidden)?,
4088                cmp_kv_row: f(max_latent)?,
4089                cmp_sc_row: f(max_latent)?,
4090                cmp_emit: f(2 * max_d)?,
4091                cmp_shift: f(max_shift.max(1))?,
4092                sink_scores: f(heads * (win + idx_tail))?,
4093                sink_evals: f(heads * (win + idx_tail))?,
4094                sink_den: s.alloc_zeros::<f64>(heads).map_err(e("ws f64"))?,
4095                head_mixes: f(hc)?,
4096                head_pre: f(hc)?,
4097                collapsed: f(hidden)?,
4098                logits: f(vocab)?,
4099                argmax: i(1)?,
4100                tok: i(1)?,
4101            });
4102        }
4103        Ok(out)
4104    }
4105
4106    /// Incremental compressor step (reference decode state machine, M:344-377): append
4107    /// this position's RAW wkv/wgate rows to the pending state; when the block
4108    /// completes ((pos+1) % ratio == 0), emit block pos/ratio into `store` row
4109    /// row0 + j via the SAME pooling kernel prefill uses (overlap rides a 2-block
4110    /// launch whose block 1 reads prev rows [0,ratio) through dims [0,d) and cur rows
4111    /// [ratio,2ratio) through dims [d,2d) — the emission pooling verbatim), then
4112    /// norm→rope(j·ratio)→QAT, and shift cur→prev.
4113    #[allow(clippy::too_many_arguments)]
4114    #[allow(clippy::manual_is_multiple_of)] // allow: divisor is runtime-derived; the modulo form keeps a zero divisor loud (a panic), where is_multiple_of would return false silently
4115    fn cmp_decode(
4116        &self,
4117        st: &Stage,
4118        cmp: &CmpDev,
4119        x: &CudaSlice<f32>, // [1, hidden] post-attn-norm
4120        pos: usize,
4121        hidden: usize,
4122        fc_dev: &CudaSlice<f32>,
4123        rd: usize,
4124        eps: f32,
4125        pend_kv: &mut CudaSlice<f32>,
4126        pend_score: &mut CudaSlice<f32>,
4127        store: &mut CudaSlice<f32>,
4128        row0: usize,
4129        blocks: &mut usize,
4130    ) -> Res<()> {
4131        let stream = st.gpu.stream();
4132        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
4133        let mut kv_row = stream.alloc_zeros::<f32>(latent).map_err(e("dkv"))?;
4134        let mut sc_row = stream.alloc_zeros::<f32>(latent).map_err(e("dsc"))?;
4135        Self::dots(st, x, &cmp.wkv, 1, hidden, latent, &mut kv_row)?;
4136        Self::dots(st, x, &cmp.wgate, 1, hidden, latent, &mut sc_row)?;
4137        let slot = if cmp.overlap {
4138            ratio + pos % ratio
4139        } else {
4140            pos % ratio
4141        };
4142        {
4143            let src = kv_row.slice(0..latent);
4144            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
4145            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
4146            let src = sc_row.slice(0..latent);
4147            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
4148            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
4149        }
4150        if (pos + 1) % ratio != 0 {
4151            return Ok(());
4152        }
4153        let j = pos / ratio;
4154        let nb_launch = if cmp.overlap { 2usize } else { 1 };
4155        let row_off = if cmp.overlap { d } else { 0 };
4156        let mut out = stream
4157            .alloc_zeros::<f32>(nb_launch * d)
4158            .map_err(e("emit"))?;
4159        unsafe {
4160            ck(
4161                "compressor_pool dec",
4162                k::memra_dsv4_compressor_pool(
4163                    dpf!(pend_kv, &stream),
4164                    dpf!(pend_score, &stream),
4165                    dpf!(cmp.ape, &stream),
4166                    dpm!(out, &stream),
4167                    nb_launch as i32,
4168                    ratio as i32,
4169                    d as i32,
4170                    latent as i32,
4171                    cmp.overlap as i32,
4172                    sp(&stream),
4173                ),
4174            )?;
4175            // in-place row ops at the emitted row (base + row_off), lane-4 ptr idiom
4176            let row_c = (out.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
4177            let row_m = (out.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
4178            ck(
4179                "rmsnorm dec cmp",
4180                k::memra_dsv4_rmsnorm(
4181                    row_c,
4182                    dpf!(cmp.norm, &stream),
4183                    row_m,
4184                    1,
4185                    d as i32,
4186                    eps,
4187                    sp(&stream),
4188                ),
4189            )?;
4190            let pos_dev = upload_i32(&stream, &[(j * ratio) as i32])?;
4191            ck(
4192                "rope dec cmp",
4193                k::memra_dsv4_rope(
4194                    row_m,
4195                    1,
4196                    1,
4197                    d as i32,
4198                    rd as i32,
4199                    dpf!(fc_dev, &stream),
4200                    pos_dev.device_ptr(&stream).0 as *const i32,
4201                    0,
4202                    sp(&stream),
4203                ),
4204            )?;
4205            if cmp.rotate {
4206                let scale = (d as f32).powf(-0.5);
4207                ck(
4208                    "hadamard dec cmp",
4209                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
4210                )?;
4211                ck(
4212                    "fp4 dec cmp",
4213                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
4214                )?;
4215            } else {
4216                ck(
4217                    "act_quant dec cmp",
4218                    k::memra_dsv4_act_quant(
4219                        row_m,
4220                        1,
4221                        d as i64,
4222                        (d - rd) as i32,
4223                        64,
4224                        (self.variant == ActQuantVariant::ClampOnly) as i32,
4225                        sp(&stream),
4226                    ),
4227                )?;
4228            }
4229        }
4230        {
4231            let src = out.slice(row_off..row_off + d);
4232            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
4233            stream
4234                .memcpy_dtod(&src, &mut dst)
4235                .map_err(e("emit store"))?;
4236        }
4237        if cmp.overlap {
4238            // shift cur -> prev through a bounce (same-buffer D2D ranges must not alias)
4239            let mut tmp = stream
4240                .alloc_zeros::<f32>(ratio * latent)
4241                .map_err(e("shift tmp"))?;
4242            {
4243                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
4244                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift1"))?;
4245            }
4246            {
4247                let mut dst = pend_kv.slice_mut(0..ratio * latent);
4248                stream
4249                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4250                    .map_err(e("shift2"))?;
4251            }
4252            {
4253                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
4254                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift3"))?;
4255            }
4256            {
4257                let mut dst = pend_score.slice_mut(0..ratio * latent);
4258                stream
4259                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4260                    .map_err(e("shift4"))?;
4261            }
4262        }
4263        *blocks = j + 1;
4264        Ok(())
4265    }
4266
4267    /// One trunk block, single-token decode. h is [1, hc, hidden] f32 on the stage.
4268    /// Mirrors the reference decode branches: ring write (M:530), indexer with its
4269    /// compressor BEFORE scoring (M:415), attention compressor before sparse_attn
4270    /// (M:531), window/compressed index law (M:255-276). `dump` (diagnostic only)
4271    /// collects named intermediates for the bisect probe.
4272    #[allow(clippy::too_many_arguments)]
4273    #[allow(clippy::manual_checked_ops)] // allow: the explicit zero guard names the degenerate-ratio case; checked ops would hide the sentinel
4274    fn block_decode(
4275        &self,
4276        st: &Stage,
4277        layer: &LayerDev,
4278        cache: &mut LayerCache,
4279        h: &CudaSlice<f32>,
4280        pos: usize,
4281        tok: u32,
4282        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
4283    ) -> Res<CudaSlice<f32>> {
4284        let d = self.model.cfg();
4285        let mc = &self.model.mc;
4286        let hc = d.hc_mult as usize;
4287        let hidden = mc.n_embd as usize;
4288        let heads = mc.n_head as usize;
4289        let hd = d.head_dim as usize;
4290        let rd = d.qk_rope_head_dim as usize;
4291        let q_lora = d.q_lora_rank as usize;
4292        let win = d.sliding_window as usize;
4293        let o_groups = d.o_groups as usize;
4294        let o_lora = d.o_lora_rank as usize;
4295        let eps = mc.rms_eps;
4296        let iters = d.hc_sinkhorn_iters;
4297        let hc_eps = d.hc_eps;
4298        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
4299        let stream = st.gpu.stream();
4300        let fc_dev = if layer.ratio != 0 {
4301            &st.fc_yarn
4302        } else {
4303            &st.fc_plain
4304        };
4305        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
4306        let LayerCache {
4307            kvc,
4308            n_blocks,
4309            pend_kv,
4310            pend_score,
4311            ikvc,
4312            i_blocks,
4313            ipend_kv,
4314            ipend_score,
4315        } = cache;
4316
4317        // ---- attention sub-block
4318        let (y, post, comb) = Self::hc_pre(
4319            st,
4320            h,
4321            &layer.hc_attn_fn,
4322            &layer.hc_attn_base,
4323            &layer.hc_attn_scale,
4324            1,
4325            hc,
4326            hidden,
4327            iters,
4328            hc_eps,
4329        )?;
4330        let mut x = stream.alloc_zeros::<f32>(hidden).map_err(e("x"))?;
4331        unsafe {
4332            ck(
4333                "rmsnorm attn",
4334                k::memra_dsv4_rmsnorm(
4335                    dpf!(y, &stream),
4336                    dpf!(layer.attn_norm, &stream),
4337                    dpm!(x, &stream),
4338                    1,
4339                    hidden as i32,
4340                    eps,
4341                    sp(&stream),
4342                ),
4343            )?;
4344        }
4345        if let Some(dm) = dump.as_deref_mut() {
4346            dm.push((format!("layer{}.x", layer.il), dtoh_f32(&stream, &x)?));
4347        }
4348
4349        // q path (item 3: `.dev()` is lawful here — the legacy path with the fp8
4350        // dense arm is a BOOT refusal, so these slabs are always device-resident)
4351        let mut qr = stream.alloc_zeros::<f32>(q_lora).map_err(e("qr"))?;
4352        Self::gemm(st, &x, layer.wq_a.dev(), 0, 1, q_lora, hidden, &mut qr)?;
4353        unsafe {
4354            ck(
4355                "rmsnorm q",
4356                k::memra_dsv4_rmsnorm(
4357                    dpf!(qr, &stream),
4358                    dpf!(layer.q_norm, &stream),
4359                    dpm!(qr, &stream),
4360                    1,
4361                    q_lora as i32,
4362                    eps,
4363                    sp(&stream),
4364                ),
4365            )?;
4366        }
4367        let mut qr_b = stream.alloc_zeros::<u8>(q_lora * 2).map_err(e("qr_b"))?;
4368        unsafe {
4369            ck(
4370                "cvt qr",
4371                k::memra_dsv4_cvt_bf16(
4372                    dpf!(qr, &stream),
4373                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
4374                    q_lora as i64,
4375                    sp(&stream),
4376                ),
4377            )?;
4378        }
4379        let mut q = stream.alloc_zeros::<f32>(heads * hd).map_err(e("q"))?;
4380        Self::gemm_pre(
4381            st,
4382            &qr_b,
4383            layer.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4384            1,
4385            heads * hd,
4386            q_lora,
4387            &mut q,
4388        )?;
4389        let pos_dev = upload_i32(&stream, &[pos as i32])?;
4390        unsafe {
4391            ck(
4392                "headrms",
4393                k::memra_dsv4_headrms(dpm!(q, &stream), heads as i32, hd as i32, eps, sp(&stream)),
4394            )?;
4395            ck(
4396                "rope q",
4397                k::memra_dsv4_rope(
4398                    dpm!(q, &stream),
4399                    1,
4400                    heads as i32,
4401                    hd as i32,
4402                    rd as i32,
4403                    dpf!(fc_dev, &stream),
4404                    pos_dev.device_ptr(&stream).0 as *const i32,
4405                    0,
4406                    sp(&stream),
4407                ),
4408            )?;
4409        }
4410
4411        if let Some(dm) = dump.as_deref_mut() {
4412            dm.push((format!("layer{}.q", layer.il), dtoh_f32(&stream, &q)?));
4413        }
4414        // shared K==V latent row + window QAT, written into the ring at pos % win
4415        let mut kv = stream.alloc_zeros::<f32>(hd).map_err(e("kv"))?;
4416        Self::gemm(st, &x, layer.wkv.dev(), 0, 1, hd, hidden, &mut kv)?;
4417        unsafe {
4418            ck(
4419                "rmsnorm kv",
4420                k::memra_dsv4_rmsnorm(
4421                    dpf!(kv, &stream),
4422                    dpf!(layer.kv_norm, &stream),
4423                    dpm!(kv, &stream),
4424                    1,
4425                    hd as i32,
4426                    eps,
4427                    sp(&stream),
4428                ),
4429            )?;
4430            ck(
4431                "rope kv",
4432                k::memra_dsv4_rope(
4433                    dpm!(kv, &stream),
4434                    1,
4435                    1,
4436                    hd as i32,
4437                    rd as i32,
4438                    dpf!(fc_dev, &stream),
4439                    pos_dev.device_ptr(&stream).0 as *const i32,
4440                    0,
4441                    sp(&stream),
4442                ),
4443            )?;
4444            ck(
4445                "act_quant kv",
4446                k::memra_dsv4_act_quant(
4447                    dpm!(kv, &stream),
4448                    1,
4449                    hd as i64,
4450                    (hd - rd) as i32,
4451                    64,
4452                    clamp_only,
4453                    sp(&stream),
4454                ),
4455            )?;
4456        }
4457        {
4458            let slot = pos % win;
4459            let src = kv.slice(0..hd);
4460            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
4461            stream
4462                .memcpy_dtod(&src, &mut dst)
4463                .map_err(e("ring write"))?;
4464        }
4465        if let Some(dm) = dump.as_deref_mut() {
4466            dm.push((format!("layer{}.kv", layer.il), dtoh_f32(&stream, &kv)?));
4467        }
4468
4469        // index assembly: window part (M:255-262 decode branches), fixed width win
4470        let mut idxs: Vec<i64> = vec![-1; win];
4471        if pos >= win - 1 {
4472            let sp_ = pos % win;
4473            let mut k_ = 0usize;
4474            for s_ in (sp_ + 1)..win {
4475                idxs[k_] = s_ as i64;
4476                k_ += 1;
4477            }
4478            for s_ in 0..=sp_ {
4479                idxs[k_] = s_ as i64;
4480                k_ += 1;
4481            }
4482        } else {
4483            for (p, v) in idxs.iter_mut().enumerate().take(pos + 1) {
4484                *v = p as i64;
4485            }
4486        }
4487
4488        if layer.ratio != 0 {
4489            let cidx: Vec<i64> = if let Some(ix) = &layer.idx {
4490                // indexer q
4491                let mut qi = stream
4492                    .alloc_zeros::<f32>(ix.heads * ix.hd)
4493                    .map_err(e("qi"))?;
4494                Self::gemm_pre(
4495                    st,
4496                    &qr_b,
4497                    ix.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4498                    1,
4499                    ix.heads * ix.hd,
4500                    q_lora,
4501                    &mut qi,
4502                )?;
4503                unsafe {
4504                    ck(
4505                        "rope qi",
4506                        k::memra_dsv4_rope(
4507                            dpm!(qi, &stream),
4508                            1,
4509                            ix.heads as i32,
4510                            ix.hd as i32,
4511                            rd as i32,
4512                            dpf!(fc_dev, &stream),
4513                            pos_dev.device_ptr(&stream).0 as *const i32,
4514                            0,
4515                            sp(&stream),
4516                        ),
4517                    )?;
4518                    let scale = (ix.hd as f32).powf(-0.5);
4519                    ck(
4520                        "hadamard qi",
4521                        k::memra_dsv4_hadamard(
4522                            dpm!(qi, &stream),
4523                            ix.heads as i32,
4524                            ix.hd as i32,
4525                            scale,
4526                            sp(&stream),
4527                        ),
4528                    )?;
4529                    ck(
4530                        "fp4 qi",
4531                        k::memra_dsv4_fp4_act_quant(
4532                            dpm!(qi, &stream),
4533                            ix.heads as i32,
4534                            ix.hd as i64,
4535                            ix.hd as i32,
4536                            sp(&stream),
4537                        ),
4538                    )?;
4539                }
4540                // indexer compressor BEFORE scoring (M:415): this step's block is scored
4541                self.cmp_decode(
4542                    st,
4543                    &ix.cmp,
4544                    &x,
4545                    pos,
4546                    hidden,
4547                    fc_dev,
4548                    rd,
4549                    eps,
4550                    ipend_kv.as_mut().expect("ipend"),
4551                    ipend_score.as_mut().expect("ipend"),
4552                    ikvc.as_mut().expect("ikvc"),
4553                    0,
4554                    i_blocks,
4555                )?;
4556                let nb = *i_blocks;
4557                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
4558                if nb > 0 {
4559                    let mut wproj = stream.alloc_zeros::<f32>(ix.heads).map_err(e("wp"))?;
4560                    Self::gemm(
4561                        st,
4562                        &x,
4563                        ix.weights_proj.dev(),
4564                        0,
4565                        1,
4566                        ix.heads,
4567                        hidden,
4568                        &mut wproj,
4569                    )?;
4570                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
4571                    let mut score = stream.alloc_zeros::<f32>(nb).map_err(e("iscore"))?;
4572                    unsafe {
4573                        ck(
4574                            "indexer_score dec",
4575                            k::memra_dsv4_indexer_score(
4576                                dpf!(qi, &stream),
4577                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
4578                                dpf!(wproj, &stream),
4579                                wscale,
4580                                dpm!(score, &stream),
4581                                1,
4582                                ix.heads as i32,
4583                                ix.hd as i32,
4584                                nb as i32,
4585                                layer.ratio as i32,
4586                                nb as i32, // decode law: store is causal, lim = nb
4587                                sp(&stream),
4588                            ),
4589                        )?;
4590                    }
4591                    let score_h = dtoh_f32(&stream, &score)?;
4592                    // host topk, oracle ordering (value desc, index asc), offset = win
4593                    let kk = ix.topk.min(nb);
4594                    let mut order: Vec<usize> = (0..nb).collect();
4595                    order.sort_by(|&a, &b| {
4596                        score_h[b]
4597                            .partial_cmp(&score_h[a])
4598                            .unwrap_or(std::cmp::Ordering::Equal)
4599                            .then(a.cmp(&b))
4600                    });
4601                    order
4602                        .into_iter()
4603                        .take(kk)
4604                        .map(|j| (j + win) as i64)
4605                        .collect()
4606                } else {
4607                    Vec::new()
4608                }
4609            } else {
4610                // coarse: all blocks incl. the one emitted this step (M:268-271 decode)
4611                let nb = (pos + 1) / layer.ratio;
4612                (0..nb).map(|j| (j + win) as i64).collect()
4613            };
4614            // attention compressor before sparse_attn (M:531)
4615            self.cmp_decode(
4616                st,
4617                layer.cmp.as_ref().expect("ratio!=0 has compressor"),
4618                &x,
4619                pos,
4620                hidden,
4621                fc_dev,
4622                rd,
4623                eps,
4624                pend_kv.as_mut().expect("pend"),
4625                pend_score.as_mut().expect("pend"),
4626                kvc,
4627                win,
4628                n_blocks,
4629            )?;
4630            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
4631            idxs.extend_from_slice(&cidx);
4632        }
4633        let slots = idxs.len();
4634        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
4635        let idx_dev = upload_i32(&stream, &idxs_i32)?;
4636
4637        // sparse sink attention over the layer cache + query-position de-rotation
4638        let mut o = stream.alloc_zeros::<f32>(heads * hd).map_err(e("o"))?;
4639        let scale = (hd as f64).powf(-0.5) as f32;
4640        unsafe {
4641            ck(
4642                "sink_attn dec",
4643                k::memra_dsv4_sink_attn(
4644                    dpf!(q, &stream),
4645                    dpf!(kvc, &stream),
4646                    idx_dev.device_ptr(&stream).0 as *const i32,
4647                    dpf!(layer.sink, &stream),
4648                    dpm!(o, &stream),
4649                    1,
4650                    heads as i32,
4651                    hd as i32,
4652                    slots as i32,
4653                    scale,
4654                    sp(&stream),
4655                ),
4656            )?;
4657            ck(
4658                "rope o inv",
4659                k::memra_dsv4_rope(
4660                    dpm!(o, &stream),
4661                    1,
4662                    heads as i32,
4663                    hd as i32,
4664                    rd as i32,
4665                    dpf!(fc_dev, &stream),
4666                    pos_dev.device_ptr(&stream).0 as *const i32,
4667                    1,
4668                    sp(&stream),
4669                ),
4670            )?;
4671        }
4672
4673        if let Some(dm) = dump.as_deref_mut() {
4674            dm.push((format!("layer{}.o", layer.il), dtoh_f32(&stream, &o)?));
4675        }
4676        // grouped wo (identical to prefill at s=1)
4677        let gw = heads / o_groups * hd;
4678        let mut og = stream
4679            .alloc_zeros::<f32>(o_groups * o_lora)
4680            .map_err(e("og"))?;
4681        let mut o_grp = stream.alloc_zeros::<f32>(gw).map_err(e("o_grp"))?;
4682        let mut y_grp = stream.alloc_zeros::<f32>(o_lora).map_err(e("y_grp"))?;
4683        for g in 0..o_groups {
4684            unsafe {
4685                ck(
4686                    "take_cols",
4687                    k::memra_dsv4_take_cols(
4688                        dpf!(o, &stream),
4689                        dpm!(o_grp, &stream),
4690                        1,
4691                        gw as i32,
4692                        (heads * hd) as i64,
4693                        (g * gw) as i64,
4694                        sp(&stream),
4695                    ),
4696                )?;
4697            }
4698            Self::gemm(
4699                st,
4700                &o_grp,
4701                layer.wo_a.dev(),
4702                g * o_lora * gw,
4703                1,
4704                o_lora,
4705                gw,
4706                &mut y_grp,
4707            )?;
4708            unsafe {
4709                ck(
4710                    "place_cols",
4711                    k::memra_dsv4_place_cols(
4712                        dpf!(y_grp, &stream),
4713                        dpm!(og, &stream),
4714                        1,
4715                        o_lora as i32,
4716                        (o_groups * o_lora) as i64,
4717                        (g * o_lora) as i64,
4718                        sp(&stream),
4719                    ),
4720                )?;
4721            }
4722        }
4723        let mut attn_out = stream.alloc_zeros::<f32>(hidden).map_err(e("ao"))?;
4724        Self::gemm(
4725            st,
4726            &og,
4727            layer.wo_b.dev(),
4728            0,
4729            1,
4730            hidden,
4731            o_groups * o_lora,
4732            &mut attn_out,
4733        )?;
4734
4735        if let Some(dm) = dump.as_deref_mut() {
4736            dm.push((
4737                format!("layer{}.attn_out", layer.il),
4738                dtoh_f32(&stream, &attn_out)?,
4739            ));
4740        }
4741        // hc_post (attention)
4742        let mut h2 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h2"))?;
4743        unsafe {
4744            ck(
4745                "hc_post attn",
4746                k::memra_dsv4_hc_post(
4747                    dpf!(attn_out, &stream),
4748                    dpf!(h, &stream),
4749                    dpf!(post, &stream),
4750                    dpf!(comb, &stream),
4751                    dpm!(h2, &stream),
4752                    1,
4753                    hc as i32,
4754                    hidden as i32,
4755                    sp(&stream),
4756                ),
4757            )?;
4758        }
4759
4760        // ---- ffn sub-block
4761        let (y2, post2, comb2) = Self::hc_pre(
4762            st,
4763            &h2,
4764            &layer.hc_ffn_fn,
4765            &layer.hc_ffn_base,
4766            &layer.hc_ffn_scale,
4767            1,
4768            hc,
4769            hidden,
4770            iters,
4771            hc_eps,
4772        )?;
4773        let mut xf = stream.alloc_zeros::<f32>(hidden).map_err(e("xf"))?;
4774        unsafe {
4775            ck(
4776                "rmsnorm ffn",
4777                k::memra_dsv4_rmsnorm(
4778                    dpf!(y2, &stream),
4779                    dpf!(layer.ffn_norm, &stream),
4780                    dpm!(xf, &stream),
4781                    1,
4782                    hidden as i32,
4783                    eps,
4784                    sp(&stream),
4785                ),
4786            )?;
4787        }
4788        let moe_out = self.moe_forward(st, layer, &xf, 1, &[tok])?;
4789        if let Some(dm) = dump.as_deref_mut() {
4790            dm.push((
4791                format!("layer{}.moe_out", layer.il),
4792                dtoh_f32(&stream, &moe_out)?,
4793            ));
4794        }
4795        let mut h3 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h3"))?;
4796        unsafe {
4797            ck(
4798                "hc_post ffn",
4799                k::memra_dsv4_hc_post(
4800                    dpf!(moe_out, &stream),
4801                    dpf!(h2, &stream),
4802                    dpf!(post2, &stream),
4803                    dpf!(comb2, &stream),
4804                    dpm!(h3, &stream),
4805                    1,
4806                    hc as i32,
4807                    hidden as i32,
4808                    sp(&stream),
4809                ),
4810            )?;
4811        }
4812        if let Some(dm) = dump {
4813            dm.push((format!("layer{}.h3", layer.il), dtoh_f32(&stream, &h3)?));
4814        }
4815        Ok(h3)
4816    }
4817
4818    /// One incremental decode step: consume `tok` at position state.pos through all
4819    /// trunk layers + head using the caches (hc state carried across the PP boundary
4820    /// by host bounce, one copy per step). Returns the full logits row predicting
4821    /// position state.pos + 1.
4822    pub fn decode_step(&self, tok: u32, state: &mut DecodeState) -> Res<Vec<f32>> {
4823        self.decode_step_impl(tok, state, None)
4824    }
4825
4826    /// Diagnostic twin: returns (logits, named per-layer intermediates).
4827    #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
4828    pub fn decode_step_probe(
4829        &self,
4830        tok: u32,
4831        state: &mut DecodeState,
4832    ) -> Res<(Vec<f32>, Vec<(String, Vec<f32>)>)> {
4833        let mut dump = Vec::new();
4834        let logits = self.decode_step_impl(tok, state, Some(&mut dump))?;
4835        Ok((logits, dump))
4836    }
4837
4838    // ------------------------------------------------------------ lane 8: device path
4839
4840    /// bf16 GEMV with the arena cvt scratch and raw pointers (device decode path,
4841    /// m = 1): cvt_bf16 then the deterministic fixed-tree memra_dsv4_gemv_bf16 —
4842    /// the lane-8 class-II realization of the cuBLASLt m=1 GEMMs (gated).
4843    #[allow(clippy::too_many_arguments)]
4844    fn gemm_dev(
4845        st: &Stage,
4846        x_f32: *const f32,
4847        xb: &mut CudaSlice<u8>,
4848        w: DW,
4849        m: usize,
4850        n: usize,
4851        kdim: usize,
4852        y_ptr: *mut f32,
4853    ) -> Res<()> {
4854        assert_eq!(m, 1, "gemm_dev is the m=1 decode path");
4855        let stream = st.gpu.stream();
4856        unsafe {
4857            ck(
4858                "cvt_bf16 dev",
4859                k::memra_dsv4_cvt_bf16(
4860                    x_f32,
4861                    xb.device_ptr_mut(&stream).0 as *mut c_void,
4862                    kdim as i64,
4863                    sp(&stream),
4864                ),
4865            )?;
4866        }
4867        let xb_ptr = xb.device_ptr(&stream).0 as *const c_void;
4868        Self::gemv_pre_dev(st, xb_ptr, w, n, kdim, y_ptr)
4869    }
4870
4871    /// GEMV from an already-bf16 activation buffer (device decode path, m = 1).
4872    /// Dispatches on the dense-weight realization: bf16 slab, or the iteration-5 FP8
4873    /// pair through the bit-identical twin.
4874    fn gemv_pre_dev(
4875        st: &Stage,
4876        xb_ptr: *const c_void,
4877        w: DW,
4878        n: usize,
4879        kdim: usize,
4880        y_ptr: *mut f32,
4881    ) -> Res<()> {
4882        let stream = st.gpu.stream();
4883        unsafe {
4884            match w {
4885                DW::Bf16(w_ptr) => ck(
4886                    "gemv_bf16 pre dev",
4887                    k::memra_dsv4_gemv_bf16(
4888                        w_ptr,
4889                        xb_ptr,
4890                        y_ptr,
4891                        n as i32,
4892                        kdim as i32,
4893                        sp(&stream),
4894                    ),
4895                )?,
4896                DW::Fp8 {
4897                    codes,
4898                    scales,
4899                    sc_cols,
4900                } => ck(
4901                    "gemv_fp8 pre dev",
4902                    k::memra_dsv4_gemv_fp8(
4903                        codes,
4904                        scales,
4905                        sc_cols,
4906                        xb_ptr,
4907                        y_ptr,
4908                        n as i32,
4909                        kdim as i32,
4910                        sp(&stream),
4911                    ),
4912                )?,
4913            }
4914        }
4915        Ok(())
4916    }
4917
4918    /// hc_pre on the device path: dots + rowsq (unchanged kernels) then Sinkhorn either
4919    /// on the HOST (byte-identity arm — hc_split_sinkhorn verbatim, results uploaded
4920    /// into the arena) or as the single-thread device kernel (realization fork, class
4921    /// gated). Writes ws {mixes, pre, post, comb, y_hc}.
4922    #[allow(clippy::too_many_arguments)]
4923    // ── 0731 re-gate extension rung dispatch (MEMRA_DSV4_DOTS_ARM=f32x): each helper
4924    // picks the f64 kernel (default — the pinned oracle-truth bytes, also the lane-9
4925    // `f32` arm's bytes) or its f32acc twin. DEVICE decode path only; prefill and the
4926    // legacy path never route through these.
4927    #[allow(clippy::too_many_arguments)]
4928    unsafe fn rmsnorm_arm(
4929        &self,
4930        x: *const f32,
4931        w: *const f32,
4932        dst: *mut f32,
4933        rows: i32,
4934        ncols: i32,
4935        eps: f32,
4936        sv: *mut c_void,
4937    ) -> i32 {
4938        unsafe {
4939            if self.chains_f32 {
4940                k::memra_dsv4_rmsnorm_f32acc(x, w, dst, rows, ncols, eps, sv)
4941            } else {
4942                k::memra_dsv4_rmsnorm(x, w, dst, rows, ncols, eps, sv)
4943            }
4944        }
4945    }
4946
4947    unsafe fn headrms_arm(&self, x: *mut f32, rows: i32, d: i32, eps: f32, sv: *mut c_void) -> i32 {
4948        unsafe {
4949            if self.chains_f32 {
4950                k::memra_dsv4_headrms_f32acc(x, rows, d, eps, sv)
4951            } else {
4952                k::memra_dsv4_headrms(x, rows, d, eps, sv)
4953            }
4954        }
4955    }
4956
4957    #[allow(clippy::too_many_arguments)]
4958    unsafe fn rowsq_scale_arm(
4959        &self,
4960        x: *const f32,
4961        mixes: *mut f32,
4962        s: i32,
4963        w: i32,
4964        rows: i32,
4965        eps: f32,
4966        sv: *mut c_void,
4967    ) -> i32 {
4968        unsafe {
4969            if self.chains_f32 {
4970                k::memra_dsv4_rowsq_scale_f32acc(x, mixes, s, w, rows, eps, sv)
4971            } else {
4972                k::memra_dsv4_rowsq_scale(x, mixes, s, w, rows, eps, sv)
4973            }
4974        }
4975    }
4976
4977    #[allow(clippy::too_many_arguments)]
4978    unsafe fn indexer_score_arm(
4979        &self,
4980        q: *const f32,
4981        ckv: *const f32,
4982        w: *const f32,
4983        wscale: f32,
4984        score: *mut f32,
4985        s: i32,
4986        heads: i32,
4987        hd: i32,
4988        nb: i32,
4989        ratio: i32,
4990        lim0: i32,
4991        sv: *mut c_void,
4992    ) -> i32 {
4993        unsafe {
4994            if self.chains_f32 {
4995                k::memra_dsv4_indexer_score_f32acc(
4996                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
4997                )
4998            } else {
4999                k::memra_dsv4_indexer_score(
5000                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
5001                )
5002            }
5003        }
5004    }
5005
5006    /// `den` is the f64 workspace either way; the f32acc twin rides a FLOAT view of the
5007    /// same allocation (K2 writes it, K3 reads it, within the one FFI entry).
5008    #[allow(clippy::too_many_arguments)]
5009    unsafe fn sink_attn_dec_arm(
5010        &self,
5011        q: *const f32,
5012        kv: *const f32,
5013        idxs: *const i32,
5014        sink: *const f32,
5015        scores: *mut f32,
5016        evals: *mut f32,
5017        den: *mut f64,
5018        o: *mut f32,
5019        heads: i32,
5020        hd: i32,
5021        slots: i32,
5022        scale: f32,
5023        sv: *mut c_void,
5024    ) -> i32 {
5025        unsafe {
5026            if self.chains_f32 {
5027                k::memra_dsv4_sink_attn_dec_f32acc(
5028                    q,
5029                    kv,
5030                    idxs,
5031                    sink,
5032                    scores,
5033                    evals,
5034                    den as *mut f32,
5035                    o,
5036                    heads,
5037                    hd,
5038                    slots,
5039                    scale,
5040                    sv,
5041                )
5042            } else {
5043                k::memra_dsv4_sink_attn_dec(
5044                    q, kv, idxs, sink, scores, evals, den, o, heads, hd, slots, scale, sv,
5045                )
5046            }
5047        }
5048    }
5049
5050    #[allow(clippy::too_many_arguments)] // allow: the parameter list mirrors the kernel/FFI/call contract; bundling into a struct is a refactor, not a lint fix
5051    fn hc_pre_dev(
5052        &self,
5053        st: &Stage,
5054        h: &CudaSlice<f32>,
5055        fn_w: &CudaSlice<f32>,
5056        base_host: &[f32],
5057        scale_host: &[f32],
5058        base_dev: &CudaSlice<f32>,
5059        scale_dev: &CudaSlice<f32>,
5060        mixes: &mut CudaSlice<f32>,
5061        pre: &mut CudaSlice<f32>,
5062        post: &mut CudaSlice<f32>,
5063        comb: &mut CudaSlice<f32>,
5064        y_hc: &mut CudaSlice<f32>,
5065        hc: usize,
5066        hidden: usize,
5067        iters: u32,
5068        hc_eps: f32,
5069        host_math: bool,
5070    ) -> Res<()> {
5071        let stream = st.gpu.stream();
5072        let w = hc * hidden;
5073        let rows = (2 + hc) * hc;
5074        self.dots_dev(st, h, fn_w, 1, w, rows, mixes)?;
5075        unsafe {
5076            ck(
5077                "rowsq_scale dev",
5078                self.rowsq_scale_arm(
5079                    dpf!(h, &stream),
5080                    dpm!(*mixes, &stream),
5081                    1,
5082                    w as i32,
5083                    rows as i32,
5084                    hc_eps,
5085                    sp(&stream),
5086                ),
5087            )?;
5088        }
5089        if host_math {
5090            let mixes_h = dtoh_f32(&stream, mixes)?;
5091            let (pre_h, post_h, comb_h) =
5092                hc_split_sinkhorn(&mixes_h, 1, hc, scale_host, base_host, iters, hc_eps);
5093            stream.memcpy_htod(&pre_h, pre).map_err(e("htod pre"))?;
5094            stream.memcpy_htod(&post_h, post).map_err(e("htod post"))?;
5095            stream.memcpy_htod(&comb_h, comb).map_err(e("htod comb"))?;
5096        } else {
5097            unsafe {
5098                ck(
5099                    "hc_sinkhorn",
5100                    k::memra_dsv4_hc_sinkhorn(
5101                        dpf!(*mixes, &stream),
5102                        dpf!(scale_dev, &stream),
5103                        dpf!(base_dev, &stream),
5104                        dpm!(*pre, &stream),
5105                        dpm!(*post, &stream),
5106                        dpm!(*comb, &stream),
5107                        hc as i32,
5108                        iters as i32,
5109                        hc_eps,
5110                        sp(&stream),
5111                    ),
5112                )?;
5113            }
5114        }
5115        unsafe {
5116            ck(
5117                "hc_collapse dev",
5118                k::memra_dsv4_hc_collapse(
5119                    dpf!(h, &stream),
5120                    dpf!(*pre, &stream),
5121                    dpm!(*y_hc, &stream),
5122                    1,
5123                    hc as i32,
5124                    hidden as i32,
5125                    sp(&stream),
5126                ),
5127            )?;
5128        }
5129        Ok(())
5130    }
5131
5132    /// Incremental compressor step on the arena (cmp_decode's arithmetic verbatim:
5133    /// same kernels, same D2D moves; rope via the scalar-position launcher — identical
5134    /// kernel body). No allocations.
5135    #[allow(clippy::too_many_arguments)]
5136    #[allow(clippy::manual_is_multiple_of)] // allow: divisor is runtime-derived; the modulo form keeps a zero divisor loud (a panic), where is_multiple_of would return false silently
5137    fn cmp_decode_dev(
5138        &self,
5139        st: &Stage,
5140        cmp: &CmpDev,
5141        x: &CudaSlice<f32>,
5142        pos: usize,
5143        hidden: usize,
5144        fc_dev: &CudaSlice<f32>,
5145        rd: usize,
5146        eps: f32,
5147        kv_row: &mut CudaSlice<f32>,
5148        sc_row: &mut CudaSlice<f32>,
5149        emit: &mut CudaSlice<f32>,
5150        shift: &mut CudaSlice<f32>,
5151        pend_kv: &mut CudaSlice<f32>,
5152        pend_score: &mut CudaSlice<f32>,
5153        store: &mut CudaSlice<f32>,
5154        row0: usize,
5155        blocks: &mut usize,
5156    ) -> Res<()> {
5157        let stream = st.gpu.stream();
5158        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
5159        self.dots_dev(st, x, &cmp.wkv, 1, hidden, latent, kv_row)?;
5160        self.dots_dev(st, x, &cmp.wgate, 1, hidden, latent, sc_row)?;
5161        let slot = if cmp.overlap {
5162            ratio + pos % ratio
5163        } else {
5164            pos % ratio
5165        };
5166        {
5167            let src = kv_row.slice(0..latent);
5168            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
5169            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
5170            let src = sc_row.slice(0..latent);
5171            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
5172            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
5173        }
5174        if (pos + 1) % ratio != 0 {
5175            return Ok(());
5176        }
5177        let j = pos / ratio;
5178        let nb_launch = if cmp.overlap { 2usize } else { 1 };
5179        let row_off = if cmp.overlap { d } else { 0 };
5180        unsafe {
5181            ck(
5182                "compressor_pool dec",
5183                k::memra_dsv4_compressor_pool(
5184                    dpf!(*pend_kv, &stream),
5185                    dpf!(*pend_score, &stream),
5186                    dpf!(cmp.ape, &stream),
5187                    dpm!(*emit, &stream),
5188                    nb_launch as i32,
5189                    ratio as i32,
5190                    d as i32,
5191                    latent as i32,
5192                    cmp.overlap as i32,
5193                    sp(&stream),
5194                ),
5195            )?;
5196            let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
5197            let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
5198            ck(
5199                "rmsnorm dec cmp",
5200                self.rmsnorm_arm(
5201                    row_c,
5202                    dpf!(cmp.norm, &stream),
5203                    row_m,
5204                    1,
5205                    d as i32,
5206                    eps,
5207                    sp(&stream),
5208                ),
5209            )?;
5210            ck(
5211                "rope_at dec cmp",
5212                k::memra_dsv4_rope_at(
5213                    row_m,
5214                    1,
5215                    d as i32,
5216                    rd as i32,
5217                    dpf!(fc_dev, &stream),
5218                    (j * ratio) as i32,
5219                    0,
5220                    sp(&stream),
5221                ),
5222            )?;
5223            if cmp.rotate {
5224                let scale = (d as f32).powf(-0.5);
5225                ck(
5226                    "hadamard dec cmp",
5227                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
5228                )?;
5229                ck(
5230                    "fp4 dec cmp",
5231                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
5232                )?;
5233            } else {
5234                ck(
5235                    "act_quant dec cmp",
5236                    k::memra_dsv4_act_quant(
5237                        row_m,
5238                        1,
5239                        d as i64,
5240                        (d - rd) as i32,
5241                        64,
5242                        (self.variant == ActQuantVariant::ClampOnly) as i32,
5243                        sp(&stream),
5244                    ),
5245                )?;
5246            }
5247        }
5248        {
5249            let src = emit.slice(row_off..row_off + d);
5250            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
5251            stream
5252                .memcpy_dtod(&src, &mut dst)
5253                .map_err(e("emit store"))?;
5254        }
5255        if cmp.overlap {
5256            {
5257                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
5258                let mut dst = shift.slice_mut(0..ratio * latent);
5259                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift1"))?;
5260            }
5261            {
5262                let src = shift.slice(0..ratio * latent);
5263                let mut dst = pend_kv.slice_mut(0..ratio * latent);
5264                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift2"))?;
5265            }
5266            {
5267                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
5268                let mut dst = shift.slice_mut(0..ratio * latent);
5269                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift3"))?;
5270            }
5271            {
5272                let src = shift.slice(0..ratio * latent);
5273                let mut dst = pend_score.slice_mut(0..ratio * latent);
5274                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift4"))?;
5275            }
5276        }
5277        *blocks = j + 1;
5278        Ok(())
5279    }
5280
5281    /// One trunk block, single-token decode, device path (block_decode's flow on the
5282    /// arena; per-value arithmetic identical under host_math — deviations under device
5283    /// math are the banked Sinkhorn/router realization forks). Input h is ws.h_a
5284    /// (or ws.h_rx right after the boundary); output lands in ws.h_a.
5285    #[allow(clippy::too_many_arguments)]
5286    fn block_decode_dev(
5287        &self,
5288        st: &Stage,
5289        layer: &LayerDev,
5290        cache: &mut LayerCache,
5291        ws: &mut StepWs,
5292        input_rx: bool,
5293        pos: usize,
5294        tok: u32,
5295        host_math: bool,
5296    ) -> Res<()> {
5297        let d = self.model.cfg();
5298        let mc = &self.model.mc;
5299        let hc = d.hc_mult as usize;
5300        let hidden = mc.n_embd as usize;
5301        let heads = mc.n_head as usize;
5302        let hd = d.head_dim as usize;
5303        let rd = d.qk_rope_head_dim as usize;
5304        let q_lora = d.q_lora_rank as usize;
5305        let win = d.sliding_window as usize;
5306        let o_groups = d.o_groups as usize;
5307        let o_lora = d.o_lora_rank as usize;
5308        let eps = mc.rms_eps;
5309        let iters = d.hc_sinkhorn_iters;
5310        let hc_eps = d.hc_eps;
5311        let stream = st.gpu.stream();
5312        let fc_dev: *const f32 = if layer.ratio != 0 {
5313            st.fc_yarn.device_ptr(&stream).0 as *const f32
5314        } else {
5315            st.fc_plain.device_ptr(&stream).0 as *const f32
5316        };
5317        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
5318        let LayerCache {
5319            kvc,
5320            n_blocks,
5321            pend_kv,
5322            pend_score,
5323            ikvc,
5324            i_blocks,
5325            ipend_kv,
5326            ipend_score,
5327        } = cache;
5328
5329        // ---- attention sub-block
5330        {
5331            // split-borrow the arena fields we need for hc_pre
5332            let StepWs {
5333                h_a,
5334                h_rx,
5335                mixes,
5336                pre,
5337                post,
5338                comb,
5339                y_hc,
5340                ..
5341            } = ws;
5342            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5343            self.hc_pre_dev(
5344                st,
5345                h_in,
5346                &layer.hc_attn_fn,
5347                &layer.hc_attn_base,
5348                &layer.hc_attn_scale,
5349                &layer.hc_attn_base_dev,
5350                &layer.hc_attn_scale_dev,
5351                mixes,
5352                pre,
5353                post,
5354                comb,
5355                y_hc,
5356                hc,
5357                hidden,
5358                iters,
5359                hc_eps,
5360                host_math,
5361            )?;
5362        }
5363        unsafe {
5364            ck(
5365                "rmsnorm attn dev",
5366                self.rmsnorm_arm(
5367                    dpf!(ws.y_hc, &stream),
5368                    dpf!(layer.attn_norm, &stream),
5369                    dpm!(ws.x, &stream),
5370                    1,
5371                    hidden as i32,
5372                    eps,
5373                    sp(&stream),
5374                ),
5375            )?;
5376        }
5377
5378        // q path
5379        Self::gemm_dev(
5380            st,
5381            ws.x.device_ptr(&stream).0 as *const f32,
5382            &mut ws.gemm_xb,
5383            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
5384            1,
5385            q_lora,
5386            hidden,
5387            ws.qr.device_ptr_mut(&stream).0 as *mut f32,
5388        )?;
5389        unsafe {
5390            ck(
5391                "rmsnorm q dev",
5392                self.rmsnorm_arm(
5393                    dpf!(ws.qr, &stream),
5394                    dpf!(layer.q_norm, &stream),
5395                    dpm!(ws.qr, &stream),
5396                    1,
5397                    q_lora as i32,
5398                    eps,
5399                    sp(&stream),
5400                ),
5401            )?;
5402            ck(
5403                "cvt qr dev",
5404                k::memra_dsv4_cvt_bf16(
5405                    dpf!(ws.qr, &stream),
5406                    ws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
5407                    q_lora as i64,
5408                    sp(&stream),
5409                ),
5410            )?;
5411        }
5412        Self::gemv_pre_dev(
5413            st,
5414            ws.qr_b.device_ptr(&stream).0 as *const c_void,
5415            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
5416            heads * hd,
5417            q_lora,
5418            ws.q.device_ptr_mut(&stream).0 as *mut f32,
5419        )?;
5420        unsafe {
5421            ck(
5422                "headrms dev",
5423                self.headrms_arm(
5424                    dpm!(ws.q, &stream),
5425                    heads as i32,
5426                    hd as i32,
5427                    eps,
5428                    sp(&stream),
5429                ),
5430            )?;
5431            ck(
5432                "rope_at q dev",
5433                k::memra_dsv4_rope_at(
5434                    dpm!(ws.q, &stream),
5435                    heads as i32,
5436                    hd as i32,
5437                    rd as i32,
5438                    fc_dev,
5439                    pos as i32,
5440                    0,
5441                    sp(&stream),
5442                ),
5443            )?;
5444        }
5445
5446        // shared K==V latent row + window QAT + ring write
5447        Self::gemm_dev(
5448            st,
5449            ws.x.device_ptr(&stream).0 as *const f32,
5450            &mut ws.gemm_xb,
5451            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
5452            1,
5453            hd,
5454            hidden,
5455            ws.kv.device_ptr_mut(&stream).0 as *mut f32,
5456        )?;
5457        unsafe {
5458            ck(
5459                "rmsnorm kv dev",
5460                self.rmsnorm_arm(
5461                    dpf!(ws.kv, &stream),
5462                    dpf!(layer.kv_norm, &stream),
5463                    dpm!(ws.kv, &stream),
5464                    1,
5465                    hd as i32,
5466                    eps,
5467                    sp(&stream),
5468                ),
5469            )?;
5470            ck(
5471                "rope_at kv dev",
5472                k::memra_dsv4_rope_at(
5473                    dpm!(ws.kv, &stream),
5474                    1,
5475                    hd as i32,
5476                    rd as i32,
5477                    fc_dev,
5478                    pos as i32,
5479                    0,
5480                    sp(&stream),
5481                ),
5482            )?;
5483            ck(
5484                "act_quant kv dev",
5485                k::memra_dsv4_act_quant(
5486                    dpm!(ws.kv, &stream),
5487                    1,
5488                    hd as i64,
5489                    (hd - rd) as i32,
5490                    64,
5491                    clamp_only,
5492                    sp(&stream),
5493                ),
5494            )?;
5495        }
5496        {
5497            let slot = pos % win;
5498            let src = ws.kv.slice(0..hd);
5499            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
5500            stream
5501                .memcpy_dtod(&src, &mut dst)
5502                .map_err(e("ring write"))?;
5503        }
5504
5505        // index list: window part on device (block_decode's builder verbatim)
5506        let mut slots = win;
5507        if layer.ratio != 0 {
5508            if let Some(ix) = &layer.idx {
5509                // indexer q
5510                Self::gemv_pre_dev(
5511                    st,
5512                    ws.qr_b.device_ptr(&stream).0 as *const c_void,
5513                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
5514                    ix.heads * ix.hd,
5515                    q_lora,
5516                    ws.qi.device_ptr_mut(&stream).0 as *mut f32,
5517                )?;
5518                unsafe {
5519                    ck(
5520                        "rope_at qi dev",
5521                        k::memra_dsv4_rope_at(
5522                            dpm!(ws.qi, &stream),
5523                            ix.heads as i32,
5524                            ix.hd as i32,
5525                            rd as i32,
5526                            fc_dev,
5527                            pos as i32,
5528                            0,
5529                            sp(&stream),
5530                        ),
5531                    )?;
5532                    let scale = (ix.hd as f32).powf(-0.5);
5533                    ck(
5534                        "hadamard qi dev",
5535                        k::memra_dsv4_hadamard(
5536                            dpm!(ws.qi, &stream),
5537                            ix.heads as i32,
5538                            ix.hd as i32,
5539                            scale,
5540                            sp(&stream),
5541                        ),
5542                    )?;
5543                    ck(
5544                        "fp4 qi dev",
5545                        k::memra_dsv4_fp4_act_quant(
5546                            dpm!(ws.qi, &stream),
5547                            ix.heads as i32,
5548                            ix.hd as i64,
5549                            ix.hd as i32,
5550                            sp(&stream),
5551                        ),
5552                    )?;
5553                }
5554                // indexer compressor BEFORE scoring (M:415)
5555                {
5556                    let StepWs {
5557                        x,
5558                        cmp_kv_row,
5559                        cmp_sc_row,
5560                        cmp_emit,
5561                        cmp_shift,
5562                        ..
5563                    } = ws;
5564                    self.cmp_decode_dev(
5565                        st,
5566                        &ix.cmp,
5567                        x,
5568                        pos,
5569                        hidden,
5570                        if layer.ratio != 0 {
5571                            &st.fc_yarn
5572                        } else {
5573                            &st.fc_plain
5574                        },
5575                        rd,
5576                        eps,
5577                        cmp_kv_row,
5578                        cmp_sc_row,
5579                        cmp_emit,
5580                        cmp_shift,
5581                        ipend_kv.as_mut().expect("ipend"),
5582                        ipend_score.as_mut().expect("ipend"),
5583                        ikvc.as_mut().expect("ikvc"),
5584                        0,
5585                        i_blocks,
5586                    )?;
5587                }
5588                let nb = *i_blocks;
5589                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
5590                // window part (fills [0, win)); fine tail written by the top-k below
5591                unsafe {
5592                    ck(
5593                        "build_idx win",
5594                        k::memra_dsv4_build_idx(
5595                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5596                            pos as i32,
5597                            win as i32,
5598                            -1,
5599                            win as i32,
5600                            sp(&stream),
5601                        ),
5602                    )?;
5603                }
5604                if nb > 0 {
5605                    Self::gemm_dev(
5606                        st,
5607                        ws.x.device_ptr(&stream).0 as *const f32,
5608                        &mut ws.gemm_xb,
5609                        dwsel(
5610                            self.dense_fp8,
5611                            &stream,
5612                            &ix.weights_proj,
5613                            &ix.weights_proj_fp8,
5614                        ),
5615                        1,
5616                        ix.heads,
5617                        hidden,
5618                        ws.wproj.device_ptr_mut(&stream).0 as *mut f32,
5619                    )?;
5620                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
5621                    unsafe {
5622                        ck(
5623                            "indexer_score dev",
5624                            self.indexer_score_arm(
5625                                dpf!(ws.qi, &stream),
5626                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
5627                                dpf!(ws.wproj, &stream),
5628                                wscale,
5629                                dpm!(ws.score, &stream),
5630                                1,
5631                                ix.heads as i32,
5632                                ix.hd as i32,
5633                                nb as i32,
5634                                layer.ratio as i32,
5635                                nb as i32,
5636                                sp(&stream),
5637                            ),
5638                        )?;
5639                    }
5640                    let kk = ix.topk.min(nb);
5641                    if host_math {
5642                        // byte-identity arm: the legacy host sort verbatim, uploaded
5643                        // into the arena index tail
5644                        let score_h = {
5645                            let view = ws.score.slice(0..nb);
5646                            let mut v = vec![0f32; nb];
5647                            stream
5648                                .memcpy_dtoh(&view, &mut v[..])
5649                                .map_err(e("dtoh sc"))?;
5650                            stream.synchronize().map_err(e("sync sc"))?;
5651                            v
5652                        };
5653                        let mut order: Vec<usize> = (0..nb).collect();
5654                        order.sort_by(|&a, &b| {
5655                            score_h[b]
5656                                .partial_cmp(&score_h[a])
5657                                .unwrap_or(std::cmp::Ordering::Equal)
5658                                .then(a.cmp(&b))
5659                        });
5660                        let cidx: Vec<i32> = order
5661                            .into_iter()
5662                            .take(kk)
5663                            .map(|j| (j + win) as i32)
5664                            .collect();
5665                        let mut dst = ws.idx.slice_mut(win..win + kk);
5666                        stream.memcpy_htod(&cidx, &mut dst).map_err(e("htod idx"))?;
5667                    } else {
5668                        unsafe {
5669                            let idx_tail =
5670                                (ws.idx.device_ptr_mut(&stream).0 as usize + win * 4) as *mut i32;
5671                            ck(
5672                                "topk_idx dev",
5673                                k::memra_dsv4_topk_idx(
5674                                    dpf!(ws.score, &stream),
5675                                    nb as i32,
5676                                    kk as i32,
5677                                    win as i32,
5678                                    idx_tail,
5679                                    sp(&stream),
5680                                ),
5681                            )?;
5682                        }
5683                    }
5684                    slots = win + kk;
5685                }
5686            } else {
5687                // coarse: all blocks incl. the one emitted this step — but the ATTENTION
5688                // compressor below is what emits it, so the count is (pos+1)/ratio
5689                let nb = (pos + 1) / layer.ratio;
5690                unsafe {
5691                    ck(
5692                        "build_idx coarse",
5693                        k::memra_dsv4_build_idx(
5694                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5695                            pos as i32,
5696                            win as i32,
5697                            nb as i32,
5698                            (win + nb) as i32,
5699                            sp(&stream),
5700                        ),
5701                    )?;
5702                }
5703                slots = win + nb;
5704            }
5705            // attention compressor before sparse_attn (M:531)
5706            {
5707                let StepWs {
5708                    x,
5709                    cmp_kv_row,
5710                    cmp_sc_row,
5711                    cmp_emit,
5712                    cmp_shift,
5713                    ..
5714                } = ws;
5715                self.cmp_decode_dev(
5716                    st,
5717                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
5718                    x,
5719                    pos,
5720                    hidden,
5721                    &st.fc_yarn,
5722                    rd,
5723                    eps,
5724                    cmp_kv_row,
5725                    cmp_sc_row,
5726                    cmp_emit,
5727                    cmp_shift,
5728                    pend_kv.as_mut().expect("pend"),
5729                    pend_score.as_mut().expect("pend"),
5730                    kvc,
5731                    win,
5732                    n_blocks,
5733                )?;
5734            }
5735            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
5736        } else {
5737            // window-only layer: fixed-width window part with -1 pads (legacy widths)
5738            unsafe {
5739                ck(
5740                    "build_idx window-only",
5741                    k::memra_dsv4_build_idx(
5742                        ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5743                        pos as i32,
5744                        win as i32,
5745                        -1,
5746                        win as i32,
5747                        sp(&stream),
5748                    ),
5749                )?;
5750            }
5751        }
5752
5753        // sparse sink attention (lane-8 three-kernel split, bit-exact — see the .cu
5754        // notes) + query-position de-rotation
5755        let scale = (hd as f64).powf(-0.5) as f32;
5756        unsafe {
5757            ck(
5758                "sink_attn_dec dev",
5759                self.sink_attn_dec_arm(
5760                    dpf!(ws.q, &stream),
5761                    dpf!(kvc, &stream),
5762                    ws.idx.device_ptr(&stream).0 as *const i32,
5763                    dpf!(layer.sink, &stream),
5764                    dpm!(ws.sink_scores, &stream),
5765                    dpm!(ws.sink_evals, &stream),
5766                    ws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
5767                    dpm!(ws.o, &stream),
5768                    heads as i32,
5769                    hd as i32,
5770                    slots as i32,
5771                    scale,
5772                    sp(&stream),
5773                ),
5774            )?;
5775            ck(
5776                "rope_at o inv dev",
5777                k::memra_dsv4_rope_at(
5778                    dpm!(ws.o, &stream),
5779                    heads as i32,
5780                    hd as i32,
5781                    rd as i32,
5782                    fc_dev,
5783                    pos as i32,
5784                    1,
5785                    sp(&stream),
5786                ),
5787            )?;
5788        }
5789
5790        // grouped wo: cvt o ONCE (elementwise — bit-equal to the legacy per-group cvt),
5791        // then per-group offset GEMMs straight into og slices (take/place_cols are pure
5792        // offsets at s=1), then wo_b.
5793        let gw = heads / o_groups * hd;
5794        unsafe {
5795            ck(
5796                "cvt o dev",
5797                k::memra_dsv4_cvt_bf16(
5798                    dpf!(ws.o, &stream),
5799                    ws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
5800                    (heads * hd) as i64,
5801                    sp(&stream),
5802                ),
5803            )?;
5804        }
5805        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
5806        for g in 0..o_groups {
5807            Self::gemv_pre_dev(
5808                st,
5809                (ws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
5810                wo_a_dw.offset_rows(g * o_lora, gw),
5811                o_lora,
5812                gw,
5813                (ws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
5814            )?;
5815        }
5816        Self::gemm_dev(
5817            st,
5818            ws.og.device_ptr(&stream).0 as *const f32,
5819            &mut ws.gemm_xb,
5820            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
5821            1,
5822            hidden,
5823            o_groups * o_lora,
5824            ws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
5825        )?;
5826
5827        // hc_post (attention): h2 = ws.h_b from residual h_in
5828        {
5829            let StepWs {
5830                h_a,
5831                h_b,
5832                h_rx,
5833                attn_out,
5834                post,
5835                comb,
5836                ..
5837            } = ws;
5838            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5839            unsafe {
5840                ck(
5841                    "hc_post attn dev",
5842                    k::memra_dsv4_hc_post(
5843                        dpf!(attn_out, &stream),
5844                        dpf!(h_in, &stream),
5845                        dpf!(post, &stream),
5846                        dpf!(comb, &stream),
5847                        dpm!(*h_b, &stream),
5848                        1,
5849                        hc as i32,
5850                        hidden as i32,
5851                        sp(&stream),
5852                    ),
5853                )?;
5854            }
5855        }
5856
5857        // ---- ffn sub-block (input h2 = ws.h_b, output h3 = ws.h_a)
5858        {
5859            let StepWs {
5860                h_b,
5861                mixes,
5862                pre,
5863                post,
5864                comb,
5865                y_hc,
5866                ..
5867            } = ws;
5868            self.hc_pre_dev(
5869                st,
5870                h_b,
5871                &layer.hc_ffn_fn,
5872                &layer.hc_ffn_base,
5873                &layer.hc_ffn_scale,
5874                &layer.hc_ffn_base_dev,
5875                &layer.hc_ffn_scale_dev,
5876                mixes,
5877                pre,
5878                post,
5879                comb,
5880                y_hc,
5881                hc,
5882                hidden,
5883                iters,
5884                hc_eps,
5885                host_math,
5886            )?;
5887        }
5888        unsafe {
5889            ck(
5890                "rmsnorm ffn dev",
5891                self.rmsnorm_arm(
5892                    dpf!(ws.y_hc, &stream),
5893                    dpf!(layer.ffn_norm, &stream),
5894                    dpm!(ws.xf, &stream),
5895                    1,
5896                    hidden as i32,
5897                    eps,
5898                    sp(&stream),
5899                ),
5900            )?;
5901        }
5902        self.moe_forward_dev(st, layer, ws, tok, host_math)?;
5903        {
5904            let StepWs {
5905                h_a,
5906                h_b,
5907                y,
5908                post,
5909                comb,
5910                ..
5911            } = ws;
5912            unsafe {
5913                ck(
5914                    "hc_post ffn dev",
5915                    k::memra_dsv4_hc_post(
5916                        dpf!(y, &stream),
5917                        dpf!(h_b, &stream),
5918                        dpf!(post, &stream),
5919                        dpf!(comb, &stream),
5920                        dpm!(*h_a, &stream),
5921                        1,
5922                        hc as i32,
5923                        hidden as i32,
5924                        sp(&stream),
5925                    ),
5926                )?;
5927            }
5928        }
5929        Ok(())
5930    }
5931
5932    /// MoE on the device path (native fp4 arm only, asserted at load): routing via the
5933    /// device kernel (or route_host under host_math), then ONE launch per projection
5934    /// over all active-expert slots (indirect fused dispatch — attack #3 at s=1),
5935    /// combine in ascending-expert-id order (the legacy scatter sequence), shared
5936    /// expert on the lane-4 bf16 rung. Writes ws.y.
5937    fn moe_forward_dev(
5938        &self,
5939        st: &Stage,
5940        layer: &LayerDev,
5941        ws: &mut StepWs,
5942        tok: u32,
5943        host_math: bool,
5944    ) -> Res<()> {
5945        let mc = &self.model.mc;
5946        let d = self.model.cfg();
5947        let moe = mc.moe.as_ref().expect("moe");
5948        let hidden = mc.n_embd as usize;
5949        let ne = moe.expert_count as usize;
5950        let topk = moe.expert_used_count as usize;
5951        let inter = moe.expert_ff_length as usize;
5952        let limit = d.swiglu_limit;
5953        let stream = st.gpu.stream();
5954        let kind = match layer.expert_kind {
5955            ExpertKind::Nvfp4 => 0i32,
5956            ExpertKind::Mxfp4 => 1i32,
5957        };
5958        let wstride = (inter * hidden / 2) as i64;
5959        let sstride = match layer.expert_kind {
5960            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
5961            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
5962        };
5963
5964        self.dots_dev(st, &ws.xf, &layer.gate_w, 1, hidden, ne, &mut ws.raw)?;
5965        if host_math {
5966            let raw_h = dtoh_f32(&stream, &ws.raw)?;
5967            let (indices, weights) =
5968                Self::route_host(layer, &raw_h, &[tok], 1, ne, topk, d.routed_scaling_factor);
5969            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
5970            let mut order: Vec<i32> = (0..topk as i32).collect();
5971            order.sort_by_key(|&s| indices[s as usize]);
5972            stream
5973                .memcpy_htod(&sel, &mut ws.sel)
5974                .map_err(e("htod sel"))?;
5975            stream
5976                .memcpy_htod(&weights, &mut ws.selw)
5977                .map_err(e("htod selw"))?;
5978            stream
5979                .memcpy_htod(&order, &mut ws.order)
5980                .map_err(e("htod order"))?;
5981        } else {
5982            unsafe {
5983                ck(
5984                    "route dev",
5985                    k::memra_dsv4_route(
5986                        dpf!(ws.raw, &stream),
5987                        layer
5988                            .gate_bias_dev
5989                            .as_ref()
5990                            .map(|b| b.device_ptr(&stream).0 as *const f32)
5991                            .unwrap_or(std::ptr::null()),
5992                        layer
5993                            .tid2eid_dev
5994                            .as_ref()
5995                            .map(|t| t.device_ptr(&stream).0 as *const i32)
5996                            .unwrap_or(std::ptr::null()),
5997                        ws.tok.device_ptr(&stream).0 as *const i32,
5998                        ne as i32,
5999                        topk as i32,
6000                        d.routed_scaling_factor,
6001                        ws.sel.device_ptr_mut(&stream).0 as *mut i32,
6002                        ws.selw.device_ptr_mut(&stream).0 as *mut f32,
6003                        ws.order.device_ptr_mut(&stream).0 as *mut i32,
6004                        sp(&stream),
6005                    ),
6006                )?;
6007            }
6008        }
6009
6010        unsafe {
6011            ck(
6012                "act_quant_fp8 x dev",
6013                k::memra_dsv4_act_quant_fp8(
6014                    dpf!(ws.xf, &stream),
6015                    ws.xq.device_ptr_mut(&stream).0 as *mut c_void,
6016                    dpm!(ws.xs, &stream),
6017                    1,
6018                    hidden as i32,
6019                    sp(&stream),
6020                ),
6021            )?;
6022            for (proj, dst) in [(0i32, &mut ws.g1), (2i32, &mut ws.g3)] {
6023                ck(
6024                    "fp4_gemm_sel w1/w3",
6025                    k::memra_dsv4_fp4_gemm_sel(
6026                        dp!(ws.xq, &stream),
6027                        dpf!(ws.xs, &stream),
6028                        dp!(layer.experts_w, &stream),
6029                        dp!(layer.experts_sc, &stream),
6030                        dpf!(layer.experts_s2_dev, &stream),
6031                        ws.sel.device_ptr(&stream).0 as *const i32,
6032                        proj,
6033                        0,
6034                        kind,
6035                        dpm!(*dst, &stream),
6036                        topk as i32,
6037                        inter as i32,
6038                        hidden as i32,
6039                        wstride,
6040                        sstride,
6041                        sp(&stream),
6042                    ),
6043                )?;
6044            }
6045            ck(
6046                "swiglu dev",
6047                k::memra_dsv4_swiglu(
6048                    dpf!(ws.g1, &stream),
6049                    dpf!(ws.g3, &stream),
6050                    dpm!(ws.hbuf, &stream),
6051                    topk as i32,
6052                    inter as i32,
6053                    limit,
6054                    ws.selw.device_ptr(&stream).0 as *const f32,
6055                    sp(&stream),
6056                ),
6057            )?;
6058            ck(
6059                "act_quant_fp8 h dev",
6060                k::memra_dsv4_act_quant_fp8(
6061                    dpf!(ws.hbuf, &stream),
6062                    ws.hq.device_ptr_mut(&stream).0 as *mut c_void,
6063                    dpm!(ws.hs, &stream),
6064                    topk as i32,
6065                    inter as i32,
6066                    sp(&stream),
6067                ),
6068            )?;
6069            ck(
6070                "fp4_gemm_sel w2",
6071                k::memra_dsv4_fp4_gemm_sel(
6072                    dp!(ws.hq, &stream),
6073                    dpf!(ws.hs, &stream),
6074                    dp!(layer.experts_w, &stream),
6075                    dp!(layer.experts_sc, &stream),
6076                    dpf!(layer.experts_s2_dev, &stream),
6077                    ws.sel.device_ptr(&stream).0 as *const i32,
6078                    1,
6079                    1,
6080                    kind,
6081                    dpm!(ws.contrib, &stream),
6082                    topk as i32,
6083                    hidden as i32,
6084                    inter as i32,
6085                    wstride,
6086                    sstride,
6087                    sp(&stream),
6088                ),
6089            )?;
6090            ck(
6091                "combine dev",
6092                k::memra_dsv4_combine_rows(
6093                    dpf!(ws.contrib, &stream),
6094                    ws.order.device_ptr(&stream).0 as *const i32,
6095                    topk as i32,
6096                    dpm!(ws.y, &stream),
6097                    hidden as i64,
6098                    sp(&stream),
6099                ),
6100            )?;
6101            // shared expert (lane-4 bf16 rung — the lane-7 FP8-linear decision)
6102            ck(
6103                "cvt xb dev",
6104                k::memra_dsv4_cvt_bf16(
6105                    dpf!(ws.xf, &stream),
6106                    ws.xb.device_ptr_mut(&stream).0 as *mut c_void,
6107                    hidden as i64,
6108                    sp(&stream),
6109                ),
6110            )?;
6111        }
6112        let sh_inter = ws.sg1.len();
6113        Self::gemv_pre_dev(
6114            st,
6115            ws.xb.device_ptr(&stream).0 as *const c_void,
6116            dwsel(
6117                self.dense_fp8,
6118                &stream,
6119                &layer.shared_w[0],
6120                &layer.shared_fp8[0],
6121            ),
6122            sh_inter,
6123            hidden,
6124            ws.sg1.device_ptr_mut(&stream).0 as *mut f32,
6125        )?;
6126        Self::gemv_pre_dev(
6127            st,
6128            ws.xb.device_ptr(&stream).0 as *const c_void,
6129            dwsel(
6130                self.dense_fp8,
6131                &stream,
6132                &layer.shared_w[2],
6133                &layer.shared_fp8[2],
6134            ),
6135            sh_inter,
6136            hidden,
6137            ws.sg3.device_ptr_mut(&stream).0 as *mut f32,
6138        )?;
6139        unsafe {
6140            ck(
6141                "swiglu sh dev",
6142                k::memra_dsv4_swiglu(
6143                    dpf!(ws.sg1, &stream),
6144                    dpf!(ws.sg3, &stream),
6145                    dpm!(ws.shbuf, &stream),
6146                    1,
6147                    sh_inter as i32,
6148                    limit,
6149                    std::ptr::null(),
6150                    sp(&stream),
6151                ),
6152            )?;
6153            ck(
6154                "cvt sh dev",
6155                k::memra_dsv4_cvt_bf16(
6156                    dpf!(ws.shbuf, &stream),
6157                    ws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
6158                    sh_inter as i64,
6159                    sp(&stream),
6160                ),
6161            )?;
6162        }
6163        Self::gemv_pre_dev(
6164            st,
6165            ws.shb16.device_ptr(&stream).0 as *const c_void,
6166            dwsel(
6167                self.dense_fp8,
6168                &stream,
6169                &layer.shared_w[1],
6170                &layer.shared_fp8[1],
6171            ),
6172            hidden,
6173            sh_inter,
6174            ws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
6175        )?;
6176        unsafe {
6177            ck(
6178                "add shared dev",
6179                k::memra_dsv4_add_inplace(
6180                    dpm!(ws.y, &stream),
6181                    dpf!(ws.sh_out, &stream),
6182                    hidden as i64,
6183                    sp(&stream),
6184                ),
6185            )?;
6186        }
6187        Ok(())
6188    }
6189
6190    /// Head on the device path: hc_head gate + collapse + trunk norm + vocab dots into
6191    /// ws.logits (dtoh'd by the caller when wanted). head_logits_row's arithmetic with
6192    /// the host sigmoid either kept (host_math) or run as the tiny gate kernel.
6193    fn head_logits_dev(&self, ws: &mut StepWs, host_math: bool) -> Res<()> {
6194        let d = self.model.cfg();
6195        let mc = &self.model.mc;
6196        let hc = d.hc_mult as usize;
6197        let hidden = mc.n_embd as usize;
6198        let eps = mc.rms_eps;
6199        let last = self.stages.len() - 1;
6200        let st = &self.stages[last];
6201        let stream = st.gpu.stream();
6202        let w = hc * hidden;
6203        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
6204        let norm = st.trunk_norm.as_ref().expect("trunk norm");
6205        self.dots_dev(st, &ws.h_a, fn_w, 1, w, hc, &mut ws.head_mixes)?;
6206        unsafe {
6207            ck(
6208                "rowsq head dev",
6209                self.rowsq_scale_arm(
6210                    dpf!(ws.h_a, &stream),
6211                    dpm!(ws.head_mixes, &stream),
6212                    1,
6213                    w as i32,
6214                    hc as i32,
6215                    eps,
6216                    sp(&stream),
6217                ),
6218            )?;
6219        }
6220        if host_math {
6221            let mut mixes_h = dtoh_f32(&stream, &ws.head_mixes)?;
6222            #[allow(clippy::needless_range_loop)]
6223            // allow: the explicit index loop keeps the offset arithmetic visible and aligned with the device-side indexing
6224            for c in 0..hc {
6225                let m = mixes_h[c];
6226                mixes_h[c] =
6227                    sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
6228            }
6229            stream
6230                .memcpy_htod(&mixes_h, &mut ws.head_pre)
6231                .map_err(e("htod head pre"))?;
6232        } else {
6233            unsafe {
6234                ck(
6235                    "hc_head_pre dev",
6236                    k::memra_dsv4_hc_head_pre(
6237                        dpf!(ws.head_mixes, &stream),
6238                        st.hc_head_scale_dev
6239                            .as_ref()
6240                            .expect("head scale dev")
6241                            .device_ptr(&stream)
6242                            .0 as *const f32,
6243                        st.hc_head_base_dev
6244                            .as_ref()
6245                            .expect("head base dev")
6246                            .device_ptr(&stream)
6247                            .0 as *const f32,
6248                        dpm!(ws.head_pre, &stream),
6249                        hc as i32,
6250                        d.hc_eps,
6251                        sp(&stream),
6252                    ),
6253                )?;
6254            }
6255        }
6256        unsafe {
6257            ck(
6258                "hc_collapse head dev",
6259                k::memra_dsv4_hc_collapse(
6260                    dpf!(ws.h_a, &stream),
6261                    dpf!(ws.head_pre, &stream),
6262                    dpm!(ws.collapsed, &stream),
6263                    1,
6264                    hc as i32,
6265                    hidden as i32,
6266                    sp(&stream),
6267                ),
6268            )?;
6269            ck(
6270                "rmsnorm head dev",
6271                self.rmsnorm_arm(
6272                    dpf!(ws.collapsed, &stream),
6273                    dpf!(norm, &stream),
6274                    dpm!(ws.collapsed, &stream),
6275                    1,
6276                    hidden as i32,
6277                    eps,
6278                    sp(&stream),
6279                ),
6280            )?;
6281            let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
6282            if self.dots_f32 {
6283                ck(
6284                    "head dots f32acc dev",
6285                    k::memra_dsv4_dots_f32acc(
6286                        dpf!(ws.collapsed, &stream),
6287                        head_ptr,
6288                        1,
6289                        dpm!(ws.logits, &stream),
6290                        1,
6291                        hidden as i32,
6292                        ws.logits.len() as i32,
6293                        sp(&stream),
6294                    ),
6295                )?;
6296            } else {
6297                ck(
6298                    "head dots dev",
6299                    k::memra_dsv4_dots_f32(
6300                        dpf!(ws.collapsed, &stream),
6301                        head_ptr,
6302                        1,
6303                        dpm!(ws.logits, &stream),
6304                        1,
6305                        hidden as i32,
6306                        ws.logits.len() as i32,
6307                        sp(&stream),
6308                    ),
6309                )?;
6310            }
6311        }
6312        Ok(())
6313    }
6314
6315    /// One device-path decode step. `want_logits` = dtoh the full row (the gates'
6316    /// contract); otherwise the greedy token comes back through the device argmax
6317    /// (4-byte D2H). Exactly one boundary peer copy per crossed stage boundary.
6318    fn decode_step_fast(
6319        &self,
6320        tok: u32,
6321        state: &mut DecodeState,
6322        want_logits: bool,
6323        host_math: bool,
6324    ) -> Res<(Option<Vec<f32>>, u32)> {
6325        self.decode_step_fast_tap(tok, state, want_logits, host_math, None)
6326    }
6327
6328    /// [`Self::decode_step_fast`] with the iteration-3 DSpark trunk tap: when `taps`
6329    /// is Some((buffer, base)), the hc-mean of the post-block hc state at each drafter
6330    /// target layer (40/41/42) is written at buffer[base + k*hidden ..] (concat in
6331    /// target order, M:917-925) — a pure capture; no kernel computes anything
6332    /// differently.
6333    fn decode_step_fast_tap(
6334        &self,
6335        tok: u32,
6336        state: &mut DecodeState,
6337        want_logits: bool,
6338        host_math: bool,
6339        mut taps: Option<(&mut CudaSlice<f32>, usize)>,
6340    ) -> Res<(Option<Vec<f32>>, u32)> {
6341        let mc = &self.model.mc;
6342        let d = self.model.cfg();
6343        let pos = state.pos;
6344        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6345        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6346        let hidden = mc.n_embd as usize;
6347        let hc = d.hc_mult as usize;
6348        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6349        let ws_all = state.ws.as_mut().expect("device path needs StepWs");
6350
6351        // stage 0: token -> embed -> hc state
6352        let st0 = &self.stages[0];
6353        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6354        let stream0 = st0.gpu.stream();
6355        {
6356            let ws0 = &mut ws_all[0];
6357            stream0
6358                .memcpy_htod(&[tok as i32], &mut ws0.tok)
6359                .map_err(e("htod tok"))?;
6360            unsafe {
6361                ck(
6362                    "embed_rows dev",
6363                    k::memra_dsv4_embed_rows(
6364                        st0.embed
6365                            .as_ref()
6366                            .expect("embed on stage 0")
6367                            .device_ptr(&stream0)
6368                            .0 as *const c_void,
6369                        ws0.tok.device_ptr(&stream0).0 as *const i32,
6370                        dpm!(ws0.emb, &stream0),
6371                        1,
6372                        hidden as i32,
6373                        sp(&stream0),
6374                    ),
6375                )?;
6376                ck(
6377                    "repeat_hc dev",
6378                    k::memra_dsv4_repeat_hc(
6379                        dpf!(ws0.emb, &stream0),
6380                        dpm!(ws0.h_a, &stream0),
6381                        1,
6382                        hc as i32,
6383                        hidden as i32,
6384                        sp(&stream0),
6385                    ),
6386                )?;
6387            }
6388        }
6389
6390        let mut cur_stage = 0usize;
6391        let mut input_rx = false;
6392        for il in 0..n_trunk {
6393            let stage = self.layer_stage[il as usize];
6394            if stage != cur_stage {
6395                // boundary: peer-copy h (TX stream) + event; tok for the hash layers
6396                // never crosses (they live on stage 0)
6397                let bytes = hc * hidden * std::mem::size_of::<f32>();
6398                let src_stream = self.stages[cur_stage].gpu.stream();
6399                let dst_stream = self.stages[stage].gpu.stream();
6400                let (ws_src, ws_dst) = ws_all.split_at_mut(stage);
6401                let src_ws = &ws_src[cur_stage];
6402                let dst_ws = &mut ws_dst[0];
6403                self.stages[cur_stage]
6404                    .gpu
6405                    .ctx
6406                    .bind_to_thread()
6407                    .map_err(e("bind tx"))?;
6408                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
6409                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
6410                unsafe {
6411                    cudarc::driver::result::memcpy_peer_async(
6412                        self.stages[stage].gpu.ctx.cu_ctx(),
6413                        dp_,
6414                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
6415                        sp_,
6416                        bytes,
6417                        src_stream.cu_stream(),
6418                    )
6419                    .map_err(e("peer copy h"))?;
6420                }
6421                let bnd = stage - 1;
6422                self.boundary_ev[bnd]
6423                    .record(&src_stream)
6424                    .map_err(e("ev record"))?;
6425                dst_stream
6426                    .wait(&self.boundary_ev[bnd])
6427                    .map_err(e("ev wait"))?;
6428                self.stages[stage]
6429                    .gpu
6430                    .ctx
6431                    .bind_to_thread()
6432                    .map_err(e("bind rx"))?;
6433                cur_stage = stage;
6434                input_rx = true;
6435            }
6436            let st = &self.stages[stage];
6437            let lidx = st
6438                .layers
6439                .iter()
6440                .position(|l| l.il == il)
6441                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6442            self.block_decode_dev(
6443                st,
6444                &st.layers[lidx],
6445                &mut state.caches[il as usize],
6446                &mut ws_all[stage],
6447                input_rx,
6448                pos,
6449                tok,
6450                host_math,
6451            )?;
6452            input_rx = false;
6453            // iteration-3 DSpark tap (capture-only): hc-mean of this layer's output
6454            // hc state into the tap row at the target's concat offset.
6455            if let Some((t, base)) = taps.as_mut()
6456                && let Some(ds) = &self.dspark
6457                && let Some(k) = ds.targets.iter().position(|&tl| tl == il as usize)
6458            {
6459                let stream = self.stages[stage].gpu.stream();
6460                let hidden_i = hidden as i32;
6461                unsafe {
6462                    ck(
6463                        "hc_mean tap dev",
6464                        k::memra_dsv4_hc_mean(
6465                            dpf!(ws_all[stage].h_a, &stream),
6466                            (t.device_ptr_mut(&stream).0 as usize + (*base + k * hidden) * 4)
6467                                as *mut f32,
6468                            1,
6469                            hc as i32,
6470                            hidden_i,
6471                            sp(&stream),
6472                        ),
6473                    )?;
6474                }
6475            }
6476        }
6477
6478        let last = self.stages.len() - 1;
6479        assert_eq!(cur_stage, last, "device path expects the head stage last");
6480        self.head_logits_dev(&mut ws_all[last], host_math)?;
6481        let stream_last = self.stages[last].gpu.stream();
6482        state.pos += 1;
6483        if want_logits {
6484            let logits = dtoh_f32(&stream_last, &ws_all[last].logits)?;
6485            let mut best = 0usize;
6486            for i in 1..logits.len() {
6487                if logits[i] > logits[best] {
6488                    best = i;
6489                }
6490            }
6491            Ok((Some(logits), best as u32))
6492        } else {
6493            unsafe {
6494                ck(
6495                    "argmax dev",
6496                    k::memra_dsv4_argmax(
6497                        dpf!(ws_all[last].logits, &stream_last),
6498                        ws_all[last].logits.len() as i64,
6499                        ws_all[last].argmax.device_ptr_mut(&stream_last).0 as *mut i32,
6500                        sp(&stream_last),
6501                    ),
6502                )?;
6503            }
6504            let mut out = [0i32; 1];
6505            stream_last
6506                .memcpy_dtoh(&ws_all[last].argmax, &mut out[..])
6507                .map_err(e("dtoh argmax"))?;
6508            stream_last.synchronize().map_err(e("sync argmax"))?;
6509            Ok((None, out[0] as u32))
6510        }
6511    }
6512
6513    /// Greedy decode step (bench serving shape): returns ONLY the next token; on the
6514    /// device path the argmax runs on-device and 4 bytes cross back. Legacy path
6515    /// falls back to the full-logits step + host argmax (same value by the argmax
6516    /// tie-rule equivalence).
6517    pub fn decode_step_greedy(&self, tok: u32, state: &mut DecodeState) -> Res<u32> {
6518        match self.decode_path {
6519            DecodePath::Legacy => {
6520                let logits = self.decode_step_impl(tok, state, None)?;
6521                let mut best = 0usize;
6522                for i in 1..logits.len() {
6523                    if logits[i] > logits[best] {
6524                        best = i;
6525                    }
6526                }
6527                Ok(best as u32)
6528            }
6529            DecodePath::Device { host_math } => {
6530                Ok(self.decode_step_fast(tok, state, false, host_math)?.1)
6531            }
6532        }
6533    }
6534
6535    fn decode_step_impl(
6536        &self,
6537        tok: u32,
6538        state: &mut DecodeState,
6539        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
6540    ) -> Res<Vec<f32>> {
6541        if let DecodePath::Device { host_math } = self.decode_path {
6542            assert!(
6543                dump.is_none(),
6544                "decode_step_probe is a legacy-path diagnostic (set MEMRA_DSV4_DECODE_PATH=legacy)"
6545            );
6546            let (logits, _) = self.decode_step_fast(tok, state, true, host_math)?;
6547            return Ok(logits.expect("want_logits"));
6548        }
6549        let mc = &self.model.mc;
6550        let d = self.model.cfg();
6551        let pos = state.pos;
6552        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6553        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6554        let hidden = mc.n_embd as usize;
6555        let hc = d.hc_mult as usize;
6556        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6557
6558        // stage 0: embed row -> hc state
6559        let st0 = &self.stages[0];
6560        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6561        let stream0 = st0.gpu.stream();
6562        let ids_dev = upload_i32(&stream0, &[tok as i32])?;
6563        let mut emb = stream0.alloc_zeros::<f32>(hidden).map_err(e("emb"))?;
6564        unsafe {
6565            ck(
6566                "embed_rows",
6567                k::memra_dsv4_embed_rows(
6568                    st0.embed
6569                        .as_ref()
6570                        .expect("embed on stage 0")
6571                        .device_ptr(&stream0)
6572                        .0 as *const c_void,
6573                    ids_dev.device_ptr(&stream0).0 as *const i32,
6574                    dpm!(emb, &stream0),
6575                    1,
6576                    hidden as i32,
6577                    sp(&stream0),
6578                ),
6579            )?;
6580        }
6581        let mut h = stream0.alloc_zeros::<f32>(hc * hidden).map_err(e("h0"))?;
6582        unsafe {
6583            ck(
6584                "repeat_hc",
6585                k::memra_dsv4_repeat_hc(
6586                    dpf!(emb, &stream0),
6587                    dpm!(h, &stream0),
6588                    1,
6589                    hc as i32,
6590                    hidden as i32,
6591                    sp(&stream0),
6592                ),
6593            )?;
6594        }
6595
6596        let mut cur_stage = 0usize;
6597        for il in 0..n_trunk {
6598            let stage = self.layer_stage[il as usize];
6599            if stage != cur_stage {
6600                let src_stream = self.stages[cur_stage].gpu.stream();
6601                let host = dtoh_f32(&src_stream, &h)?;
6602                let dst_stream = self.stages[stage].gpu.stream();
6603                self.stages[stage]
6604                    .gpu
6605                    .ctx
6606                    .bind_to_thread()
6607                    .map_err(e("bind"))?;
6608                h = upload_f32(&dst_stream, &host)?;
6609                cur_stage = stage;
6610            }
6611            let st = &self.stages[stage];
6612            let lidx = st
6613                .layers
6614                .iter()
6615                .position(|l| l.il == il)
6616                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6617            h = self.block_decode(
6618                st,
6619                &st.layers[lidx],
6620                &mut state.caches[il as usize],
6621                &h,
6622                pos,
6623                tok,
6624                dump.as_deref_mut(),
6625            )?;
6626        }
6627
6628        let last = self.stages.len() - 1;
6629        if cur_stage != last {
6630            let src_stream = self.stages[cur_stage].gpu.stream();
6631            let host = dtoh_f32(&src_stream, &h)?;
6632            let dst_stream = self.stages[last].gpu.stream();
6633            h = upload_f32(&dst_stream, &host)?;
6634        }
6635        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
6636        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
6637        let logits = self.head_logits_from(
6638            &h,
6639            1,
6640            hc_head_fn,
6641            &self.hc_head_base,
6642            &self.hc_head_scale,
6643            trunk_norm,
6644        )?;
6645        state.pos += 1;
6646        Ok(logits)
6647    }
6648}
6649
6650// ================================================================ iteration 3: DSpark drafter (device)
6651//
6652// Semantic law: DSPARK-SEMANTICS.md (M-cites); numeric truth: the lane-10 CPU oracle
6653// (memra_gguf::dsv4_dspark) — every gate compares against its fixtures/trajectory.
6654// Realization: the PREFILL-class helpers (Self::hc_pre host-Sinkhorn, cuBLASLt bf16
6655// gemm, moe_forward bf16-dequant experts, prefill sink_attn) at s = block_size —
6656// the lane-4-gated numeric class; the drafter's arena/native-expert perf rungs are
6657// banked follow-ups, never correctness requirements.
6658impl Dsv4Gpu {
6659    fn dspark(&self) -> &DsparkDev {
6660        self.dspark
6661            .as_ref()
6662            .expect("MEMRA_DSV4_DRAFTER=dspark not loaded")
6663    }
6664
6665    /// The drafter's exit-head island dots, HOISTED across the block's rows (weight row
6666    /// read once instead of once per row) with the rung-4c arm selection. The f64 branch
6667    /// is BIT-EXACT vs the pinned `Self::dots` — identical per-(t, j) element order and
6668    /// reduction tree — so the default arm's bytes are unchanged by the hoist; the f32x
6669    /// branch is the measured fork (`MEMRA_DSV4_DSPARK_HEAD_ARM=f32x`) offered for owner
6670    /// ratification. Either way this touches only WHICH tokens are drafted: verification
6671    /// always emits the trunk's own argmax, so the emitted stream cannot depend on it.
6672    #[allow(clippy::too_many_arguments)]
6673    fn dspark_head_dots(
6674        &self,
6675        st: &Stage,
6676        x: *const f32,
6677        w: *const c_void,
6678        w_is_bf16: i32,
6679        s: usize,
6680        kdim: usize,
6681        n: usize,
6682        y: *mut f32,
6683    ) -> Res<()> {
6684        let stream = st.gpu.stream();
6685        unsafe {
6686            if self.dspark_head_f32 {
6687                ck(
6688                    "dspark head dots f32acc_mrow",
6689                    k::memra_dsv4_dots_f32acc_mrow(
6690                        x,
6691                        w,
6692                        w_is_bf16,
6693                        y,
6694                        s as i32,
6695                        kdim as i32,
6696                        n as i32,
6697                        sp(&stream),
6698                    ),
6699                )
6700            } else {
6701                ck(
6702                    "dspark head dots f32_mrow",
6703                    k::memra_dsv4_dots_f32_mrow(
6704                        x,
6705                        w,
6706                        w_is_bf16,
6707                        y,
6708                        s as i32,
6709                        kdim as i32,
6710                        n as i32,
6711                        sp(&stream),
6712                    ),
6713                )
6714            }
6715        }
6716    }
6717
6718    /// Allocate the drafter decode state on the last stage: 3 rings [win + block, hd]
6719    /// (ring + transient draft rows, struct doc) + the tap rows [block+1, n_t*hidden].
6720    pub fn dspark_alloc_state(&self) -> Res<DsparkState> {
6721        let ds = self.dspark();
6722        let d = self.model.cfg();
6723        let hd = d.head_dim as usize;
6724        let win = d.sliding_window as usize;
6725        let hidden = self.model.mc.n_embd as usize;
6726        let last = self.stages.len() - 1;
6727        let stream = self.stages[last].gpu.stream();
6728        let mut rings = Vec::with_capacity(ds.blocks.len());
6729        for _ in 0..ds.blocks.len() {
6730            rings.push(
6731                stream
6732                    .alloc_zeros::<f32>((win + ds.block_size) * hd)
6733                    .map_err(e("dspark ring"))?,
6734            );
6735        }
6736        let taps = stream
6737            .alloc_zeros::<f32>((ds.block_size + 1) * ds.targets.len() * hidden)
6738            .map_err(e("dspark taps"))?;
6739        Ok(DsparkState { rings, taps })
6740    }
6741
6742    /// main_x = main_norm(main_proj(main_hidden)) (M:853), s rows on the last stage.
6743    fn dspark_main_x(&self, main_hidden: &CudaSlice<f32>, s: usize) -> Res<CudaSlice<f32>> {
6744        let ds = self.dspark();
6745        let hidden = self.model.mc.n_embd as usize;
6746        let k = ds.targets.len() * hidden;
6747        let last = self.stages.len() - 1;
6748        let st = &self.stages[last];
6749        let stream = st.gpu.stream();
6750        let mut mx = stream.alloc_zeros::<f32>(s * hidden).map_err(e("main_x"))?;
6751        Self::gemm(st, main_hidden, &ds.main_proj, 0, s, hidden, k, &mut mx)?;
6752        unsafe {
6753            ck(
6754                "rmsnorm main_x",
6755                k::memra_dsv4_rmsnorm(
6756                    dpf!(mx, &stream),
6757                    dpf!(ds.main_norm, &stream),
6758                    dpm!(mx, &stream),
6759                    s as i32,
6760                    hidden as i32,
6761                    self.model.mc.rms_eps,
6762                    sp(&stream),
6763                ),
6764            )?;
6765        }
6766        Ok(mx)
6767    }
6768
6769    /// Per-block main_kv rows (M:758-761): kv_norm(wkv(main_x)) + rope(REAL positions)
6770    /// + group-64 FP8 QAT on the nope dims. Returns [s, hd] on the last stage.
6771    fn dspark_main_kv(
6772        &self,
6773        blk: &LayerDev,
6774        main_x: &CudaSlice<f32>,
6775        s: usize,
6776        positions: &[i32],
6777    ) -> Res<CudaSlice<f32>> {
6778        let d = self.model.cfg();
6779        let hd = d.head_dim as usize;
6780        let rd = d.qk_rope_head_dim as usize;
6781        let hidden = self.model.mc.n_embd as usize;
6782        let eps = self.model.mc.rms_eps;
6783        let last = self.stages.len() - 1;
6784        let st = &self.stages[last];
6785        let stream = st.gpu.stream();
6786        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
6787        // item 3: `.dev()` — the drafter blocks carry no fp8 twins this rung, so
6788        // their bf16 slabs are always device-resident; if a future rung stages them,
6789        // this must fail loudly rather than pay a per-round upload silently.
6790        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("dspark kv"))?;
6791        Self::gemm(st, main_x, blk.wkv.dev(), 0, s, hd, hidden, &mut kv)?;
6792        let pos_dev = upload_i32(&stream, positions)?;
6793        unsafe {
6794            ck(
6795                "rmsnorm dspark kv",
6796                k::memra_dsv4_rmsnorm(
6797                    dpf!(kv, &stream),
6798                    dpf!(blk.kv_norm, &stream),
6799                    dpm!(kv, &stream),
6800                    s as i32,
6801                    hd as i32,
6802                    eps,
6803                    sp(&stream),
6804                ),
6805            )?;
6806            ck(
6807                "rope dspark kv",
6808                k::memra_dsv4_rope(
6809                    dpm!(kv, &stream),
6810                    s as i32,
6811                    1,
6812                    hd as i32,
6813                    rd as i32,
6814                    dpf!(st.fc_plain, &stream),
6815                    pos_dev.device_ptr(&stream).0 as *const i32,
6816                    0,
6817                    sp(&stream),
6818                ),
6819            )?;
6820            ck(
6821                "act_quant dspark kv",
6822                k::memra_dsv4_act_quant(
6823                    dpm!(kv, &stream),
6824                    s as i32,
6825                    hd as i64,
6826                    (hd - rd) as i32,
6827                    64,
6828                    clamp_only,
6829                    sp(&stream),
6830                ),
6831            )?;
6832        }
6833        Ok(kv)
6834    }
6835
6836    /// Prefill ring priming (M:763-769): last min(s, win) positions land at slot
6837    /// p % win. `main_hidden` = [s, n_t*hidden] tap rows from the prefill.
6838    pub fn dspark_prime_prefill(
6839        &self,
6840        state: &mut DsparkState,
6841        main_hidden: &CudaSlice<f32>,
6842        s: usize,
6843    ) -> Res<()> {
6844        let d = self.model.cfg();
6845        let hd = d.head_dim as usize;
6846        let win = d.sliding_window as usize;
6847        let last = self.stages.len() - 1;
6848        let stream = self.stages[last].gpu.stream();
6849        let mx = self.dspark_main_x(main_hidden, s)?;
6850        let positions: Vec<i32> = (0..s as i32).collect();
6851        let n_blocks = self.dspark().blocks.len();
6852        for bi in 0..n_blocks {
6853            let blk = &self.dspark().blocks[bi];
6854            let kv = self.dspark_main_kv(blk, &mx, s, &positions)?;
6855            for p in s.saturating_sub(win)..s {
6856                let slot = p % win;
6857                let src = kv.slice(p * hd..(p + 1) * hd);
6858                let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
6859                stream
6860                    .memcpy_dtod(&src, &mut dst)
6861                    .map_err(e("prime ring"))?;
6862            }
6863        }
6864        Ok(())
6865    }
6866
6867    /// Trunk prefill + DSpark ring prime in ONE pass — the device twin of the CPU
6868    /// oracle's `trunk.forward(&seq[..p0], 0)` + `dspark.prime_prefill(&pre.main_hidden,
6869    /// p0)` pair (dsv4_dspark_gate components mode).
6870    ///
6871    /// The prefill taps come from the existing `GpuCapture::layer_out` hook (the target
6872    /// layers' full hc state `[s, hc, hidden]`), then run through the SAME
6873    /// `memra_dsv4_hc_mean` kernel the decode tap uses — prefill and decode taps must
6874    /// not be two numeric realizations of one tap — and are placed at the target's
6875    /// concat stride with `place_cols`, reproducing the oracle's
6876    /// `main_hidden[(p*n_t + k)*hidden ..]` layout exactly.
6877    pub fn dspark_prefill_prime(
6878        &self,
6879        ids: &[u32],
6880        state: &mut DecodeState,
6881        dstate: &mut DsparkState,
6882    ) -> Res<ForwardOut> {
6883        assert_eq!(
6884            state.pos, 0,
6885            "dspark_prefill_prime needs a fresh DecodeState"
6886        );
6887        assert!(!ids.is_empty(), "empty prompt");
6888        let hidden = self.model.mc.n_embd as usize;
6889        let hc = self.model.cfg().hc_mult as usize;
6890        let s = ids.len();
6891        let targets = self.dspark().targets.clone();
6892        let n_t = targets.len();
6893        let mut cap = GpuCapture {
6894            want: targets.iter().map(|&t| t as u32).collect(),
6895            ..Default::default()
6896        };
6897        let out = self
6898            .forward_impl(ids, Some(&mut cap), None, Some(state))?
6899            .expect("prefill logits");
6900        state.pos = s;
6901
6902        let last = self.stages.len() - 1;
6903        let stream = self.stages[last].gpu.stream();
6904        self.stages[last]
6905            .gpu
6906            .ctx
6907            .bind_to_thread()
6908            .map_err(e("bind ctx prime"))?;
6909        let mut main_hidden = stream
6910            .alloc_zeros::<f32>(s * n_t * hidden)
6911            .map_err(e("prefill main_hidden"))?;
6912        let mut tmp = stream
6913            .alloc_zeros::<f32>(s * hidden)
6914            .map_err(e("tap tmp"))?;
6915        for (k, &il) in targets.iter().enumerate() {
6916            let h = cap
6917                .layer_out
6918                .get(&(il as u32))
6919                .unwrap_or_else(|| panic!("prefill capture missing target layer {il}"));
6920            assert_eq!(
6921                h.len(),
6922                s * hc * hidden,
6923                "target layer {il} capture is not [s, hc, hidden]"
6924            );
6925            let h_dev = upload_f32(&stream, h)?;
6926            unsafe {
6927                ck(
6928                    "hc_mean prefill tap",
6929                    k::memra_dsv4_hc_mean(
6930                        dpf!(h_dev, &stream),
6931                        dpm!(tmp, &stream),
6932                        s as i32,
6933                        hc as i32,
6934                        hidden as i32,
6935                        sp(&stream),
6936                    ),
6937                )?;
6938                ck(
6939                    "place_cols prefill tap",
6940                    k::memra_dsv4_place_cols(
6941                        dpf!(tmp, &stream),
6942                        dpm!(main_hidden, &stream),
6943                        s as i32,
6944                        hidden as i32,
6945                        (n_t * hidden) as i64,
6946                        (k * hidden) as i64,
6947                        sp(&stream),
6948                    ),
6949                )?;
6950            }
6951        }
6952        self.dspark_prime_prefill(dstate, &main_hidden, s)?;
6953        // Seed taps row 0 with the LAST prefill position's tap: the generic spec loop's
6954        // first proposal is `propose(t, mh_last, p0-1)` with mh_last = pre_taps row
6955        // p0-1 (spec_oracle::run_spec_greedy) — without this the first round would draft
6956        // off a zeroed tap.
6957        {
6958            let src = main_hidden.slice((s - 1) * n_t * hidden..s * n_t * hidden);
6959            let mut dst = dstate.taps.slice_mut(0..n_t * hidden);
6960            stream
6961                .memcpy_dtod(&src, &mut dst)
6962                .map_err(e("seed tap row"))?;
6963        }
6964        stream.synchronize().map_err(e("prime sync"))?;
6965        Ok(out)
6966    }
6967
6968    /// Ring advance for ONE committed position (§3.1 drafter rule: accepted positions
6969    /// only). `tap_row` indexes into `state.taps` (the row that holds position `pos`'s
6970    /// hc-mean concat).
6971    pub fn dspark_write_rings(
6972        &self,
6973        state: &mut DsparkState,
6974        tap_row: usize,
6975        pos: usize,
6976    ) -> Res<()> {
6977        let d = self.model.cfg();
6978        let hd = d.head_dim as usize;
6979        let win = d.sliding_window as usize;
6980        let hidden = self.model.mc.n_embd as usize;
6981        let n_t = self.dspark().targets.len();
6982        let last = self.stages.len() - 1;
6983        let stream = self.stages[last].gpu.stream();
6984        let tap = {
6985            // one-row view as an owned slice copy (gemm wants a base slice)
6986            let mut row = stream
6987                .alloc_zeros::<f32>(n_t * hidden)
6988                .map_err(e("tap row"))?;
6989            let src = state
6990                .taps
6991                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
6992            stream.memcpy_dtod(&src, &mut row).map_err(e("tap copy"))?;
6993            row
6994        };
6995        let mx = self.dspark_main_x(&tap, 1)?;
6996        let n_blocks = self.dspark().blocks.len();
6997        for bi in 0..n_blocks {
6998            let blk = &self.dspark().blocks[bi];
6999            let kv = self.dspark_main_kv(blk, &mx, 1, &[pos as i32])?;
7000            let slot = pos % win;
7001            let src = kv.slice(0..hd);
7002            let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
7003            stream
7004                .memcpy_dtod(&src, &mut dst)
7005                .map_err(e("ring write"))?;
7006        }
7007        Ok(())
7008    }
7009
7010    /// One DSpark draft-block forward (M:695-707 body with DSparkAttention M:771-792):
7011    /// h [block, hc, hidden] -> same shape. Reads the ring; writes ONLY the transient
7012    /// draft-kv rows [win, win+block) of `ring` (never persistent ring slots).
7013    #[allow(clippy::too_many_arguments)]
7014    fn dspark_block_forward(
7015        &self,
7016        blk: &LayerDev,
7017        ring: &mut CudaSlice<f32>,
7018        h: &CudaSlice<f32>,
7019        block: usize,
7020        pos: usize,
7021    ) -> Res<CudaSlice<f32>> {
7022        let d = self.model.cfg();
7023        let mc = &self.model.mc;
7024        let hc = d.hc_mult as usize;
7025        let hidden = mc.n_embd as usize;
7026        let heads = mc.n_head as usize;
7027        let hd = d.head_dim as usize;
7028        let rd = d.qk_rope_head_dim as usize;
7029        let q_lora = d.q_lora_rank as usize;
7030        let win = d.sliding_window as usize;
7031        let o_groups = d.o_groups as usize;
7032        let o_lora = d.o_lora_rank as usize;
7033        let eps = mc.rms_eps;
7034        let iters = d.hc_sinkhorn_iters;
7035        let hc_eps = d.hc_eps;
7036        let last = self.stages.len() - 1;
7037        let st = &self.stages[last];
7038        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx dspark"))?;
7039        let stream = st.gpu.stream();
7040        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
7041        // draft positions pos+1 .. pos+block (M:772)
7042        let positions: Vec<i32> = (1..=block as i32).map(|j| pos as i32 + j).collect();
7043        let pos_dev = upload_i32(&stream, &positions)?;
7044
7045        // ---- attention sub-block
7046        let (y, post, comb) = Self::hc_pre(
7047            st,
7048            h,
7049            &blk.hc_attn_fn,
7050            &blk.hc_attn_base,
7051            &blk.hc_attn_scale,
7052            block,
7053            hc,
7054            hidden,
7055            iters,
7056            hc_eps,
7057        )?;
7058        let mut x = stream.alloc_zeros::<f32>(block * hidden).map_err(e("x"))?;
7059        unsafe {
7060            ck(
7061                "rmsnorm dspark attn",
7062                k::memra_dsv4_rmsnorm(
7063                    dpf!(y, &stream),
7064                    dpf!(blk.attn_norm, &stream),
7065                    dpm!(x, &stream),
7066                    block as i32,
7067                    hidden as i32,
7068                    eps,
7069                    sp(&stream),
7070                ),
7071            )?;
7072        }
7073        // q path (trunk-identical, M:774-777)
7074        let mut qr = stream.alloc_zeros::<f32>(block * q_lora).map_err(e("qr"))?;
7075        Self::gemm(st, &x, blk.wq_a.dev(), 0, block, q_lora, hidden, &mut qr)?;
7076        unsafe {
7077            ck(
7078                "rmsnorm dspark q",
7079                k::memra_dsv4_rmsnorm(
7080                    dpf!(qr, &stream),
7081                    dpf!(blk.q_norm, &stream),
7082                    dpm!(qr, &stream),
7083                    block as i32,
7084                    q_lora as i32,
7085                    eps,
7086                    sp(&stream),
7087                ),
7088            )?;
7089        }
7090        let mut qr_b = stream
7091            .alloc_zeros::<u8>(block * q_lora * 2)
7092            .map_err(e("qr_b"))?;
7093        unsafe {
7094            ck(
7095                "cvt dspark qr",
7096                k::memra_dsv4_cvt_bf16(
7097                    dpf!(qr, &stream),
7098                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
7099                    (block * q_lora) as i64,
7100                    sp(&stream),
7101                ),
7102            )?;
7103        }
7104        let mut q = stream
7105            .alloc_zeros::<f32>(block * heads * hd)
7106            .map_err(e("q"))?;
7107        Self::gemm_pre(
7108            st,
7109            &qr_b,
7110            blk.wq_b.dev().device_ptr(&stream).0 as *const c_void,
7111            block,
7112            heads * hd,
7113            q_lora,
7114            &mut q,
7115        )?;
7116        unsafe {
7117            ck(
7118                "headrms dspark",
7119                k::memra_dsv4_headrms(
7120                    dpm!(q, &stream),
7121                    (block * heads) as i32,
7122                    hd as i32,
7123                    eps,
7124                    sp(&stream),
7125                ),
7126            )?;
7127            ck(
7128                "rope dspark q",
7129                k::memra_dsv4_rope(
7130                    dpm!(q, &stream),
7131                    block as i32,
7132                    heads as i32,
7133                    hd as i32,
7134                    rd as i32,
7135                    dpf!(st.fc_plain, &stream),
7136                    pos_dev.device_ptr(&stream).0 as *const i32,
7137                    0,
7138                    sp(&stream),
7139                ),
7140            )?;
7141        }
7142        // draft kv (M:778-780) -> transient rows [win, win+block) of the ring buffer
7143        {
7144            let mut kv = stream.alloc_zeros::<f32>(block * hd).map_err(e("dkv"))?;
7145            Self::gemm(st, &x, blk.wkv.dev(), 0, block, hd, hidden, &mut kv)?;
7146            unsafe {
7147                ck(
7148                    "rmsnorm dspark dkv",
7149                    k::memra_dsv4_rmsnorm(
7150                        dpf!(kv, &stream),
7151                        dpf!(blk.kv_norm, &stream),
7152                        dpm!(kv, &stream),
7153                        block as i32,
7154                        hd as i32,
7155                        eps,
7156                        sp(&stream),
7157                    ),
7158                )?;
7159                ck(
7160                    "rope dspark dkv",
7161                    k::memra_dsv4_rope(
7162                        dpm!(kv, &stream),
7163                        block as i32,
7164                        1,
7165                        hd as i32,
7166                        rd as i32,
7167                        dpf!(st.fc_plain, &stream),
7168                        pos_dev.device_ptr(&stream).0 as *const i32,
7169                        0,
7170                        sp(&stream),
7171                    ),
7172                )?;
7173                ck(
7174                    "act_quant dspark dkv",
7175                    k::memra_dsv4_act_quant(
7176                        dpm!(kv, &stream),
7177                        block as i32,
7178                        hd as i64,
7179                        (hd - rd) as i32,
7180                        64,
7181                        clamp_only,
7182                        sp(&stream),
7183                    ),
7184                )?;
7185            }
7186            let src = kv.slice(0..block * hd);
7187            let mut dst = ring.slice_mut(win * hd..(win + block) * hd);
7188            stream.memcpy_dtod(&src, &mut dst).map_err(e("draft kv"))?;
7189        }
7190        // attention set (M:743-747): ring slots 0..min(win, pos+1) then the block's
7191        // transient rows — ONE shared row for every draft query (bidirectional
7192        // intra-block attention), replicated per query for the prefill kernel.
7193        let n_ring = win.min(pos + 1);
7194        let mut idx_row: Vec<i32> = (0..n_ring as i32).collect();
7195        idx_row.extend((0..block as i32).map(|j| win as i32 + j));
7196        let slots = idx_row.len();
7197        let mut idxs = Vec::with_capacity(block * slots);
7198        for _ in 0..block {
7199            idxs.extend_from_slice(&idx_row);
7200        }
7201        let idx_dev = upload_i32(&stream, &idxs)?;
7202        let mut o = stream
7203            .alloc_zeros::<f32>(block * heads * hd)
7204            .map_err(e("o"))?;
7205        let scale = (hd as f64).powf(-0.5) as f32;
7206        unsafe {
7207            ck(
7208                "sink_attn dspark",
7209                k::memra_dsv4_sink_attn(
7210                    dpf!(q, &stream),
7211                    dpf!(ring, &stream),
7212                    idx_dev.device_ptr(&stream).0 as *const i32,
7213                    dpf!(blk.sink, &stream),
7214                    dpm!(o, &stream),
7215                    block as i32,
7216                    heads as i32,
7217                    hd as i32,
7218                    slots as i32,
7219                    scale,
7220                    sp(&stream),
7221                ),
7222            )?;
7223            ck(
7224                "rope dspark o inv",
7225                k::memra_dsv4_rope(
7226                    dpm!(o, &stream),
7227                    block as i32,
7228                    heads as i32,
7229                    hd as i32,
7230                    rd as i32,
7231                    dpf!(st.fc_plain, &stream),
7232                    pos_dev.device_ptr(&stream).0 as *const i32,
7233                    1,
7234                    sp(&stream),
7235                ),
7236            )?;
7237        }
7238        // grouped wo (trunk-identical)
7239        let gw = heads / o_groups * hd;
7240        let mut og = stream
7241            .alloc_zeros::<f32>(block * o_groups * o_lora)
7242            .map_err(e("og"))?;
7243        let mut o_grp = stream.alloc_zeros::<f32>(block * gw).map_err(e("o_grp"))?;
7244        let mut y_grp = stream
7245            .alloc_zeros::<f32>(block * o_lora)
7246            .map_err(e("y_grp"))?;
7247        for g in 0..o_groups {
7248            unsafe {
7249                ck(
7250                    "take_cols dspark",
7251                    k::memra_dsv4_take_cols(
7252                        dpf!(o, &stream),
7253                        dpm!(o_grp, &stream),
7254                        block as i32,
7255                        gw as i32,
7256                        (heads * hd) as i64,
7257                        (g * gw) as i64,
7258                        sp(&stream),
7259                    ),
7260                )?;
7261            }
7262            Self::gemm(
7263                st,
7264                &o_grp,
7265                blk.wo_a.dev(),
7266                g * o_lora * gw,
7267                block,
7268                o_lora,
7269                gw,
7270                &mut y_grp,
7271            )?;
7272            unsafe {
7273                ck(
7274                    "place_cols dspark",
7275                    k::memra_dsv4_place_cols(
7276                        dpf!(y_grp, &stream),
7277                        dpm!(og, &stream),
7278                        block as i32,
7279                        o_lora as i32,
7280                        (o_groups * o_lora) as i64,
7281                        (g * o_lora) as i64,
7282                        sp(&stream),
7283                    ),
7284                )?;
7285            }
7286        }
7287        let mut attn_out = stream.alloc_zeros::<f32>(block * hidden).map_err(e("ao"))?;
7288        Self::gemm(
7289            st,
7290            &og,
7291            blk.wo_b.dev(),
7292            0,
7293            block,
7294            hidden,
7295            o_groups * o_lora,
7296            &mut attn_out,
7297        )?;
7298        let mut h2 = stream
7299            .alloc_zeros::<f32>(block * hc * hidden)
7300            .map_err(e("h2"))?;
7301        unsafe {
7302            ck(
7303                "hc_post dspark attn",
7304                k::memra_dsv4_hc_post(
7305                    dpf!(attn_out, &stream),
7306                    dpf!(h, &stream),
7307                    dpf!(post, &stream),
7308                    dpf!(comb, &stream),
7309                    dpm!(h2, &stream),
7310                    block as i32,
7311                    hc as i32,
7312                    hidden as i32,
7313                    sp(&stream),
7314                ),
7315            )?;
7316        }
7317        // ---- ffn sub-block (score-routed MoE; ids unused by a non-hash gate)
7318        let (y2, post2, comb2) = Self::hc_pre(
7319            st,
7320            &h2,
7321            &blk.hc_ffn_fn,
7322            &blk.hc_ffn_base,
7323            &blk.hc_ffn_scale,
7324            block,
7325            hc,
7326            hidden,
7327            iters,
7328            hc_eps,
7329        )?;
7330        let mut xf = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xf"))?;
7331        unsafe {
7332            ck(
7333                "rmsnorm dspark ffn",
7334                k::memra_dsv4_rmsnorm(
7335                    dpf!(y2, &stream),
7336                    dpf!(blk.ffn_norm, &stream),
7337                    dpm!(xf, &stream),
7338                    block as i32,
7339                    hidden as i32,
7340                    eps,
7341                    sp(&stream),
7342                ),
7343            )?;
7344        }
7345        let ids = vec![0u32; block];
7346        let moe_out = self.moe_forward(st, blk, &xf, block, &ids)?;
7347        let mut h3 = stream
7348            .alloc_zeros::<f32>(block * hc * hidden)
7349            .map_err(e("h3"))?;
7350        unsafe {
7351            ck(
7352                "hc_post dspark ffn",
7353                k::memra_dsv4_hc_post(
7354                    dpf!(moe_out, &stream),
7355                    dpf!(h2, &stream),
7356                    dpf!(post2, &stream),
7357                    dpf!(comb2, &stream),
7358                    dpm!(h3, &stream),
7359                    block as i32,
7360                    hc as i32,
7361                    hidden as i32,
7362                    sp(&stream),
7363                ),
7364            )?;
7365        }
7366        Ok(h3)
7367    }
7368
7369    /// forward_spec (M:928-936) + forward_head (M:860-874) on the device: ONE parallel
7370    /// noise-block draft through the 3 blocks, shared trunk head over all block rows,
7371    /// sequential rank-256 markov chaining (greedy), fp32 confidence. Mutates ONLY the
7372    /// rings' transient rows (drafting is side-effect-free on trunk + persistent ring
7373    /// state — §3.1). `tap_row` = the taps row holding position `pos`'s hc-mean concat.
7374    pub fn dspark_forward_spec(
7375        &self,
7376        state: &mut DsparkState,
7377        input_token: u32,
7378        tap_row: usize,
7379        pos: usize,
7380        capture: bool,
7381    ) -> Res<DsparkProposal> {
7382        let ds = self.dspark();
7383        let mc = &self.model.mc;
7384        let d = self.model.cfg();
7385        let hc = d.hc_mult as usize;
7386        let hidden = mc.n_embd as usize;
7387        let eps = mc.rms_eps;
7388        let block = ds.block_size;
7389        let rank = ds.rank;
7390        let vocab = ds.vocab;
7391        let n_t = ds.targets.len();
7392        let last = self.stages.len() - 1;
7393        let st = &self.stages[last];
7394        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx spec"))?;
7395        let stream = st.gpu.stream();
7396
7397        let prof = if dsv4_prof_on() {
7398            Some(stream.clone())
7399        } else {
7400            None
7401        };
7402        // main_x from the tap row (computed once per call, M:930-932)
7403        let tap = {
7404            let _p = phase!("1a.tap_copy", prof.as_ref());
7405            let mut row = stream.alloc_zeros::<f32>(n_t * hidden).map_err(e("tapr"))?;
7406            let src = state
7407                .taps
7408                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
7409            stream.memcpy_dtod(&src, &mut row).map_err(e("tap cp"))?;
7410            row
7411        };
7412        let mx = {
7413            let _p = phase!("1b.main_x", prof.as_ref());
7414            self.dspark_main_x(&tap, 1)?
7415        };
7416        let (cap_main_hidden, cap_main_x) = if capture {
7417            (
7418                Some(dtoh_f32(&stream, &tap)?),
7419                Some(dtoh_f32(&stream, &mx)?),
7420            )
7421        } else {
7422            (None, None)
7423        };
7424        // draft block ids: [input token, noise ×(block-1)] via the SHARED trunk embed
7425        // (host-gathered — the MtpDev precedent)
7426        let _p_embed = phase!("1c.embed_h2d_repeat", prof.as_ref());
7427        let mut draft_ids = vec![ds.noise_token; block];
7428        draft_ids[0] = input_token;
7429        let e_rows = self.model.embed_rows(&draft_ids);
7430        let e_dev = upload_f32(&stream, &e_rows)?;
7431        let mut h = stream
7432            .alloc_zeros::<f32>(block * hc * hidden)
7433            .map_err(e("h0"))?;
7434        unsafe {
7435            ck(
7436                "repeat_hc dspark",
7437                k::memra_dsv4_repeat_hc(
7438                    dpf!(e_dev, &stream),
7439                    dpm!(h, &stream),
7440                    block as i32,
7441                    hc as i32,
7442                    hidden as i32,
7443                    sp(&stream),
7444                ),
7445            )?;
7446        }
7447        drop(_p_embed);
7448        let mut block_outs: Vec<Vec<f32>> = Vec::new();
7449        let _p_blocks = phase!("1d.drafter_blocks", prof.as_ref());
7450        let n_blocks = ds.blocks.len();
7451        for bi in 0..n_blocks {
7452            // rings[bi] transient rows are rewritten; persistent slots untouched
7453            let mut ring = std::mem::replace(
7454                &mut state.rings[bi],
7455                stream.alloc_zeros::<f32>(0).map_err(e("swap"))?,
7456            );
7457            let out =
7458                self.dspark_block_forward(&self.dspark().blocks[bi], &mut ring, &h, block, pos);
7459            state.rings[bi] = ring;
7460            h = out?;
7461            if capture {
7462                block_outs.push(dtoh_f32(&stream, &h)?);
7463            }
7464        }
7465        drop(_p_blocks);
7466        // exit head (mtp.2): pre-only hc collapse -> xc (pre-norm, feeds confidence),
7467        // norm, shared trunk head over ALL block rows
7468        let w = hc * hidden;
7469        let _p_mix = phase!("1e.exit_mix_dots", prof.as_ref());
7470        let mut mixes = stream.alloc_zeros::<f32>(block * hc).map_err(e("mx"))?;
7471        // hoisted (weight row read once across the block's rows). The f64 twin is
7472        // BIT-EXACT vs `Self::dots` — same per-(t, j) element order and reduction tree —
7473        // so the default arm's values are untouched by the hoist.
7474        self.dspark_head_dots(
7475            st,
7476            h.device_ptr(&stream).0 as *const f32,
7477            ds.hc_head_fn.device_ptr(&stream).0 as *const c_void,
7478            0,
7479            block,
7480            w,
7481            hc,
7482            mixes.device_ptr_mut(&stream).0 as *mut f32,
7483        )?;
7484        unsafe {
7485            ck(
7486                "rowsq dspark head",
7487                k::memra_dsv4_rowsq_scale(
7488                    dpf!(h, &stream),
7489                    dpm!(mixes, &stream),
7490                    block as i32,
7491                    w as i32,
7492                    hc as i32,
7493                    eps,
7494                    sp(&stream),
7495                ),
7496            )?;
7497        }
7498        drop(_p_mix);
7499        let _p_mixrt = phase!("1f.mix_D2H_host_H2D", prof.as_ref());
7500        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
7501        for t in 0..block {
7502            for c in 0..hc {
7503                let m = mixes_h[t * hc + c];
7504                mixes_h[t * hc + c] =
7505                    sigmoid_f32(m * ds.hc_head_scale[0] + ds.hc_head_base[c]) + d.hc_eps;
7506            }
7507        }
7508        let pre_d = upload_f32(&stream, &mixes_h)?;
7509        drop(_p_mixrt);
7510        let _p_cn = phase!("1g.collapse_norm", prof.as_ref());
7511        let mut xc = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xc"))?;
7512        unsafe {
7513            ck(
7514                "hc_collapse dspark",
7515                k::memra_dsv4_hc_collapse(
7516                    dpf!(h, &stream),
7517                    dpf!(pre_d, &stream),
7518                    dpm!(xc, &stream),
7519                    block as i32,
7520                    hc as i32,
7521                    hidden as i32,
7522                    sp(&stream),
7523                ),
7524            )?;
7525        }
7526        let mut normed = stream.alloc_zeros::<f32>(block * hidden).map_err(e("nr"))?;
7527        unsafe {
7528            ck(
7529                "rmsnorm dspark head",
7530                k::memra_dsv4_rmsnorm(
7531                    dpf!(xc, &stream),
7532                    dpf!(ds.norm, &stream),
7533                    dpm!(normed, &stream),
7534                    block as i32,
7535                    hidden as i32,
7536                    eps,
7537                    sp(&stream),
7538                ),
7539            )?;
7540        }
7541        drop(_p_cn);
7542        let _p_head = phase!("1h.exit_head_dots", prof.as_ref());
7543        let mut logits = stream.alloc_zeros::<f32>(block * vocab).map_err(e("lg"))?;
7544        // THE 21%-of-a-round instance (nsys, rung 4c): vocab x block over the 1.06 GiB
7545        // shared head. f64 default (gated bytes, hoisted bit-exactly);
7546        // MEMRA_DSV4_DSPARK_HEAD_ARM=f32x switches it to the ratified accumulation class.
7547        self.dspark_head_dots(
7548            st,
7549            normed.device_ptr(&stream).0 as *const f32,
7550            st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
7551            1,
7552            block,
7553            hidden,
7554            vocab,
7555            logits.device_ptr_mut(&stream).0 as *mut f32,
7556        )?;
7557        drop(_p_head);
7558        // pre-markov head logits (the gate's logits_pre array) — captured BEFORE the
7559        // chaining loop adds any bias row in place.
7560        let cap_logits_pre = if capture {
7561            Some(dtoh_f32(&stream, &logits)?)
7562        } else {
7563            None
7564        };
7565        // sequential markov chaining (M:866-871), greedy (temperature 0).
7566        //
7567        // ITERATION-5 (F itemisation, rung 2): the chain is inherently sequential -- draft i+1's
7568        // markov row is indexed by draft i -- but the DEPENDENCY never needed a HOST round trip.
7569        // The shipped loop reads each argmax back (4 B D2H + `stream.synchronize()`) and each
7570        // confidence back the same way, so a block_size-5 chain DRAINS the only stream TEN times
7571        // per round. Those drains are pure F: T-independent, all latency, no work.
7572        // `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the chain resident -- the argmax lands in
7573        // `am_dev[i + 1]`, the next markov row is gathered BY DEVICE INDEX, confidences
7574        // accumulate into `conf_out[i]`, and ONE D2H at the end of the loop returns every id and
7575        // confidence together. Same kernels, same operands, same reduction order: the arm is
7576        // bit-identical BY CONSTRUCTION rather than by tolerance, because only transport moved.
7577        let chain_device = dsv4_dspark_chain_device();
7578        let markov_rowblk = dsv4_dspark_markov_rowblk();
7579        let mut w1_row = stream.alloc_zeros::<f32>(rank).map_err(e("w1r"))?;
7580        let mut bias = stream.alloc_zeros::<f32>(vocab).map_err(e("bias"))?;
7581        // Slot 0 carries the round's input token so even the FIRST gather is device-indexed and
7582        // the two arms share one code path.
7583        let mut am_dev = stream.alloc_zeros::<i32>(block + 1).map_err(e("am"))?;
7584        {
7585            let mut dst = am_dev.slice_mut(0..1);
7586            stream
7587                .memcpy_htod(&[input_token as i32][..], &mut dst)
7588                .map_err(e("htod am0"))?;
7589        }
7590        let mut out_ids = vec![input_token];
7591        let mut margins = Vec::with_capacity(block);
7592        let mut top1_logits = Vec::with_capacity(block);
7593        let mut conf_in = stream.alloc_zeros::<f32>(hidden + rank).map_err(e("cin"))?;
7594        let mut conf_out = stream.alloc_zeros::<f32>(block).map_err(e("cout"))?;
7595        let mut confidence = Vec::with_capacity(block);
7596        let _p_mk = phase!("1i.markov_chain", prof.as_ref());
7597        for i in 0..block {
7598            {
7599                let _p = phase!("1i1.markov_w1_gather", prof.as_ref());
7600                if chain_device {
7601                    unsafe {
7602                        ck(
7603                            "markov w1 gather dev",
7604                            k::memra_dsv4_gather_row_by_idx(
7605                                dpf!(ds.markov_w1, &stream),
7606                                am_dev.device_ptr(&stream).0 as *const i32,
7607                                i as i32,
7608                                dpm!(w1_row, &stream),
7609                                rank as i32,
7610                                sp(&stream),
7611                            ),
7612                        )?;
7613                    }
7614                } else {
7615                    let prev = out_ids[i] as usize;
7616                    let src = ds.markov_w1.slice(prev * rank..(prev + 1) * rank);
7617                    stream.memcpy_dtod(&src, &mut w1_row).map_err(e("w1 cp"))?;
7618                }
7619            }
7620            {
7621                let _p = phase!("1i2.markov_bias_gemv", prof.as_ref());
7622                if markov_rowblk {
7623                    unsafe {
7624                        ck(
7625                            "dots_f32 markov rowblk",
7626                            k::memra_dsv4_dots_f32_rowblk(
7627                                dpf!(w1_row, &stream),
7628                                dp!(ds.markov_w2, &stream),
7629                                0,
7630                                dpm!(bias, &stream),
7631                                1,
7632                                rank as i32,
7633                                vocab as i32,
7634                                sp(&stream),
7635                            ),
7636                        )?;
7637                    }
7638                } else {
7639                    Self::dots(st, &w1_row, &ds.markov_w2, 1, rank, vocab, &mut bias)?;
7640                }
7641            }
7642            let _p_aa = phase!("1i3.markov_add_argmax", prof.as_ref());
7643            unsafe {
7644                ck(
7645                    "markov add dspark",
7646                    k::memra_dsv4_add_inplace(
7647                        (logits.device_ptr_mut(&stream).0 as usize + i * vocab * 4) as *mut f32,
7648                        dpf!(bias, &stream),
7649                        vocab as i64,
7650                        sp(&stream),
7651                    ),
7652                )?;
7653                ck(
7654                    "argmax dspark",
7655                    k::memra_dsv4_argmax(
7656                        (logits.device_ptr(&stream).0 as usize + i * vocab * 4) as *const f32,
7657                        vocab as i64,
7658                        (am_dev.device_ptr_mut(&stream).0 as usize + (i + 1) * 4) as *mut i32,
7659                        sp(&stream),
7660                    ),
7661                )?;
7662            }
7663            drop(_p_aa);
7664            if !chain_device {
7665                let _p_d2h = phase!("1i4.markov_argmax_D2H_SYNC", None);
7666                let mut am = [0i32; 1];
7667                let view = am_dev.slice(i + 1..i + 2);
7668                stream
7669                    .memcpy_dtoh(&view, &mut am[..])
7670                    .map_err(e("dtoh am"))?;
7671                stream.synchronize().map_err(e("sync am"))?;
7672                out_ids.push(am[0] as u32);
7673            }
7674            // confidence (M:807-815): fp32 proj of concat(PRE-norm xc row, markov_embed)
7675            {
7676                let _p = phase!("1i5.conf_in_copies", prof.as_ref());
7677                let src = xc.slice(i * hidden..(i + 1) * hidden);
7678                let mut dst = conf_in.slice_mut(0..hidden);
7679                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin x"))?;
7680                let src = w1_row.slice(0..rank);
7681                let mut dst = conf_in.slice_mut(hidden..hidden + rank);
7682                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin m"))?;
7683            }
7684            {
7685                // `Self::dots` writes y[0]; the confidence now lands in slot i of a
7686                // block-wide buffer, so the launcher is called with the offset directly
7687                // (the same pointer-arithmetic pattern the add/argmax above use). Kernel,
7688                // f64 accumulation and operand order are untouched.
7689                let _p = phase!("1i6.conf_dots", prof.as_ref());
7690                unsafe {
7691                    ck(
7692                        "dots_f32 conf dspark",
7693                        k::memra_dsv4_dots_f32(
7694                            dpf!(conf_in, &stream),
7695                            dp!(ds.conf_w, &stream),
7696                            0,
7697                            (conf_out.device_ptr_mut(&stream).0 as usize + i * 4) as *mut f32,
7698                            1,
7699                            (hidden + rank) as i32,
7700                            1,
7701                            sp(&stream),
7702                        ),
7703                    )?;
7704                }
7705            }
7706            if !chain_device {
7707                let _p = phase!("1i7.conf_D2H_SYNC", None);
7708                let mut c = [0f32; 1];
7709                let view = conf_out.slice(i..i + 1);
7710                stream
7711                    .memcpy_dtoh(&view, &mut c[..])
7712                    .map_err(e("dtoh cf"))?;
7713                stream.synchronize().map_err(e("sync cf"))?;
7714                confidence.push(c[0]);
7715            }
7716        }
7717        if chain_device {
7718            // ONE drain for the whole chain: block ids + block confidences.
7719            let _p = phase!("1i8.chain_D2H_SYNC_once", None);
7720            let mut ids = vec![0i32; block];
7721            let view = am_dev.slice(1..block + 1);
7722            stream
7723                .memcpy_dtoh(&view, &mut ids[..])
7724                .map_err(e("dtoh chain ids"))?;
7725            let mut cf = vec![0f32; block];
7726            stream
7727                .memcpy_dtoh(&conf_out, &mut cf[..])
7728                .map_err(e("dtoh chain conf"))?;
7729            stream.synchronize().map_err(e("sync chain"))?;
7730            out_ids.extend(ids.iter().map(|&x| x as u32));
7731            confidence.extend_from_slice(&cf);
7732        }
7733        drop(_p_mk);
7734        // `markov_embed`, `margins` and `top1_logits` are CAPTURE-ONLY observables, and wanting
7735        // them mid-chain was the other reason the shipped loop had to know each id on the host.
7736        // `add_inplace` touches logits row i only at step i, so every row is final once the loop
7737        // ends and one post-loop read is bit-identical to the per-step reads it replaces.
7738        let membeds: Vec<f32> = if capture {
7739            let mut m = Vec::with_capacity(block * rank);
7740            #[allow(clippy::needless_range_loop)]
7741            // allow: the explicit index loop keeps the offset arithmetic visible and aligned with the device-side indexing
7742            for i in 0..block {
7743                let prev = out_ids[i] as usize;
7744                m.extend_from_slice(&ds.markov_w1_host[prev * rank..(prev + 1) * rank]);
7745            }
7746            m
7747        } else {
7748            Vec::new()
7749        };
7750        let cap = if capture {
7751            let logits_post = dtoh_f32(&stream, &logits)?;
7752            for i in 0..block {
7753                let row = &logits_post[i * vocab..(i + 1) * vocab];
7754                let top = out_ids[i + 1];
7755                let mut second = f32::NEG_INFINITY;
7756                for (vv, &val) in row.iter().enumerate() {
7757                    if vv as u32 != top && val > second {
7758                        second = val;
7759                    }
7760                }
7761                margins.push(row[top as usize] - second);
7762                top1_logits.push(row[top as usize]);
7763            }
7764            Some(DsparkCaptureOut {
7765                main_hidden: cap_main_hidden.unwrap(),
7766                main_x: cap_main_x.unwrap(),
7767                block_outs,
7768                x_collapsed: dtoh_f32(&stream, &xc)?,
7769                logits_pre: cap_logits_pre.unwrap(),
7770                logits_post,
7771                markov_embed: membeds,
7772            })
7773        } else {
7774            None
7775        };
7776        Ok(DsparkProposal {
7777            out_ids,
7778            confidence,
7779            margins,
7780            top1_logits,
7781            capture: cap,
7782        })
7783    }
7784
7785    /// Device decode step + the DSpark tap into `dspark_state.taps` row `tap_row`
7786    /// (full logits — the gates' contract).
7787    pub fn decode_step_tap(
7788        &self,
7789        tok: u32,
7790        state: &mut DecodeState,
7791        dspark_state: &mut DsparkState,
7792        tap_row: usize,
7793    ) -> Res<Vec<f32>> {
7794        let DecodePath::Device { host_math } = self.decode_path else {
7795            return Err("decode_step_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7796        };
7797        let n_t = self.dspark().targets.len();
7798        let hidden = self.model.mc.n_embd as usize;
7799        let (logits, _) = self.decode_step_fast_tap(
7800            tok,
7801            state,
7802            true,
7803            host_math,
7804            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7805        )?;
7806        Ok(logits.expect("want_logits"))
7807    }
7808
7809    /// Greedy twin of [`Self::decode_step_tap`] (device argmax, 4-byte D2H).
7810    pub fn decode_step_greedy_tap(
7811        &self,
7812        tok: u32,
7813        state: &mut DecodeState,
7814        dspark_state: &mut DsparkState,
7815        tap_row: usize,
7816    ) -> Res<u32> {
7817        let DecodePath::Device { host_math } = self.decode_path else {
7818            return Err("decode_step_greedy_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7819        };
7820        let ds = self.dspark();
7821        let hidden = self.model.mc.n_embd as usize;
7822        let n_t = ds.targets.len();
7823        let (_, tok_next) = self.decode_step_fast_tap(
7824            tok,
7825            state,
7826            false,
7827            host_math,
7828            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7829        )?;
7830        Ok(tok_next)
7831    }
7832}
7833
7834// ================================================================ iteration 3, rung 4: batched T=k+1 device verify
7835//
7836// The rung that makes drafted decode pay. Design law (banked in the iteration-3 receipts
7837// before this code was written, and restated in cu/dsv4_gpu.cu's batched section):
7838//
7839//   1. BIT-EXACT against T sequential single-position steps. The greedy spec==plain
7840//      identity law is this lane's verdict instrument; if the verify pass computed
7841//      different logits than the plain pass, identity would break silently at every
7842//      near-tie and no gate could tell a port bug from a rounding fork. Achievable
7843//      because the device decode path's dense projections are OUR kernels: the batched
7844//      twins hoist the WEIGHT load across T activation rows without touching any
7845//      accumulation order. cuBLASLt is deliberately absent from this path (its m-order
7846//      changes split-K plans and shifts logits 0.18-3.08 — banked).
7847//   2. §3.1 ring hazard, exactly as GATED on the CPU oracle: window-ring writes go to
7848//      TRANSIENT rows (kvc rows [win+cap_blocks, win+cap_blocks+T)) and reads of
7849//      in-round positions are redirected there (`dsv4_build_idx_redirect`); the
7850//      compressor/indexer pending state advances in place with a snapshot + replay
7851//      payload; the append-only stores roll back by high-water mark; the drafter rings
7852//      advance for ACCEPTED positions only.
7853//   3. Where a kernel cannot batch (per-position compressor state machine, per-position
7854//      indexer top-k), the loop runs t = 0..T-1 in POSITION ORDER — the sequential
7855//      program's order, so in-round block emissions are visible to later queries exactly
7856//      as they would be sequentially.
7857//
7858// The one place uniformity is imposed: the batched sink attention takes ONE `slots`
7859// width for all T queries (the max over the round) and shorter queries' index tails are
7860// -1 pads. That is bit-inert by the pinned kernels' own pad contract (score -inf ->
7861// eval +0.0 -> skipped in both the denominator and the output chain), which is why it is
7862// legal rather than merely convenient.
7863
7864/// Per-stage batched-verify workspace: the lane-8 arena widened to `tmax` rows. Held
7865/// separately from [`StepWs`] so the gated single-position path's allocations, launches
7866/// and bytes are literally untouched by this rung.
7867pub struct VerifyWs {
7868    pub tmax: usize,
7869    h_a: CudaSlice<f32>,
7870    h_b: CudaSlice<f32>,
7871    h_rx: CudaSlice<f32>,
7872    emb: CudaSlice<f32>,
7873    mixes: CudaSlice<f32>,
7874    pre: CudaSlice<f32>,
7875    post: CudaSlice<f32>,
7876    comb: CudaSlice<f32>,
7877    y_hc: CudaSlice<f32>,
7878    x: CudaSlice<f32>,
7879    xf: CudaSlice<f32>,
7880    qr: CudaSlice<f32>,
7881    qr_b: CudaSlice<u8>,
7882    q: CudaSlice<f32>,
7883    kv: CudaSlice<f32>,
7884    qi: CudaSlice<f32>,
7885    wproj: CudaSlice<f32>,
7886    score: CudaSlice<f32>,
7887    idx: CudaSlice<i32>,
7888    idx_stride: usize,
7889    o: CudaSlice<f32>,
7890    o_b: CudaSlice<u8>,
7891    og: CudaSlice<f32>,
7892    attn_out: CudaSlice<f32>,
7893    gemm_xb: CudaSlice<u8>,
7894    raw: CudaSlice<f32>,
7895    sel: CudaSlice<i32>,
7896    selw: CudaSlice<f32>,
7897    order: CudaSlice<i32>,
7898    xq: CudaSlice<u8>,
7899    xs: CudaSlice<f32>,
7900    g1: CudaSlice<f32>,
7901    g3: CudaSlice<f32>,
7902    hbuf: CudaSlice<f32>,
7903    hq: CudaSlice<u8>,
7904    hs: CudaSlice<f32>,
7905    contrib: CudaSlice<f32>,
7906    y: CudaSlice<f32>,
7907    xb: CudaSlice<u8>,
7908    sg1: CudaSlice<f32>,
7909    sg3: CudaSlice<f32>,
7910    shbuf: CudaSlice<f32>,
7911    shb16: CudaSlice<u8>,
7912    sh_out: CudaSlice<f32>,
7913    cmp_emit: CudaSlice<f32>,
7914    cmp_shift: CudaSlice<f32>,
7915    sink_scores: CudaSlice<f32>,
7916    sink_evals: CudaSlice<f32>,
7917    sink_den: CudaSlice<f64>,
7918    head_mixes: CudaSlice<f32>,
7919    head_pre: CudaSlice<f32>,
7920    collapsed: CudaSlice<f32>,
7921    logits: CudaSlice<f32>,
7922    tok: CudaSlice<i32>,
7923    pos_dev: CudaSlice<i32>,
7924    argmax: CudaSlice<i32>,
7925    /// ring-commit staging: transient rows copied out, then scattered to ring slots
7926    /// (source and destination live in the same `kvc` allocation, so the bounce is a
7927    /// borrow requirement, not a numeric one).
7928    bounce: CudaSlice<f32>,
7929    slot_rows: CudaSlice<i32>,
7930    /// hc-mean staging for the DSpark tap (one target at a time, then `place_cols`)
7931    tap_tmp: CudaSlice<f32>,
7932}
7933
7934/// One compressor's verify-round checkpoint on device — the CPU oracle's `CompCkpt`,
7935/// device-realized: full pending snapshot + the per-position RAW (kv, score) rows that
7936/// were written, plus the store high-water mark. `dst` and `emitted` are pure functions
7937/// of the position, so nothing has to come back to the host to replay.
7938struct CmpCkptDev {
7939    kv_snap: CudaSlice<f32>,
7940    sc_snap: CudaSlice<f32>,
7941    rows_kv: CudaSlice<f32>,
7942    rows_sc: CudaSlice<f32>,
7943    latent: usize,
7944    ratio: usize,
7945    overlap: bool,
7946    n_blocks0: usize,
7947}
7948
7949/// One trunk layer's verify-round checkpoint: the two compressor payloads. The window
7950/// ring needs no payload at all — the round never wrote it (transient rows instead).
7951struct LayerCkptDev {
7952    cmp: Option<CmpCkptDev>,
7953    idx: Option<CmpCkptDev>,
7954    /// first transient row id in this layer's `kvc` (== win + cap_blocks)
7955    trans_base: usize,
7956}
7957
7958/// Whole-round verify state: the per-stage arenas + the per-layer §3.1 checkpoints.
7959pub struct VerifyState {
7960    ws: Vec<VerifyWs>,
7961    layers: Vec<LayerCkptDev>,
7962    pub tmax: usize,
7963    /// (pos0, t) of the open round; `None` between rounds. `commit_verify_dev` closes it.
7964    open: Option<(usize, usize)>,
7965    /// allocated bytes per device index (reported next to the drafter VRAM plan)
7966    pub bytes: Vec<u64>,
7967}
7968
7969impl Dsv4Gpu {
7970    /// Verify-round depth ceiling: block_size + 1 with the drafter loaded, else 0 (and
7971    /// then no transient rows are reserved anywhere — today's exact allocation).
7972    pub fn verify_tmax(&self) -> usize {
7973        self.dspark.as_ref().map(|d| d.block_size + 1).unwrap_or(0)
7974    }
7975
7976    /// Allocate the batched-verify state (arenas + §3.1 checkpoints). Requires the
7977    /// drafter (the only producer of rounds) and the device decode path.
7978    pub fn alloc_verify_state(&self) -> Res<VerifyState> {
7979        let tmax = self.verify_tmax();
7980        if tmax == 0 {
7981            return Err("alloc_verify_state needs MEMRA_DSV4_DRAFTER=dspark".into());
7982        }
7983        if !matches!(self.decode_path, DecodePath::Device { .. }) {
7984            return Err(
7985                "batched verify is a device-path rung (MEMRA_DSV4_DECODE_PATH=device)".into(),
7986            );
7987        }
7988        let d = self.model.cfg();
7989        let mc = &self.model.mc;
7990        let moe = mc.moe.as_ref().expect("moe");
7991        let hc = d.hc_mult as usize;
7992        let hidden = mc.n_embd as usize;
7993        let heads = mc.n_head as usize;
7994        let hd = d.head_dim as usize;
7995        let q_lora = d.q_lora_rank as usize;
7996        let win = d.sliding_window as usize;
7997        let o_groups = d.o_groups as usize;
7998        let o_lora = d.o_lora_rank as usize;
7999        let iheads = d.index_n_heads as usize;
8000        let ihd = d.index_head_dim as usize;
8001        let topk = moe.expert_used_count as usize;
8002        let ne = moe.expert_count as usize;
8003        let inter = moe.expert_ff_length as usize;
8004        let itopk = d.index_topk as usize;
8005        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
8006        let vocab = {
8007            let (info, _) = self.model.st.raw("head.weight").expect("head");
8008            info.shape[0] as usize
8009        };
8010        let sh_inter = {
8011            let (info, _) = self
8012                .model
8013                .st
8014                .raw("layers.0.ffn.shared_experts.w1.weight")
8015                .expect("shared w1");
8016            info.shape[0] as usize
8017        };
8018        let mut max_d = 0usize;
8019        let mut max_shift = 0usize;
8020        let mut min_ratio = usize::MAX;
8021        for st in &self.stages {
8022            for l in &st.layers {
8023                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
8024                    max_d = max_d.max(cmp.d);
8025                    if cmp.overlap {
8026                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
8027                    }
8028                    min_ratio = min_ratio.min(cmp.ratio);
8029                }
8030            }
8031        }
8032        assert!(min_ratio != usize::MAX, "no compressor layers?");
8033        let score_cap = self.max_seq / min_ratio + 1;
8034        let idx_tail = itopk.max(self.max_seq / 128 + 1);
8035        let idx_stride = win + idx_tail;
8036        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
8037        let mut bytes = vec![0u64; self.stages.len()];
8038        let mut ws = Vec::with_capacity(self.stages.len());
8039        for st in &self.stages {
8040            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx vws"))?;
8041            let s = st.gpu.stream();
8042            let acc = std::cell::Cell::new(0u64);
8043            let f = |n: usize| {
8044                acc.set(acc.get() + (n * 4) as u64);
8045                s.alloc_zeros::<f32>(n).map_err(e("vws f32"))
8046            };
8047            let b = |n: usize| {
8048                acc.set(acc.get() + n as u64);
8049                s.alloc_zeros::<u8>(n).map_err(e("vws u8"))
8050            };
8051            let i = |n: usize| {
8052                acc.set(acc.get() + (n * 4) as u64);
8053                s.alloc_zeros::<i32>(n).map_err(e("vws i32"))
8054            };
8055            let w = VerifyWs {
8056                tmax,
8057                h_a: f(tmax * hc * hidden)?,
8058                h_b: f(tmax * hc * hidden)?,
8059                h_rx: f(tmax * hc * hidden)?,
8060                emb: f(tmax * hidden)?,
8061                mixes: f(tmax * (2 + hc) * hc)?,
8062                pre: f(tmax * hc)?,
8063                post: f(tmax * hc)?,
8064                comb: f(tmax * hc * hc)?,
8065                y_hc: f(tmax * hidden)?,
8066                x: f(tmax * hidden)?,
8067                xf: f(tmax * hidden)?,
8068                qr: f(tmax * q_lora)?,
8069                qr_b: b(tmax * q_lora * 2)?,
8070                q: f(tmax * heads * hd)?,
8071                kv: f(tmax * hd)?,
8072                qi: f(tmax * iheads * ihd)?,
8073                wproj: f(tmax * iheads)?,
8074                score: f(score_cap)?,
8075                idx: i(tmax * idx_stride)?,
8076                idx_stride,
8077                o: f(tmax * heads * hd)?,
8078                o_b: b(tmax * heads * hd * 2)?,
8079                og: f(tmax * o_groups * o_lora)?,
8080                attn_out: f(tmax * hidden)?,
8081                gemm_xb: b(tmax * max_gemm_k * 2)?,
8082                raw: f(tmax * ne)?,
8083                sel: i(tmax * topk)?,
8084                selw: f(tmax * topk)?,
8085                order: i(tmax * topk)?,
8086                xq: b(tmax * hidden)?,
8087                xs: f(tmax * hidden / 128)?,
8088                g1: f(tmax * topk * inter)?,
8089                g3: f(tmax * topk * inter)?,
8090                hbuf: f(tmax * topk * inter)?,
8091                hq: b(tmax * topk * inter)?,
8092                hs: f(tmax * topk * inter / 128)?,
8093                contrib: f(tmax * topk * hidden)?,
8094                y: f(tmax * hidden)?,
8095                xb: b(tmax * hidden * 2)?,
8096                sg1: f(tmax * sh_inter)?,
8097                sg3: f(tmax * sh_inter)?,
8098                shbuf: f(tmax * sh_inter)?,
8099                shb16: b(tmax * sh_inter * 2)?,
8100                sh_out: f(tmax * hidden)?,
8101                cmp_emit: f(2 * max_d)?,
8102                cmp_shift: f(max_shift.max(1))?,
8103                sink_scores: f(tmax * heads * idx_stride)?,
8104                sink_evals: f(tmax * heads * idx_stride)?,
8105                sink_den: {
8106                    acc.set(acc.get() + (tmax * heads * 8) as u64);
8107                    s.alloc_zeros::<f64>(tmax * heads).map_err(e("vws f64"))?
8108                },
8109                head_mixes: f(tmax * hc)?,
8110                head_pre: f(tmax * hc)?,
8111                collapsed: f(tmax * hidden)?,
8112                logits: f(tmax * vocab)?,
8113                tok: i(tmax)?,
8114                pos_dev: i(tmax)?,
8115                argmax: i(tmax)?,
8116                bounce: f(tmax * hd)?,
8117                slot_rows: i(tmax)?,
8118                tap_tmp: f(tmax * hidden)?,
8119            };
8120            bytes[st.dev] += acc.get();
8121            ws.push(w);
8122        }
8123        // per-layer §3.1 checkpoints, each on the layer's own device
8124        let mut layers = Vec::with_capacity(n_trunk);
8125        for il in 0..n_trunk {
8126            let stage_i = self.layer_stage[il];
8127            let st = &self.stages[stage_i];
8128            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ckpt"))?;
8129            let stream = st.gpu.stream();
8130            let lidx = st
8131                .layers
8132                .iter()
8133                .position(|l| l.il == il as u32)
8134                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
8135            let layer = &st.layers[lidx];
8136            let cap_blocks = self.max_seq.checked_div(layer.ratio).unwrap_or(0);
8137            let mk = |cmp: &CmpDev| -> Res<CmpCkptDev> {
8138                let slots = if cmp.overlap {
8139                    2 * cmp.ratio
8140                } else {
8141                    cmp.ratio
8142                };
8143                Ok(CmpCkptDev {
8144                    kv_snap: stream
8145                        .alloc_zeros::<f32>(slots * cmp.latent)
8146                        .map_err(e("ckpt kv snap"))?,
8147                    sc_snap: stream
8148                        .alloc_zeros::<f32>(slots * cmp.latent)
8149                        .map_err(e("ckpt sc snap"))?,
8150                    rows_kv: stream
8151                        .alloc_zeros::<f32>(tmax * cmp.latent)
8152                        .map_err(e("ckpt rows kv"))?,
8153                    rows_sc: stream
8154                        .alloc_zeros::<f32>(tmax * cmp.latent)
8155                        .map_err(e("ckpt rows sc"))?,
8156                    latent: cmp.latent,
8157                    ratio: cmp.ratio,
8158                    overlap: cmp.overlap,
8159                    n_blocks0: 0,
8160                })
8161            };
8162            let cmp = match &layer.cmp {
8163                Some(c) => Some(mk(c)?),
8164                None => None,
8165            };
8166            let idxc = match &layer.idx {
8167                Some(ix) => Some(mk(&ix.cmp)?),
8168                None => None,
8169            };
8170            for c in cmp.iter().chain(idxc.iter()) {
8171                let slots = if c.overlap { 2 * c.ratio } else { c.ratio };
8172                bytes[st.dev] += ((2 * slots * c.latent + 2 * tmax * c.latent) * 4) as u64;
8173            }
8174            layers.push(LayerCkptDev {
8175                cmp,
8176                idx: idxc,
8177                trans_base: d.sliding_window as usize + cap_blocks,
8178            });
8179        }
8180        for st in &self.stages {
8181            st.gpu.stream().synchronize().map_err(e("vws sync"))?;
8182        }
8183        Ok(VerifyState {
8184            ws,
8185            layers,
8186            tmax,
8187            open: None,
8188            bytes,
8189        })
8190    }
8191
8192    /// Batched bf16 GEMV: y[m, n] = x[m, k] @ W[n, k]^T with the weight row read once.
8193    /// `xstride`/`ystride` in elements (0 == packed) — the grouped output projection is
8194    /// the only caller that needs them.
8195    #[allow(clippy::too_many_arguments)]
8196    fn gemv_m_dev(
8197        st: &Stage,
8198        w: DW,
8199        x_ptr: *const c_void,
8200        y_ptr: *mut f32,
8201        m: usize,
8202        n: usize,
8203        kdim: usize,
8204        xstride: usize,
8205        ystride: usize,
8206    ) -> Res<()> {
8207        let stream = st.gpu.stream();
8208        unsafe {
8209            match w {
8210                DW::Bf16(w_ptr) => ck(
8211                    "gemv_bf16_m dev",
8212                    k::memra_dsv4_gemv_bf16_m(
8213                        w_ptr,
8214                        x_ptr,
8215                        y_ptr,
8216                        m as i32,
8217                        n as i32,
8218                        kdim as i32,
8219                        xstride as i32,
8220                        ystride as i32,
8221                        sp(&stream),
8222                    ),
8223                ),
8224                DW::Fp8 {
8225                    codes,
8226                    scales,
8227                    sc_cols,
8228                } => ck(
8229                    "gemv_fp8_m dev",
8230                    k::memra_dsv4_gemv_fp8_m(
8231                        codes,
8232                        scales,
8233                        sc_cols,
8234                        x_ptr,
8235                        y_ptr,
8236                        m as i32,
8237                        n as i32,
8238                        kdim as i32,
8239                        xstride as i32,
8240                        ystride as i32,
8241                        sp(&stream),
8242                    ),
8243                ),
8244            }
8245        }
8246    }
8247
8248    /// f32 cvt + batched GEMV (the m=T twin of `gemm_dev`).
8249    #[allow(clippy::too_many_arguments)]
8250    fn gemm_m_dev(
8251        st: &Stage,
8252        x_f32: *const f32,
8253        xb: &mut CudaSlice<u8>,
8254        w: DW,
8255        m: usize,
8256        n: usize,
8257        kdim: usize,
8258        y_ptr: *mut f32,
8259    ) -> Res<()> {
8260        let stream = st.gpu.stream();
8261        unsafe {
8262            ck(
8263                "cvt_bf16 m dev",
8264                k::memra_dsv4_cvt_bf16(
8265                    x_f32,
8266                    xb.device_ptr_mut(&stream).0 as *mut c_void,
8267                    (m * kdim) as i64,
8268                    sp(&stream),
8269                ),
8270            )?;
8271        }
8272        Self::gemv_m_dev(
8273            st,
8274            w,
8275            xb.device_ptr(&stream).0 as *const c_void,
8276            y_ptr,
8277            m,
8278            n,
8279            kdim,
8280            0,
8281            0,
8282        )
8283    }
8284
8285    /// Island dots, batched rows, weight row hoisted. Same arm selection as `dots_dev`.
8286    #[allow(clippy::too_many_arguments)]
8287    fn dots_m_dev(
8288        &self,
8289        st: &Stage,
8290        x: *const f32,
8291        w_f32: *const c_void,
8292        w_is_bf16: i32,
8293        s: usize,
8294        kdim: usize,
8295        n: usize,
8296        y: *mut f32,
8297    ) -> Res<()> {
8298        let stream = st.gpu.stream();
8299        unsafe {
8300            if self.dots_f32 {
8301                ck(
8302                    "dots_f32acc_mrow",
8303                    k::memra_dsv4_dots_f32acc_mrow(
8304                        x,
8305                        w_f32,
8306                        w_is_bf16,
8307                        y,
8308                        s as i32,
8309                        kdim as i32,
8310                        n as i32,
8311                        sp(&stream),
8312                    ),
8313                )
8314            } else {
8315                ck(
8316                    "dots_f32_mrow",
8317                    k::memra_dsv4_dots_f32_mrow(
8318                        x,
8319                        w_f32,
8320                        w_is_bf16,
8321                        y,
8322                        s as i32,
8323                        kdim as i32,
8324                        n as i32,
8325                        sp(&stream),
8326                    ),
8327                )
8328            }
8329        }
8330    }
8331}
8332
8333impl Dsv4Gpu {
8334    /// hc_pre for T rows: the `hc_pre_dev` program with every kernel taking the row
8335    /// count (Sinkhorn either the host closure per row — byte-identity arm — or the
8336    /// one-block-per-position device twin).
8337    #[allow(clippy::too_many_arguments)]
8338    fn hc_pre_batch_dev(
8339        &self,
8340        st: &Stage,
8341        h_ptr: *const f32,
8342        fn_w: &CudaSlice<f32>,
8343        base_host: &[f32],
8344        scale_host: &[f32],
8345        base_dev: &CudaSlice<f32>,
8346        scale_dev: &CudaSlice<f32>,
8347        vws: &mut VerifyWs,
8348        t: usize,
8349        hc: usize,
8350        hidden: usize,
8351        iters: u32,
8352        hc_eps: f32,
8353        host_math: bool,
8354    ) -> Res<()> {
8355        let stream = st.gpu.stream();
8356        let w = hc * hidden;
8357        let rows = (2 + hc) * hc;
8358        self.dots_m_dev(
8359            st,
8360            h_ptr,
8361            fn_w.device_ptr(&stream).0 as *const c_void,
8362            0,
8363            t,
8364            w,
8365            rows,
8366            vws.mixes.device_ptr_mut(&stream).0 as *mut f32,
8367        )?;
8368        unsafe {
8369            ck(
8370                "rowsq_scale batch",
8371                self.rowsq_scale_arm(
8372                    h_ptr,
8373                    dpm!(vws.mixes, &stream),
8374                    t as i32,
8375                    w as i32,
8376                    rows as i32,
8377                    hc_eps,
8378                    sp(&stream),
8379                ),
8380            )?;
8381        }
8382        if host_math {
8383            let mut mixes_h = vec![0f32; t * rows];
8384            let view = vws.mixes.slice(0..t * rows);
8385            stream
8386                .memcpy_dtoh(&view, &mut mixes_h[..])
8387                .map_err(e("dtoh mixes batch"))?;
8388            stream.synchronize().map_err(e("sync mixes batch"))?;
8389            let (pre_h, post_h, comb_h) =
8390                hc_split_sinkhorn(&mixes_h, t, hc, scale_host, base_host, iters, hc_eps);
8391            let mut dp = vws.pre.slice_mut(0..t * hc);
8392            stream
8393                .memcpy_htod(&pre_h, &mut dp)
8394                .map_err(e("htod pre b"))?;
8395            let mut dp = vws.post.slice_mut(0..t * hc);
8396            stream
8397                .memcpy_htod(&post_h, &mut dp)
8398                .map_err(e("htod post b"))?;
8399            let mut dp = vws.comb.slice_mut(0..t * hc * hc);
8400            stream
8401                .memcpy_htod(&comb_h, &mut dp)
8402                .map_err(e("htod comb b"))?;
8403        } else {
8404            unsafe {
8405                ck(
8406                    "hc_sinkhorn_m",
8407                    k::memra_dsv4_hc_sinkhorn_m(
8408                        dpf!(vws.mixes, &stream),
8409                        dpf!(scale_dev, &stream),
8410                        dpf!(base_dev, &stream),
8411                        dpm!(vws.pre, &stream),
8412                        dpm!(vws.post, &stream),
8413                        dpm!(vws.comb, &stream),
8414                        t as i32,
8415                        hc as i32,
8416                        iters as i32,
8417                        hc_eps,
8418                        sp(&stream),
8419                    ),
8420                )?;
8421            }
8422        }
8423        unsafe {
8424            ck(
8425                "hc_collapse batch",
8426                k::memra_dsv4_hc_collapse(
8427                    h_ptr,
8428                    dpf!(vws.pre, &stream),
8429                    dpm!(vws.y_hc, &stream),
8430                    t as i32,
8431                    hc as i32,
8432                    hidden as i32,
8433                    sp(&stream),
8434                ),
8435            )?;
8436        }
8437        Ok(())
8438    }
8439
8440    /// Compressor advance for a whole verify round (§3.1): the two projection GEMMs run
8441    /// batched STRAIGHT INTO the checkpoint's row payload (which is both the record and
8442    /// the source of the pending writes — one copy, not two), then the pending state
8443    /// machine + emissions run t = 0..T-1 in POSITION ORDER, exactly the sequential
8444    /// program. The snapshot is taken before the first write.
8445    #[allow(clippy::too_many_arguments)]
8446    #[allow(clippy::manual_is_multiple_of)] // allow: divisor is runtime-derived; the modulo form keeps a zero divisor loud (a panic), where is_multiple_of would return false silently
8447    fn cmp_decode_batch_dev(
8448        &self,
8449        st: &Stage,
8450        cmp: &CmpDev,
8451        x_ptr: *const f32,
8452        t: usize,
8453        pos0: usize,
8454        hidden: usize,
8455        fc_dev: &CudaSlice<f32>,
8456        rd: usize,
8457        eps: f32,
8458        ck_dev: &mut CmpCkptDev,
8459        emit: &mut CudaSlice<f32>,
8460        shift: &mut CudaSlice<f32>,
8461        pend_kv: &mut CudaSlice<f32>,
8462        pend_score: &mut CudaSlice<f32>,
8463        store: &mut CudaSlice<f32>,
8464        row0: usize,
8465        blocks: &mut usize,
8466    ) -> Res<()> {
8467        let stream = st.gpu.stream();
8468        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
8469        // snapshot + high-water mark BEFORE anything is written
8470        stream
8471            .memcpy_dtod(pend_kv, &mut ck_dev.kv_snap)
8472            .map_err(e("ckpt snap kv"))?;
8473        stream
8474            .memcpy_dtod(pend_score, &mut ck_dev.sc_snap)
8475            .map_err(e("ckpt snap sc"))?;
8476        ck_dev.n_blocks0 = *blocks;
8477        self.dots_m_dev(
8478            st,
8479            x_ptr,
8480            cmp.wkv.device_ptr(&stream).0 as *const c_void,
8481            0,
8482            t,
8483            hidden,
8484            latent,
8485            ck_dev.rows_kv.device_ptr_mut(&stream).0 as *mut f32,
8486        )?;
8487        self.dots_m_dev(
8488            st,
8489            x_ptr,
8490            cmp.wgate.device_ptr(&stream).0 as *const c_void,
8491            0,
8492            t,
8493            hidden,
8494            latent,
8495            ck_dev.rows_sc.device_ptr_mut(&stream).0 as *mut f32,
8496        )?;
8497        for i in 0..t {
8498            let pos = pos0 + i;
8499            let slot = if cmp.overlap {
8500                ratio + pos % ratio
8501            } else {
8502                pos % ratio
8503            };
8504            {
8505                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8506                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8507                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv b"))?;
8508                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8509                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8510                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc b"))?;
8511            }
8512            if (pos + 1) % ratio != 0 {
8513                continue;
8514            }
8515            let j = pos / ratio;
8516            let nb_launch = if cmp.overlap { 2usize } else { 1 };
8517            let row_off = if cmp.overlap { d } else { 0 };
8518            unsafe {
8519                ck(
8520                    "compressor_pool batch",
8521                    k::memra_dsv4_compressor_pool(
8522                        dpf!(*pend_kv, &stream),
8523                        dpf!(*pend_score, &stream),
8524                        dpf!(cmp.ape, &stream),
8525                        dpm!(*emit, &stream),
8526                        nb_launch as i32,
8527                        ratio as i32,
8528                        d as i32,
8529                        latent as i32,
8530                        cmp.overlap as i32,
8531                        sp(&stream),
8532                    ),
8533                )?;
8534                let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
8535                let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
8536                ck(
8537                    "rmsnorm batch cmp",
8538                    self.rmsnorm_arm(
8539                        row_c,
8540                        dpf!(cmp.norm, &stream),
8541                        row_m,
8542                        1,
8543                        d as i32,
8544                        eps,
8545                        sp(&stream),
8546                    ),
8547                )?;
8548                ck(
8549                    "rope_at batch cmp",
8550                    k::memra_dsv4_rope_at(
8551                        row_m,
8552                        1,
8553                        d as i32,
8554                        rd as i32,
8555                        dpf!(fc_dev, &stream),
8556                        (j * ratio) as i32,
8557                        0,
8558                        sp(&stream),
8559                    ),
8560                )?;
8561                if cmp.rotate {
8562                    let scale = (d as f32).powf(-0.5);
8563                    ck(
8564                        "hadamard batch cmp",
8565                        k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
8566                    )?;
8567                    ck(
8568                        "fp4 batch cmp",
8569                        k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
8570                    )?;
8571                } else {
8572                    ck(
8573                        "act_quant batch cmp",
8574                        k::memra_dsv4_act_quant(
8575                            row_m,
8576                            1,
8577                            d as i64,
8578                            (d - rd) as i32,
8579                            64,
8580                            (self.variant == ActQuantVariant::ClampOnly) as i32,
8581                            sp(&stream),
8582                        ),
8583                    )?;
8584                }
8585            }
8586            {
8587                let src = emit.slice(row_off..row_off + d);
8588                let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
8589                stream
8590                    .memcpy_dtod(&src, &mut dst)
8591                    .map_err(e("emit store b"))?;
8592            }
8593            if cmp.overlap {
8594                {
8595                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8596                    let mut dst = shift.slice_mut(0..ratio * latent);
8597                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift1"))?;
8598                }
8599                {
8600                    let src = shift.slice(0..ratio * latent);
8601                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8602                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift2"))?;
8603                }
8604                {
8605                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8606                    let mut dst = shift.slice_mut(0..ratio * latent);
8607                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift3"))?;
8608                }
8609                {
8610                    let src = shift.slice(0..ratio * latent);
8611                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8612                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift4"))?;
8613                }
8614            }
8615            *blocks = j + 1;
8616        }
8617        Ok(())
8618    }
8619
8620    /// §3.1 compressor rollback: restore the snapshot, then REPLAY the committed
8621    /// positions' row writes + cur->prev shifts + block accounting. Emitted store rows
8622    /// of the committed prefix are kept as the round wrote them (bit-identical to the
8623    /// sequential twin — the batch advanced the pending in position order, so every
8624    /// emission pooled the same inputs). The CPU oracle's `rollback_replay`, verbatim.
8625    #[allow(clippy::too_many_arguments)]
8626    #[allow(clippy::manual_is_multiple_of)] // allow: divisor is runtime-derived; the modulo form keeps a zero divisor loud (a panic), where is_multiple_of would return false silently
8627    fn cmp_rollback_replay_dev(
8628        &self,
8629        st: &Stage,
8630        ck_dev: &CmpCkptDev,
8631        n_commit: usize,
8632        t: usize,
8633        pos0: usize,
8634        shift: &mut CudaSlice<f32>,
8635        pend_kv: &mut CudaSlice<f32>,
8636        pend_score: &mut CudaSlice<f32>,
8637        blocks: &mut usize,
8638    ) -> Res<()> {
8639        if n_commit == t {
8640            return Ok(()); // fully committed: the in-place batch state is already exact
8641        }
8642        let stream = st.gpu.stream();
8643        let (ratio, latent, overlap) = (ck_dev.ratio, ck_dev.latent, ck_dev.overlap);
8644        stream
8645            .memcpy_dtod(&ck_dev.kv_snap, pend_kv)
8646            .map_err(e("rb kv snap"))?;
8647        stream
8648            .memcpy_dtod(&ck_dev.sc_snap, pend_score)
8649            .map_err(e("rb sc snap"))?;
8650        *blocks = ck_dev.n_blocks0;
8651        for i in 0..n_commit {
8652            let pos = pos0 + i;
8653            let slot = if overlap {
8654                ratio + pos % ratio
8655            } else {
8656                pos % ratio
8657            };
8658            {
8659                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8660                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8661                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row kv"))?;
8662                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8663                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8664                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row sc"))?;
8665            }
8666            if (pos + 1) % ratio != 0 {
8667                continue;
8668            }
8669            if overlap {
8670                {
8671                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8672                    let mut dst = shift.slice_mut(0..ratio * latent);
8673                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift1"))?;
8674                }
8675                {
8676                    let src = shift.slice(0..ratio * latent);
8677                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8678                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift2"))?;
8679                }
8680                {
8681                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8682                    let mut dst = shift.slice_mut(0..ratio * latent);
8683                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift3"))?;
8684                }
8685                {
8686                    let src = shift.slice(0..ratio * latent);
8687                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8688                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift4"))?;
8689                }
8690            }
8691            *blocks += 1;
8692        }
8693        Ok(())
8694    }
8695}
8696
8697impl Dsv4Gpu {
8698    /// One trunk block, BATCHED T-position verify (§3.1). Positions pos0..pos0+t-1,
8699    /// tokens `toks`. Input h is vws.h_a (or vws.h_rx right after a stage boundary);
8700    /// output lands in vws.h_a. Window-ring writes go to the layer's TRANSIENT kvc rows
8701    /// and every query's index list is built with the redirect, so the persistent ring
8702    /// is read-only for the whole round.
8703    #[allow(clippy::too_many_arguments)]
8704    fn block_verify_dev(
8705        &self,
8706        st: &Stage,
8707        layer: &LayerDev,
8708        cache: &mut LayerCache,
8709        lck: &mut LayerCkptDev,
8710        vws: &mut VerifyWs,
8711        input_rx: bool,
8712        pos0: usize,
8713        t: usize,
8714        toks: &[u32],
8715        host_math: bool,
8716    ) -> Res<()> {
8717        let d = self.model.cfg();
8718        let mc = &self.model.mc;
8719        let hc = d.hc_mult as usize;
8720        let hidden = mc.n_embd as usize;
8721        let heads = mc.n_head as usize;
8722        let hd = d.head_dim as usize;
8723        let rd = d.qk_rope_head_dim as usize;
8724        let q_lora = d.q_lora_rank as usize;
8725        let win = d.sliding_window as usize;
8726        let o_groups = d.o_groups as usize;
8727        let o_lora = d.o_lora_rank as usize;
8728        let eps = mc.rms_eps;
8729        let iters = d.hc_sinkhorn_iters;
8730        let hc_eps = d.hc_eps;
8731        let stream = st.gpu.stream();
8732        let fc_dev: *const f32 = if layer.ratio != 0 {
8733            st.fc_yarn.device_ptr(&stream).0 as *const f32
8734        } else {
8735            st.fc_plain.device_ptr(&stream).0 as *const f32
8736        };
8737        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
8738        let trans_base = lck.trans_base;
8739        let LayerCache {
8740            kvc,
8741            n_blocks,
8742            pend_kv,
8743            pend_score,
8744            ikvc,
8745            i_blocks,
8746            ipend_kv,
8747            ipend_score,
8748        } = cache;
8749
8750        // ---- attention sub-block
8751        let h_in_ptr: *const f32 = if input_rx {
8752            vws.h_rx.device_ptr(&stream).0 as *const f32
8753        } else {
8754            vws.h_a.device_ptr(&stream).0 as *const f32
8755        };
8756        self.hc_pre_batch_dev(
8757            st,
8758            h_in_ptr,
8759            &layer.hc_attn_fn,
8760            &layer.hc_attn_base,
8761            &layer.hc_attn_scale,
8762            &layer.hc_attn_base_dev,
8763            &layer.hc_attn_scale_dev,
8764            vws,
8765            t,
8766            hc,
8767            hidden,
8768            iters,
8769            hc_eps,
8770            host_math,
8771        )?;
8772        unsafe {
8773            ck(
8774                "rmsnorm attn batch",
8775                self.rmsnorm_arm(
8776                    dpf!(vws.y_hc, &stream),
8777                    dpf!(layer.attn_norm, &stream),
8778                    dpm!(vws.x, &stream),
8779                    t as i32,
8780                    hidden as i32,
8781                    eps,
8782                    sp(&stream),
8783                ),
8784            )?;
8785        }
8786
8787        // q path (weights read once for all t rows)
8788        Self::gemm_m_dev(
8789            st,
8790            vws.x.device_ptr(&stream).0 as *const f32,
8791            &mut vws.gemm_xb,
8792            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
8793            t,
8794            q_lora,
8795            hidden,
8796            vws.qr.device_ptr_mut(&stream).0 as *mut f32,
8797        )?;
8798        unsafe {
8799            ck(
8800                "rmsnorm q batch",
8801                self.rmsnorm_arm(
8802                    dpf!(vws.qr, &stream),
8803                    dpf!(layer.q_norm, &stream),
8804                    dpm!(vws.qr, &stream),
8805                    t as i32,
8806                    q_lora as i32,
8807                    eps,
8808                    sp(&stream),
8809                ),
8810            )?;
8811            ck(
8812                "cvt qr batch",
8813                k::memra_dsv4_cvt_bf16(
8814                    dpf!(vws.qr, &stream),
8815                    vws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
8816                    (t * q_lora) as i64,
8817                    sp(&stream),
8818                ),
8819            )?;
8820        }
8821        Self::gemv_m_dev(
8822            st,
8823            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
8824            vws.qr_b.device_ptr(&stream).0 as *const c_void,
8825            vws.q.device_ptr_mut(&stream).0 as *mut f32,
8826            t,
8827            heads * hd,
8828            q_lora,
8829            0,
8830            0,
8831        )?;
8832        unsafe {
8833            ck(
8834                "headrms batch",
8835                self.headrms_arm(
8836                    dpm!(vws.q, &stream),
8837                    (t * heads) as i32,
8838                    hd as i32,
8839                    eps,
8840                    sp(&stream),
8841                ),
8842            )?;
8843            ck(
8844                "rope q batch",
8845                k::memra_dsv4_rope(
8846                    dpm!(vws.q, &stream),
8847                    t as i32,
8848                    heads as i32,
8849                    hd as i32,
8850                    rd as i32,
8851                    fc_dev,
8852                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8853                    0,
8854                    sp(&stream),
8855                ),
8856            )?;
8857        }
8858
8859        // shared K==V latent rows + window QAT, then the TRANSIENT ring write
8860        Self::gemm_m_dev(
8861            st,
8862            vws.x.device_ptr(&stream).0 as *const f32,
8863            &mut vws.gemm_xb,
8864            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
8865            t,
8866            hd,
8867            hidden,
8868            vws.kv.device_ptr_mut(&stream).0 as *mut f32,
8869        )?;
8870        unsafe {
8871            ck(
8872                "rmsnorm kv batch",
8873                self.rmsnorm_arm(
8874                    dpf!(vws.kv, &stream),
8875                    dpf!(layer.kv_norm, &stream),
8876                    dpm!(vws.kv, &stream),
8877                    t as i32,
8878                    hd as i32,
8879                    eps,
8880                    sp(&stream),
8881                ),
8882            )?;
8883            ck(
8884                "rope kv batch",
8885                k::memra_dsv4_rope(
8886                    dpm!(vws.kv, &stream),
8887                    t as i32,
8888                    1,
8889                    hd as i32,
8890                    rd as i32,
8891                    fc_dev,
8892                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8893                    0,
8894                    sp(&stream),
8895                ),
8896            )?;
8897            ck(
8898                "act_quant kv batch",
8899                k::memra_dsv4_act_quant(
8900                    dpm!(vws.kv, &stream),
8901                    t as i32,
8902                    hd as i64,
8903                    (hd - rd) as i32,
8904                    64,
8905                    clamp_only,
8906                    sp(&stream),
8907                ),
8908            )?;
8909        }
8910        {
8911            let src = vws.kv.slice(0..t * hd);
8912            let mut dst = kvc.slice_mut(trans_base * hd..(trans_base + t) * hd);
8913            stream
8914                .memcpy_dtod(&src, &mut dst)
8915                .map_err(e("transient ring write"))?;
8916        }
8917
8918        // ---- per-position index lists (redirected) + compressor advances
8919        let mut slots = win;
8920        if layer.ratio != 0 {
8921            let ratio = layer.ratio;
8922            // the round's per-position block counts (host arithmetic, exactly the
8923            // sequential program's `(pos+1)/ratio`)
8924            let nbs: Vec<usize> = (0..t).map(|i| (pos0 + i + 1) / ratio).collect();
8925            if let Some(ix) = &layer.idx {
8926                // indexer q, batched
8927                Self::gemv_m_dev(
8928                    st,
8929                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
8930                    vws.qr_b.device_ptr(&stream).0 as *const c_void,
8931                    vws.qi.device_ptr_mut(&stream).0 as *mut f32,
8932                    t,
8933                    ix.heads * ix.hd,
8934                    q_lora,
8935                    0,
8936                    0,
8937                )?;
8938                unsafe {
8939                    ck(
8940                        "rope qi batch",
8941                        k::memra_dsv4_rope(
8942                            dpm!(vws.qi, &stream),
8943                            t as i32,
8944                            ix.heads as i32,
8945                            ix.hd as i32,
8946                            rd as i32,
8947                            fc_dev,
8948                            vws.pos_dev.device_ptr(&stream).0 as *const i32,
8949                            0,
8950                            sp(&stream),
8951                        ),
8952                    )?;
8953                    let scale = (ix.hd as f32).powf(-0.5);
8954                    ck(
8955                        "hadamard qi batch",
8956                        k::memra_dsv4_hadamard(
8957                            dpm!(vws.qi, &stream),
8958                            (t * ix.heads) as i32,
8959                            ix.hd as i32,
8960                            scale,
8961                            sp(&stream),
8962                        ),
8963                    )?;
8964                    ck(
8965                        "fp4 qi batch",
8966                        k::memra_dsv4_fp4_act_quant(
8967                            dpm!(vws.qi, &stream),
8968                            (t * ix.heads) as i32,
8969                            ix.hd as i64,
8970                            ix.hd as i32,
8971                            sp(&stream),
8972                        ),
8973                    )?;
8974                }
8975                // indexer weights projection, batched
8976                Self::gemm_m_dev(
8977                    st,
8978                    vws.x.device_ptr(&stream).0 as *const f32,
8979                    &mut vws.gemm_xb,
8980                    dwsel(
8981                        self.dense_fp8,
8982                        &stream,
8983                        &ix.weights_proj,
8984                        &ix.weights_proj_fp8,
8985                    ),
8986                    t,
8987                    ix.heads,
8988                    hidden,
8989                    vws.wproj.device_ptr_mut(&stream).0 as *mut f32,
8990                )?;
8991                // indexer compressor: batched projections + position-ordered state machine
8992                {
8993                    let VerifyWs {
8994                        x,
8995                        cmp_emit,
8996                        cmp_shift,
8997                        ..
8998                    } = vws;
8999                    self.cmp_decode_batch_dev(
9000                        st,
9001                        &ix.cmp,
9002                        x.device_ptr(&stream).0 as *const f32,
9003                        t,
9004                        pos0,
9005                        hidden,
9006                        &st.fc_yarn,
9007                        rd,
9008                        eps,
9009                        lck.idx.as_mut().expect("idx ckpt"),
9010                        cmp_emit,
9011                        cmp_shift,
9012                        ipend_kv.as_mut().expect("ipend"),
9013                        ipend_score.as_mut().expect("ipend"),
9014                        ikvc.as_mut().expect("ikvc"),
9015                        0,
9016                        i_blocks,
9017                    )?;
9018                }
9019                debug_assert_eq!(*i_blocks, nbs[t - 1], "indexer block count (batch)");
9020                let kks: Vec<usize> = nbs.iter().map(|&nb| ix.topk.min(nb)).collect();
9021                let tail_max = kks.iter().cloned().max().unwrap_or(0);
9022                slots = win + tail_max;
9023                for i in 0..t {
9024                    let pos = pos0 + i;
9025                    let idx_off = i * vws.idx_stride;
9026                    unsafe {
9027                        ck(
9028                            "build_idx_redirect fine",
9029                            k::memra_dsv4_build_idx_redirect(
9030                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9031                                    as *mut i32,
9032                                pos as i32,
9033                                win as i32,
9034                                0, // fine layers: -1 pads over the whole tail; top-k overwrites
9035                                slots as i32,
9036                                pos0 as i32,
9037                                trans_base as i32,
9038                                sp(&stream),
9039                            ),
9040                        )?;
9041                    }
9042                    let nb = nbs[i];
9043                    if nb == 0 {
9044                        continue;
9045                    }
9046                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
9047                    unsafe {
9048                        ck(
9049                            "indexer_score batch",
9050                            self.indexer_score_arm(
9051                                (vws.qi.device_ptr(&stream).0 as usize + i * ix.heads * ix.hd * 4)
9052                                    as *const f32,
9053                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
9054                                (vws.wproj.device_ptr(&stream).0 as usize + i * ix.heads * 4)
9055                                    as *const f32,
9056                                wscale,
9057                                dpm!(vws.score, &stream),
9058                                1,
9059                                ix.heads as i32,
9060                                ix.hd as i32,
9061                                nb as i32,
9062                                ratio as i32,
9063                                nb as i32,
9064                                sp(&stream),
9065                            ),
9066                        )?;
9067                    }
9068                    let kk = kks[i];
9069                    if host_math {
9070                        let score_h = {
9071                            let view = vws.score.slice(0..nb);
9072                            let mut v = vec![0f32; nb];
9073                            stream
9074                                .memcpy_dtoh(&view, &mut v[..])
9075                                .map_err(e("dtoh sc b"))?;
9076                            stream.synchronize().map_err(e("sync sc b"))?;
9077                            v
9078                        };
9079                        let mut order: Vec<usize> = (0..nb).collect();
9080                        order.sort_by(|&a, &b| {
9081                            score_h[b]
9082                                .partial_cmp(&score_h[a])
9083                                .unwrap_or(std::cmp::Ordering::Equal)
9084                                .then(a.cmp(&b))
9085                        });
9086                        let cidx: Vec<i32> = order
9087                            .into_iter()
9088                            .take(kk)
9089                            .map(|j| (j + win) as i32)
9090                            .collect();
9091                        let mut dst = vws.idx.slice_mut(idx_off + win..idx_off + win + kk);
9092                        stream
9093                            .memcpy_htod(&cidx, &mut dst)
9094                            .map_err(e("htod idx b"))?;
9095                    } else {
9096                        unsafe {
9097                            let idx_tail_ptr = (vws.idx.device_ptr_mut(&stream).0 as usize
9098                                + (idx_off + win) * 4)
9099                                as *mut i32;
9100                            ck(
9101                                "topk_idx batch",
9102                                k::memra_dsv4_topk_idx(
9103                                    dpf!(vws.score, &stream),
9104                                    nb as i32,
9105                                    kk as i32,
9106                                    win as i32,
9107                                    idx_tail_ptr,
9108                                    sp(&stream),
9109                                ),
9110                            )?;
9111                        }
9112                    }
9113                }
9114            } else {
9115                let tail_max = nbs.iter().cloned().max().unwrap_or(0);
9116                slots = win + tail_max;
9117                for (i, &nb_i) in nbs.iter().enumerate() {
9118                    let pos = pos0 + i;
9119                    let idx_off = i * vws.idx_stride;
9120                    unsafe {
9121                        ck(
9122                            "build_idx_redirect coarse",
9123                            k::memra_dsv4_build_idx_redirect(
9124                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9125                                    as *mut i32,
9126                                pos as i32,
9127                                win as i32,
9128                                nb_i as i32,
9129                                slots as i32,
9130                                pos0 as i32,
9131                                trans_base as i32,
9132                                sp(&stream),
9133                            ),
9134                        )?;
9135                    }
9136                }
9137            }
9138            // attention compressor: batched projections + position-ordered state machine
9139            {
9140                let VerifyWs {
9141                    x,
9142                    cmp_emit,
9143                    cmp_shift,
9144                    ..
9145                } = vws;
9146                self.cmp_decode_batch_dev(
9147                    st,
9148                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
9149                    x.device_ptr(&stream).0 as *const f32,
9150                    t,
9151                    pos0,
9152                    hidden,
9153                    &st.fc_yarn,
9154                    rd,
9155                    eps,
9156                    lck.cmp.as_mut().expect("cmp ckpt"),
9157                    cmp_emit,
9158                    cmp_shift,
9159                    pend_kv.as_mut().expect("pend"),
9160                    pend_score.as_mut().expect("pend"),
9161                    kvc,
9162                    win,
9163                    n_blocks,
9164                )?;
9165            }
9166            debug_assert_eq!(*n_blocks, nbs[t - 1], "attn block count (batch)");
9167        } else {
9168            for i in 0..t {
9169                let pos = pos0 + i;
9170                let idx_off = i * vws.idx_stride;
9171                unsafe {
9172                    ck(
9173                        "build_idx_redirect window-only",
9174                        k::memra_dsv4_build_idx_redirect(
9175                            (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4) as *mut i32,
9176                            pos as i32,
9177                            win as i32,
9178                            -1,
9179                            win as i32,
9180                            pos0 as i32,
9181                            trans_base as i32,
9182                            sp(&stream),
9183                        ),
9184                    )?;
9185                }
9186            }
9187        }
9188
9189        // sparse sink attention, T queries in one launch (uniform `slots`, -1 pads —
9190        // bit-inert by the pinned pad contract) + per-position de-rotation
9191        let scale = (hd as f64).powf(-0.5) as f32;
9192        unsafe {
9193            if self.chains_f32 {
9194                ck(
9195                    "sink_attn_dec_mq_f32acc",
9196                    k::memra_dsv4_sink_attn_dec_mq_f32acc(
9197                        dpf!(vws.q, &stream),
9198                        dpf!(kvc, &stream),
9199                        vws.idx.device_ptr(&stream).0 as *const i32,
9200                        dpf!(layer.sink, &stream),
9201                        dpm!(vws.sink_scores, &stream),
9202                        dpm!(vws.sink_evals, &stream),
9203                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f32,
9204                        dpm!(vws.o, &stream),
9205                        t as i32,
9206                        heads as i32,
9207                        hd as i32,
9208                        slots as i32,
9209                        vws.idx_stride as i32,
9210                        scale,
9211                        sp(&stream),
9212                    ),
9213                )?;
9214            } else {
9215                ck(
9216                    "sink_attn_dec_mq",
9217                    k::memra_dsv4_sink_attn_dec_mq(
9218                        dpf!(vws.q, &stream),
9219                        dpf!(kvc, &stream),
9220                        vws.idx.device_ptr(&stream).0 as *const i32,
9221                        dpf!(layer.sink, &stream),
9222                        dpm!(vws.sink_scores, &stream),
9223                        dpm!(vws.sink_evals, &stream),
9224                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
9225                        dpm!(vws.o, &stream),
9226                        t as i32,
9227                        heads as i32,
9228                        hd as i32,
9229                        slots as i32,
9230                        vws.idx_stride as i32,
9231                        scale,
9232                        sp(&stream),
9233                    ),
9234                )?;
9235            }
9236            ck(
9237                "rope o inv batch",
9238                k::memra_dsv4_rope(
9239                    dpm!(vws.o, &stream),
9240                    t as i32,
9241                    heads as i32,
9242                    hd as i32,
9243                    rd as i32,
9244                    fc_dev,
9245                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
9246                    1,
9247                    sp(&stream),
9248                ),
9249            )?;
9250        }
9251
9252        // grouped output projection: cvt o once, then per-group strided batched GEMVs
9253        let gw = heads / o_groups * hd;
9254        unsafe {
9255            ck(
9256                "cvt o batch",
9257                k::memra_dsv4_cvt_bf16(
9258                    dpf!(vws.o, &stream),
9259                    vws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
9260                    (t * heads * hd) as i64,
9261                    sp(&stream),
9262                ),
9263            )?;
9264        }
9265        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
9266        for g in 0..o_groups {
9267            Self::gemv_m_dev(
9268                st,
9269                wo_a_dw.offset_rows(g * o_lora, gw),
9270                (vws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
9271                (vws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
9272                t,
9273                o_lora,
9274                gw,
9275                heads * hd,
9276                o_groups * o_lora,
9277            )?;
9278        }
9279        Self::gemm_m_dev(
9280            st,
9281            vws.og.device_ptr(&stream).0 as *const f32,
9282            &mut vws.gemm_xb,
9283            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
9284            t,
9285            hidden,
9286            o_groups * o_lora,
9287            vws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
9288        )?;
9289
9290        // hc_post (attention) -> vws.h_b
9291        unsafe {
9292            ck(
9293                "hc_post attn batch",
9294                k::memra_dsv4_hc_post(
9295                    dpf!(vws.attn_out, &stream),
9296                    h_in_ptr,
9297                    dpf!(vws.post, &stream),
9298                    dpf!(vws.comb, &stream),
9299                    dpm!(vws.h_b, &stream),
9300                    t as i32,
9301                    hc as i32,
9302                    hidden as i32,
9303                    sp(&stream),
9304                ),
9305            )?;
9306        }
9307
9308        // ---- ffn sub-block (input vws.h_b, output vws.h_a)
9309        let h_b_ptr = vws.h_b.device_ptr(&stream).0 as *const f32;
9310        self.hc_pre_batch_dev(
9311            st,
9312            h_b_ptr,
9313            &layer.hc_ffn_fn,
9314            &layer.hc_ffn_base,
9315            &layer.hc_ffn_scale,
9316            &layer.hc_ffn_base_dev,
9317            &layer.hc_ffn_scale_dev,
9318            vws,
9319            t,
9320            hc,
9321            hidden,
9322            iters,
9323            hc_eps,
9324            host_math,
9325        )?;
9326        unsafe {
9327            ck(
9328                "rmsnorm ffn batch",
9329                self.rmsnorm_arm(
9330                    dpf!(vws.y_hc, &stream),
9331                    dpf!(layer.ffn_norm, &stream),
9332                    dpm!(vws.xf, &stream),
9333                    t as i32,
9334                    hidden as i32,
9335                    eps,
9336                    sp(&stream),
9337                ),
9338            )?;
9339        }
9340        self.moe_verify_dev(st, layer, vws, t, toks, host_math)?;
9341        unsafe {
9342            ck(
9343                "hc_post ffn batch",
9344                k::memra_dsv4_hc_post(
9345                    dpf!(vws.y, &stream),
9346                    dpf!(vws.h_b, &stream),
9347                    dpf!(vws.post, &stream),
9348                    dpf!(vws.comb, &stream),
9349                    dpm!(vws.h_a, &stream),
9350                    t as i32,
9351                    hc as i32,
9352                    hidden as i32,
9353                    sp(&stream),
9354                ),
9355            )?;
9356        }
9357        Ok(())
9358    }
9359
9360    /// MoE for T rows: per-position routing (the hash layers need the per-position TOKEN,
9361    /// which is why a round carries a token array), then ONE launch per projection over
9362    /// the whole T x topk slot set — routed-expert weight traffic scales with T (each
9363    /// position's experts are its own) while the shared expert and the gate amortize.
9364    fn moe_verify_dev(
9365        &self,
9366        st: &Stage,
9367        layer: &LayerDev,
9368        vws: &mut VerifyWs,
9369        t: usize,
9370        toks: &[u32],
9371        host_math: bool,
9372    ) -> Res<()> {
9373        let mc = &self.model.mc;
9374        let d = self.model.cfg();
9375        let moe = mc.moe.as_ref().expect("moe");
9376        let hidden = mc.n_embd as usize;
9377        let ne = moe.expert_count as usize;
9378        let topk = moe.expert_used_count as usize;
9379        let inter = moe.expert_ff_length as usize;
9380        let limit = d.swiglu_limit;
9381        let stream = st.gpu.stream();
9382        let kind = match layer.expert_kind {
9383            ExpertKind::Nvfp4 => 0i32,
9384            ExpertKind::Mxfp4 => 1i32,
9385        };
9386        let wstride = (inter * hidden / 2) as i64;
9387        let sstride = match layer.expert_kind {
9388            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
9389            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
9390        };
9391        let slots = t * topk;
9392
9393        self.dots_m_dev(
9394            st,
9395            vws.xf.device_ptr(&stream).0 as *const f32,
9396            layer.gate_w.device_ptr(&stream).0 as *const c_void,
9397            0,
9398            t,
9399            hidden,
9400            ne,
9401            vws.raw.device_ptr_mut(&stream).0 as *mut f32,
9402        )?;
9403        if host_math {
9404            let raw_h = {
9405                let view = vws.raw.slice(0..t * ne);
9406                let mut v = vec![0f32; t * ne];
9407                stream
9408                    .memcpy_dtoh(&view, &mut v[..])
9409                    .map_err(e("dtoh raw b"))?;
9410                stream.synchronize().map_err(e("sync raw b"))?;
9411                v
9412            };
9413            let (indices, weights) =
9414                Self::route_host(layer, &raw_h, toks, t, ne, topk, d.routed_scaling_factor);
9415            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
9416            let mut order = vec![0i32; t * topk];
9417            for p in 0..t {
9418                let mut o: Vec<i32> = (0..topk as i32).collect();
9419                o.sort_by_key(|&s| indices[p * topk + s as usize]);
9420                order[p * topk..(p + 1) * topk].copy_from_slice(&o);
9421            }
9422            let mut dst = vws.sel.slice_mut(0..t * topk);
9423            stream
9424                .memcpy_htod(&sel, &mut dst)
9425                .map_err(e("htod sel b"))?;
9426            let mut dst = vws.selw.slice_mut(0..t * topk);
9427            stream
9428                .memcpy_htod(&weights, &mut dst)
9429                .map_err(e("htod selw b"))?;
9430            let mut dst = vws.order.slice_mut(0..t * topk);
9431            stream
9432                .memcpy_htod(&order, &mut dst)
9433                .map_err(e("htod order b"))?;
9434        } else {
9435            unsafe {
9436                ck(
9437                    "route_m",
9438                    k::memra_dsv4_route_m(
9439                        dpf!(vws.raw, &stream),
9440                        layer
9441                            .gate_bias_dev
9442                            .as_ref()
9443                            .map(|b| b.device_ptr(&stream).0 as *const f32)
9444                            .unwrap_or(std::ptr::null()),
9445                        layer
9446                            .tid2eid_dev
9447                            .as_ref()
9448                            .map(|x| x.device_ptr(&stream).0 as *const i32)
9449                            .unwrap_or(std::ptr::null()),
9450                        vws.tok.device_ptr(&stream).0 as *const i32,
9451                        t as i32,
9452                        ne as i32,
9453                        topk as i32,
9454                        d.routed_scaling_factor,
9455                        vws.sel.device_ptr_mut(&stream).0 as *mut i32,
9456                        vws.selw.device_ptr_mut(&stream).0 as *mut f32,
9457                        vws.order.device_ptr_mut(&stream).0 as *mut i32,
9458                        sp(&stream),
9459                    ),
9460                )?;
9461            }
9462        }
9463
9464        unsafe {
9465            ck(
9466                "act_quant_fp8 x batch",
9467                k::memra_dsv4_act_quant_fp8(
9468                    dpf!(vws.xf, &stream),
9469                    vws.xq.device_ptr_mut(&stream).0 as *mut c_void,
9470                    dpm!(vws.xs, &stream),
9471                    t as i32,
9472                    hidden as i32,
9473                    sp(&stream),
9474                ),
9475            )?;
9476            for (proj, dst) in [(0i32, &mut vws.g1), (2i32, &mut vws.g3)] {
9477                ck(
9478                    "fp4_gemm_sel_g w1/w3",
9479                    k::memra_dsv4_fp4_gemm_sel_g(
9480                        dp!(vws.xq, &stream),
9481                        dpf!(vws.xs, &stream),
9482                        dp!(layer.experts_w, &stream),
9483                        dp!(layer.experts_sc, &stream),
9484                        dpf!(layer.experts_s2_dev, &stream),
9485                        vws.sel.device_ptr(&stream).0 as *const i32,
9486                        proj,
9487                        0,
9488                        kind,
9489                        dpm!(*dst, &stream),
9490                        slots as i32,
9491                        inter as i32,
9492                        hidden as i32,
9493                        wstride,
9494                        sstride,
9495                        topk as i32,
9496                        sp(&stream),
9497                    ),
9498                )?;
9499            }
9500            ck(
9501                "swiglu batch",
9502                k::memra_dsv4_swiglu(
9503                    dpf!(vws.g1, &stream),
9504                    dpf!(vws.g3, &stream),
9505                    dpm!(vws.hbuf, &stream),
9506                    slots as i32,
9507                    inter as i32,
9508                    limit,
9509                    vws.selw.device_ptr(&stream).0 as *const f32,
9510                    sp(&stream),
9511                ),
9512            )?;
9513            ck(
9514                "act_quant_fp8 h batch",
9515                k::memra_dsv4_act_quant_fp8(
9516                    dpf!(vws.hbuf, &stream),
9517                    vws.hq.device_ptr_mut(&stream).0 as *mut c_void,
9518                    dpm!(vws.hs, &stream),
9519                    slots as i32,
9520                    inter as i32,
9521                    sp(&stream),
9522                ),
9523            )?;
9524            ck(
9525                "fp4_gemm_sel_g w2",
9526                k::memra_dsv4_fp4_gemm_sel_g(
9527                    dp!(vws.hq, &stream),
9528                    dpf!(vws.hs, &stream),
9529                    dp!(layer.experts_w, &stream),
9530                    dp!(layer.experts_sc, &stream),
9531                    dpf!(layer.experts_s2_dev, &stream),
9532                    vws.sel.device_ptr(&stream).0 as *const i32,
9533                    1,
9534                    1,
9535                    kind,
9536                    dpm!(vws.contrib, &stream),
9537                    slots as i32,
9538                    hidden as i32,
9539                    inter as i32,
9540                    wstride,
9541                    sstride,
9542                    0,
9543                    sp(&stream),
9544                ),
9545            )?;
9546            ck(
9547                "combine_rows_m",
9548                k::memra_dsv4_combine_rows_m(
9549                    dpf!(vws.contrib, &stream),
9550                    vws.order.device_ptr(&stream).0 as *const i32,
9551                    topk as i32,
9552                    dpm!(vws.y, &stream),
9553                    hidden as i64,
9554                    t as i32,
9555                    sp(&stream),
9556                ),
9557            )?;
9558            ck(
9559                "cvt xb batch",
9560                k::memra_dsv4_cvt_bf16(
9561                    dpf!(vws.xf, &stream),
9562                    vws.xb.device_ptr_mut(&stream).0 as *mut c_void,
9563                    (t * hidden) as i64,
9564                    sp(&stream),
9565                ),
9566            )?;
9567        }
9568        let sh_inter = vws.sg1.len() / vws.tmax;
9569        Self::gemv_m_dev(
9570            st,
9571            dwsel(
9572                self.dense_fp8,
9573                &stream,
9574                &layer.shared_w[0],
9575                &layer.shared_fp8[0],
9576            ),
9577            vws.xb.device_ptr(&stream).0 as *const c_void,
9578            vws.sg1.device_ptr_mut(&stream).0 as *mut f32,
9579            t,
9580            sh_inter,
9581            hidden,
9582            0,
9583            0,
9584        )?;
9585        Self::gemv_m_dev(
9586            st,
9587            dwsel(
9588                self.dense_fp8,
9589                &stream,
9590                &layer.shared_w[2],
9591                &layer.shared_fp8[2],
9592            ),
9593            vws.xb.device_ptr(&stream).0 as *const c_void,
9594            vws.sg3.device_ptr_mut(&stream).0 as *mut f32,
9595            t,
9596            sh_inter,
9597            hidden,
9598            0,
9599            0,
9600        )?;
9601        unsafe {
9602            ck(
9603                "swiglu sh batch",
9604                k::memra_dsv4_swiglu(
9605                    dpf!(vws.sg1, &stream),
9606                    dpf!(vws.sg3, &stream),
9607                    dpm!(vws.shbuf, &stream),
9608                    t as i32,
9609                    sh_inter as i32,
9610                    limit,
9611                    std::ptr::null(),
9612                    sp(&stream),
9613                ),
9614            )?;
9615            ck(
9616                "cvt sh batch",
9617                k::memra_dsv4_cvt_bf16(
9618                    dpf!(vws.shbuf, &stream),
9619                    vws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
9620                    (t * sh_inter) as i64,
9621                    sp(&stream),
9622                ),
9623            )?;
9624        }
9625        Self::gemv_m_dev(
9626            st,
9627            dwsel(
9628                self.dense_fp8,
9629                &stream,
9630                &layer.shared_w[1],
9631                &layer.shared_fp8[1],
9632            ),
9633            vws.shb16.device_ptr(&stream).0 as *const c_void,
9634            vws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
9635            t,
9636            hidden,
9637            sh_inter,
9638            0,
9639            0,
9640        )?;
9641        unsafe {
9642            ck(
9643                "add shared batch",
9644                k::memra_dsv4_add_inplace(
9645                    dpm!(vws.y, &stream),
9646                    dpf!(vws.sh_out, &stream),
9647                    (t * hidden) as i64,
9648                    sp(&stream),
9649                ),
9650            )?;
9651        }
9652        Ok(())
9653    }
9654
9655    /// Head for T rows: the `head_logits_dev` program with the row count, and the vocab
9656    /// dots on the batched island kernel so the 1.06 GiB head slab is read ONCE per round
9657    /// instead of once per verified position.
9658    fn head_logits_batch_dev(&self, vws: &mut VerifyWs, t: usize, host_math: bool) -> Res<()> {
9659        let d = self.model.cfg();
9660        let mc = &self.model.mc;
9661        let hc = d.hc_mult as usize;
9662        let hidden = mc.n_embd as usize;
9663        let eps = mc.rms_eps;
9664        let last = self.stages.len() - 1;
9665        let st = &self.stages[last];
9666        let stream = st.gpu.stream();
9667        let w = hc * hidden;
9668        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
9669        let norm = st.trunk_norm.as_ref().expect("trunk norm");
9670        let vocab = vws.logits.len() / vws.tmax;
9671        self.dots_m_dev(
9672            st,
9673            vws.h_a.device_ptr(&stream).0 as *const f32,
9674            fn_w.device_ptr(&stream).0 as *const c_void,
9675            0,
9676            t,
9677            w,
9678            hc,
9679            vws.head_mixes.device_ptr_mut(&stream).0 as *mut f32,
9680        )?;
9681        unsafe {
9682            ck(
9683                "rowsq head batch",
9684                self.rowsq_scale_arm(
9685                    dpf!(vws.h_a, &stream),
9686                    dpm!(vws.head_mixes, &stream),
9687                    t as i32,
9688                    w as i32,
9689                    hc as i32,
9690                    eps,
9691                    sp(&stream),
9692                ),
9693            )?;
9694        }
9695        if host_math {
9696            let mut mixes_h = vec![0f32; t * hc];
9697            let view = vws.head_mixes.slice(0..t * hc);
9698            stream
9699                .memcpy_dtoh(&view, &mut mixes_h[..])
9700                .map_err(e("dtoh head mixes b"))?;
9701            stream.synchronize().map_err(e("sync head mixes b"))?;
9702            for p in 0..t {
9703                for c in 0..hc {
9704                    let m = mixes_h[p * hc + c];
9705                    mixes_h[p * hc + c] =
9706                        sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
9707                }
9708            }
9709            let mut dst = vws.head_pre.slice_mut(0..t * hc);
9710            stream
9711                .memcpy_htod(&mixes_h, &mut dst)
9712                .map_err(e("htod head pre b"))?;
9713        } else {
9714            unsafe {
9715                ck(
9716                    "hc_head_pre_m",
9717                    k::memra_dsv4_hc_head_pre_m(
9718                        dpf!(vws.head_mixes, &stream),
9719                        st.hc_head_scale_dev
9720                            .as_ref()
9721                            .expect("head scale dev")
9722                            .device_ptr(&stream)
9723                            .0 as *const f32,
9724                        st.hc_head_base_dev
9725                            .as_ref()
9726                            .expect("head base dev")
9727                            .device_ptr(&stream)
9728                            .0 as *const f32,
9729                        dpm!(vws.head_pre, &stream),
9730                        t as i32,
9731                        hc as i32,
9732                        d.hc_eps,
9733                        sp(&stream),
9734                    ),
9735                )?;
9736            }
9737        }
9738        unsafe {
9739            ck(
9740                "hc_collapse head batch",
9741                k::memra_dsv4_hc_collapse(
9742                    dpf!(vws.h_a, &stream),
9743                    dpf!(vws.head_pre, &stream),
9744                    dpm!(vws.collapsed, &stream),
9745                    t as i32,
9746                    hc as i32,
9747                    hidden as i32,
9748                    sp(&stream),
9749                ),
9750            )?;
9751            ck(
9752                "rmsnorm head batch",
9753                self.rmsnorm_arm(
9754                    dpf!(vws.collapsed, &stream),
9755                    dpf!(norm, &stream),
9756                    dpm!(vws.collapsed, &stream),
9757                    t as i32,
9758                    hidden as i32,
9759                    eps,
9760                    sp(&stream),
9761                ),
9762            )?;
9763        }
9764        let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
9765        self.dots_m_dev(
9766            st,
9767            vws.collapsed.device_ptr(&stream).0 as *const f32,
9768            head_ptr,
9769            1,
9770            t,
9771            hidden,
9772            vocab,
9773            vws.logits.device_ptr_mut(&stream).0 as *mut f32,
9774        )?;
9775        Ok(())
9776    }
9777}
9778
9779/// One verify round's bookkeeping (the device twin of `spec_oracle::SpecRound`).
9780pub struct SpecRoundGpu {
9781    pub start_pos: usize,
9782    pub drafts: Vec<u32>,
9783    pub accepts: usize,
9784    pub verified: usize,
9785    /// batch depth actually forwarded this round (T = 1 + verifiable drafts)
9786    pub t_batch: usize,
9787    /// STRUCTURAL depth ceiling for this round: min(k_drafts + 1, MEMRA_DSV4_SPEC_DEPTH,
9788    /// vstate.tmax) -- i.e. `t_batch` before the n_new budget is applied. `t_batch < t_cap`
9789    /// is exactly "the budget truncated this round", which is what `carry_pending` keys on.
9790    pub t_cap: usize,
9791    /// The drafter's fp32 per-slot confidence for this round's proposal (pre-sigmoid
9792    /// logits; the head is supervised on c* = 1 - TV, i.e. conditional acceptance
9793    /// probability). Banked per round so the DSpark Algorithm-1 scheduler can be scored
9794    /// offline against measured round costs -- never consumed by the round itself.
9795    pub confidence: Vec<f32>,
9796    /// tokens this round contributed to the output stream (head + accepted drafts)
9797    pub emitted: usize,
9798    /// wall time of the whole round — proposal, batched verify, commit/rollback, drafter
9799    /// ring advance — with the drafter stream synchronized at the round boundary so no
9800    /// work leaks into the next round's measurement. The A/B instrument.
9801    pub round_us: u64,
9802}
9803
9804pub struct SpecRunGpu {
9805    pub tokens: Vec<u32>,
9806    pub rounds: Vec<SpecRoundGpu>,
9807}
9808
9809impl Dsv4Gpu {
9810    /// Batched T=k+1 verify forward (§3.1): ONE trunk pass over `toks` at positions
9811    /// state.pos .. state.pos+T-1, logits for EVERY position (the accept walk needs them
9812    /// all), state advanced PROVISIONALLY for all T. Exactly one
9813    /// [`Self::commit_verify_dev`] must follow, which makes the accepted prefix permanent
9814    /// and rolls the rest back. The DSpark trunk tap is written for all T rows when
9815    /// `taps` is Some (rows 0..T-1 of the drafter's taps buffer).
9816    ///
9817    /// Returns (logits `[T, vocab]` when `want_logits`, per-position argmax `[T]`).
9818    pub fn verify_batch_dev(
9819        &self,
9820        toks: &[u32],
9821        state: &mut DecodeState,
9822        vstate: &mut VerifyState,
9823        taps: Option<&mut CudaSlice<f32>>,
9824        want_logits: bool,
9825    ) -> Res<(Option<Vec<f32>>, Vec<u32>)> {
9826        let DecodePath::Device { host_math } = self.decode_path else {
9827            return Err("verify_batch_dev requires MEMRA_DSV4_DECODE_PATH=device".into());
9828        };
9829        let mc = &self.model.mc;
9830        let d = self.model.cfg();
9831        let t = toks.len();
9832        assert!(
9833            t >= 1 && t <= vstate.tmax,
9834            "round depth {t} > tmax {}",
9835            vstate.tmax
9836        );
9837        assert!(vstate.open.is_none(), "verify_batch_dev with an open round");
9838        let pos0 = state.pos;
9839        assert!(pos0 > 0, "batched verify needs prefill_with_cache first");
9840        assert!(
9841            pos0 + t <= self.max_seq,
9842            "round [{pos0}, {}) exceeds max_seq {}",
9843            pos0 + t,
9844            self.max_seq
9845        );
9846        let hidden = mc.n_embd as usize;
9847        let hc = d.hc_mult as usize;
9848        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
9849        let tok_i32: Vec<i32> = toks.iter().map(|&x| x as i32).collect();
9850        let pos_i32: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
9851
9852        // per-stage round constants (the hash layers read the token array; every layer's
9853        // ropes read the position array — both live on whichever stage the layer does)
9854        for (si, st) in self.stages.iter().enumerate() {
9855            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx round"))?;
9856            let stream = st.gpu.stream();
9857            let vws = &mut vstate.ws[si];
9858            let mut dst = vws.tok.slice_mut(0..t);
9859            stream
9860                .memcpy_htod(&tok_i32, &mut dst)
9861                .map_err(e("htod tok round"))?;
9862            let mut dst = vws.pos_dev.slice_mut(0..t);
9863            stream
9864                .memcpy_htod(&pos_i32, &mut dst)
9865                .map_err(e("htod pos round"))?;
9866        }
9867
9868        // stage 0: tokens -> embed rows -> hc state
9869        {
9870            let st0 = &self.stages[0];
9871            st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0 round"))?;
9872            let stream0 = st0.gpu.stream();
9873            let vws0 = &mut vstate.ws[0];
9874            unsafe {
9875                ck(
9876                    "embed_rows batch",
9877                    k::memra_dsv4_embed_rows(
9878                        st0.embed
9879                            .as_ref()
9880                            .expect("embed on stage 0")
9881                            .device_ptr(&stream0)
9882                            .0 as *const c_void,
9883                        vws0.tok.device_ptr(&stream0).0 as *const i32,
9884                        dpm!(vws0.emb, &stream0),
9885                        t as i32,
9886                        hidden as i32,
9887                        sp(&stream0),
9888                    ),
9889                )?;
9890                ck(
9891                    "repeat_hc batch",
9892                    k::memra_dsv4_repeat_hc(
9893                        dpf!(vws0.emb, &stream0),
9894                        dpm!(vws0.h_a, &stream0),
9895                        t as i32,
9896                        hc as i32,
9897                        hidden as i32,
9898                        sp(&stream0),
9899                    ),
9900                )?;
9901            }
9902        }
9903
9904        let targets = self.dspark.as_ref().map(|ds| ds.targets.clone());
9905        let n_t = targets.as_ref().map(|x| x.len()).unwrap_or(0);
9906        let mut taps = taps;
9907        let mut cur_stage = 0usize;
9908        let mut input_rx = false;
9909        for il in 0..n_trunk {
9910            let stage = self.layer_stage[il];
9911            if stage != cur_stage {
9912                let bytes = t * hc * hidden * std::mem::size_of::<f32>();
9913                let src_stream = self.stages[cur_stage].gpu.stream();
9914                let dst_stream = self.stages[stage].gpu.stream();
9915                let (ws_src, ws_dst) = vstate.ws.split_at_mut(stage);
9916                let src_ws = &ws_src[cur_stage];
9917                let dst_ws = &mut ws_dst[0];
9918                self.stages[cur_stage]
9919                    .gpu
9920                    .ctx
9921                    .bind_to_thread()
9922                    .map_err(e("bind tx round"))?;
9923                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
9924                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
9925                unsafe {
9926                    cudarc::driver::result::memcpy_peer_async(
9927                        self.stages[stage].gpu.ctx.cu_ctx(),
9928                        dp_,
9929                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
9930                        sp_,
9931                        bytes,
9932                        src_stream.cu_stream(),
9933                    )
9934                    .map_err(e("peer copy h round"))?;
9935                }
9936                let bnd = stage - 1;
9937                self.boundary_ev[bnd]
9938                    .record(&src_stream)
9939                    .map_err(e("ev record round"))?;
9940                dst_stream
9941                    .wait(&self.boundary_ev[bnd])
9942                    .map_err(e("ev wait round"))?;
9943                self.stages[stage]
9944                    .gpu
9945                    .ctx
9946                    .bind_to_thread()
9947                    .map_err(e("bind rx round"))?;
9948                cur_stage = stage;
9949                input_rx = true;
9950            }
9951            let st = &self.stages[stage];
9952            let lidx = st
9953                .layers
9954                .iter()
9955                .position(|l| l.il == il as u32)
9956                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
9957            self.block_verify_dev(
9958                st,
9959                &st.layers[lidx],
9960                &mut state.caches[il],
9961                &mut vstate.layers[il],
9962                &mut vstate.ws[stage],
9963                input_rx,
9964                pos0,
9965                t,
9966                toks,
9967                host_math,
9968            )?;
9969            input_rx = false;
9970            // DSpark trunk tap for all T rows (capture only)
9971            if let (Some(tp), Some(tg)) = (taps.as_mut(), targets.as_ref())
9972                && let Some(kk) = tg.iter().position(|&tl| tl == il)
9973            {
9974                let stream = self.stages[stage].gpu.stream();
9975                let vws = &mut vstate.ws[stage];
9976                unsafe {
9977                    ck(
9978                        "hc_mean tap batch",
9979                        k::memra_dsv4_hc_mean(
9980                            dpf!(vws.h_a, &stream),
9981                            dpm!(vws.tap_tmp, &stream),
9982                            t as i32,
9983                            hc as i32,
9984                            hidden as i32,
9985                            sp(&stream),
9986                        ),
9987                    )?;
9988                    ck(
9989                        "place_cols tap batch",
9990                        k::memra_dsv4_place_cols(
9991                            dpf!(vws.tap_tmp, &stream),
9992                            dpm!(**tp, &stream),
9993                            t as i32,
9994                            hidden as i32,
9995                            (n_t * hidden) as i64,
9996                            (kk * hidden) as i64,
9997                            sp(&stream),
9998                        ),
9999                    )?;
10000                }
10001            }
10002        }
10003
10004        let last = self.stages.len() - 1;
10005        assert_eq!(cur_stage, last, "device path expects the head stage last");
10006        self.head_logits_batch_dev(&mut vstate.ws[last], t, host_math)?;
10007        let stream_last = self.stages[last].gpu.stream();
10008        let vws = &mut vstate.ws[last];
10009        let vocab = vws.logits.len() / vws.tmax;
10010        let logits = if want_logits {
10011            let mut v = vec![0f32; t * vocab];
10012            let view = vws.logits.slice(0..t * vocab);
10013            stream_last
10014                .memcpy_dtoh(&view, &mut v[..])
10015                .map_err(e("dtoh logits batch"))?;
10016            stream_last.synchronize().map_err(e("sync logits batch"))?;
10017            Some(v)
10018        } else {
10019            None
10020        };
10021        let mut am = vec![0i32; t];
10022        if let Some(lg) = &logits {
10023            for (i, slot) in am.iter_mut().enumerate() {
10024                let row = &lg[i * vocab..(i + 1) * vocab];
10025                let mut best = 0usize;
10026                for j in 1..vocab {
10027                    if row[j] > row[best] {
10028                        best = j;
10029                    }
10030                }
10031                *slot = best as i32;
10032            }
10033        } else {
10034            unsafe {
10035                for i in 0..t {
10036                    ck(
10037                        "argmax batch",
10038                        k::memra_dsv4_argmax(
10039                            (vws.logits.device_ptr(&stream_last).0 as usize + i * vocab * 4)
10040                                as *const f32,
10041                            vocab as i64,
10042                            (vws.argmax.device_ptr_mut(&stream_last).0 as usize + i * 4)
10043                                as *mut i32,
10044                            sp(&stream_last),
10045                        ),
10046                    )?;
10047                }
10048            }
10049            let view = vws.argmax.slice(0..t);
10050            stream_last
10051                .memcpy_dtoh(&view, &mut am[..])
10052                .map_err(e("dtoh argmax batch"))?;
10053            stream_last.synchronize().map_err(e("sync argmax batch"))?;
10054        }
10055        vstate.open = Some((pos0, t));
10056        Ok((logits, am.into_iter().map(|x| x as u32).collect()))
10057    }
10058
10059    /// Commit the first `n_commit` positions of the open round and roll the rest back
10060    /// (§3.1 invariant: every trunk cache class ends bit-identical to plain sequential
10061    /// decode of exactly the committed positions). Ring slots take their transient rows;
10062    /// the compressors replay; the append-only stores fall back to their high-water mark.
10063    pub fn commit_verify_dev(
10064        &self,
10065        state: &mut DecodeState,
10066        vstate: &mut VerifyState,
10067        n_commit: usize,
10068    ) -> Res<()> {
10069        let (pos0, t) = vstate
10070            .open
10071            .take()
10072            .ok_or_else(|| "commit_verify_dev without an open round".to_string())?;
10073        assert!(
10074            n_commit >= 1 && n_commit <= t,
10075            "commit {n_commit} outside round width {t}"
10076        );
10077        let d = self.model.cfg();
10078        let mc = &self.model.mc;
10079        let hd = d.head_dim as usize;
10080        let win = d.sliding_window as usize;
10081        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
10082        let slot_rows: Vec<i32> = (0..n_commit).map(|j| ((pos0 + j) % win) as i32).collect();
10083        for il in 0..n_trunk {
10084            let stage = self.layer_stage[il];
10085            let st = &self.stages[stage];
10086            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx commit"))?;
10087            let stream = st.gpu.stream();
10088            let lck = &mut vstate.layers[il];
10089            let vws = &mut vstate.ws[stage];
10090            let cache = &mut state.caches[il];
10091            let trans_base = lck.trans_base;
10092            // ring commit: bounce out the transient rows (same allocation as the ring),
10093            // then scatter to slot (pos0+j) % win in one launch
10094            {
10095                let src = cache
10096                    .kvc
10097                    .slice(trans_base * hd..(trans_base + n_commit) * hd);
10098                let mut dst = vws.bounce.slice_mut(0..n_commit * hd);
10099                stream
10100                    .memcpy_dtod(&src, &mut dst)
10101                    .map_err(e("commit bounce"))?;
10102            }
10103            {
10104                let mut dst = vws.slot_rows.slice_mut(0..n_commit);
10105                stream
10106                    .memcpy_htod(&slot_rows, &mut dst)
10107                    .map_err(e("htod slot rows"))?;
10108            }
10109            unsafe {
10110                ck(
10111                    "scatter_rows commit",
10112                    k::memra_dsv4_scatter_rows(
10113                        dpf!(vws.bounce, &stream),
10114                        dpm!(cache.kvc, &stream),
10115                        vws.slot_rows.device_ptr(&stream).0 as *const i32,
10116                        n_commit as i32,
10117                        hd as i32,
10118                        sp(&stream),
10119                    ),
10120                )?;
10121            }
10122            if let Some(ckd) = &lck.cmp {
10123                self.cmp_rollback_replay_dev(
10124                    st,
10125                    ckd,
10126                    n_commit,
10127                    t,
10128                    pos0,
10129                    &mut vws.cmp_shift,
10130                    cache.pend_kv.as_mut().expect("pend kv"),
10131                    cache.pend_score.as_mut().expect("pend sc"),
10132                    &mut cache.n_blocks,
10133                )?;
10134            }
10135            if let Some(ckd) = &lck.idx {
10136                self.cmp_rollback_replay_dev(
10137                    st,
10138                    ckd,
10139                    n_commit,
10140                    t,
10141                    pos0,
10142                    &mut vws.cmp_shift,
10143                    cache.ipend_kv.as_mut().expect("ipend kv"),
10144                    cache.ipend_score.as_mut().expect("ipend sc"),
10145                    &mut cache.i_blocks,
10146                )?;
10147            }
10148        }
10149        for st in &self.stages {
10150            st.gpu
10151                .ctx
10152                .bind_to_thread()
10153                .map_err(e("bind ctx commit sync"))?;
10154            st.gpu.stream().synchronize().map_err(e("commit sync"))?;
10155        }
10156        state.pos = pos0 + n_commit;
10157        Ok(())
10158    }
10159
10160    /// The device propose-then-verify greedy loop with BATCHED verification — the
10161    /// engine-side twin of `spec_oracle::run_spec_greedy_batched`, including its
10162    /// round/budget accounting (the budget-truncated final round and its pending-carry
10163    /// no-propose tail), so proposal streams and token streams are comparable
10164    /// item-for-item with the CPU oracle's.
10165    ///
10166    /// Greedy law: the trunk's own argmax is ALWAYS the emitted token, so the output
10167    /// stream is plain greedy by construction — and because every batched kernel on this
10168    /// path is bit-exact against its single-position twin, that identity is byte-exact on
10169    /// device too, not merely mathematical.
10170    /// Reads the `MEMRA_DSV4_SPEC_DEPTH` knob and delegates to
10171    /// [`Self::spec_greedy_batched_depth`]. Every existing gate and bench calls this form,
10172    /// so their behaviour is decided by the environment exactly as before.
10173    pub fn spec_greedy_batched_with(
10174        &self,
10175        prompt: &[u32],
10176        n_new: usize,
10177        state: &mut DecodeState,
10178        dstate: &mut DsparkState,
10179        vstate: &mut VerifyState,
10180    ) -> Res<SpecRunGpu> {
10181        // MEMRA_DSV4_SPEC_DEPTH=T: structural cap on the batched verify depth (T rows =
10182        // 1 head + T-1 verified drafts). Unset or 0 => no cap, which reproduces the
10183        // pre-knob driver exactly. Clamped to >= 1 so a typo cannot ask for a zero-row
10184        // verify.
10185        let depth_cap = std::env::var("MEMRA_DSV4_SPEC_DEPTH")
10186            .ok()
10187            .and_then(|v| v.trim().parse::<usize>().ok())
10188            .filter(|t| *t > 0)
10189            .unwrap_or(usize::MAX)
10190            .max(1);
10191        if depth_cap != usize::MAX {
10192            println!("[spec] verify depth capped at T={depth_cap} (MEMRA_DSV4_SPEC_DEPTH)");
10193        }
10194        self.spec_greedy_batched_depth(prompt, n_new, state, dstate, vstate, depth_cap)
10195    }
10196
10197    /// [`Self::spec_greedy_batched_with`] with the verify-depth ceiling passed explicitly.
10198    /// `usize::MAX` means "no cap" (the drafter's own `block_size + 1`).
10199    ///
10200    /// Greedy identity is preserved at every cap by construction: truncating the proposal
10201    /// only shortens the accepted prefix, and the head token of every round is the trunk's
10202    /// own argmax. That is what makes a depth sweep measurable without re-earning the
10203    /// identity law at each rung -- though the sweep still asserts it per arm.
10204    pub fn spec_greedy_batched_depth(
10205        &self,
10206        prompt: &[u32],
10207        n_new: usize,
10208        state: &mut DecodeState,
10209        dstate: &mut DsparkState,
10210        vstate: &mut VerifyState,
10211        depth_cap: usize,
10212    ) -> Res<SpecRunGpu> {
10213        // ds4f rung 1: confidence-window policy, read once per run (see resolve_vt).
10214        // Off reproduces the pre-policy t_cap expression exactly (vt_drafts == k_drafts).
10215        let vt = resolve_vt(
10216            std::env::var("MEMRA_DSV4_VT").ok().as_deref(),
10217            std::env::var("MEMRA_DSV4_VT_TAU").ok().as_deref(),
10218            std::env::var("MEMRA_DSV4_VT_FLOOR").ok().as_deref(),
10219        )?;
10220        self.spec_greedy_batched_policy(prompt, n_new, state, dstate, vstate, depth_cap, vt)
10221    }
10222
10223    /// [`Self::spec_greedy_batched_depth`] with the vt policy passed EXPLICITLY — the
10224    /// in-process multi-arm sweep entry (one load, one thermal window; the env seam
10225    /// stays the serving/gate path). `Dsv4Vt::Off` + the same depth_cap is
10226    /// byte-identical to the env path with `MEMRA_DSV4_VT` unset.
10227    #[allow(clippy::too_many_arguments)]
10228    #[allow(clippy::too_many_arguments)]
10229    pub fn spec_greedy_batched_policy(
10230        &self,
10231        prompt: &[u32],
10232        n_new: usize,
10233        state: &mut DecodeState,
10234        dstate: &mut DsparkState,
10235        vstate: &mut VerifyState,
10236        depth_cap: usize,
10237        vt: Dsv4Vt,
10238    ) -> Res<SpecRunGpu> {
10239        self.spec_greedy_batched_stream(prompt, n_new, state, dstate, vstate, depth_cap, vt, None)
10240    }
10241
10242    /// ds4f rung 3 — [`Self::spec_greedy_batched_policy`] with a per-round COMMIT
10243    /// callback: `round_cb` receives every newly committed token slice after the
10244    /// round's ring writes + close sync (i.e. the tokens are final), and returning
10245    /// `false` stops generation at that round boundary — the serve door's streaming,
10246    /// EOS/stop-string, and client-disconnect cancel all ride this one seam. `None`
10247    /// is byte-identical to the gated driver (the closure is never constructed).
10248    #[allow(clippy::too_many_arguments)]
10249    #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
10250    pub fn spec_greedy_batched_stream(
10251        &self,
10252        prompt: &[u32],
10253        n_new: usize,
10254        state: &mut DecodeState,
10255        dstate: &mut DsparkState,
10256        vstate: &mut VerifyState,
10257        depth_cap: usize,
10258        vt: Dsv4Vt,
10259        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10260    ) -> Res<SpecRunGpu> {
10261        let p0 = prompt.len();
10262        assert!(n_new >= 1, "n_new must be positive");
10263        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10264        let mut t_tok = {
10265            let lg = &pre.logits;
10266            let mut best = 0usize;
10267            for i in 1..lg.len() {
10268                if lg[i] > lg[best] {
10269                    best = i;
10270                }
10271            }
10272            best as u32
10273        };
10274        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10275        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10276        let mut mh_row = 0usize; // taps row holding the tap of the position behind `t_tok`
10277        let mut carry_pending = false;
10278        // MEMRA_DSV4_BENCH_PROFILE=1: bracket steady-state ROUNDS [4, 12) with
10279        // cudaProfilerStart/Stop so `nsys profile -c cudaProfilerApi` captures only
10280        // rounds — no load, no prefill/prime, no warmup. Read ONCE (never per round).
10281        // Profiling runs are rung-0 instruments, never A/B observations.
10282        let profile_bracket = std::env::var("MEMRA_DSV4_BENCH_PROFILE").as_deref() == Ok("1");
10283        let depth_cap = depth_cap.max(1);
10284        if let Dsv4Vt::Slot { tau_logit, floor } = vt {
10285            println!(
10286                "[spec] vt policy: slot (tau_logit {tau_logit:.6}, floor {floor}) — \
10287                 per-round verify window from the confidence head"
10288            );
10289        }
10290        while tokens.len() < n_new {
10291            if profile_bracket && rounds.len() == 4 {
10292                cudarc::driver::safe::profiler_start().map_err(e("profiler_start"))?;
10293            }
10294            if profile_bracket && rounds.len() == 12 {
10295                cudarc::driver::safe::profiler_stop().map_err(e("profiler_stop"))?;
10296            }
10297            let cb_from = tokens.len();
10298            if carry_pending {
10299                tokens.push(t_tok);
10300                if let Some(cb) = round_cb.as_deref_mut() {
10301                    cb(&tokens[cb_from..]);
10302                }
10303                break;
10304            }
10305            let round_t0 = std::time::Instant::now();
10306            let prof_stream = if dsv4_prof_on() {
10307                Some(self.stages[self.stages.len() - 1].gpu.stream())
10308            } else {
10309                None
10310            };
10311            let _p_round = phase!("round", prof_stream.as_ref());
10312            let m0 = p0 + tokens.len();
10313            let prop = {
10314                let _p = phase!("1.drafter_forward", prof_stream.as_ref());
10315                self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?
10316            };
10317            let k_drafts = prop.out_ids.len() - 1;
10318            tokens.push(t_tok);
10319            if tokens.len() == n_new {
10320                rounds.push(SpecRoundGpu {
10321                    start_pos: m0 - 1,
10322                    drafts: prop.out_ids[1..].to_vec(),
10323                    accepts: 0,
10324                    verified: 0,
10325                    t_batch: 0,
10326                    t_cap: 0,
10327                    confidence: prop.confidence.clone(),
10328                    emitted: 1,
10329                    round_us: round_t0.elapsed().as_micros() as u64,
10330                });
10331                if let Some(cb) = round_cb.as_deref_mut() {
10332                    cb(&tokens[cb_from..]);
10333                }
10334                break;
10335            }
10336            let forwards_left = n_new - tokens.len();
10337            // STRUCTURAL ceiling (drafts available / depth knob / vt window /
10338            // verify-state capacity), then the n_new BUDGET on top. Keeping them
10339            // separate is what lets the depth knob (and the vt window, which is a
10340            // per-round depth) shorten a round without it looking like "we ran out of
10341            // tokens" — carry_pending below fires on the BUDGET only.
10342            let vt_drafts = match vt {
10343                Dsv4Vt::Off => k_drafts,
10344                Dsv4Vt::Slot { tau_logit, floor } => {
10345                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10346                }
10347            };
10348            let t_cap = (vt_drafts + 1)
10349                .min(k_drafts + 1)
10350                .min(depth_cap)
10351                .min(vstate.tmax);
10352            let t_batch = t_cap.min(forwards_left);
10353            let kv = t_batch - 1;
10354            let mut batch_ids = Vec::with_capacity(t_batch);
10355            batch_ids.push(t_tok);
10356            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10357            let (_, am) = {
10358                let _p = phase!("2.verify_batch", prof_stream.as_ref());
10359                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), false)?
10360            };
10361            // accept walk: row i (position m0+i) arbitrates draft i+1
10362            let mut c_d = 0usize;
10363            let mut t_next = 0u32;
10364            for i in 0..t_batch {
10365                let a = am[i];
10366                if i < kv && a == batch_ids[i + 1] {
10367                    c_d += 1;
10368                    continue;
10369                }
10370                t_next = a;
10371                break;
10372            }
10373            let n_commit = c_d + 1;
10374            {
10375                let _p = phase!("3.commit_rollback", prof_stream.as_ref());
10376                self.commit_verify_dev(state, vstate, n_commit)?;
10377            }
10378            // drafter rings advance for EVERY accepted position and no rejected one
10379            {
10380                let _p = phase!("4.ring_writes", prof_stream.as_ref());
10381                for i in 0..n_commit {
10382                    self.dspark_write_rings(dstate, i, m0 + i)?;
10383                }
10384            }
10385            {
10386                let _p = phase!("5.round_close_sync", None);
10387                // close the round on device too, so the ring advance is inside THIS
10388                // round's measurement and not the next one's
10389                let last = self.stages.len() - 1;
10390                self.stages[last]
10391                    .gpu
10392                    .stream()
10393                    .synchronize()
10394                    .map_err(e("round close sync"))?;
10395            }
10396            mh_row = c_d;
10397            for i in 0..c_d {
10398                tokens.push(batch_ids[i + 1]);
10399            }
10400            // Carry (= stop after emitting the bonus token) only when the n_new BUDGET
10401            // truncated this round -- never when the depth knob did. Identical to the old
10402            // `kv < k_drafts` whenever the knob is unset and vstate.tmax >= k_drafts + 1.
10403            carry_pending = c_d == kv && t_batch < t_cap;
10404            rounds.push(SpecRoundGpu {
10405                start_pos: m0 - 1,
10406                drafts: prop.out_ids[1..].to_vec(),
10407                accepts: c_d,
10408                verified: (c_d + 1).min(kv),
10409                t_batch,
10410                t_cap,
10411                confidence: prop.confidence.clone(),
10412                emitted: 1 + c_d,
10413                round_us: round_t0.elapsed().as_micros() as u64,
10414            });
10415            t_tok = t_next;
10416            if let Some(cb) = round_cb.as_deref_mut()
10417                && !cb(&tokens[cb_from..])
10418            {
10419                break;
10420            }
10421        }
10422        Ok(SpecRunGpu { tokens, rounds })
10423    }
10424
10425    /// [`Self::spec_greedy_batched_with`] with freshly allocated state (gate shape).
10426    pub fn spec_greedy_batched(&self, prompt: &[u32], n_new: usize) -> Res<SpecRunGpu> {
10427        let mut state = self.alloc_decode_state()?;
10428        let mut dstate = self.dspark_alloc_state()?;
10429        let mut vstate = self.alloc_verify_state()?;
10430        self.spec_greedy_batched_with(prompt, n_new, &mut state, &mut dstate, &mut vstate)
10431    }
10432
10433    /// ds4f rung 2 (slice 1) — the SAMPLED propose-then-verify loop (it5 item 8).
10434    ///
10435    /// A deliberate near-copy of [`Self::spec_greedy_batched_policy`] with the accept
10436    /// walk arbitrated by POSITION-KEYED seeded target draws instead of argmax — the
10437    /// gated greedy driver's bytes are not touched (its accept-sha receipts stay the
10438    /// witness; a shared parameterized loop would put those bytes at refactor risk for
10439    /// zero measurement gain). Identity law: the emitted stream equals the plain
10440    /// sampled stream at the same seed BY CONSTRUCTION — row i of the batched verify
10441    /// is bit-exact against the sequential step's row at the same position (the it3
10442    /// gate (c) proof) and [`dsv4_sample_row`] is a pure function of (row, pos, seed).
10443    /// The drafter proposes greedily (deterministic one-hot proposal); a draft is
10444    /// accepted iff it EQUALS the target draw at its position — the correct
10445    /// arbitration for a one-hot proposal (full min(1, p/q) rejection sampling
10446    /// degenerates to exactly this when q is one-hot). Penalties are slice 2 and NOT
10447    /// claimed here.
10448    #[allow(clippy::too_many_arguments)]
10449    pub fn spec_sampled_batched_policy(
10450        &self,
10451        prompt: &[u32],
10452        n_new: usize,
10453        state: &mut DecodeState,
10454        dstate: &mut DsparkState,
10455        vstate: &mut VerifyState,
10456        depth_cap: usize,
10457        vt: Dsv4Vt,
10458        sample: &Dsv4SampleCfg,
10459    ) -> Res<SpecRunGpu> {
10460        self.spec_sampled_batched_stream(
10461            prompt, n_new, state, dstate, vstate, depth_cap, vt, sample, None,
10462        )
10463    }
10464
10465    /// [`Self::spec_sampled_batched_policy`] with the rung-3 per-round commit callback
10466    /// (see [`Self::spec_greedy_batched_stream`] — same seam, same None-is-byte-identical
10467    /// contract).
10468    #[allow(clippy::too_many_arguments)]
10469    #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
10470    pub fn spec_sampled_batched_stream(
10471        &self,
10472        prompt: &[u32],
10473        n_new: usize,
10474        state: &mut DecodeState,
10475        dstate: &mut DsparkState,
10476        vstate: &mut VerifyState,
10477        depth_cap: usize,
10478        vt: Dsv4Vt,
10479        sample: &Dsv4SampleCfg,
10480        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10481    ) -> Res<SpecRunGpu> {
10482        self.spec_sampled_batched_pen(
10483            prompt,
10484            n_new,
10485            state,
10486            dstate,
10487            vstate,
10488            depth_cap,
10489            vt,
10490            sample,
10491            None,
10492            round_cb.take(),
10493        )
10494    }
10495
10496    /// ds4f rung-2 slice 2 — the sampled driver with PENALTIES over the true
10497    /// per-state window (row-incremental: row r penalizes over prompt ++ committed
10498    /// ++ this round's accepts before r — the q38 penalized-sampled law). `None` is
10499    /// byte-identical to the unpenalized driver. Identity vs the plain penalized
10500    /// loop is structural for the same reason as the unpenalized path: the window at
10501    /// a given position is a pure function of the shared committed prefix.
10502    #[allow(clippy::too_many_arguments)]
10503    #[allow(clippy::type_complexity)] // allow: one-shot composite type; naming it would hide the shape that matters at the call site
10504    pub fn spec_sampled_batched_pen(
10505        &self,
10506        prompt: &[u32],
10507        n_new: usize,
10508        state: &mut DecodeState,
10509        dstate: &mut DsparkState,
10510        vstate: &mut VerifyState,
10511        depth_cap: usize,
10512        vt: Dsv4Vt,
10513        sample: &Dsv4SampleCfg,
10514        pen: Option<&Dsv4PenaltyCfg>,
10515        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10516    ) -> Res<SpecRunGpu> {
10517        let p0 = prompt.len();
10518        assert!(n_new >= 1, "n_new must be positive");
10519        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10520        // token at absolute position p0 (output index 0): the seeded draw, keyed p0
10521        let mut t_tok = if let Some(pc) = pen {
10522            let mut row = pre.logits.clone();
10523            dsv4_penalize_row(&mut row, prompt, pc);
10524            dsv4_sample_row(&row, p0, sample)?
10525        } else {
10526            dsv4_sample_row(&pre.logits, p0, sample)?
10527        };
10528        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10529        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10530        let mut mh_row = 0usize;
10531        let mut carry_pending = false;
10532        let depth_cap = depth_cap.max(1);
10533        while tokens.len() < n_new {
10534            let cb_from = tokens.len();
10535            if carry_pending {
10536                tokens.push(t_tok);
10537                if let Some(cb) = round_cb.as_deref_mut() {
10538                    cb(&tokens[cb_from..]);
10539                }
10540                break;
10541            }
10542            let round_t0 = std::time::Instant::now();
10543            let m0 = p0 + tokens.len();
10544            let prop = self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?;
10545            let k_drafts = prop.out_ids.len() - 1;
10546            tokens.push(t_tok);
10547            if tokens.len() == n_new {
10548                rounds.push(SpecRoundGpu {
10549                    start_pos: m0 - 1,
10550                    drafts: prop.out_ids[1..].to_vec(),
10551                    accepts: 0,
10552                    verified: 0,
10553                    t_batch: 0,
10554                    t_cap: 0,
10555                    confidence: prop.confidence.clone(),
10556                    emitted: 1,
10557                    round_us: round_t0.elapsed().as_micros() as u64,
10558                });
10559                if let Some(cb) = round_cb.as_deref_mut() {
10560                    cb(&tokens[cb_from..]);
10561                }
10562                break;
10563            }
10564            let forwards_left = n_new - tokens.len();
10565            let vt_drafts = match vt {
10566                Dsv4Vt::Off => k_drafts,
10567                Dsv4Vt::Slot { tau_logit, floor } => {
10568                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10569                }
10570            };
10571            let t_cap = (vt_drafts + 1)
10572                .min(k_drafts + 1)
10573                .min(depth_cap)
10574                .min(vstate.tmax);
10575            let t_batch = t_cap.min(forwards_left);
10576            let kv = t_batch - 1;
10577            let mut batch_ids = Vec::with_capacity(t_batch);
10578            batch_ids.push(t_tok);
10579            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10580            let (rows, _am) =
10581                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), true)?;
10582            let rows = rows.expect("verify_batch_dev(want_logits=true) returned rows");
10583            let vocab = rows.len() / t_batch;
10584            // sampled accept walk: row i's input token sits at position m0 + i, so the
10585            // row PREDICTS the token at position m0 + i + 1 — that predicted position
10586            // is the draw key (the plain loop keys every token by its own absolute
10587            // position; misaligning this by one would silently break the identity law
10588            // at every accepted draft). Draft i+1 is accepted iff it equals the draw.
10589            let mut c_d = 0usize;
10590            let mut t_next = 0u32;
10591            // row-incremental penalty window: prompt ++ tokens (head included) ++ the
10592            // accepts of rows < i in THIS round (batch_ids[1..=c_d] at walk time).
10593            let mut wround: Vec<u32> = Vec::new();
10594            for i in 0..t_batch {
10595                let s = if let Some(pc) = pen {
10596                    let mut row = rows[i * vocab..(i + 1) * vocab].to_vec();
10597                    let mut window = Vec::with_capacity(prompt.len() + tokens.len() + wround.len());
10598                    window.extend_from_slice(prompt);
10599                    window.extend_from_slice(&tokens);
10600                    window.extend_from_slice(&wround);
10601                    dsv4_penalize_row(&mut row, &window, pc);
10602                    dsv4_sample_row(&row, m0 + i + 1, sample)?
10603                } else {
10604                    dsv4_sample_row(&rows[i * vocab..(i + 1) * vocab], m0 + i + 1, sample)?
10605                };
10606                if i < kv && s == batch_ids[i + 1] {
10607                    c_d += 1;
10608                    wround.push(batch_ids[i + 1]);
10609                    continue;
10610                }
10611                t_next = s;
10612                break;
10613            }
10614            let n_commit = c_d + 1;
10615            self.commit_verify_dev(state, vstate, n_commit)?;
10616            for i in 0..n_commit {
10617                self.dspark_write_rings(dstate, i, m0 + i)?;
10618            }
10619            {
10620                let last = self.stages.len() - 1;
10621                self.stages[last]
10622                    .gpu
10623                    .stream()
10624                    .synchronize()
10625                    .map_err(e("round close sync"))?;
10626            }
10627            mh_row = c_d;
10628            for i in 0..c_d {
10629                tokens.push(batch_ids[i + 1]);
10630            }
10631            carry_pending = c_d == kv && t_batch < t_cap;
10632            rounds.push(SpecRoundGpu {
10633                start_pos: m0 - 1,
10634                drafts: prop.out_ids[1..].to_vec(),
10635                accepts: c_d,
10636                verified: (c_d + 1).min(kv),
10637                t_batch,
10638                t_cap,
10639                confidence: prop.confidence.clone(),
10640                emitted: 1 + c_d,
10641                round_us: round_t0.elapsed().as_micros() as u64,
10642            });
10643            t_tok = t_next;
10644            if let Some(cb) = round_cb.as_deref_mut()
10645                && !cb(&tokens[cb_from..])
10646            {
10647                break;
10648            }
10649        }
10650        Ok(SpecRunGpu { tokens, rounds })
10651    }
10652}
10653
10654impl Dsv4Gpu {
10655    /// Every LIVE trunk cache class, per layer, as host f32 arrays — the instrument for
10656    /// the §3.1 device state gate (batched round + commit vs plain sequential decode of
10657    /// the committed tokens, bit for bit). "Live" is load-bearing: bytes past `n_blocks`
10658    /// in an append-only store, and the TRANSIENT verify rows, are dead scratch and are
10659    /// deliberately excluded (the CPU-oracle gate draws the same line).
10660    pub fn cache_classes(&self, state: &DecodeState) -> Res<Vec<(String, Vec<f32>)>> {
10661        let d = self.model.cfg();
10662        let hd = d.head_dim as usize;
10663        let win = d.sliding_window as usize;
10664        let mut out = Vec::new();
10665        for (il, cache) in state.caches.iter().enumerate() {
10666            let stage_i = self.layer_stage[il];
10667            let st = &self.stages[stage_i];
10668            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx classes"))?;
10669            let stream = st.gpu.stream();
10670            let lidx = st
10671                .layers
10672                .iter()
10673                .position(|l| l.il == il as u32)
10674                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
10675            let layer = &st.layers[lidx];
10676            let read = |sl: cudarc::driver::CudaView<'_, f32>| -> Res<Vec<f32>> {
10677                let mut v = vec![0f32; sl.len()];
10678                stream
10679                    .memcpy_dtoh(&sl, &mut v[..])
10680                    .map_err(e("dtoh class"))?;
10681                stream.synchronize().map_err(e("sync class"))?;
10682                Ok(v)
10683            };
10684            out.push((format!("l{il}.ring"), read(cache.kvc.slice(0..win * hd))?));
10685            if let Some(cmp) = &layer.cmp {
10686                out.push((
10687                    format!("l{il}.cmp_store"),
10688                    read(cache.kvc.slice(win * hd..(win + cache.n_blocks) * cmp.d))?,
10689                ));
10690                out.push((
10691                    format!("l{il}.cmp_pend_kv"),
10692                    read(cache.pend_kv.as_ref().expect("pend kv").slice(..))?,
10693                ));
10694                out.push((
10695                    format!("l{il}.cmp_pend_score"),
10696                    read(cache.pend_score.as_ref().expect("pend sc").slice(..))?,
10697                ));
10698            }
10699            if let Some(ix) = &layer.idx {
10700                let ikvc = cache.ikvc.as_ref().expect("ikvc");
10701                out.push((
10702                    format!("l{il}.idx_store"),
10703                    read(ikvc.slice(0..cache.i_blocks * ix.cmp.d))?,
10704                ));
10705                out.push((
10706                    format!("l{il}.idx_pend_kv"),
10707                    read(cache.ipend_kv.as_ref().expect("ipend kv").slice(..))?,
10708                ));
10709                out.push((
10710                    format!("l{il}.idx_pend_score"),
10711                    read(cache.ipend_score.as_ref().expect("ipend sc").slice(..))?,
10712                ));
10713            }
10714        }
10715        Ok(out)
10716    }
10717
10718    /// The DSpark drafter's main_kv rings as host f32 arrays (accepted-position-only
10719    /// ring-write rule gate: the batched drafted arm's rings must end bit-identical to a
10720    /// plain greedy run that wrote a ring row at EVERY decoded position).
10721    pub fn dspark_ring_classes(&self, dstate: &DsparkState) -> Res<Vec<(String, Vec<f32>)>> {
10722        let last = self.stages.len() - 1;
10723        let st = &self.stages[last];
10724        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx rings"))?;
10725        let stream = st.gpu.stream();
10726        let d = self.model.cfg();
10727        let hd = d.head_dim as usize;
10728        let win = d.sliding_window as usize;
10729        let mut out = Vec::new();
10730        for (bi, ring) in dstate.rings.iter().enumerate() {
10731            // persistent ring only — rows [win, win+block) are the drafter's transient
10732            // draft-kv scratch, rewritten by every propose and never state.
10733            let view = ring.slice(0..win * hd);
10734            let mut v = vec![0f32; view.len()];
10735            stream
10736                .memcpy_dtoh(&view, &mut v[..])
10737                .map_err(e("dtoh ring class"))?;
10738            stream.synchronize().map_err(e("sync ring class"))?;
10739            out.push((format!("dspark.ring{bi}"), v));
10740        }
10741        Ok(out)
10742    }
10743}
10744
10745/// The dense-arm resolution, pure for the flip's toothed tests (owner ratification
10746/// 2026-08-20, executed v0.98): unset = `fp8` on the DEVICE decode path, `bf16` on
10747/// legacy (device-scoped default, the 82a754fbec dots-default shape); explicit values
10748/// keep their exact prior semantics including the legacy+fp8 refusal and the
10749/// unknown-value refusal.
10750pub fn resolve_dense_arm(v: Option<&str>, on_device: bool) -> Result<bool, String> {
10751    match v {
10752        None | Some("") => Ok(on_device),
10753        Some("bf16") => Ok(false),
10754        Some("fp8") if !on_device => Err(
10755            "MEMRA_DSV4_DENSE_ARM=fp8 requires MEMRA_DSV4_DECODE_PATH=device (the \
10756             fp8 GEMV twins exist on the device decode/verify paths only; prefill \
10757             and the legacy path consume the bf16 slabs)"
10758                .to_string(),
10759        ),
10760        Some("fp8") => Ok(true),
10761        Some(other) => Err(format!(
10762            "MEMRA_DSV4_DENSE_ARM '{other}' unknown (bf16 | fp8)"
10763        )),
10764    }
10765}
10766
10767/// ds4f rung 1 — per-round verify-window policy from the drafter's OWN confidence head
10768/// (`MEMRA_DSV4_VT={off|slot}`, unset = off = the byte-identical round driver).
10769///
10770/// `slot` is the owner-directive per-slot reading. The q38 H4 verdict transfers as a
10771/// MECHANISM, never as receipts (no-generic-support): their head emits MARGINAL accept
10772/// probabilities, so cumprod-survival double-counts depth decay — and dsv4's own head
10773/// was independently measured discriminative per-slot (AUC 0.871–0.918, it5 rung 4,
10774/// where STS recalibration was the measured NEGATIVE — the policy consumes RAW
10775/// sigmoids by design). Verification still arbitrates every forwarded draft, so the
10776/// policy moves acceptance ECONOMICS only; greedy identity holds at any window (the
10777/// `MEMRA_DSV4_SPEC_DEPTH` argument, verbatim — this is a per-round depth).
10778///
10779/// Knobs: `MEMRA_DSV4_VT_TAU` in (0,1) exclusive, default 0.5; `MEMRA_DSV4_VT_FLOOR`
10780/// = minimum drafts forwarded, default 0, max `DSPARK_BLOCK-1` (0 is legal: a
10781/// fully-unconfident proposal degenerates to a 1-row verify — the it5 Algorithm-1
10782/// scans price exactly that round shape). Unknown values, out-of-range tau/floor, and
10783/// orphan knobs (tau/floor set without `slot`) REFUSE BY NAME.
10784#[derive(Clone, Copy, Debug, PartialEq)]
10785pub enum Dsv4Vt {
10786    Off,
10787    /// tau stored in LOGIT space (sigmoid(c) >= tau  <=>  c >= tau_logit, exact for
10788    /// tau = 0.5 -> 0.0); floor = minimum number of drafts forwarded per round.
10789    Slot {
10790        tau_logit: f32,
10791        floor: usize,
10792    },
10793}
10794
10795pub fn resolve_vt(
10796    policy: Option<&str>,
10797    tau: Option<&str>,
10798    floor: Option<&str>,
10799) -> Result<Dsv4Vt, String> {
10800    match policy {
10801        None | Some("") | Some("off") => {
10802            if let Some(t) = tau {
10803                return Err(format!(
10804                    "MEMRA_DSV4_VT_TAU='{t}' set without MEMRA_DSV4_VT=slot (orphan knob \
10805                     would be silently inert — refuse instead)"
10806                ));
10807            }
10808            if let Some(f) = floor {
10809                return Err(format!(
10810                    "MEMRA_DSV4_VT_FLOOR='{f}' set without MEMRA_DSV4_VT=slot (orphan \
10811                     knob would be silently inert — refuse instead)"
10812                ));
10813            }
10814            Ok(Dsv4Vt::Off)
10815        }
10816        Some("slot") => {
10817            let tau_v: f32 = match tau {
10818                None => 0.5,
10819                Some(s) => s
10820                    .trim()
10821                    .parse::<f32>()
10822                    .map_err(|_| format!("MEMRA_DSV4_VT_TAU '{s}' is not a float in (0,1)"))?,
10823            };
10824            if !(tau_v > 0.0 && tau_v < 1.0) {
10825                return Err(format!(
10826                    "MEMRA_DSV4_VT_TAU {tau_v} out of range: need 0 < tau < 1 \
10827                     (a probability threshold on the per-slot sigmoid)"
10828                ));
10829            }
10830            let floor_v: usize = match floor {
10831                None => 0,
10832                Some(s) => s.trim().parse::<usize>().map_err(|_| {
10833                    format!("MEMRA_DSV4_VT_FLOOR '{s}' is not a non-negative integer")
10834                })?,
10835            };
10836            // block_size is baked into the weights at 5 (DSPARK-SEMANTICS §1.5); a
10837            // floor >= block would pin the window fully open, i.e. silently disable
10838            // the policy while claiming to run it.
10839            if floor_v >= 5 {
10840                return Err(format!(
10841                    "MEMRA_DSV4_VT_FLOOR {floor_v} >= dspark block size 5 would pin the \
10842                     window fully open (use MEMRA_DSV4_VT=off to disable)"
10843                ));
10844            }
10845            Ok(Dsv4Vt::Slot {
10846                tau_logit: (tau_v / (1.0 - tau_v)).ln(),
10847                floor: floor_v,
10848            })
10849        }
10850        Some(other) => Err(format!("MEMRA_DSV4_VT '{other}' unknown (off | slot)")),
10851    }
10852}
10853
10854/// ds4f rung 2 (slice 1) — the dsv4 SAMPLED path's sampler: deterministic,
10855/// POSITION-KEYED seeded draws over a temperature/top-k/top-p-filtered target row.
10856///
10857/// Position keying is the identity law's load-bearing choice: the uniform draw for
10858/// absolute position `pos` is a pure function of (seed, pos), never of how many draws
10859/// happened before — so the plain sampled loop and the sampled-leader verify walk
10860/// consume IDENTICAL randomness at every position, and (because the batched verify's
10861/// logits rows are bit-exact against the sequential step's — the it3 gate (c) proof)
10862/// **sampled spec == sampled plain identity is structural, per seed**, exactly like
10863/// greedy. The drafter keeps proposing greedily (its chain is a deterministic
10864/// proposal policy); arbitration is sample-match against the target draw — the
10865/// correct accept rule for a one-hot proposal (the q38 "cold sampled leader" shape).
10866///
10867/// Filter semantics (vendor-posture defaults live at the call sites: temperature 1.0,
10868/// top_p 0.95, top_k off): logits/T -> softmax -> top-k by (value desc, index asc)
10869/// -> smallest prefix of that order with cumulative mass >= top_p (always >= 1 token)
10870/// -> renormalize -> inverse-CDF draw at u(seed, pos). temperature <= 0 REFUSES BY
10871/// NAME (greedy is the greedy driver's job; a silent argmax fallback here would be
10872/// the q38 penalized-greedy footgun).
10873#[derive(Clone, Copy, Debug)]
10874pub struct Dsv4SampleCfg {
10875    pub temperature: f32,
10876    pub top_p: f32,
10877    pub top_k: usize,
10878    pub seed: u64,
10879}
10880
10881fn splitmix64(mut x: u64) -> u64 {
10882    x = x.wrapping_add(0x9e3779b97f4a7c15);
10883    let mut z = x;
10884    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
10885    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
10886    z ^ (z >> 31)
10887}
10888
10889/// The uniform draw for absolute position `pos` under `seed` — in [0, 1).
10890pub fn dsv4_pos_uniform(seed: u64, pos: usize) -> f64 {
10891    let h = splitmix64(seed ^ (pos as u64).wrapping_mul(0xa24baed4963ee407));
10892    (h >> 11) as f64 / (1u64 << 53) as f64
10893}
10894
10895/// One sampled token from a full-vocab logits row at absolute position `pos`.
10896#[allow(clippy::neg_cmp_op_on_partial_ord)] // allow: NaN must take this branch; !(a > b) is not a <= b under IEEE comparisons
10897pub fn dsv4_sample_row(logits: &[f32], pos: usize, cfg: &Dsv4SampleCfg) -> Result<u32, String> {
10898    if !(cfg.temperature > 0.0) {
10899        return Err(format!(
10900            "dsv4 sampled path: temperature {} refused (need > 0; greedy is served by \
10901             the greedy driver, never a silent argmax fallback)",
10902            cfg.temperature
10903        ));
10904    }
10905    if !(cfg.top_p > 0.0 && cfg.top_p <= 1.0) {
10906        return Err(format!(
10907            "dsv4 sampled path: top_p {} out of (0, 1]",
10908            cfg.top_p
10909        ));
10910    }
10911    // candidate order: value desc, index asc (the house tie ordering)
10912    let k = if cfg.top_k == 0 || cfg.top_k > logits.len() {
10913        logits.len()
10914    } else {
10915        cfg.top_k
10916    };
10917    let mut idx: Vec<u32> = (0..logits.len() as u32).collect();
10918    idx.sort_by(|&a, &b| {
10919        let (va, vb) = (logits[a as usize], logits[b as usize]);
10920        vb.partial_cmp(&va)
10921            .unwrap_or(std::cmp::Ordering::Equal)
10922            .then(a.cmp(&b))
10923    });
10924    idx.truncate(k);
10925    // softmax over the kept set in kept order (f64 accumulation, max-shifted)
10926    let m = logits[idx[0] as usize] as f64;
10927    let t = cfg.temperature as f64;
10928    let mut probs: Vec<f64> = idx
10929        .iter()
10930        .map(|&i| (((logits[i as usize] as f64) - m) / t).exp())
10931        .collect();
10932    let z: f64 = probs.iter().sum();
10933    for p in &mut probs {
10934        *p /= z;
10935    }
10936    // nucleus: smallest prefix with cumulative >= top_p (>= 1 token), renormalize
10937    let mut cum = 0.0f64;
10938    let mut keep = probs.len();
10939    for (i, p) in probs.iter().enumerate() {
10940        cum += p;
10941        if cum >= cfg.top_p as f64 {
10942            keep = i + 1;
10943            break;
10944        }
10945    }
10946    probs.truncate(keep);
10947    idx.truncate(keep);
10948    let z2: f64 = probs.iter().sum();
10949    let u = dsv4_pos_uniform(cfg.seed, pos) * z2;
10950    let mut acc = 0.0f64;
10951    for (i, p) in probs.iter().enumerate() {
10952        acc += p;
10953        if u < acc {
10954            return Ok(idx[i]);
10955        }
10956    }
10957    Ok(idx[keep - 1]) // u landed on the tail boundary (float roundoff)
10958}
10959
10960/// ds4f rung 2 slice 2 — penalties for the dsv4 sampled path, over an EXPLICIT
10961/// window. The rule is `memra-sampling`'s own `Sampler::apply_penalties` (Keskar
10962/// repeat divide/multiply toward 0 + frequency*count + presence), replicated here
10963/// because the dsv4 path needs per-ROW windows (the spec verify's row-incremental
10964/// state: row r penalizes over prompt ++ committed ++ this round's accepts < r),
10965/// and CROSS-PINNED by unit test against a real `Sampler` so the two
10966/// implementations cannot drift apart silently.
10967#[derive(Clone, Copy, Debug)]
10968pub struct Dsv4PenaltyCfg {
10969    pub last_n: usize,
10970    pub repeat: f32,
10971    pub freq: f32,
10972    pub present: f32,
10973}
10974
10975impl Dsv4PenaltyCfg {
10976    pub fn armed(&self) -> bool {
10977        self.last_n > 0 && (self.repeat != 1.0 || self.freq != 0.0 || self.present != 0.0)
10978    }
10979}
10980
10981/// Apply the Keskar penalties in place over `window`'s last `cfg.last_n` entries.
10982pub fn dsv4_penalize_row(logits: &mut [f32], window: &[u32], cfg: &Dsv4PenaltyCfg) {
10983    if !cfg.armed() {
10984        return;
10985    }
10986    let start = window.len().saturating_sub(cfg.last_n);
10987    let win = &window[start..];
10988    if win.is_empty() {
10989        return;
10990    }
10991    let mut counts: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
10992    for &t in win {
10993        *counts.entry(t).or_insert(0) += 1;
10994    }
10995    for (&id, &cnt) in &counts {
10996        let Some(l) = logits.get_mut(id as usize) else {
10997            continue;
10998        };
10999        if cfg.repeat != 1.0 {
11000            if *l > 0.0 {
11001                *l /= cfg.repeat;
11002            } else {
11003                *l *= cfg.repeat;
11004            }
11005        }
11006        *l -= cfg.freq * cnt as f32;
11007        if cnt > 0 {
11008            *l -= cfg.present;
11009        }
11010    }
11011}
11012
11013/// Drafts to forward under the slot policy: the longest LEADING prefix of `conf`
11014/// (the drafter's pre-sigmoid per-slot logits) with `c >= tau_logit`, raised to
11015/// `floor`, clamped to `conf.len()`. A NaN slot compares false = unconfident
11016/// (conservative: it truncates, and verification still owns correctness).
11017pub fn vt_slot_drafts(conf: &[f32], tau_logit: f32, floor: usize) -> usize {
11018    let mut k = 0usize;
11019    for &c in conf {
11020        if c >= tau_logit {
11021            k += 1;
11022        } else {
11023            break;
11024        }
11025    }
11026    k.max(floor).min(conf.len())
11027}
11028
11029#[cfg(test)]
11030mod peer_probe_tests {
11031    use super::{dsv4_peer_probe_ladder, dsv4_peer_probe_mismatches, dsv4_peer_probe_pattern};
11032
11033    /// TOOTH for the lane-8 byte probe (host-side halves; the on-box halves are the boot
11034    /// PASS line and the MEMRA_DSV4_PEER_PROBE_POISON refusal arm): the pattern must be
11035    /// deterministic, non-trivial, and keyed per (bytes, boundary, src, dst) so a stuck or
11036    /// crossed lane cannot alias another probe's expectation; the mismatch count must see
11037    /// single-byte flips, inversion (the poison), and truncation.
11038    #[test]
11039    fn peer_probe_pattern_is_keyed_and_mismatches_are_counted() {
11040        let a = dsv4_peer_probe_pattern(4096, 0, 0, 1);
11041        assert_eq!(a.len(), 4096);
11042        assert_eq!(a, dsv4_peer_probe_pattern(4096, 0, 0, 1), "deterministic");
11043        assert_ne!(a, dsv4_peer_probe_pattern(4096, 0, 1, 0), "direction-keyed");
11044        assert_ne!(a, dsv4_peer_probe_pattern(4096, 1, 0, 1), "boundary-keyed");
11045        assert!(a.iter().any(|&b| b != a[0]), "non-constant pattern");
11046
11047        assert_eq!(dsv4_peer_probe_mismatches(&a, &a), 0);
11048        let mut flipped = a.clone();
11049        flipped[17] ^= 1;
11050        assert_eq!(dsv4_peer_probe_mismatches(&a, &flipped), 1);
11051        let poison: Vec<u8> = a.iter().map(|b| !b).collect();
11052        assert_eq!(dsv4_peer_probe_mismatches(&a, &poison), a.len());
11053        assert_eq!(dsv4_peer_probe_mismatches(&a, &a[..4000]), 96);
11054    }
11055
11056    #[test]
11057    fn peer_probe_ladder_contains_live_hc_payloads() {
11058        let ladder = dsv4_peer_probe_ladder(4096, 4);
11059        assert!(ladder.contains(&(64 << 10)), "one-token hc state");
11060        assert!(ladder.contains(&(512 << 10)), "eight-row verify hc state");
11061        assert!(ladder.contains(&(64 << 20)), "maximum prefill handoff");
11062    }
11063}
11064
11065#[cfg(test)]
11066mod dense_arm_default_tests {
11067    use super::resolve_dense_arm;
11068
11069    /// The owner-ratified flip (2026-08-20): unset env on the device decode path = fp8.
11070    /// Mutating the default back to bf16 fails this with the evidence named.
11071    #[test]
11072    fn ratified_default_dense_arm_is_fp8_on_device() {
11073        assert_eq!(
11074            resolve_dense_arm(None, true),
11075            Ok(true),
11076            "owner-ratified 2026-08-20: unset MEMRA_DSV4_DENSE_ARM defaults the DEVICE \
11077             decode path to fp8 (bit-identical on four boxes, x5 A/B 41.06->47.19, \
11078             item-3 residency green on box7)"
11079        );
11080        assert_eq!(resolve_dense_arm(Some(""), true), Ok(true));
11081        // Legacy path: unset resolves bf16 (no fp8 twins there — must keep booting).
11082        assert_eq!(resolve_dense_arm(None, false), Ok(false));
11083        // Explicit values keep their exact prior semantics.
11084        assert_eq!(resolve_dense_arm(Some("bf16"), true), Ok(false));
11085        assert_eq!(resolve_dense_arm(Some("fp8"), true), Ok(true));
11086        assert!(
11087            resolve_dense_arm(Some("fp8"), false).is_err(),
11088            "legacy+fp8 stays a refusal"
11089        );
11090        assert!(
11091            resolve_dense_arm(Some("q8"), true).is_err(),
11092            "unknown values refuse"
11093        );
11094    }
11095}
11096
11097#[cfg(test)]
11098mod vt_policy_tests {
11099    use super::{Dsv4Vt, resolve_vt, vt_slot_drafts};
11100
11101    /// Unset env = Off = the byte-identical round driver. Mutating the default fails
11102    /// this by name.
11103    #[test]
11104    fn default_vt_is_off_and_byte_inert() {
11105        assert_eq!(resolve_vt(None, None, None), Ok(Dsv4Vt::Off));
11106        assert_eq!(resolve_vt(Some(""), None, None), Ok(Dsv4Vt::Off));
11107        assert_eq!(resolve_vt(Some("off"), None, None), Ok(Dsv4Vt::Off));
11108    }
11109
11110    #[test]
11111    fn slot_defaults_tau_half_floor_zero() {
11112        // tau 0.5 must land on tau_logit 0.0 EXACTLY (ln(0.5/0.5) = ln(1) = 0), so the
11113        // default threshold admits c = 0.0 with no float fuzz.
11114        match resolve_vt(Some("slot"), None, None) {
11115            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11116                assert_eq!(tau_logit, 0.0);
11117                assert_eq!(floor, 0);
11118            }
11119            other => panic!("slot default parse broke: {other:?}"),
11120        }
11121        // explicit tau round-trips through logit space
11122        match resolve_vt(Some("slot"), Some("0.6"), Some("2")) {
11123            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11124                assert!((tau_logit - (0.6f32 / 0.4).ln()).abs() < 1e-6);
11125                assert_eq!(floor, 2);
11126            }
11127            other => panic!("slot tau/floor parse broke: {other:?}"),
11128        }
11129    }
11130
11131    #[test]
11132    fn unknown_and_out_of_range_refuse_by_name() {
11133        for (p, t, f) in [
11134            (Some("banana"), None, None),    // unknown policy
11135            (Some("slot"), Some("0"), None), // tau not in (0,1)
11136            (Some("slot"), Some("1"), None),
11137            (Some("slot"), Some("nan"), None),
11138            (Some("slot"), Some("x"), None),
11139            (Some("slot"), None, Some("5")), // floor pins window open
11140            (Some("slot"), None, Some("-1")),
11141            (None, Some("0.5"), None),      // orphan tau
11142            (Some("off"), None, Some("2")), // orphan floor
11143        ] {
11144            let r = resolve_vt(p, t, f);
11145            assert!(r.is_err(), "({p:?},{t:?},{f:?}) must refuse, got {r:?}");
11146            let msg = r.unwrap_err();
11147            assert!(
11148                msg.contains("MEMRA_DSV4_VT"),
11149                "refusal must name the knob: {msg}"
11150            );
11151        }
11152    }
11153
11154    /// The slot rule is a LEADING-prefix rule: a confident slot after an unconfident
11155    /// one is never forwarded (chained markov ids past a rejected slot are garbage).
11156    #[test]
11157    fn slot_truncation_is_leading_prefix_with_floor() {
11158        let up = 3.0f32; // sigmoid ~0.95
11159        let dn = -3.0f32; // sigmoid ~0.05
11160        assert_eq!(vt_slot_drafts(&[up, up, up, up, up], 0.0, 0), 5);
11161        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 0), 0);
11162        assert_eq!(vt_slot_drafts(&[up, up, dn, up, up], 0.0, 0), 2);
11163        // boundary equality counts as confident (>=): tau 0.5 admits c = 0.0
11164        assert_eq!(vt_slot_drafts(&[0.0, dn, dn, dn, dn], 0.0, 0), 1);
11165        // floor raises a fully-unconfident round; clamped to the block
11166        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 2), 2);
11167        assert_eq!(vt_slot_drafts(&[dn, dn], 0.0, 4), 2);
11168        // NaN slot is unconfident (conservative), never a panic
11169        assert_eq!(vt_slot_drafts(&[f32::NAN, up, up, up, up], 0.0, 0), 0);
11170        assert_eq!(vt_slot_drafts(&[], 0.0, 0), 0);
11171    }
11172
11173    /// Off must reproduce the pre-policy t_cap expression exactly: with
11174    /// vt_drafts == k_drafts, (vt_drafts+1).min(k_drafts+1) == k_drafts+1.
11175    #[test]
11176    fn off_arm_t_cap_expression_is_identity() {
11177        for k_drafts in 0usize..=5 {
11178            let vt_drafts = k_drafts; // the Off branch in the driver
11179            assert_eq!((vt_drafts + 1).min(k_drafts + 1), k_drafts + 1);
11180        }
11181    }
11182}
11183
11184#[cfg(test)]
11185mod penalty_cross_pin_tests {
11186    use super::{Dsv4PenaltyCfg, dsv4_penalize_row};
11187
11188    /// The dsv4 explicit-window penalty rule must equal memra-sampling's own
11189    /// `Sampler::apply_penalties` (the house Keskar law) — pinned by running BOTH on
11190    /// the same rows/windows and comparing the penalized-greedy argmax, plus a direct
11191    /// per-element check through the Sampler's greedy path. Drift in either
11192    /// implementation fails here by name.
11193    #[test]
11194    fn penalize_matches_the_sampling_crate_reference() {
11195        let mk_row = |seed: u32| -> Vec<f32> {
11196            (0..64u32)
11197                .map(|i| {
11198                    let h = i.wrapping_mul(2654435761).wrapping_add(seed);
11199                    ((h % 2000) as f32 / 100.0) - 10.0
11200                })
11201                .collect()
11202        };
11203        for (seed, window, last_n, rep, freq, present) in [
11204            (
11205                1u32,
11206                vec![3u32, 3, 3, 7, 12, 3],
11207                8usize,
11208                1.8f32,
11209                0.4f32,
11210                0.6f32,
11211            ),
11212            (2, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4, 1.3, 0.0, 0.0),
11213            (3, vec![63, 63, 63, 63], 64, 1.0, 1.1, 0.0),
11214            (4, vec![5], 1, 2.5, 0.7, 1.3),
11215        ] {
11216            let row = mk_row(seed);
11217            // ours
11218            let mut ours = row.clone();
11219            dsv4_penalize_row(
11220                &mut ours,
11221                &window,
11222                &Dsv4PenaltyCfg {
11223                    last_n,
11224                    repeat: rep,
11225                    freq,
11226                    present,
11227                },
11228            );
11229            let our_pick = ours
11230                .iter()
11231                .enumerate()
11232                .max_by(|a, b| a.1.total_cmp(b.1))
11233                .unwrap()
11234                .0 as u32;
11235            // the house reference: greedy Sampler with penalties + the window as history
11236            let mut sampler = memra_sampling::Sampler::new(memra_sampling::SamplerConfig {
11237                temperature: 0.0,
11238                top_k: 0,
11239                top_p: 1.0,
11240                min_p: 0.0,
11241                penalty_last_n: last_n,
11242                penalty_repeat: rep,
11243                penalty_freq: freq,
11244                penalty_present: present,
11245                seed: 0,
11246            });
11247            for &t in &window {
11248                sampler.accept(t);
11249            }
11250            let ref_pick = sampler.sample(&row);
11251            assert_eq!(
11252                our_pick, ref_pick,
11253                "penalized argmax diverged from memra-sampling (seed {seed}): \
11254                 ours {our_pick} vs reference {ref_pick}"
11255            );
11256        }
11257    }
11258}
11259
11260#[cfg(test)]
11261mod sampled_path_tests {
11262    use super::{Dsv4SampleCfg, dsv4_pos_uniform, dsv4_sample_row};
11263
11264    fn cfg(seed: u64) -> Dsv4SampleCfg {
11265        Dsv4SampleCfg {
11266            temperature: 1.0,
11267            top_p: 0.95,
11268            top_k: 0,
11269            seed,
11270        }
11271    }
11272
11273    /// The identity law's anchor: the draw is a pure function of (row, pos, seed) —
11274    /// same inputs, same token, always; different positions decouple.
11275    #[test]
11276    fn draws_are_position_keyed_and_deterministic() {
11277        let row = [0.1f32, 2.0, -1.0, 1.9, 0.0];
11278        let a = dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap();
11279        for _ in 0..8 {
11280            assert_eq!(dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap(), a);
11281        }
11282        // uniforms at neighboring positions must not be equal (keying is real)
11283        let u0 = dsv4_pos_uniform(20260822, 40);
11284        let u1 = dsv4_pos_uniform(20260822, 41);
11285        let v0 = dsv4_pos_uniform(7, 40);
11286        assert_ne!(u0, u1);
11287        assert_ne!(u0, v0);
11288        assert!((0.0..1.0).contains(&u0));
11289    }
11290
11291    /// temperature <= 0 refuses BY NAME (the penalized-greedy footgun class);
11292    /// bad top_p refuses too.
11293    #[test]
11294    fn t0_and_bad_topp_refuse_by_name() {
11295        let row = [0.0f32, 1.0];
11296        let mut c = cfg(1);
11297        c.temperature = 0.0;
11298        let e = dsv4_sample_row(&row, 0, &c).unwrap_err();
11299        assert!(e.contains("temperature"), "{e}");
11300        let mut c2 = cfg(1);
11301        c2.top_p = 0.0;
11302        assert!(dsv4_sample_row(&row, 0, &c2).is_err());
11303    }
11304
11305    /// top-k 1 and a tight nucleus both collapse to argmax regardless of the draw;
11306    /// ties break by lowest index (the house ordering).
11307    #[test]
11308    fn filters_collapse_to_argmax_and_ties_break_low_index() {
11309        let row = [0.0f32, 5.0, 5.0, -2.0];
11310        let mut c = cfg(99);
11311        c.top_k = 1;
11312        for pos in 0..64 {
11313            assert_eq!(dsv4_sample_row(&row, pos, &c).unwrap(), 1);
11314        }
11315        let mut c2 = cfg(99);
11316        c2.top_p = 1e-9; // nucleus keeps exactly the top-1
11317        for pos in 0..64 {
11318            assert_eq!(dsv4_sample_row(&row, pos, &c2).unwrap(), 1);
11319        }
11320    }
11321
11322    /// The sampled distribution honors the filtered target: over many positions a
11323    /// dominant token wins the majority, and a token outside top-k never appears.
11324    #[test]
11325    fn draw_frequencies_track_the_filtered_target() {
11326        let row = [3.0f32, 1.0, 0.0, -50.0];
11327        let mut c = cfg(20260822);
11328        c.top_k = 3;
11329        c.top_p = 1.0;
11330        let mut counts = [0usize; 4];
11331        for pos in 0..4096 {
11332            counts[dsv4_sample_row(&row, pos, &c).unwrap() as usize] += 1;
11333        }
11334        assert_eq!(counts[3], 0, "outside top-k must never be drawn");
11335        assert!(counts[0] > 2600, "p(tok0) ~ 0.84, got {}/4096", counts[0]);
11336        assert!(counts[1] > 100, "tail token starved: {}", counts[1]);
11337    }
11338}