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
665/// One probed copy src->dst at `bytes`. Destination is poisoned with the inverted pattern
666/// first, so a silently dropped copy reads back as full-length corruption, never as PASS.
667fn dsv4_peer_probe_copy(src: &Stage, dst: &Stage, boundary: usize, bytes: usize) -> Res<()> {
668    let expected = dsv4_peer_probe_pattern(bytes, boundary, src.dev, dst.dev);
669    src.gpu.ctx.bind_to_thread().map_err(e("probe bind src"))?;
670    let src_stream = src.gpu.stream();
671    let src_buf = upload_u8(&src_stream, &expected)?;
672    src_stream.synchronize().map_err(e("probe sync src htod"))?;
673
674    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind dst"))?;
675    let dst_stream = dst.gpu.stream();
676    let poison: Vec<u8> = expected.iter().map(|b| !b).collect();
677    let mut dst_buf = upload_u8(&dst_stream, &poison)?;
678    dst_stream.synchronize().map_err(e("probe sync poison"))?;
679
680    // the production call shape: peer copy issued on the TX (source) stream.
681    src.gpu.ctx.bind_to_thread().map_err(e("probe bind tx"))?;
682    {
683        let (sp, _g0) = src_buf.device_ptr(&src_stream);
684        let (dp, _g1) = dst_buf.device_ptr_mut(&src_stream);
685        unsafe {
686            cudarc::driver::result::memcpy_peer_async(
687                dst.gpu.ctx.cu_ctx(),
688                dp,
689                src.gpu.ctx.cu_ctx(),
690                sp,
691                bytes,
692                src_stream.cu_stream(),
693            )
694            .map_err(e("probe peer copy"))?;
695        }
696    }
697    src_stream.synchronize().map_err(e("probe sync copy"))?;
698
699    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind rx"))?;
700    let mut readback = vec![0u8; bytes];
701    dst_stream
702        .memcpy_dtoh(&dst_buf, &mut readback[..])
703        .map_err(e("probe readback"))?;
704    dst_stream.synchronize().map_err(e("probe sync readback"))?;
705    // TEETH DOOR (diagnostics only, never a tuning knob): MEMRA_DSV4_PEER_PROBE_POISON=1
706    // flips one readback byte so the refusal arm can be proven live on a healthy fabric —
707    // a probe that can only be observed passing proves nothing (serve-stress-gate law).
708    if std::env::var("MEMRA_DSV4_PEER_PROBE_POISON").as_deref() == Ok("1") && !readback.is_empty() {
709        readback[0] ^= 1;
710    }
711    let mismatches = dsv4_peer_probe_mismatches(&expected, &readback);
712    if mismatches == 0 {
713        Ok(())
714    } else {
715        Err(format!("{mismatches} mismatched byte(s) of {bytes}"))
716    }
717}
718
719// ================================================== iteration-5: drafted-round phase instruments
720//
721// WHY: iteration 4 measured `cost(T) = F + 0.272*T` plain steps with F = 1.057 plain steps on
722// the f32x exit head, proved the marginal term is ~65-70% irreducible expert-union traffic, and
723// showed the ENTIRE drafted gap to the bar is F. F cannot be attacked until it is itemised into
724// named components with sizes, which is what these two instruments produce. Both are OFF by
725// default and their env knobs are read ONCE through a `OnceLock` (never per round), so the
726// shipping path is untouched: with both unset `Dsv4Phase::new` returns `None` before any work.
727//
728//   MEMRA_DSV4_ROUND_PROFILE=1 -- sync-bracketed host timers. Every phase boundary
729//       synchronizes the head stage's stream, so per-phase wall times SUM to the round's wall
730//       time and can be quoted in F's own unit (plain steps). It PERTURBS: the added syncs
731//       expose latency a queued round would have overlapped, so the report always prints the
732//       bracketed round total for comparison against the unbracketed A/B baseline. A
733//       sync-bracketed run is a rung-0 instrument, NEVER an A/B observation.
734//
735//   MEMRA_DSV4_NVTX=1 -- NVTX push/pop only, no added syncs, so the round is undisturbed.
736//       `nsys profile -t cuda,nvtx` then gives `nvtx_gpu_proj_sum` (GPU-busy attributed to the
737//       range that launched each op) and `nvtx_sum` (host wall per range). GPU-busy is the real
738//       kernel work; wall minus GPU-busy inside a sync-terminated phase is the exposed stall.
739//
740// The accumulator is thread-local and the phase stack makes nesting exact: each row keeps
741// INCLUSIVE time plus the time its direct children consumed, so `self = inclusive - children`
742// is a true exclusive cost and the leaves partition the round.
743#[derive(Default, Clone)]
744struct Dsv4PhaseAcc {
745    /// (label, inclusive_us, direct_child_us, calls)
746    rows: Vec<(&'static str, u64, u64, u64)>,
747    /// (row index, direct-child us accumulated for the open range)
748    stack: Vec<(usize, u64)>,
749}
750
751thread_local! {
752    static DSV4_PHASES: std::cell::RefCell<Dsv4PhaseAcc> =
753        std::cell::RefCell::new(Dsv4PhaseAcc::default());
754}
755
756fn dsv4_prof_sync() -> bool {
757    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
758    *V.get_or_init(|| std::env::var("MEMRA_DSV4_ROUND_PROFILE").as_deref() == Ok("1"))
759}
760
761fn dsv4_prof_nvtx() -> bool {
762    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
763    *V.get_or_init(|| std::env::var("MEMRA_DSV4_NVTX").as_deref() == Ok("1"))
764}
765
766/// True when either phase instrument is armed. Checked first in `Dsv4Phase::new` so an
767/// unprofiled build pays one relaxed load per bracket and nothing else.
768pub fn dsv4_prof_on() -> bool {
769    dsv4_prof_sync() || dsv4_prof_nvtx()
770}
771
772/// A named, nestable phase bracket. Constructed through the `phase!` macro, which supplies a
773/// NUL-terminated literal so the NVTX push needs no allocation.
774pub struct Dsv4Phase<'a> {
775    stream: Option<&'a std::sync::Arc<CudaStream>>,
776    t0: std::time::Instant,
777    nvtx: bool,
778}
779
780impl<'a> Dsv4Phase<'a> {
781    /// `name` MUST end in `\0` (use the `phase!` macro). `stream` is the stream whose queue
782    /// this phase's work rides; it is synchronized on drop under `MEMRA_DSV4_ROUND_PROFILE=1`
783    /// and ignored otherwise.
784    pub fn new(name: &'static str, stream: Option<&'a std::sync::Arc<CudaStream>>) -> Option<Self> {
785        if !dsv4_prof_on() {
786            return None;
787        }
788        let nvtx = dsv4_prof_nvtx();
789        if nvtx {
790            unsafe {
791                k::memra_dsv4_nvtx_push(name.as_ptr() as *const std::os::raw::c_char);
792            }
793        }
794        let label = &name[..name.len() - 1];
795        DSV4_PHASES.with(|p| {
796            let mut p = p.borrow_mut();
797            let idx = match p.rows.iter().position(|r| r.0 == label) {
798                Some(i) => i,
799                None => {
800                    p.rows.push((label, 0, 0, 0));
801                    p.rows.len() - 1
802                }
803            };
804            p.stack.push((idx, 0));
805        });
806        Some(Dsv4Phase {
807            stream: if dsv4_prof_sync() { stream } else { None },
808            t0: std::time::Instant::now(),
809            nvtx,
810        })
811    }
812}
813
814impl Drop for Dsv4Phase<'_> {
815    fn drop(&mut self) {
816        // sync BEFORE stopping the clock: under the sync-bracketed instrument the phase's cost
817        // includes the GPU work it queued, which is the only way the rows can sum to the round.
818        if let Some(s) = self.stream {
819            let _ = s.synchronize();
820        }
821        let us = self.t0.elapsed().as_micros() as u64;
822        if self.nvtx {
823            unsafe {
824                k::memra_dsv4_nvtx_pop();
825            }
826        }
827        DSV4_PHASES.with(|p| {
828            let mut p = p.borrow_mut();
829            if let Some((idx, child)) = p.stack.pop() {
830                let r = &mut p.rows[idx];
831                r.1 += us;
832                r.2 += child;
833                r.3 += 1;
834                if let Some(top) = p.stack.last_mut() {
835                    top.1 += us;
836                }
837            }
838        });
839    }
840}
841
842/// Bracket a phase. `phase!("name", stream_opt)` -> `Option<Dsv4Phase>`; bind it to a `_p`
843/// local so it drops at the end of the scope.
844macro_rules! phase {
845    ($name:literal, $stream:expr) => {
846        crate::dsv4_gpu::Dsv4Phase::new(concat!($name, "\0"), $stream)
847    };
848}
849
850/// Print the accumulated itemisation. `plain_us` is the measured PLAIN step wall time so each
851/// row can be quoted in plain steps, which is the unit `F` is expressed in; pass 0.0 to omit.
852pub fn dsv4_phase_report(tag: &str, rounds: u64, plain_us: f64) {
853    DSV4_PHASES.with(|p| {
854        let p = p.borrow();
855        if p.rows.is_empty() {
856            return;
857        }
858        let mode = if dsv4_prof_sync() {
859            "sync-bracketed (PERTURBS: compare the round total against the unbracketed A/B)"
860        } else {
861            "nvtx-only (host wall; GPU-busy comes from nsys nvtx_gpu_proj_sum)"
862        };
863        println!("\n[phase] === F ITEMISATION: {tag} ===");
864        println!("[phase] rounds={rounds}  plain step={plain_us:.1} us  mode={mode}");
865        println!(
866            "[phase] {:<26} {:>11} {:>11} {:>9} {:>12} {:>12}",
867            "phase", "incl_us/rd", "self_us/rd", "calls/rd", "self_plainstp", "incl_plainstp"
868        );
869        let mut rows = p.rows.clone();
870        rows.sort_by(|a, b| (b.1.saturating_sub(b.2)).cmp(&(a.1.saturating_sub(a.2))));
871        let r = rounds.max(1) as f64;
872        let mut leaf_sum = 0f64;
873        for (name, incl, child, calls) in rows {
874            let selfus = incl.saturating_sub(child) as f64 / r;
875            let inclus = incl as f64 / r;
876            leaf_sum += selfus;
877            let (sp, ip) = if plain_us > 0.0 {
878                (selfus / plain_us, inclus / plain_us)
879            } else {
880                (0.0, 0.0)
881            };
882            println!(
883                "[phase] {name:<26} {inclus:>11.1} {selfus:>11.1} {:>9.2} {sp:>12.4} {ip:>12.4}",
884                calls as f64 / r
885            );
886        }
887        println!(
888            "[phase] {:<26} {:>11} {:>11.1} {:>9} {:>12.4}",
889            "SUM of self",
890            "",
891            leaf_sum,
892            "",
893            if plain_us > 0.0 {
894                leaf_sum / plain_us
895            } else {
896                0.0
897            }
898        );
899    });
900}
901
902/// `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the DSpark markov chain resident on the device (see
903/// `dspark_forward_spec`). Default (`host`, or unset) reproduces the pre-iteration-5 transport
904/// exactly, including its ten per-round stream drains, so the shipped arm is unchanged until an
905/// A/B and the gate battery say otherwise.
906fn dsv4_dspark_chain_device() -> bool {
907    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
908    *V.get_or_init(|| {
909        let on = std::env::var("MEMRA_DSV4_DSPARK_CHAIN").as_deref() == Ok("device");
910        if on {
911            println!(
912                "[spec] DSpark markov chain RESIDENT ON DEVICE (MEMRA_DSV4_DSPARK_CHAIN=device): \
913                 one D2H per round instead of 2 x block_size"
914            );
915        }
916        on
917    })
918}
919
920/// `MEMRA_DSV4_DSPARK_MARKOV=rowblk` runs the DSpark markov bias GEMV through the row-blocked
921/// twin of the f64 island dots. Bit-identical output (same accumulation order and reduction tree,
922/// only R rows share a block), so this is a pure geometry change; the default `base` keeps the
923/// shipped kernel. Measured defect it addresses: 5 x 318 us/round at 416 GB/s = 26% of roofline,
924/// latency-bound on one 7-level reduction tree per 1 KB of weights read.
925fn dsv4_dspark_markov_rowblk() -> bool {
926    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
927    *V.get_or_init(|| {
928        let on = std::env::var("MEMRA_DSV4_DSPARK_MARKOV").as_deref() == Ok("rowblk");
929        if on {
930            println!(
931                "[spec] DSpark markov bias GEMV on the ROW-BLOCKED dots twin \
932                 (MEMRA_DSV4_DSPARK_MARKOV=rowblk; bit-identical, geometry only)"
933            );
934        }
935        on
936    })
937}
938
939/// Drop everything accumulated so far (used to keep the plain arm's brackets out of the
940/// drafted arm's table).
941pub fn dsv4_phase_reset() {
942    DSV4_PHASES.with(|p| *p.borrow_mut() = Dsv4PhaseAcc::default());
943}
944
945macro_rules! dp {
946    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const c_void }};
947}
948macro_rules! dpf {
949    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const f32 }};
950}
951macro_rules! dpm {
952    ($slice:expr, $stream:expr) => {{ $slice.device_ptr_mut($stream).0 as *mut f32 }};
953}
954
955// ---------------------------------------------------------------- loading
956
957/// f32 (already NaN-checked by tensor_f32) -> bf16 with a bit-level exactness REFUSAL:
958/// every value in the lane-4 rungs is exactly representable (see receipts); a non-zero
959/// low half means the exactness proof broke and the load must stop, not round.
960fn f32_to_bf16_exact(name: &str, v: &[f32]) -> Vec<u8> {
961    let mut out = Vec::with_capacity(v.len() * 2);
962    for (i, x) in v.iter().enumerate() {
963        let bits = x.to_bits();
964        assert!(
965            bits & 0xFFFF == 0,
966            "{name}: element {i} = {x} not exactly representable in bf16 — lane-4 rung \
967             exactness violated"
968        );
969        out.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
970    }
971    out
972}
973
974impl Dsv4Gpu {
975    /// Upload a tensor as bf16: BF16-stored tensors ride raw bytes; FP8-blk tensors are
976    /// host-dequantized (lane-1 decoder) and cast with the exactness refusal.
977    fn tensor_bf16(&mut self, stage: usize, name: &str) -> Res<CudaSlice<u8>> {
978        let raw_name = format!("{name}.weight");
979        let is_bf16_raw = self
980            .model
981            .st
982            .raw(&raw_name)
983            .map(|(i, _)| i.dtype == "BF16")
984            .unwrap_or(false)
985            || self
986                .model
987                .st
988                .raw(name)
989                .map(|(i, _)| i.dtype == "BF16")
990                .unwrap_or(false);
991        let stream = self.stages[stage].gpu.stream();
992        let bytes: u64;
993        let out = if is_bf16_raw {
994            let (_, raw) = self
995                .model
996                .st
997                .raw(&raw_name)
998                .or_else(|| self.model.st.raw(name))
999                .unwrap();
1000            bytes = raw.len() as u64;
1001            upload_u8(&stream, raw)?
1002        } else {
1003            let (_, v) = self.model.tensor_f32(name);
1004            let b = f32_to_bf16_exact(name, &v);
1005            bytes = b.len() as u64;
1006            upload_u8(&stream, &b)?
1007        };
1008        self.stages[stage].loaded_bytes += bytes;
1009        Ok(out)
1010    }
1011
1012    /// Iteration-5 FP8 dense arm loader. bf16 arm (or no fp8 twin): the device-resident
1013    /// bf16 dequant slab, today's exact bytes. fp8 arm on an F8_E4M3-stored `fp8_ok`
1014    /// tensor (trunk layers only this rung): the as-stored codes + host-decoded f32
1015    /// scale grid go to the device, and the bf16 slab drops to STAGED residency
1016    /// ([`DenseBf16::Host`], it5 ledger item 3) — the fp8 twins own every device
1017    /// decode/verify read and prefill stages the same bytes per pass, so the
1018    /// +~2.7 GiB/card dual residency is gone. Load-time refusals: missing/mis-shaped
1019    /// scale grid, e8m0 NaN code, cols not a multiple of 8 (the uint2 chunk contract),
1020    /// and a 1,024-element stride-sampled BIT check
1021    /// `e4m3(code[r,c]) * sc[r/128, c/128] == host_dequant[r,c]` — the layout/indexing
1022    /// proof, in the load-refusal tradition of the bf16 slab's own exactness check.
1023    fn tensor_dense(
1024        &mut self,
1025        stage: usize,
1026        name: &str,
1027        fp8_ok: bool,
1028    ) -> Res<(DenseBf16, Option<Fp8Dense>)> {
1029        let raw_name = format!("{name}.weight");
1030        let is_bf16_raw = self
1031            .model
1032            .st
1033            .raw(&raw_name)
1034            .map(|(i, _)| i.dtype == "BF16")
1035            .unwrap_or(false)
1036            || self
1037                .model
1038                .st
1039                .raw(name)
1040                .map(|(i, _)| i.dtype == "BF16")
1041                .unwrap_or(false);
1042        if is_bf16_raw || !fp8_ok || !self.dense_fp8 {
1043            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1044        }
1045        // FP8-blk path: resolve the weight raw + its scale sibling.
1046        let (wi, wraw, stem) = if let Some((i, r)) = self.model.st.raw(&raw_name) {
1047            (i.clone(), r.to_vec(), name.to_string())
1048        } else {
1049            let (i, r) = self
1050                .model
1051                .st
1052                .raw(name)
1053                .unwrap_or_else(|| panic!("missing dense tensor {name}"));
1054            let stem = name.strip_suffix(".weight").unwrap_or(name).to_string();
1055            (i.clone(), r.to_vec(), stem)
1056        };
1057        if wi.dtype != "F8_E4M3" {
1058            // not the FP8-blk class (e.g. a BF16-raw special) — bf16 slab only.
1059            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1060        }
1061        assert_eq!(wi.shape.len(), 2, "{name}: fp8 dense tensor must be 2-D");
1062        let rows = wi.shape[0] as usize;
1063        let cols = wi.shape[1] as usize;
1064        assert_eq!(cols % 8, 0, "{name}: fp8 dense cols {cols} % 8 != 0");
1065        assert_eq!(wraw.len(), rows * cols, "{name}: fp8 byte count");
1066        let scale_name = format!("{stem}.scale");
1067        let (si, sraw) = self
1068            .model
1069            .st
1070            .raw(&scale_name)
1071            .unwrap_or_else(|| panic!("{name}: F8_E4M3 weight without {scale_name}"));
1072        assert_eq!(si.dtype, "F8_E8M0", "{scale_name}: dtype");
1073        let sc_rows = rows.div_ceil(128);
1074        let sc_cols = cols.div_ceil(128);
1075        assert_eq!(
1076            (si.shape[0] as usize, si.shape[1] as usize),
1077            (sc_rows, sc_cols),
1078            "{scale_name}: scale grid shape vs [ceil({rows}/128), ceil({cols}/128)]"
1079        );
1080        let sc_f32: Vec<f32> = sraw
1081            .iter()
1082            .map(|&b| {
1083                assert_ne!(b, 0xFF, "{scale_name}: e8m0 NaN code");
1084                memra_gguf::dsv4::e8m0_to_f32(b)
1085            })
1086            .collect();
1087        // host dequant (the bf16 slab's own source) + the sampled layout bit-check.
1088        let (_, v) = self.model.tensor_f32(name);
1089        assert_eq!(v.len(), rows * cols, "{name}: dequant len");
1090        let step = (v.len() / 1024).max(1);
1091        for idx in (0..v.len()).step_by(step) {
1092            let (r, c) = (idx / cols, idx % cols);
1093            let got = memra_gguf::nvfp4_repack::fp8_e4m3_to_f32(wraw[idx])
1094                * sc_f32[(r / 128) * sc_cols + c / 128];
1095            assert_eq!(
1096                got.to_bits(),
1097                v[idx].to_bits(),
1098                "{name}: fp8 arm layout check failed at [{r},{c}] ({got} vs {})",
1099                v[idx]
1100            );
1101        }
1102        let b = f32_to_bf16_exact(name, &v);
1103        let stream = self.stages[stage].gpu.stream();
1104        // item 3: the bf16 slab is NOT uploaded — the fp8 pair owns every device
1105        // decode/verify read (dwsel) and prefill stages `b` per pass. loaded_bytes
1106        // counts DEVICE bytes only, so vram_report stays honest.
1107        let codes = upload_u8(&stream, &wraw)?;
1108        let scales = upload_f32(&stream, &sc_f32)?;
1109        self.stages[stage].loaded_bytes += (wraw.len() + sc_f32.len() * 4) as u64;
1110        Ok((
1111            DenseBf16::Host(b),
1112            Some(Fp8Dense {
1113                codes,
1114                scales,
1115                sc_cols,
1116                rows,
1117                cols,
1118            }),
1119        ))
1120    }
1121
1122    /// Upload a tensor as f32 (islands): any storage dtype goes through the proven
1123    /// tensor_f32 decode.
1124    fn tensor_f32_dev(&mut self, stage: usize, name: &str) -> Res<CudaSlice<f32>> {
1125        let (_, v) = self.model.tensor_f32(name);
1126        let stream = self.stages[stage].gpu.stream();
1127        self.stages[stage].loaded_bytes += (v.len() * 4) as u64;
1128        upload_f32(&stream, &v)
1129    }
1130
1131    fn load_cmp(
1132        &mut self,
1133        stage: usize,
1134        prefix: &str,
1135        ratio: usize,
1136        d: usize,
1137        rotate: bool,
1138    ) -> Res<CmpDev> {
1139        let (wkv_shape, _) = self.model.tensor_f32(&format!("{prefix}.wkv.weight"));
1140        let latent = wkv_shape[0];
1141        let overlap = ratio == 4;
1142        assert_eq!(latent, if overlap { 2 * d } else { d }, "{prefix} latent");
1143        Ok(CmpDev {
1144            ratio,
1145            d,
1146            latent,
1147            overlap,
1148            rotate,
1149            wkv: self.tensor_f32_dev(stage, &format!("{prefix}.wkv.weight"))?,
1150            wgate: self.tensor_f32_dev(stage, &format!("{prefix}.wgate.weight"))?,
1151            norm: self.tensor_f32_dev(stage, &format!("{prefix}.norm.weight"))?,
1152            ape: self.tensor_f32_dev(stage, &format!("{prefix}.ape"))?,
1153        })
1154    }
1155
1156    /// Load one block's device weights. `prefix` is "layers.N" for trunk, "mtp.0" for
1157    /// the MTP block (whose layer id is n_trunk — ratio 0, score-routed, MXFP4 experts).
1158    fn load_layer(&mut self, stage: usize, il: u32, prefix: &str) -> Res<LayerDev> {
1159        let d = self.model.cfg().clone();
1160        let moe = self.model.mc.moe.clone().expect("moe block");
1161        let ratio = d.compress_ratio(il) as usize;
1162        let hd = d.head_dim as usize;
1163        let p = prefix.to_string();
1164        let hash = d.is_hash_layer(il);
1165        let ne = moe.expert_count as usize;
1166        let inter = moe.expert_ff_length as usize;
1167        let hidden = self.model.mc.n_embd as usize;
1168
1169        // hc host params
1170        let hc_load = |m: &Dsv4Model, fam: &str| -> (Vec<f32>, Vec<f32>, Vec<f32>) {
1171            let fn_w = m.tensor_f32(&format!("{p}.hc_{fam}_fn")).1;
1172            let base = m.tensor_f32(&format!("{p}.hc_{fam}_base")).1;
1173            let scale = m.tensor_f32(&format!("{p}.hc_{fam}_scale")).1;
1174            (fn_w, base, scale)
1175        };
1176        let (attn_fn, attn_base, attn_scale) = hc_load(&self.model, "attn");
1177        let (ffn_fn, ffn_base, ffn_scale) = hc_load(&self.model, "ffn");
1178        let stream = self.stages[stage].gpu.stream();
1179        let hc_attn_fn = upload_f32(&stream, &attn_fn)?;
1180        let hc_ffn_fn = upload_f32(&stream, &ffn_fn)?;
1181        self.stages[stage].loaded_bytes += ((attn_fn.len() + ffn_fn.len()) * 4) as u64;
1182
1183        // expert slab (as-stored quant bytes) — geometry derived from config; the recipe
1184        // is DETECTED from the stored dtype (U8+weight_scale+weight_scale_2 = modelopt
1185        // NVFP4; I8+scale = OCP MXFP4, the MTP experts) and refused on any surprise.
1186        let (wi0, _) = self
1187            .model
1188            .st
1189            .raw(&format!("{p}.ffn.experts.0.w1.weight"))
1190            .unwrap_or_else(|| panic!("missing {p}.ffn.experts.0.w1.weight"));
1191        let expert_kind = match wi0.dtype.as_str() {
1192            "U8" => ExpertKind::Nvfp4,
1193            "I8" => ExpertKind::Mxfp4,
1194            other => panic!("{p}: unexpected expert weight dtype {other}"),
1195        };
1196        let wbytes = inter * hidden / 2; // same for w1/w2/w3 (transposed dims)
1197        let sbytes = match expert_kind {
1198            ExpertKind::Nvfp4 => inter * hidden / 16,
1199            ExpertKind::Mxfp4 => inter * hidden / 32,
1200        };
1201        let mut experts_w = stream
1202            .alloc_zeros::<u8>(ne * 3 * wbytes)
1203            .map_err(e("alloc expert slab"))?;
1204        let mut experts_sc = stream
1205            .alloc_zeros::<u8>(ne * 3 * sbytes)
1206            .map_err(e("alloc expert scale slab"))?;
1207        let mut experts_s2 = Vec::with_capacity(ne * 3);
1208        for ex in 0..ne {
1209            for (pi, pname) in ["w1", "w2", "w3"].iter().enumerate() {
1210                let base = format!("{p}.ffn.experts.{ex}.{pname}");
1211                let (wi, wb) = self
1212                    .model
1213                    .st
1214                    .raw(&format!("{base}.weight"))
1215                    .unwrap_or_else(|| panic!("missing {base}.weight"));
1216                assert_eq!(wb.len(), wbytes, "{base}: weight bytes");
1217                let sb = match expert_kind {
1218                    ExpertKind::Nvfp4 => {
1219                        assert_eq!(wi.dtype, "U8", "{base}: expected NVFP4 U8 weight");
1220                        let (_, sb) = self
1221                            .model
1222                            .st
1223                            .raw(&format!("{base}.weight_scale"))
1224                            .unwrap_or_else(|| panic!("missing {base}.weight_scale"));
1225                        let (_, s2b) = self
1226                            .model
1227                            .st
1228                            .raw(&format!("{base}.weight_scale_2"))
1229                            .unwrap_or_else(|| panic!("missing {base}.weight_scale_2"));
1230                        let s2 = f32::from_le_bytes(s2b.try_into().expect("scale_2 4B"));
1231                        // pow2 refusal: the bf16-exactness proof of the on-the-fly dequant
1232                        // rung requires a pow2 scale_2 (receipts, "Quant rungs" §1).
1233                        assert!(
1234                            s2 > 0.0 && s2.to_bits() & 0x007F_FFFF == 0,
1235                            "{base}: scale_2 {s2} not a power of two — rung exactness violated"
1236                        );
1237                        experts_s2.push(s2);
1238                        sb
1239                    }
1240                    ExpertKind::Mxfp4 => {
1241                        assert_eq!(wi.dtype, "I8", "{base}: expected MXFP4 I8 weight");
1242                        let (si, sb) = self
1243                            .model
1244                            .st
1245                            .raw(&format!("{base}.scale"))
1246                            .unwrap_or_else(|| panic!("missing {base}.scale"));
1247                        assert_eq!(si.dtype, "F8_E8M0", "{base}: expected E8M0 scale");
1248                        // e8m0 0xFF is the NaN code — refuse at load, never zero a scale
1249                        assert!(
1250                            !sb.contains(&0xFFu8),
1251                            "{base}: E8M0 NaN scale code — refusing"
1252                        );
1253                        experts_s2.push(1.0);
1254                        sb
1255                    }
1256                };
1257                assert_eq!(sb.len(), sbytes, "{base}: scale bytes");
1258                let off = (ex * 3 + pi) * wbytes;
1259                let mut view = experts_w.slice_mut(off..off + wbytes);
1260                stream
1261                    .memcpy_htod(wb, &mut view)
1262                    .map_err(e("htod expert w"))?;
1263                let soff = (ex * 3 + pi) * sbytes;
1264                let mut sview = experts_sc.slice_mut(soff..soff + sbytes);
1265                stream
1266                    .memcpy_htod(sb, &mut sview)
1267                    .map_err(e("htod expert sc"))?;
1268            }
1269        }
1270        self.stages[stage].loaded_bytes +=
1271            (ne * 3 * (wbytes + sbytes)) as u64 + (ne * 3 * 4) as u64;
1272
1273        let cmp = if ratio != 0 {
1274            Some(self.load_cmp(stage, &format!("{p}.attn.compressor"), ratio, hd, false)?)
1275        } else {
1276            None
1277        };
1278        // iteration-5 FP8 dense arm: trunk layers only this rung (the drafter/MTP
1279        // blocks ride the prefill helpers, which consume the bf16 slabs).
1280        let fp8_ok = p.starts_with("layers.");
1281        let idx = if d.has_indexer(il) {
1282            let heads = d.index_n_heads as usize;
1283            let ihd = d.index_head_dim as usize;
1284            let (iwq_b, iwq_b_fp8) =
1285                self.tensor_dense(stage, &format!("{p}.attn.indexer.wq_b"), fp8_ok)?;
1286            let (iwp, iwp_fp8) = self.tensor_dense(
1287                stage,
1288                &format!("{p}.attn.indexer.weights_proj.weight"),
1289                fp8_ok,
1290            )?;
1291            Some(IdxDev {
1292                wq_b: iwq_b,
1293                weights_proj: iwp,
1294                wq_b_fp8: iwq_b_fp8,
1295                weights_proj_fp8: iwp_fp8,
1296                cmp: self.load_cmp(
1297                    stage,
1298                    &format!("{p}.attn.indexer.compressor"),
1299                    ratio,
1300                    ihd,
1301                    true,
1302                )?,
1303                heads,
1304                hd: ihd,
1305                topk: d.index_topk as usize,
1306            })
1307        } else {
1308            None
1309        };
1310
1311        // lane 8: device twins of the host routing/hc constants. tid2eid is validated
1312        // here ONCE (range + per-row distinctness — the checks the legacy route_host
1313        // asserts per token) because the device route kernel cannot refuse.
1314        let stream = self.stages[stage].gpu.stream();
1315        let hc_attn_base_dev = upload_f32(&stream, &attn_base)?;
1316        let hc_attn_scale_dev = upload_f32(&stream, &attn_scale)?;
1317        let hc_ffn_base_dev = upload_f32(&stream, &ffn_base)?;
1318        let hc_ffn_scale_dev = upload_f32(&stream, &ffn_scale)?;
1319        let gate_bias_host: Option<Vec<f32>> = if hash {
1320            None
1321        } else {
1322            Some(self.model.tensor_f32(&format!("{p}.ffn.gate.bias")).1)
1323        };
1324        let gate_bias_dev = match &gate_bias_host {
1325            Some(b) => Some(upload_f32(&stream, b)?),
1326            None => None,
1327        };
1328        let tid2eid_host: Option<Vec<i64>> = if hash {
1329            Some(self.model.tensor_i64(&format!("{p}.ffn.gate.tid2eid")).1)
1330        } else {
1331            None
1332        };
1333        let tid2eid_dev = match &tid2eid_host {
1334            Some(t) => {
1335                let topk = moe.expert_used_count as usize;
1336                assert_eq!(t.len() % topk, 0, "{p}: tid2eid rows");
1337                let mut t32 = Vec::with_capacity(t.len());
1338                for row in t.chunks(topk) {
1339                    let mut seen = std::collections::BTreeSet::new();
1340                    for &ex in row {
1341                        assert!(
1342                            (0..ne as i64).contains(&ex),
1343                            "{p}: tid2eid out of range at load"
1344                        );
1345                        assert!(seen.insert(ex), "{p}: duplicate expert id in tid2eid row");
1346                        t32.push(ex as i32);
1347                    }
1348                }
1349                Some(upload_i32(&stream, &t32)?)
1350            }
1351            None => None,
1352        };
1353        let experts_s2_dev = upload_f32(&stream, &experts_s2)?;
1354
1355        let (wq_a, wq_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_a"), fp8_ok)?;
1356        let (wq_b, wq_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_b"), fp8_ok)?;
1357        let (wkv, wkv_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wkv"), fp8_ok)?;
1358        let (wo_a, wo_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_a"), fp8_ok)?;
1359        let (wo_b, wo_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_b"), fp8_ok)?;
1360        let (sw1, sw1_fp8) =
1361            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w1"), fp8_ok)?;
1362        let (sw2, sw2_fp8) =
1363            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w2"), fp8_ok)?;
1364        let (sw3, sw3_fp8) =
1365            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w3"), fp8_ok)?;
1366
1367        Ok(LayerDev {
1368            il,
1369            ratio,
1370            expert_kind,
1371            hc_attn_base_dev,
1372            hc_attn_scale_dev,
1373            hc_ffn_base_dev,
1374            hc_ffn_scale_dev,
1375            gate_bias_dev,
1376            tid2eid_dev,
1377            experts_s2_dev,
1378            wq_a,
1379            wq_b,
1380            wkv,
1381            wo_a,
1382            wo_b,
1383            wq_a_fp8,
1384            wq_b_fp8,
1385            wkv_fp8,
1386            wo_a_fp8,
1387            wo_b_fp8,
1388            q_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.q_norm.weight"))?,
1389            kv_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.kv_norm.weight"))?,
1390            attn_norm: self.tensor_f32_dev(stage, &format!("{p}.attn_norm.weight"))?,
1391            ffn_norm: self.tensor_f32_dev(stage, &format!("{p}.ffn_norm.weight"))?,
1392            sink: self.tensor_f32_dev(stage, &format!("{p}.attn.attn_sink"))?,
1393            cmp,
1394            idx,
1395            hc_attn_fn,
1396            hc_ffn_fn,
1397            hc_attn_base: attn_base,
1398            hc_attn_scale: attn_scale,
1399            hc_ffn_base: ffn_base,
1400            hc_ffn_scale: ffn_scale,
1401            gate_w: self.tensor_f32_dev(stage, &format!("{p}.ffn.gate.weight"))?,
1402            gate_bias: gate_bias_host,
1403            tid2eid: tid2eid_host,
1404            experts_w,
1405            experts_sc,
1406            experts_s2,
1407            shared_w: [sw1, sw2, sw3],
1408            shared_fp8: [sw1_fp8, sw2_fp8, sw3_fp8],
1409        })
1410    }
1411
1412    /// Open the artifact and place the trunk across `devices`. `split_at` = first layer
1413    /// of stage 1, derived from per-layer byte math unless overridden.
1414    pub fn load(
1415        dir: &Path,
1416        devices: &[usize],
1417        variant: ActQuantVariant,
1418        max_seq: usize,
1419    ) -> Res<Self> {
1420        assert_eq!(devices.len(), 2, "lane 4 placement is a 2-card layer split");
1421        let model = Dsv4Model::open(dir);
1422        let d = model.cfg().clone();
1423        let mc = model.mc.clone();
1424        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
1425        let rd = d.qk_rope_head_dim as usize;
1426
1427        // split point: balance per-layer resident bytes (experts uniform; fine layers
1428        // carry the indexer). Computed from config, not hardcoded.
1429        let layer_bytes = |il: u32| -> u64 {
1430            let ratio = d.compress_ratio(il);
1431            let base = 3_875_000_000u64; // experts slab + attn bf16 (measured class math)
1432            match ratio {
1433                4 => base + 66_000_000,
1434                _ => base,
1435            }
1436        };
1437        let total: u64 = (0..n_trunk).map(layer_bytes).sum();
1438        let mut acc = 0u64;
1439        let mut split_at = n_trunk / 2;
1440        for il in 0..n_trunk {
1441            acc += layer_bytes(il);
1442            if acc * 2 >= total {
1443                split_at = il + 1;
1444                break;
1445            }
1446        }
1447
1448        let fc_yarn_host = precompute_freqs_cis(
1449            rd,
1450            max_seq,
1451            d.rope_yarn_orig_ctx,
1452            d.compress_rope_theta,
1453            d.rope_yarn_factor,
1454            d.rope_yarn_beta_fast,
1455            d.rope_yarn_beta_slow,
1456        );
1457        let fc_plain_host = precompute_freqs_cis(
1458            rd,
1459            max_seq,
1460            0,
1461            mc.rope_freq_base,
1462            d.rope_yarn_factor,
1463            d.rope_yarn_beta_fast,
1464            d.rope_yarn_beta_slow,
1465        );
1466        let flat =
1467            |fc: &FreqsCis| -> Vec<f32> { fc.cs.iter().flat_map(|&(c, s)| [c, s]).collect() };
1468
1469        let inter = mc.moe.as_ref().expect("moe").expert_ff_length as usize;
1470        let hidden = mc.n_embd as usize;
1471        let mut stages = Vec::new();
1472        for &dev in devices {
1473            let gpu = memra_runtime::Gpu::new(dev).map_err(e("Gpu::new"))?;
1474            // Engine::new idiom (lib.rs:1172): single stream per stage, explicit syncs at
1475            // the boundary — cudarc per-arg event tracking off.
1476            unsafe { gpu.ctx.disable_event_tracking() };
1477            let stream = gpu.stream();
1478            let fc_yarn = upload_f32(&stream, &flat(&fc_yarn_host))?;
1479            let fc_plain = upload_f32(&stream, &flat(&fc_plain_host))?;
1480            let ws = stream.alloc_zeros::<u8>(64 << 20).map_err(e("ws alloc"))?;
1481            let deq = [
1482                stream
1483                    .alloc_zeros::<u8>(inter * hidden * 2)
1484                    .map_err(e("deq"))?,
1485                stream
1486                    .alloc_zeros::<u8>(inter * hidden * 2)
1487                    .map_err(e("deq"))?,
1488                stream
1489                    .alloc_zeros::<u8>(inter * hidden * 2)
1490                    .map_err(e("deq"))?,
1491            ];
1492            stages.push(Stage {
1493                dev,
1494                gpu,
1495                layers: Vec::new(),
1496                embed: None,
1497                head: None,
1498                trunk_norm: None,
1499                hc_head_fn: None,
1500                fc_yarn,
1501                fc_plain,
1502                ws,
1503                deq,
1504                loaded_bytes: 0,
1505                hc_head_base_dev: None,
1506                hc_head_scale_dev: None,
1507            });
1508        }
1509
1510        // lane 8: decode-path seam (read once, printed; one binary carries both arms)
1511        let decode_path = match std::env::var("MEMRA_DSV4_DECODE_PATH").as_deref() {
1512            Err(_) | Ok("") | Ok("legacy") => DecodePath::Legacy,
1513            Ok("device-hostmath") => DecodePath::Device { host_math: true },
1514            Ok("device") => DecodePath::Device { host_math: false },
1515            Ok(other) => {
1516                return Err(format!(
1517                    "MEMRA_DSV4_DECODE_PATH '{other}' unknown (legacy | device | device-hostmath)"
1518                ));
1519            }
1520        };
1521        // lane 9: island-dots arm seam (owner-gated fork; f64 = the oracle-truth arm).
1522        // 0731 re-gate extension rung: `f32x` = the f32 dots arm PLUS f32-accumulation
1523        // twins for the remaining device-path f64 chains (owner-authorized fork).
1524        // OWNER RATIFICATION 2026-08-19: f32x is the DEFAULT device-decode dots arm
1525        // (quality-stays condition met at the owner bar — 0731 re-gate Task B gates:
1526        // decode 52/52, CPU teacher-forcing 257/260 all-in-band, tf-gate 158/160,
1527        // determinism ×2). f64 stays the selectable oracle-truth arm; hc_sinkhorn is
1528        // NOT part of f32x (never authorized). Legacy path and prefill are untouched.
1529        // The unset default is DEVICE-decode-scoped by the ratification's own words:
1530        // the legacy path never consults the flag, so on Legacy an UNSET env resolves
1531        // to the f64 oracle bytes rather than tripping the f32-requires-device refusal
1532        // (box4 find, 2026-08-20: dsv4-gpu-gate under the flipped default panicked at
1533        // load on the legacy path — the refusal is for EXPLICIT f32/f32x only).
1534        // Illegal combos are BOOT REFUSALS (Err), never post-build aborts — hermes
1535        // review fingerprint a4e3d9a8eab4cf17: an assert! after Dsv4Gpu is built dies
1536        // as a process ABORT, which a serving watchdog restarts in a crash loop; the
1537        // unknown-enum arms already refuse at parse, so the combo checks live here too.
1538        let on_device = matches!(decode_path, DecodePath::Device { .. });
1539        let (dots_f32, chains_f32) = match std::env::var("MEMRA_DSV4_DOTS_ARM").as_deref() {
1540            Err(_) | Ok("") => {
1541                // ratified default, DEVICE-decode-scoped: legacy resolves f64.
1542                if on_device {
1543                    (true, true)
1544                } else {
1545                    (false, false)
1546                }
1547            }
1548            Ok(explicit @ ("f32x" | "f32")) if !on_device => {
1549                return Err(format!(
1550                    "MEMRA_DSV4_DOTS_ARM={explicit} requires MEMRA_DSV4_DECODE_PATH=device \
1551                     (the f32 dots arms exist on the device decode path only)"
1552                ));
1553            }
1554            Ok("f32x") => (true, true),
1555            Ok("f64") => (false, false),
1556            Ok("f32") => (true, false),
1557            Ok(other) => {
1558                return Err(format!(
1559                    "MEMRA_DSV4_DOTS_ARM '{other}' unknown (f64 | f32 | f32x)"
1560                ));
1561            }
1562        };
1563
1564        // Iteration-3 rung 4c, MEASURED FORK (nsys, drafted rounds [4,12)): the DRAFTER's
1565        // shared-trunk-head projection runs `dsv4_dots_f32` — the f64 kernel — over
1566        // block_size rows, and it measured **16.3 ms of a 78 ms drafted round (21%)**, one
1567        // instance at 13-14.7 ms. The trunk's OWN head already runs the ratified f32x arm
1568        // on the SAME weights; the drafter's copy only picks DRAFTS (verification always
1569        // emits the trunk's argmax, so output identity cannot depend on it). This arm makes
1570        // the drafter's exit head follow the ratified class. Default is f64 — today's gated
1571        // bytes, untouched — because the lane-10 components gate ran the drafter at f64 and
1572        // a gated component does not change default without its gate; `f32x` is the
1573        // measured arm offered for owner ratification with the acceptance delta reported.
1574        // OWNER RATIFICATION 2026-08-19 (relayed to the box4 lane 2026-08-20): f32x is
1575        // the DEFAULT drafter exit-head arm — the fork was measured quality-INERT
1576        // (acceptance digest byte-identical across arms on the gate fixture AND 3,321
1577        // corpora rounds, iteration-3 rung 4c) and it only picks DRAFTS (the greedy
1578        // identity law keeps the emitted stream the trunk's own argmax either way).
1579        // f64 stays selectable as the lane-10 oracle-truth arm. hc_sinkhorn remains f64
1580        // in every arm — never authorized.
1581        let dspark_head_f32 = match std::env::var("MEMRA_DSV4_DSPARK_HEAD_ARM").as_deref() {
1582            Err(_) | Ok("") | Ok("f32x") => true,
1583            Ok("f64") => false,
1584            Ok(other) => {
1585                return Err(format!(
1586                    "MEMRA_DSV4_DSPARK_HEAD_ARM '{other}' unknown (f64 | f32x)"
1587                ));
1588            }
1589        };
1590
1591        // iteration-5 FP8 dense arm seam — OWNER RATIFICATION 2026-08-20 (the ratified
1592        // bundle, executed in the v0.98 train once the it5 item-3 cells went green on
1593        // box7): **fp8 is the DEFAULT DEVICE-DECODE dense arm.** Receipts: bit-identical
1594        // to bf16 on four boxes / five binaries (dsgate accept shas
1595        // 150342bae32b38b5/85603e87fadf7876 one bit pattern, tf-gate 158/160 with the
1596        // banked near-ties at steps 22+134), completed interleaved x5 A/B plain
1597        // 41.06 -> 47.19 median (+14.9%, box5), and the item-3 staged residency turns
1598        // the arm's +2.7 GiB/card dual-residency cost into a saving (box7: -5.56/-5.34
1599        // GiB/card vs the dual-resident builds, every item-3 bit-gate green).
1600        // DEVICE-scoped exactly like the ratified dots default (82a754fbec): unset on
1601        // the LEGACY path resolves bf16 (legacy has no fp8 twins and must keep
1602        // booting); explicit fp8 on legacy still refuses; bf16 stays selectable
1603        // everywhere. Resolution is the pure `resolve_dense_arm` so the flip is
1604        // toothed-testable; the `[load] dense arm:` line below is the boot receipt.
1605        let dense_fp8 = resolve_dense_arm(
1606            std::env::var("MEMRA_DSV4_DENSE_ARM").ok().as_deref(),
1607            on_device,
1608        )?;
1609
1610        let mut me = Dsv4Gpu {
1611            model,
1612            stages,
1613            layer_stage: (0..n_trunk).map(|il| usize::from(il >= split_at)).collect(),
1614            split_at,
1615            max_seq,
1616            variant,
1617            fc_yarn_host,
1618            fc_plain_host,
1619            mtp: None,
1620            expert_arm: if memra_gguf::dsv4_forward::expert_arm_native() {
1621                ExpertArm::Native
1622            } else {
1623                ExpertArm::Bf16Dequant
1624            },
1625            decode_path,
1626            dots_f32,
1627            chains_f32,
1628            dspark_head_f32,
1629            dense_fp8,
1630            dspark: None,
1631            boundary_ev: Vec::new(),
1632            hc_head_base: Vec::new(),
1633            hc_head_scale: Vec::new(),
1634        };
1635        eprintln!(
1636            "[load] expert arm: {:?} | decode path: {:?} | dots arm: {}",
1637            me.expert_arm,
1638            me.decode_path,
1639            if me.chains_f32 {
1640                "f32x (dots + sink/norm/indexer chains)"
1641            } else if me.dots_f32 {
1642                "f32"
1643            } else {
1644                "f64"
1645            }
1646        );
1647        eprintln!(
1648            "[load] dspark exit-head dots arm: {} (rung-4c fork; drafts only, never the \
1649             emitted stream)",
1650            if me.dspark_head_f32 { "f32x" } else { "f64" }
1651        );
1652        eprintln!(
1653            "[load] dense arm: {} (iteration-5; fp8 = FP8-blk linears as-stored on the \
1654             device decode/verify paths, bit-identical twins)",
1655            if me.dense_fp8 { "fp8" } else { "bf16" }
1656        );
1657        if matches!(me.decode_path, DecodePath::Device { .. }) && me.expert_arm != ExpertArm::Native
1658        {
1659            // the indirect fused dispatch is an fp4-slab program — the bf16-dequant arm
1660            // has no device-indirect twin. Boot refusal, not a post-build abort
1661            // (hermes fingerprint a4e3d9a8eab4cf17); the dots/dense combos refuse at
1662            // env-parse above for the same reason.
1663            return Err(
1664                "MEMRA_DSV4_DECODE_PATH=device requires MEMRA_DSV4_EXPERT_ARM=native".to_string(),
1665            );
1666        }
1667
1668        // lane 8: peer transport for the PP boundary (pp.rs idiom: cuCtxEnablePeerAccess
1669        // both directions + default-mempool access grants — cudarc buffers are
1670        // stream-ordered-pool allocations, unmapped by EnablePeerAccess alone).
1671        if matches!(me.decode_path, DecodePath::Device { .. }) && me.stages.len() > 1 {
1672            use cudarc::driver::sys as cus;
1673            for a in 0..me.stages.len() {
1674                for b in 0..me.stages.len() {
1675                    if a == b || me.stages[a].dev == me.stages[b].dev {
1676                        continue;
1677                    }
1678                    me.stages[a]
1679                        .gpu
1680                        .ctx
1681                        .bind_to_thread()
1682                        .map_err(e("peer bind"))?;
1683                    let rc =
1684                        unsafe { cus::cuCtxEnablePeerAccess(me.stages[b].gpu.ctx.cu_ctx(), 0) };
1685                    if rc != cus::cudaError_enum::CUDA_SUCCESS
1686                        && rc != cus::cudaError_enum::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED
1687                    {
1688                        return Err(format!(
1689                            "cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
1690                            me.stages[a].dev, me.stages[b].dev
1691                        ));
1692                    }
1693                    let dev = cudarc::driver::result::device::get(me.stages[a].dev as i32)
1694                        .map_err(e("device get"))?;
1695                    let mut pool: cus::CUmemoryPool = std::ptr::null_mut();
1696                    unsafe {
1697                        cus::cuDeviceGetDefaultMemPool(&mut pool, dev)
1698                            .result()
1699                            .map_err(e("default pool"))?;
1700                    }
1701                    let desc = cus::CUmemAccessDesc {
1702                        location: cus::CUmemLocation {
1703                            type_: cus::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
1704                            id: me.stages[b].dev as i32,
1705                        },
1706                        flags: cus::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
1707                    };
1708                    let rc = unsafe { cus::cuMemPoolSetAccess(pool, &desc, 1) };
1709                    if rc != cus::cudaError_enum::CUDA_SUCCESS {
1710                        return Err(format!(
1711                            "cuMemPoolSetAccess(dev{} pool -> dev{}) failed: {rc:?}",
1712                            me.stages[a].dev, me.stages[b].dev
1713                        ));
1714                    }
1715                }
1716            }
1717            for bnd in 0..me.stages.len() - 1 {
1718                let ev = me.stages[bnd]
1719                    .gpu
1720                    .ctx
1721                    .new_event(None)
1722                    .map_err(e("boundary event"))?;
1723                me.boundary_ev.push(ev);
1724            }
1725            // PEER BYTE-INTEGRITY PROBE (lane/hermes-perf-fixes, 2026-08-23): enable +
1726            // pool grants alone prove ADDRESSABILITY, not integrity — see the probe
1727            // helpers' header for the Pod B receipt. Ladder up to the prefill
1728            // hidden-state payload class; both directions per cross-device boundary;
1729            // FAIL-CLOSED at load (the device PP path has no host-bounce twin).
1730            {
1731                let hidden = me.model.mc.n_embd as usize;
1732                let payload = (4096usize * hidden * std::mem::size_of::<f32>()).min(64 << 20);
1733                let ladder = [16usize * 1024, 1 << 20, payload];
1734                let probe_t0 = std::time::Instant::now();
1735                let mut copies = 0usize;
1736                for bnd in 0..me.stages.len() - 1 {
1737                    if me.stages[bnd].dev == me.stages[bnd + 1].dev {
1738                        continue;
1739                    }
1740                    for (s, d) in [(bnd, bnd + 1), (bnd + 1, bnd)] {
1741                        for &bytes in &ladder {
1742                            dsv4_peer_probe_copy(&me.stages[s], &me.stages[d], bnd, bytes)
1743                                .map_err(|err| {
1744                                    format!(
1745                                        "dsv4 PP peer byte-integrity probe FAILED: \
1746                                         boundary={bnd} dev{}->dev{} bytes={bytes}: {err}; \
1747                                         refusing the device PP path (silent hidden-state \
1748                                         corruption class — fix the P2P fabric or serve a \
1749                                         non-device MEMRA_DSV4_DECODE_PATH)",
1750                                        me.stages[s].dev, me.stages[d].dev,
1751                                    )
1752                                })?;
1753                            copies += 1;
1754                        }
1755                    }
1756                }
1757                eprintln!(
1758                    "[load] lane-8 peer byte-integrity probe PASS: {} boundaries, \
1759                     {copies} copies, ladder {ladder:?} bytes, {:.1}ms",
1760                    me.boundary_ev.len(),
1761                    probe_t0.elapsed().as_secs_f64() * 1e3,
1762                );
1763            }
1764            eprintln!(
1765                "[load] lane-8 peer transport enabled ({} boundaries)",
1766                me.boundary_ev.len()
1767            );
1768        }
1769
1770        // stage 0: embed; last stage: head + trunk hc_head/norm
1771        me.stages[0].embed = Some({
1772            let (_, raw) = me.model.st.raw("embed.weight").expect("embed.weight");
1773            let stream = me.stages[0].gpu.stream();
1774            me.stages[0].loaded_bytes += raw.len() as u64;
1775            upload_u8(&stream, raw)?
1776        });
1777        let last = me.stages.len() - 1;
1778        me.stages[last].head = Some({
1779            let (_, raw) = me.model.st.raw("head.weight").expect("head.weight");
1780            let stream = me.stages[last].gpu.stream();
1781            me.stages[last].loaded_bytes += raw.len() as u64;
1782            upload_u8(&stream, raw)?
1783        });
1784        me.stages[last].trunk_norm = Some(me.tensor_f32_dev(last, "norm.weight")?);
1785        me.stages[last].hc_head_fn = Some(me.tensor_f32_dev(last, "hc_head_fn")?);
1786        me.hc_head_base = me.model.tensor_f32("hc_head_base").1;
1787        me.hc_head_scale = me.model.tensor_f32("hc_head_scale").1;
1788        {
1789            let stream = me.stages[last].gpu.stream();
1790            let base_dev = upload_f32(&stream, &me.hc_head_base)?;
1791            let scale_dev = upload_f32(&stream, &me.hc_head_scale)?;
1792            me.stages[last].hc_head_base_dev = Some(base_dev);
1793            me.stages[last].hc_head_scale_dev = Some(scale_dev);
1794        }
1795
1796        let t0 = std::time::Instant::now();
1797        for il in 0..n_trunk {
1798            let stage = me.layer_stage[il as usize];
1799            let l = me.load_layer(stage, il, &format!("layers.{il}"))?;
1800            me.stages[stage].layers.push(l);
1801            if il % 4 == 3 || il + 1 == n_trunk {
1802                eprintln!(
1803                    "[load] layer {il} -> dev{} done t={:.0}s",
1804                    me.stages[stage].dev,
1805                    t0.elapsed().as_secs_f64()
1806                );
1807            }
1808        }
1809        // MTP (NextN) block on the last stage — optional path taken because the trunk
1810        // landed with box time to spare (lane brief); layer id = n_trunk from config.
1811        // 0731 lineage: the `mtp.*` namespace is the DSPARK drafter (3 window-only
1812        // blocks; census per the mint receipts: mtp.0 main_proj/main_norm, mtp.2
1813        // markov_w1/w2 + confidence_head — no e_proj/enorm), NOT a NextN head. Its GPU
1814        // path is a separate lane; the trunk forward never consumes it. Discriminate on
1815        // the artifact's own stored structure (lane-1 law: stored tensor names are the
1816        // recipe truth): a NextN block carries `mtp.0.e_proj.weight` (RAW safetensors
1817        // name — measured on both artifacts: preview has e_proj.weight+.scale, 0731 has
1818        // no e_proj keys; the stem alone misses because `has` is raw-exact).
1819        let nextn = me.model.mc.nextn_predict_layers;
1820        if nextn > 0 && me.model.has("mtp.0.e_proj.weight") {
1821            assert_eq!(
1822                nextn, 1,
1823                "multi-NextN chains not wired (single MTP layer expected)"
1824            );
1825            let p = "mtp.0";
1826            let layer = me.load_layer(last, n_trunk, p)?;
1827            assert_eq!(
1828                layer.expert_kind,
1829                ExpertKind::Mxfp4,
1830                "MTP experts must be MXFP4"
1831            );
1832            let mtp = MtpDev {
1833                layer,
1834                enorm: me.tensor_f32_dev(last, &format!("{p}.enorm.weight"))?,
1835                hnorm: me.tensor_f32_dev(last, &format!("{p}.hnorm.weight"))?,
1836                norm: me.tensor_f32_dev(last, &format!("{p}.norm.weight"))?,
1837                e_proj: me.tensor_bf16(last, &format!("{p}.e_proj"))?,
1838                h_proj: me.tensor_bf16(last, &format!("{p}.h_proj"))?,
1839                hc_head_fn: me.tensor_f32_dev(last, &format!("{p}.hc_head_fn"))?,
1840                hc_head_base: me.model.tensor_f32(&format!("{p}.hc_head_base")).1,
1841                hc_head_scale: me.model.tensor_f32(&format!("{p}.hc_head_scale")).1,
1842            };
1843            me.mtp = Some(mtp);
1844        } else if nextn > 0 {
1845            if std::env::var("MEMRA_DSV4_DRAFTER").as_deref() == Ok("dspark") {
1846                // iteration 3: the DSpark drafter, whole module on the LAST stage
1847                // (tap layers 40/41/42 + shared head locality — VRAM plan in the
1848                // iteration-3 receipts). Census pins + NextN refusal ride the CPU
1849                // oracle's own config loader (one refusal program, two realizations).
1850                let cfg = memra_gguf::dsv4_dspark::DsparkConfig::load(dir, &me.model);
1851                let hidden = me.model.mc.n_embd as usize;
1852                let mut blocks = Vec::with_capacity(cfg.n_blocks);
1853                for k in 0..cfg.n_blocks {
1854                    let layer = me.load_layer(last, n_trunk + k as u32, &format!("mtp.{k}"))?;
1855                    assert_eq!(layer.ratio, 0, "dspark block mtp.{k} must be ratio 0");
1856                    assert_eq!(
1857                        layer.expert_kind,
1858                        ExpertKind::Mxfp4,
1859                        "dspark experts must be MXFP4"
1860                    );
1861                    blocks.push(layer);
1862                }
1863                let last_p = format!("mtp.{}", cfg.n_blocks - 1);
1864                let (mp_shape, _) = me.model.tensor_f32("mtp.0.main_proj");
1865                assert_eq!(
1866                    mp_shape,
1867                    vec![hidden, cfg.target_layer_ids.len() * hidden],
1868                    "main_proj shape"
1869                );
1870                let (w1_shape, w1) = me
1871                    .model
1872                    .tensor_f32(&format!("{last_p}.markov_head.markov_w1.weight"));
1873                let (w2_shape, w2) = me
1874                    .model
1875                    .tensor_f32(&format!("{last_p}.markov_head.markov_w2.weight"));
1876                let vocab = w1_shape[0];
1877                assert_eq!(w1_shape[1], cfg.markov_rank, "markov_w1 rank");
1878                assert_eq!(w2_shape, vec![vocab, cfg.markov_rank], "markov_w2 shape");
1879                let (cf_shape, _) = me
1880                    .model
1881                    .tensor_f32(&format!("{last_p}.confidence_head.proj.weight"));
1882                assert_eq!(
1883                    cf_shape,
1884                    vec![1, hidden + cfg.markov_rank],
1885                    "confidence proj shape"
1886                );
1887                let st_stream = me.stages[last].gpu.stream();
1888                let markov_w1 = upload_f32(&st_stream, &w1)?;
1889                let markov_w2 = upload_f32(&st_stream, &w2)?;
1890                let dspark = DsparkDev {
1891                    blocks,
1892                    main_proj: me.tensor_bf16(last, "mtp.0.main_proj")?,
1893                    main_norm: me.tensor_f32_dev(last, "mtp.0.main_norm.weight")?,
1894                    norm: me.tensor_f32_dev(last, &format!("{last_p}.norm.weight"))?,
1895                    markov_w1,
1896                    markov_w2,
1897                    markov_w1_host: w1,
1898                    conf_w: me
1899                        .tensor_f32_dev(last, &format!("{last_p}.confidence_head.proj.weight"))?,
1900                    hc_head_fn: me.tensor_f32_dev(last, &format!("{last_p}.hc_head_fn"))?,
1901                    hc_head_base: me.model.tensor_f32(&format!("{last_p}.hc_head_base")).1,
1902                    hc_head_scale: me.model.tensor_f32(&format!("{last_p}.hc_head_scale")).1,
1903                    block_size: cfg.block_size,
1904                    noise_token: cfg.noise_token_id,
1905                    targets: cfg.target_layer_ids.clone(),
1906                    rank: cfg.markov_rank,
1907                    vocab,
1908                };
1909                eprintln!(
1910                    "[load] drafter: DSpark ({} blocks, block_size {}, targets {:?}) \
1911                     resident on stage {last}",
1912                    cfg.n_blocks, cfg.block_size, cfg.target_layer_ids
1913                );
1914                me.dspark = Some(dspark);
1915            } else {
1916                eprintln!(
1917                    "[load] drafter: {nextn} DSpark block(s) (mtp.0.e_proj absent) — GPU \
1918                     drafter path off (set MEMRA_DSV4_DRAFTER=dspark); trunk-only"
1919                );
1920            }
1921        }
1922        for st in &me.stages {
1923            st.gpu.stream().synchronize().map_err(e("load sync"))?;
1924        }
1925        Ok(me)
1926    }
1927
1928    /// (free, total, resident-by-loader) bytes per device — the placement table source.
1929    pub fn vram_report(&self) -> Res<Vec<(usize, u64, u64, u64)>> {
1930        let mut out = Vec::new();
1931        for st in &self.stages {
1932            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
1933            let (free, total) = st.gpu.ctx.mem_get_info().map_err(e("mem_get_info"))?;
1934            out.push((st.dev, free as u64, total as u64, st.loaded_bytes));
1935        }
1936        Ok(out)
1937    }
1938
1939    // ---------------------------------------------------------------- forward pieces
1940
1941    /// bf16 GEMM y[mxn] f32 = x[mxk] (f32, cast here) @ w[nxk]ᵀ (bf16 resident).
1942    /// `w_off_elems` slices the weight (grouped wo_a).
1943    #[allow(clippy::too_many_arguments)]
1944    fn gemm(
1945        st: &Stage,
1946        x_f32: &CudaSlice<f32>,
1947        w_bf16: &CudaSlice<u8>,
1948        w_off_elems: usize,
1949        m: usize,
1950        n: usize,
1951        kdim: usize,
1952        y: &mut CudaSlice<f32>,
1953    ) -> Res<()> {
1954        let stream = st.gpu.stream();
1955        let mut xb = stream
1956            .alloc_zeros::<u8>(m * kdim * 2)
1957            .map_err(e("alloc xb"))?;
1958        unsafe {
1959            ck(
1960                "cvt_bf16",
1961                k::memra_dsv4_cvt_bf16(
1962                    dpf!(x_f32, &stream),
1963                    xb.device_ptr_mut(&stream).0 as *mut c_void,
1964                    (m * kdim) as i64,
1965                    sp(&stream),
1966                ),
1967            )?;
1968            ck(
1969                "gemm_bf16",
1970                k::memra_dsv4_gemm_bf16(
1971                    (w_bf16.device_ptr(&stream).0 as usize + w_off_elems * 2) as *const c_void,
1972                    dp!(xb, &stream),
1973                    dpm!(y, &stream),
1974                    m as i32,
1975                    n as i32,
1976                    kdim as i32,
1977                    st.dev as i32,
1978                    st.ws.device_ptr(&stream).0 as *mut c_void,
1979                    st.ws.len(),
1980                    sp(&stream),
1981                ),
1982            )?;
1983        }
1984        Ok(())
1985    }
1986
1987    /// bf16 GEMM from an ALREADY-bf16 activation buffer.
1988    #[allow(clippy::too_many_arguments)]
1989    fn gemm_pre(
1990        st: &Stage,
1991        xb: &CudaSlice<u8>,
1992        w_bf16_ptr: *const c_void,
1993        m: usize,
1994        n: usize,
1995        kdim: usize,
1996        y: &mut CudaSlice<f32>,
1997    ) -> Res<()> {
1998        let stream = st.gpu.stream();
1999        unsafe {
2000            ck(
2001                "gemm_bf16",
2002                k::memra_dsv4_gemm_bf16(
2003                    w_bf16_ptr,
2004                    dp!(xb, &stream),
2005                    dpm!(y, &stream),
2006                    m as i32,
2007                    n as i32,
2008                    kdim as i32,
2009                    st.dev as i32,
2010                    st.ws.device_ptr(&stream).0 as *mut c_void,
2011                    st.ws.len(),
2012                    sp(&stream),
2013                ),
2014            )?;
2015        }
2016        Ok(())
2017    }
2018
2019    /// f32-island GEMM (f64-accumulated dots kernel).
2020    fn dots(
2021        st: &Stage,
2022        x: &CudaSlice<f32>,
2023        w_f32: &CudaSlice<f32>,
2024        s: usize,
2025        kdim: usize,
2026        n: usize,
2027        y: &mut CudaSlice<f32>,
2028    ) -> Res<()> {
2029        let stream = st.gpu.stream();
2030        unsafe {
2031            ck(
2032                "dots_f32",
2033                k::memra_dsv4_dots_f32(
2034                    dpf!(x, &stream),
2035                    dp!(w_f32, &stream),
2036                    0,
2037                    dpm!(y, &stream),
2038                    s as i32,
2039                    kdim as i32,
2040                    n as i32,
2041                    sp(&stream),
2042                ),
2043            )?;
2044        }
2045        Ok(())
2046    }
2047
2048    /// Island dots on the DEVICE decode path (lane 9): routes to the f64 oracle-truth
2049    /// arm (default — byte-identical to `Self::dots`) or the owner-gated
2050    /// f32-accumulation serving arm (MEMRA_DSV4_DOTS_ARM=f32; fork gated by
2051    /// decode-gate + oracle teacher-forcing, RECEIPTS.md "Lane 9").
2052    fn dots_dev(
2053        &self,
2054        st: &Stage,
2055        x: &CudaSlice<f32>,
2056        w_f32: &CudaSlice<f32>,
2057        s: usize,
2058        kdim: usize,
2059        n: usize,
2060        y: &mut CudaSlice<f32>,
2061    ) -> Res<()> {
2062        if !self.dots_f32 {
2063            return Self::dots(st, x, w_f32, s, kdim, n, y);
2064        }
2065        let stream = st.gpu.stream();
2066        unsafe {
2067            ck(
2068                "dots_f32acc",
2069                k::memra_dsv4_dots_f32acc(
2070                    dpf!(x, &stream),
2071                    dp!(w_f32, &stream),
2072                    0,
2073                    dpm!(y, &stream),
2074                    s as i32,
2075                    kdim as i32,
2076                    n as i32,
2077                    sp(&stream),
2078                ),
2079            )?;
2080        }
2081        Ok(())
2082    }
2083
2084    /// Compressor forward (f32 island end-to-end). Returns (Some((ckv [nb, d], nb)) or
2085    /// None when no complete block, kv_raw [s, latent], score_raw [s, latent]).
2086    /// The raw GEMM outputs are ALWAYS computed (the reference does too, M:330-331) —
2087    /// lane 6 seeds the decode pending state from their trailing rows.
2088    #[allow(clippy::too_many_arguments)]
2089    fn compressor(
2090        &self,
2091        st: &Stage,
2092        cmp: &CmpDev,
2093        x: &CudaSlice<f32>, // [s, hidden] post-attn-norm
2094        s: usize,
2095        hidden: usize,
2096        fc_dev: &CudaSlice<f32>,
2097        rd: usize,
2098        eps: f32,
2099    ) -> Res<(
2100        Option<(CudaSlice<f32>, usize)>,
2101        CudaSlice<f32>,
2102        CudaSlice<f32>,
2103    )> {
2104        let stream = st.gpu.stream();
2105        let mut kv = stream
2106            .alloc_zeros::<f32>(s * cmp.latent)
2107            .map_err(e("cmp kv"))?;
2108        let mut score = stream
2109            .alloc_zeros::<f32>(s * cmp.latent)
2110            .map_err(e("cmp score"))?;
2111        Self::dots(st, x, &cmp.wkv, s, hidden, cmp.latent, &mut kv)?;
2112        Self::dots(st, x, &cmp.wgate, s, hidden, cmp.latent, &mut score)?;
2113        if s < cmp.ratio {
2114            return Ok((None, kv, score));
2115        }
2116        let cutoff = s - s % cmp.ratio;
2117        let nb = cutoff / cmp.ratio;
2118        let mut pooled = stream
2119            .alloc_zeros::<f32>(nb * cmp.d)
2120            .map_err(e("cmp out"))?;
2121        unsafe {
2122            ck(
2123                "compressor_pool",
2124                k::memra_dsv4_compressor_pool(
2125                    dpf!(kv, &stream),
2126                    dpf!(score, &stream),
2127                    dpf!(cmp.ape, &stream),
2128                    dpm!(pooled, &stream),
2129                    nb as i32,
2130                    cmp.ratio as i32,
2131                    cmp.d as i32,
2132                    cmp.latent as i32,
2133                    cmp.overlap as i32,
2134                    sp(&stream),
2135                ),
2136            )?;
2137            ck(
2138                "rmsnorm cmp",
2139                k::memra_dsv4_rmsnorm(
2140                    dpf!(pooled, &stream),
2141                    dpf!(cmp.norm, &stream),
2142                    dpm!(pooled, &stream),
2143                    nb as i32,
2144                    cmp.d as i32,
2145                    eps,
2146                    sp(&stream),
2147                ),
2148            )?;
2149            let positions: Vec<i32> = (0..nb).map(|j| (j * cmp.ratio) as i32).collect();
2150            let pos_dev = upload_i32(&stream, &positions)?;
2151            ck(
2152                "rope cmp",
2153                k::memra_dsv4_rope(
2154                    dpm!(pooled, &stream),
2155                    nb as i32,
2156                    1,
2157                    cmp.d as i32,
2158                    rd as i32,
2159                    dpf!(fc_dev, &stream),
2160                    pos_dev.device_ptr(&stream).0 as *const i32,
2161                    0,
2162                    sp(&stream),
2163                ),
2164            )?;
2165            if cmp.rotate {
2166                // oracle hadamard scale: (d as f32).powf(-0.5)
2167                let scale = (cmp.d as f32).powf(-0.5);
2168                ck(
2169                    "hadamard cmp",
2170                    k::memra_dsv4_hadamard(
2171                        dpm!(pooled, &stream),
2172                        nb as i32,
2173                        cmp.d as i32,
2174                        scale,
2175                        sp(&stream),
2176                    ),
2177                )?;
2178                ck(
2179                    "fp4 cmp",
2180                    k::memra_dsv4_fp4_act_quant(
2181                        dpm!(pooled, &stream),
2182                        nb as i32,
2183                        cmp.d as i64,
2184                        cmp.d as i32,
2185                        sp(&stream),
2186                    ),
2187                )?;
2188            } else {
2189                ck(
2190                    "act_quant cmp",
2191                    k::memra_dsv4_act_quant(
2192                        dpm!(pooled, &stream),
2193                        nb as i32,
2194                        cmp.d as i64,
2195                        (cmp.d - rd) as i32,
2196                        64,
2197                        (self.variant == ActQuantVariant::ClampOnly) as i32,
2198                        sp(&stream),
2199                    ),
2200                )?;
2201            }
2202        }
2203        Ok((Some((pooled, nb)), kv, score))
2204    }
2205
2206    /// Prefill→decode handoff for one compressor: copy the pooled blocks into the
2207    /// store rows [row0, row0+nb) and seed the pending state from the raw kv/score
2208    /// trailing rows (fine: last COMPLETE block → prev slots + remainder → cur slots,
2209    /// M:346-352; coarse: remainder → slots [0, rem)).
2210    #[allow(clippy::too_many_arguments)]
2211    fn populate_cmp_cache(
2212        stream: &std::sync::Arc<CudaStream>,
2213        s: usize,
2214        cmp_ratio: usize,
2215        latent: usize,
2216        d: usize,
2217        pooled: &Option<(CudaSlice<f32>, usize)>,
2218        kv_raw: &CudaSlice<f32>,
2219        score_raw: &CudaSlice<f32>,
2220        store: &mut CudaSlice<f32>,
2221        row0: usize,
2222        blocks: &mut usize,
2223        pend_kv: &mut CudaSlice<f32>,
2224        pend_score: &mut CudaSlice<f32>,
2225        overlap: bool,
2226    ) -> Res<()> {
2227        *blocks = 0;
2228        if let Some((buf, nb)) = pooled {
2229            let src = buf.slice(0..nb * d);
2230            let mut dst = store.slice_mut(row0 * d..(row0 + nb) * d);
2231            stream.memcpy_dtod(&src, &mut dst).map_err(e("cmp store"))?;
2232            *blocks = *nb;
2233        }
2234        let cutoff = s - s % cmp_ratio;
2235        let rem = s - cutoff;
2236        if overlap {
2237            if cutoff >= cmp_ratio {
2238                let a = (cutoff - cmp_ratio) * latent;
2239                let b = cutoff * latent;
2240                let src = kv_raw.slice(a..b);
2241                let mut dst = pend_kv.slice_mut(0..cmp_ratio * latent);
2242                stream
2243                    .memcpy_dtod(&src, &mut dst)
2244                    .map_err(e("pend kv prev"))?;
2245                let src = score_raw.slice(a..b);
2246                let mut dst = pend_score.slice_mut(0..cmp_ratio * latent);
2247                stream
2248                    .memcpy_dtod(&src, &mut dst)
2249                    .map_err(e("pend sc prev"))?;
2250            }
2251            if rem > 0 {
2252                let a = cutoff * latent;
2253                let src = kv_raw.slice(a..s * latent);
2254                let mut dst = pend_kv.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2255                stream
2256                    .memcpy_dtod(&src, &mut dst)
2257                    .map_err(e("pend kv cur"))?;
2258                let src = score_raw.slice(a..s * latent);
2259                let mut dst = pend_score.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2260                stream
2261                    .memcpy_dtod(&src, &mut dst)
2262                    .map_err(e("pend sc cur"))?;
2263            }
2264        } else if rem > 0 {
2265            let a = cutoff * latent;
2266            let src = kv_raw.slice(a..s * latent);
2267            let mut dst = pend_kv.slice_mut(0..rem * latent);
2268            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
2269            let src = score_raw.slice(a..s * latent);
2270            let mut dst = pend_score.slice_mut(0..rem * latent);
2271            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
2272        }
2273        Ok(())
2274    }
2275
2276    /// hc_pre: mixes GEMM (f32 island) + rowsq scale on GPU, Sinkhorn on HOST via the
2277    /// oracle's own hc_split_sinkhorn. Returns (y [s,hidden] dev, post dev, comb dev).
2278    #[allow(clippy::too_many_arguments)]
2279    fn hc_pre(
2280        st: &Stage,
2281        h: &CudaSlice<f32>, // [s, hc, hidden]
2282        fn_w: &CudaSlice<f32>,
2283        base: &[f32],
2284        scale: &[f32],
2285        s: usize,
2286        hc: usize,
2287        hidden: usize,
2288        iters: u32,
2289        hc_eps: f32,
2290    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)> {
2291        let stream = st.gpu.stream();
2292        let w = hc * hidden;
2293        let rows = (2 + hc) * hc;
2294        let mut mixes = stream.alloc_zeros::<f32>(s * rows).map_err(e("mixes"))?;
2295        Self::dots(st, h, fn_w, s, w, rows, &mut mixes)?;
2296        unsafe {
2297            ck(
2298                "rowsq_scale",
2299                k::memra_dsv4_rowsq_scale(
2300                    dpf!(h, &stream),
2301                    dpm!(mixes, &stream),
2302                    s as i32,
2303                    w as i32,
2304                    rows as i32,
2305                    hc_eps,
2306                    sp(&stream),
2307                ),
2308            )?;
2309        }
2310        let mixes_h = dtoh_f32(&stream, &mixes)?;
2311        let (pre, post, comb) = hc_split_sinkhorn(&mixes_h, s, hc, scale, base, iters, hc_eps);
2312        let pre_d = upload_f32(&stream, &pre)?;
2313        let post_d = upload_f32(&stream, &post)?;
2314        let comb_d = upload_f32(&stream, &comb)?;
2315        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("hc y"))?;
2316        unsafe {
2317            ck(
2318                "hc_collapse",
2319                k::memra_dsv4_hc_collapse(
2320                    dpf!(h, &stream),
2321                    dpf!(pre_d, &stream),
2322                    dpm!(y, &stream),
2323                    s as i32,
2324                    hc as i32,
2325                    hidden as i32,
2326                    sp(&stream),
2327                ),
2328            )?;
2329        }
2330        Ok((y, post_d, comb_d))
2331    }
2332
2333    /// Host routing — the oracle MoeW::forward selection/weight math verbatim.
2334    #[allow(clippy::too_many_arguments)]
2335    fn route_host(
2336        layer: &LayerDev,
2337        raw_scores: &[f32], // [s, ne] gate GEMM output (pre-softplus)
2338        ids: &[u32],
2339        s: usize,
2340        ne: usize,
2341        topk: usize,
2342        route_scale: f32,
2343    ) -> (Vec<usize>, Vec<f32>) {
2344        let mut scores = raw_scores.to_vec();
2345        for v in &mut scores {
2346            *v = softplus_f32(*v).sqrt();
2347        }
2348        let mut indices = vec![0usize; s * topk];
2349        if let Some(tid2eid) = &layer.tid2eid {
2350            for t in 0..s {
2351                let row = &tid2eid[ids[t] as usize * topk..(ids[t] as usize + 1) * topk];
2352                let mut seen = std::collections::BTreeSet::new();
2353                for (kk, &ex) in row.iter().enumerate() {
2354                    assert!(
2355                        (0..ne as i64).contains(&ex),
2356                        "layer {}: tid2eid out of range",
2357                        layer.il
2358                    );
2359                    assert!(
2360                        seen.insert(ex),
2361                        "layer {}: duplicate expert id in tid2eid row {}",
2362                        layer.il,
2363                        ids[t]
2364                    );
2365                    indices[t * topk + kk] = ex as usize;
2366                }
2367            }
2368        } else {
2369            let bias = layer.gate_bias.as_ref().expect("score layer needs bias");
2370            for t in 0..s {
2371                let biased: Vec<f32> = (0..ne).map(|ex| scores[t * ne + ex] + bias[ex]).collect();
2372                let mut order: Vec<usize> = (0..ne).collect();
2373                order.sort_by(|&a, &b| {
2374                    biased[b]
2375                        .partial_cmp(&biased[a])
2376                        .unwrap_or(std::cmp::Ordering::Equal)
2377                        .then(a.cmp(&b))
2378                });
2379                for kk in 0..topk {
2380                    indices[t * topk + kk] = order[kk];
2381                }
2382            }
2383        }
2384        let mut weights = vec![0f32; s * topk];
2385        for t in 0..s {
2386            let mut sum = 0f32;
2387            for kk in 0..topk {
2388                let w = scores[t * ne + indices[t * topk + kk]];
2389                weights[t * topk + kk] = w;
2390                sum += w;
2391            }
2392            for kk in 0..topk {
2393                weights[t * topk + kk] = weights[t * topk + kk] / sum * route_scale;
2394            }
2395        }
2396        (indices, weights)
2397    }
2398
2399    /// One trunk block on its stage. h is [s, hc, hidden] f32 on the stage device.
2400    /// `cache` (lane 6): populate this layer's decode cache while prefilling.
2401    #[allow(clippy::too_many_arguments)]
2402    fn block_forward(
2403        &self,
2404        st: &Stage,
2405        layer: &LayerDev,
2406        h: &CudaSlice<f32>,
2407        s: usize,
2408        ids: &[u32],
2409        mut capture: Option<&mut GpuCapture>,
2410        mut cache: Option<&mut LayerCache>,
2411    ) -> Res<CudaSlice<f32>> {
2412        let d = self.model.cfg();
2413        let mc = &self.model.mc;
2414        let hc = d.hc_mult as usize;
2415        let hidden = mc.n_embd as usize;
2416        let heads = mc.n_head as usize;
2417        let hd = d.head_dim as usize;
2418        let rd = d.qk_rope_head_dim as usize;
2419        let q_lora = d.q_lora_rank as usize;
2420        let win = d.sliding_window as usize;
2421        let o_groups = d.o_groups as usize;
2422        let o_lora = d.o_lora_rank as usize;
2423        let eps = mc.rms_eps;
2424        let iters = d.hc_sinkhorn_iters;
2425        let hc_eps = d.hc_eps;
2426        // Runtime-API kernel launches in the FFI TU need this stage's context current on
2427        // the calling thread (cudarc binds it inside its own ops, but the previous op may
2428        // have been another stage's).
2429        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
2430        let stream = st.gpu.stream();
2431        let fc_dev = if layer.ratio != 0 {
2432            &st.fc_yarn
2433        } else {
2434            &st.fc_plain
2435        };
2436        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
2437
2438        // ---- attention sub-block
2439        let (y, post, comb) = Self::hc_pre(
2440            st,
2441            h,
2442            &layer.hc_attn_fn,
2443            &layer.hc_attn_base,
2444            &layer.hc_attn_scale,
2445            s,
2446            hc,
2447            hidden,
2448            iters,
2449            hc_eps,
2450        )?;
2451        let mut x = stream.alloc_zeros::<f32>(s * hidden).map_err(e("x"))?;
2452        unsafe {
2453            ck(
2454                "rmsnorm attn",
2455                k::memra_dsv4_rmsnorm(
2456                    dpf!(y, &stream),
2457                    dpf!(layer.attn_norm, &stream),
2458                    dpm!(x, &stream),
2459                    s as i32,
2460                    hidden as i32,
2461                    eps,
2462                    sp(&stream),
2463                ),
2464            )?;
2465        }
2466
2467        // q path (item 3: under the fp8 dense arm the bf16 slabs are host-staged —
2468        // each `staged` view uploads a transient device copy freed, stream-ordered,
2469        // when the view drops at the end of this pass; on the bf16 arm it borrows
2470        // the resident slab and stages nothing)
2471        let wq_a_v = layer.wq_a.staged(&stream)?;
2472        let mut qr = stream.alloc_zeros::<f32>(s * q_lora).map_err(e("qr"))?;
2473        Self::gemm(st, &x, wq_a_v.slab(), 0, s, q_lora, hidden, &mut qr)?;
2474        unsafe {
2475            ck(
2476                "rmsnorm q",
2477                k::memra_dsv4_rmsnorm(
2478                    dpf!(qr, &stream),
2479                    dpf!(layer.q_norm, &stream),
2480                    dpm!(qr, &stream),
2481                    s as i32,
2482                    q_lora as i32,
2483                    eps,
2484                    sp(&stream),
2485                ),
2486            )?;
2487        }
2488        // qr as bf16 once (feeds wq_b and the indexer wq_b, oracle reuses qr the same way)
2489        let mut qr_b = stream
2490            .alloc_zeros::<u8>(s * q_lora * 2)
2491            .map_err(e("qr_b"))?;
2492        unsafe {
2493            ck(
2494                "cvt qr",
2495                k::memra_dsv4_cvt_bf16(
2496                    dpf!(qr, &stream),
2497                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
2498                    (s * q_lora) as i64,
2499                    sp(&stream),
2500                ),
2501            )?;
2502        }
2503        let wq_b_v = layer.wq_b.staged(&stream)?;
2504        let mut q = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("q"))?;
2505        Self::gemm_pre(
2506            st,
2507            &qr_b,
2508            wq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2509            s,
2510            heads * hd,
2511            q_lora,
2512            &mut q,
2513        )?;
2514        let positions: Vec<i32> = (0..s as i32).collect();
2515        let pos_dev = upload_i32(&stream, &positions)?;
2516        unsafe {
2517            ck(
2518                "headrms",
2519                k::memra_dsv4_headrms(
2520                    dpm!(q, &stream),
2521                    (s * heads) as i32,
2522                    hd as i32,
2523                    eps,
2524                    sp(&stream),
2525                ),
2526            )?;
2527            ck(
2528                "rope q",
2529                k::memra_dsv4_rope(
2530                    dpm!(q, &stream),
2531                    s as i32,
2532                    heads as i32,
2533                    hd as i32,
2534                    rd as i32,
2535                    dpf!(fc_dev, &stream),
2536                    pos_dev.device_ptr(&stream).0 as *const i32,
2537                    0,
2538                    sp(&stream),
2539                ),
2540            )?;
2541        }
2542
2543        // shared K==V latent + window QAT
2544        let wkv_v = layer.wkv.staged(&stream)?;
2545        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("kv"))?;
2546        Self::gemm(st, &x, wkv_v.slab(), 0, s, hd, hidden, &mut kv)?;
2547        unsafe {
2548            ck(
2549                "rmsnorm kv",
2550                k::memra_dsv4_rmsnorm(
2551                    dpf!(kv, &stream),
2552                    dpf!(layer.kv_norm, &stream),
2553                    dpm!(kv, &stream),
2554                    s as i32,
2555                    hd as i32,
2556                    eps,
2557                    sp(&stream),
2558                ),
2559            )?;
2560            ck(
2561                "rope kv",
2562                k::memra_dsv4_rope(
2563                    dpm!(kv, &stream),
2564                    s as i32,
2565                    1,
2566                    hd as i32,
2567                    rd as i32,
2568                    dpf!(fc_dev, &stream),
2569                    pos_dev.device_ptr(&stream).0 as *const i32,
2570                    0,
2571                    sp(&stream),
2572                ),
2573            )?;
2574            ck(
2575                "act_quant kv",
2576                k::memra_dsv4_act_quant(
2577                    dpm!(kv, &stream),
2578                    s as i32,
2579                    hd as i64,
2580                    (hd - rd) as i32,
2581                    64,
2582                    clamp_only,
2583                    sp(&stream),
2584                ),
2585            )?;
2586        }
2587        // lane 6: window ring handoff — last min(s, win) post-QAT rows at slot p % win
2588        // (M:524-527: prefill leaves the cache exactly as if the ring had been written
2589        // position by position).
2590        if let Some(c) = cache.as_deref_mut() {
2591            for p in s.saturating_sub(win)..s {
2592                let slot = p % win;
2593                let src = kv.slice(p * hd..(p + 1) * hd);
2594                let mut dst = c.kvc.slice_mut(slot * hd..(slot + 1) * hd);
2595                stream.memcpy_dtod(&src, &mut dst).map_err(e("ring copy"))?;
2596            }
2597        }
2598
2599        // index assembly (host, oracle builders) + compressed kv
2600        let (widx, wslots) = window_topk_idxs(win, s);
2601        let mut idxs: Vec<i64> = widx;
2602        let mut slots = wslots;
2603        let mut n_kv = s;
2604        let mut kv_full = kv;
2605        let mut cap_cmp: Option<(Vec<f32>, usize)> = None;
2606        let mut cap_ikv: Option<(Vec<f32>, usize)> = None;
2607        let mut cap_isc: Option<(Vec<f32>, usize)> = None;
2608        let want_cap = capture
2609            .as_ref()
2610            .map(|c| c.want.contains(&layer.il))
2611            .unwrap_or(false);
2612        if layer.ratio != 0 {
2613            let offset = s;
2614            let (cidx, cslots) = if let Some(ix) = &layer.idx {
2615                // indexer q
2616                let mut qi = stream
2617                    .alloc_zeros::<f32>(s * ix.heads * ix.hd)
2618                    .map_err(e("qi"))?;
2619                let iwq_b_v = ix.wq_b.staged(&stream)?;
2620                Self::gemm_pre(
2621                    st,
2622                    &qr_b,
2623                    iwq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2624                    s,
2625                    ix.heads * ix.hd,
2626                    q_lora,
2627                    &mut qi,
2628                )?;
2629                unsafe {
2630                    ck(
2631                        "rope qi",
2632                        k::memra_dsv4_rope(
2633                            dpm!(qi, &stream),
2634                            s as i32,
2635                            ix.heads as i32,
2636                            ix.hd as i32,
2637                            rd as i32,
2638                            dpf!(fc_dev, &stream),
2639                            pos_dev.device_ptr(&stream).0 as *const i32,
2640                            0,
2641                            sp(&stream),
2642                        ),
2643                    )?;
2644                    let scale = (ix.hd as f32).powf(-0.5);
2645                    ck(
2646                        "hadamard qi",
2647                        k::memra_dsv4_hadamard(
2648                            dpm!(qi, &stream),
2649                            (s * ix.heads) as i32,
2650                            ix.hd as i32,
2651                            scale,
2652                            sp(&stream),
2653                        ),
2654                    )?;
2655                    ck(
2656                        "fp4 qi",
2657                        k::memra_dsv4_fp4_act_quant(
2658                            dpm!(qi, &stream),
2659                            (s * ix.heads) as i32,
2660                            ix.hd as i64,
2661                            ix.hd as i32,
2662                            sp(&stream),
2663                        ),
2664                    )?;
2665                }
2666                // indexer compressed kv
2667                let (ckv_i, ikv_raw, isc_raw) =
2668                    self.compressor(st, &ix.cmp, &x, s, hidden, fc_dev, rd, eps)?;
2669                if want_cap {
2670                    if let Some((buf, nb)) = &ckv_i {
2671                        cap_ikv = Some((dtoh_f32(&stream, buf)?, *nb));
2672                    }
2673                }
2674                if let Some(c) = cache.as_deref_mut() {
2675                    let mut i_blocks = c.i_blocks;
2676                    Self::populate_cmp_cache(
2677                        &stream,
2678                        s,
2679                        ix.cmp.ratio,
2680                        ix.cmp.latent,
2681                        ix.cmp.d,
2682                        &ckv_i,
2683                        &ikv_raw,
2684                        &isc_raw,
2685                        c.ikvc.as_mut().expect("fine layer has indexer store"),
2686                        0,
2687                        &mut i_blocks,
2688                        c.ipend_kv.as_mut().expect("ipend"),
2689                        c.ipend_score.as_mut().expect("ipend"),
2690                        ix.cmp.overlap,
2691                    )?;
2692                    c.i_blocks = i_blocks;
2693                }
2694                // head weights (weights_proj is BF16 — lawful bf16 GEMM)
2695                let iwp_v = ix.weights_proj.staged(&stream)?;
2696                let mut wproj = stream.alloc_zeros::<f32>(s * ix.heads).map_err(e("wp"))?;
2697                Self::gemm(st, &x, iwp_v.slab(), 0, s, ix.heads, hidden, &mut wproj)?;
2698                if let Some((ckv, nb)) = &ckv_i {
2699                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
2700                    let mut score = stream.alloc_zeros::<f32>(s * nb).map_err(e("iscore"))?;
2701                    unsafe {
2702                        ck(
2703                            "indexer_score",
2704                            k::memra_dsv4_indexer_score(
2705                                dpf!(qi, &stream),
2706                                dpf!(ckv, &stream),
2707                                dpf!(wproj, &stream),
2708                                wscale,
2709                                dpm!(score, &stream),
2710                                s as i32,
2711                                ix.heads as i32,
2712                                ix.hd as i32,
2713                                *nb as i32,
2714                                layer.ratio as i32,
2715                                -1, // prefill law: lim = (t+1)/ratio with local t
2716                                sp(&stream),
2717                            ),
2718                        )?;
2719                    }
2720                    let score_h = dtoh_f32(&stream, &score)?;
2721                    if want_cap {
2722                        cap_isc = Some((score_h.clone(), *nb));
2723                    }
2724                    // host topk with the oracle's exact ordering + re-mask (model.py:508-510)
2725                    let kk = ix.topk.min(*nb);
2726                    let mut cidx = vec![-1i64; s * kk];
2727                    for t in 0..s {
2728                        let lim = (t + 1) / layer.ratio;
2729                        let mut order: Vec<usize> = (0..*nb).collect();
2730                        order.sort_by(|&a, &b| {
2731                            score_h[t * nb + b]
2732                                .partial_cmp(&score_h[t * nb + a])
2733                                .unwrap_or(std::cmp::Ordering::Equal)
2734                                .then(a.cmp(&b))
2735                        });
2736                        for (slot, &j) in order.iter().take(kk).enumerate() {
2737                            cidx[t * kk + slot] = if j >= lim { -1 } else { (j + offset) as i64 };
2738                        }
2739                    }
2740                    (cidx, kk)
2741                } else {
2742                    (Vec::new(), 0)
2743                }
2744            } else {
2745                compress_topk_idxs(layer.ratio, s, offset)
2746            };
2747            if cslots > 0 {
2748                let mut merged = vec![-1i64; s * (slots + cslots)];
2749                for t in 0..s {
2750                    merged[t * (slots + cslots)..t * (slots + cslots) + slots]
2751                        .copy_from_slice(&idxs[t * slots..(t + 1) * slots]);
2752                    merged[t * (slots + cslots) + slots..(t + 1) * (slots + cslots)]
2753                        .copy_from_slice(&cidx[t * cslots..(t + 1) * cslots]);
2754                }
2755                idxs = merged;
2756                slots += cslots;
2757            }
2758            // attention-side compressed kv appended to the kv stream
2759            let acmp = layer.cmp.as_ref().expect("ratio!=0 has compressor");
2760            let (ckv, akv_raw, asc_raw) =
2761                self.compressor(st, acmp, &x, s, hidden, fc_dev, rd, eps)?;
2762            if want_cap {
2763                if let Some((buf, nb)) = &ckv {
2764                    cap_cmp = Some((dtoh_f32(&stream, buf)?, *nb));
2765                }
2766            }
2767            if let Some(c) = cache.as_deref_mut() {
2768                let mut n_blocks = c.n_blocks;
2769                Self::populate_cmp_cache(
2770                    &stream,
2771                    s,
2772                    acmp.ratio,
2773                    acmp.latent,
2774                    acmp.d,
2775                    &ckv,
2776                    &akv_raw,
2777                    &asc_raw,
2778                    &mut c.kvc,
2779                    win,
2780                    &mut n_blocks,
2781                    c.pend_kv.as_mut().expect("pend"),
2782                    c.pend_score.as_mut().expect("pend"),
2783                    acmp.overlap,
2784                )?;
2785                c.n_blocks = n_blocks;
2786            }
2787            if let Some((ckv_buf, nb)) = ckv {
2788                let mut merged_kv = stream
2789                    .alloc_zeros::<f32>((s + nb) * hd)
2790                    .map_err(e("kv_full"))?;
2791                {
2792                    let mut head_view = merged_kv.slice_mut(0..s * hd);
2793                    stream
2794                        .memcpy_dtod(&kv_full.slice(0..s * hd), &mut head_view)
2795                        .map_err(e("kv copy"))?;
2796                }
2797                {
2798                    let mut tail = merged_kv.slice_mut(s * hd..(s + nb) * hd);
2799                    stream
2800                        .memcpy_dtod(&ckv_buf.slice(0..nb * hd), &mut tail)
2801                        .map_err(e("ckv copy"))?;
2802                }
2803                kv_full = merged_kv;
2804                n_kv += nb;
2805            }
2806        }
2807        let _ = n_kv;
2808        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
2809        let idx_dev = upload_i32(&stream, &idxs_i32)?;
2810
2811        // sparse sink attention + query-position de-rotation
2812        let mut o = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("o"))?;
2813        let scale = (hd as f64).powf(-0.5) as f32;
2814        unsafe {
2815            ck(
2816                "sink_attn",
2817                k::memra_dsv4_sink_attn(
2818                    dpf!(q, &stream),
2819                    dpf!(kv_full, &stream),
2820                    idx_dev.device_ptr(&stream).0 as *const i32,
2821                    dpf!(layer.sink, &stream),
2822                    dpm!(o, &stream),
2823                    s as i32,
2824                    heads as i32,
2825                    hd as i32,
2826                    slots as i32,
2827                    scale,
2828                    sp(&stream),
2829                ),
2830            )?;
2831            ck(
2832                "rope o inv",
2833                k::memra_dsv4_rope(
2834                    dpm!(o, &stream),
2835                    s as i32,
2836                    heads as i32,
2837                    hd as i32,
2838                    rd as i32,
2839                    dpf!(fc_dev, &stream),
2840                    pos_dev.device_ptr(&stream).0 as *const i32,
2841                    1,
2842                    sp(&stream),
2843                ),
2844            )?;
2845        }
2846
2847        // grouped wo: per group g, og[:, g*o_lora..] = o_g @ wo_a[g]ᵀ; then wo_b.
2848        let gw = heads / o_groups * hd;
2849        let mut og = stream
2850            .alloc_zeros::<f32>(s * o_groups * o_lora)
2851            .map_err(e("og"))?;
2852        let mut o_grp = stream.alloc_zeros::<f32>(s * gw).map_err(e("o_grp"))?;
2853        let mut y_grp = stream.alloc_zeros::<f32>(s * o_lora).map_err(e("y_grp"))?;
2854        let wo_a_v = layer.wo_a.staged(&stream)?; // once, outside the group loop
2855        for g in 0..o_groups {
2856            unsafe {
2857                ck(
2858                    "take_cols",
2859                    k::memra_dsv4_take_cols(
2860                        dpf!(o, &stream),
2861                        dpm!(o_grp, &stream),
2862                        s as i32,
2863                        gw as i32,
2864                        (heads * hd) as i64,
2865                        (g * gw) as i64,
2866                        sp(&stream),
2867                    ),
2868                )?;
2869            }
2870            Self::gemm(
2871                st,
2872                &o_grp,
2873                wo_a_v.slab(),
2874                g * o_lora * gw,
2875                s,
2876                o_lora,
2877                gw,
2878                &mut y_grp,
2879            )?;
2880            unsafe {
2881                ck(
2882                    "place_cols",
2883                    k::memra_dsv4_place_cols(
2884                        dpf!(y_grp, &stream),
2885                        dpm!(og, &stream),
2886                        s as i32,
2887                        o_lora as i32,
2888                        (o_groups * o_lora) as i64,
2889                        (g * o_lora) as i64,
2890                        sp(&stream),
2891                    ),
2892                )?;
2893            }
2894        }
2895        let wo_b_v = layer.wo_b.staged(&stream)?;
2896        let mut attn_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("ao"))?;
2897        Self::gemm(
2898            st,
2899            &og,
2900            wo_b_v.slab(),
2901            0,
2902            s,
2903            hidden,
2904            o_groups * o_lora,
2905            &mut attn_out,
2906        )?;
2907
2908        let mut cap_attn: Option<Vec<f32>> = None;
2909        if want_cap {
2910            cap_attn = Some(dtoh_f32(&stream, &attn_out)?);
2911        }
2912
2913        // hc_post (attention)
2914        let mut h2 = stream
2915            .alloc_zeros::<f32>(s * hc * hidden)
2916            .map_err(e("h2"))?;
2917        unsafe {
2918            ck(
2919                "hc_post attn",
2920                k::memra_dsv4_hc_post(
2921                    dpf!(attn_out, &stream),
2922                    dpf!(h, &stream),
2923                    dpf!(post, &stream),
2924                    dpf!(comb, &stream),
2925                    dpm!(h2, &stream),
2926                    s as i32,
2927                    hc as i32,
2928                    hidden as i32,
2929                    sp(&stream),
2930                ),
2931            )?;
2932        }
2933
2934        // ---- ffn sub-block
2935        let (y2, post2, comb2) = Self::hc_pre(
2936            st,
2937            &h2,
2938            &layer.hc_ffn_fn,
2939            &layer.hc_ffn_base,
2940            &layer.hc_ffn_scale,
2941            s,
2942            hc,
2943            hidden,
2944            iters,
2945            hc_eps,
2946        )?;
2947        let mut xf = stream.alloc_zeros::<f32>(s * hidden).map_err(e("xf"))?;
2948        unsafe {
2949            ck(
2950                "rmsnorm ffn",
2951                k::memra_dsv4_rmsnorm(
2952                    dpf!(y2, &stream),
2953                    dpf!(layer.ffn_norm, &stream),
2954                    dpm!(xf, &stream),
2955                    s as i32,
2956                    hidden as i32,
2957                    eps,
2958                    sp(&stream),
2959                ),
2960            )?;
2961        }
2962        if let Some(c) = capture.as_deref_mut() {
2963            if c.want.contains(&layer.il) {
2964                c.moe_x.insert(layer.il, dtoh_f32(&stream, &xf)?);
2965            }
2966        }
2967        let moe_out = self.moe_forward(st, layer, &xf, s, ids)?;
2968        let mut h3 = stream
2969            .alloc_zeros::<f32>(s * hc * hidden)
2970            .map_err(e("h3"))?;
2971        unsafe {
2972            ck(
2973                "hc_post ffn",
2974                k::memra_dsv4_hc_post(
2975                    dpf!(moe_out, &stream),
2976                    dpf!(h2, &stream),
2977                    dpf!(post2, &stream),
2978                    dpf!(comb2, &stream),
2979                    dpm!(h3, &stream),
2980                    s as i32,
2981                    hc as i32,
2982                    hidden as i32,
2983                    sp(&stream),
2984                ),
2985            )?;
2986        }
2987
2988        if let Some(c) = capture {
2989            if c.want.contains(&layer.il) {
2990                c.layer_out.insert(layer.il, dtoh_f32(&stream, &h3)?);
2991                c.x_dbg.insert(layer.il, dtoh_f32(&stream, &x)?);
2992                c.q_dbg.insert(layer.il, dtoh_f32(&stream, &q)?);
2993                {
2994                    let mut kvh = vec![0f32; s * hd];
2995                    stream
2996                        .memcpy_dtoh(&kv_full.slice(0..s * hd), &mut kvh[..])
2997                        .map_err(e("dtoh kv_dbg"))?;
2998                    stream.synchronize().map_err(e("sync kv_dbg"))?;
2999                    c.kv_dbg.insert(layer.il, kvh);
3000                }
3001                c.o_dbg.insert(layer.il, dtoh_f32(&stream, &o)?);
3002                if let Some(a) = cap_attn {
3003                    c.attn_out.insert(layer.il, a);
3004                }
3005                if let Some(v) = cap_cmp {
3006                    c.compressor_kv.insert(layer.il, v);
3007                }
3008                if let Some(v) = cap_ikv {
3009                    c.indexer_kv.insert(layer.il, v);
3010                }
3011                if let Some(v) = cap_isc {
3012                    c.index_score.insert(layer.il, v);
3013                }
3014            }
3015        }
3016        Ok(h3)
3017    }
3018
3019    /// MoE on GPU: gate GEMM f32 island -> host routing (oracle math) -> per-expert
3020    /// on-the-fly NVFP4 dequant + bf16 GEMMs (ascending expert order, oracle
3021    /// accumulation order) -> shared expert last.
3022    fn moe_forward(
3023        &self,
3024        st: &Stage,
3025        layer: &LayerDev,
3026        x: &CudaSlice<f32>, // [s, hidden] post-ffn-norm
3027        s: usize,
3028        ids: &[u32],
3029    ) -> Res<CudaSlice<f32>> {
3030        let mc = &self.model.mc;
3031        let d = self.model.cfg();
3032        let moe = mc.moe.as_ref().expect("moe");
3033        let hidden = mc.n_embd as usize;
3034        let ne = moe.expert_count as usize;
3035        let topk = moe.expert_used_count as usize;
3036        let inter = moe.expert_ff_length as usize;
3037        let limit = d.swiglu_limit;
3038        let stream = st.gpu.stream();
3039
3040        let mut raw = stream.alloc_zeros::<f32>(s * ne).map_err(e("gate raw"))?;
3041        Self::dots(st, x, &layer.gate_w, s, hidden, ne, &mut raw)?;
3042        let raw_h = dtoh_f32(&stream, &raw)?;
3043        let (indices, weights) =
3044            Self::route_host(layer, &raw_h, ids, s, ne, topk, d.routed_scaling_factor);
3045
3046        // x as bf16 once for all expert GEMMs
3047        let mut xb = stream
3048            .alloc_zeros::<u8>(s * hidden * 2)
3049            .map_err(e("xb moe"))?;
3050        unsafe {
3051            ck(
3052                "cvt moe x",
3053                k::memra_dsv4_cvt_bf16(
3054                    dpf!(x, &stream),
3055                    xb.device_ptr_mut(&stream).0 as *mut c_void,
3056                    (s * hidden) as i64,
3057                    sp(&stream),
3058                ),
3059            )?;
3060        }
3061        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("moe y"))?;
3062
3063        let wbytes = inter * hidden / 2;
3064        let sbytes = match layer.expert_kind {
3065            ExpertKind::Nvfp4 => inter * hidden / 16,
3066            ExpertKind::Mxfp4 => inter * hidden / 32,
3067        };
3068        let mut uniq: Vec<usize> = indices.clone();
3069        uniq.sort_unstable();
3070        uniq.dedup();
3071        if self.expert_arm == ExpertArm::Native {
3072            // lane 7: reference-law quantized expert GEMMs (RECEIPTS.md "Lane 7").
3073            // x quantized ONCE per-row-per-128 (model.py:113-115); code/scale rows
3074            // gathered per expert (row-local quant commutes with gathering exactly);
3075            // h re-quantized AFTER the routing-weight multiply (M:604-606) before w2.
3076            let kind = match layer.expert_kind {
3077                ExpertKind::Nvfp4 => 0i32,
3078                ExpertKind::Mxfp4 => 1i32,
3079            };
3080            let kq_x = hidden / 128;
3081            let kq_h = inter / 128;
3082            let mut xq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xq"))?;
3083            let mut xs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xs"))?;
3084            unsafe {
3085                ck(
3086                    "act_quant_fp8 x",
3087                    k::memra_dsv4_act_quant_fp8(
3088                        dpf!(x, &stream),
3089                        xq.device_ptr_mut(&stream).0 as *mut c_void,
3090                        dpm!(xs, &stream),
3091                        s as i32,
3092                        hidden as i32,
3093                        sp(&stream),
3094                    ),
3095                )?;
3096            }
3097            let mut xgq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xgq"))?;
3098            let mut xgs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xgs"))?;
3099            let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3100            let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3101            let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3102            let mut hq = stream.alloc_zeros::<u8>(s * inter).map_err(e("hq"))?;
3103            let mut hs = stream.alloc_zeros::<f32>(s * kq_h).map_err(e("hs"))?;
3104            let mut contrib = stream
3105                .alloc_zeros::<f32>(s * hidden)
3106                .map_err(e("contrib"))?;
3107            for &ex in &uniq {
3108                let toks: Vec<(usize, usize)> = (0..s * topk)
3109                    .filter(|i| indices[*i] == ex)
3110                    .map(|i| (i / topk, i % topk))
3111                    .collect();
3112                let g = toks.len();
3113                let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3114                let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3115                let rows_dev = upload_i32(&stream, &tok_rows)?;
3116                let wrow_dev = upload_f32(&stream, &wrow)?;
3117                unsafe {
3118                    ck(
3119                        "gather xq",
3120                        k::memra_dsv4_gather_rows_u8(
3121                            dp!(xq, &stream),
3122                            rows_dev.device_ptr(&stream).0 as *const i32,
3123                            xgq.device_ptr_mut(&stream).0 as *mut c_void,
3124                            g as i32,
3125                            hidden as i64,
3126                            sp(&stream),
3127                        ),
3128                    )?;
3129                    ck(
3130                        "gather xs",
3131                        k::memra_dsv4_gather_rows_u8(
3132                            xs.device_ptr(&stream).0 as *const c_void,
3133                            rows_dev.device_ptr(&stream).0 as *const i32,
3134                            xgs.device_ptr_mut(&stream).0 as *mut c_void,
3135                            g as i32,
3136                            (kq_x * 4) as i64,
3137                            sp(&stream),
3138                        ),
3139                    )?;
3140                    // w1 (out inter), w3 (out inter) from x codes; w2 (out hidden) from h codes
3141                    for (pi, dst) in [(0usize, &mut g1), (2usize, &mut g3)] {
3142                        let woff = (ex * 3 + pi) * wbytes;
3143                        let soff = (ex * 3 + pi) * sbytes;
3144                        ck(
3145                            "fp4_gemm w1/w3",
3146                            k::memra_dsv4_fp4_gemm(
3147                                dp!(xgq, &stream),
3148                                dpf!(xgs, &stream),
3149                                (layer.experts_w.device_ptr(&stream).0 as usize + woff)
3150                                    as *const c_void,
3151                                (layer.experts_sc.device_ptr(&stream).0 as usize + soff)
3152                                    as *const c_void,
3153                                layer.experts_s2[ex * 3 + pi],
3154                                kind,
3155                                dpm!(*dst, &stream),
3156                                g as i32,
3157                                inter as i32,
3158                                hidden as i32,
3159                                sp(&stream),
3160                            ),
3161                        )?;
3162                    }
3163                    ck(
3164                        "swiglu",
3165                        k::memra_dsv4_swiglu(
3166                            dpf!(g1, &stream),
3167                            dpf!(g3, &stream),
3168                            dpm!(hbuf, &stream),
3169                            g as i32,
3170                            inter as i32,
3171                            limit,
3172                            wrow_dev.device_ptr(&stream).0 as *const f32,
3173                            sp(&stream),
3174                        ),
3175                    )?;
3176                    ck(
3177                        "act_quant_fp8 h",
3178                        k::memra_dsv4_act_quant_fp8(
3179                            dpf!(hbuf, &stream),
3180                            hq.device_ptr_mut(&stream).0 as *mut c_void,
3181                            dpm!(hs, &stream),
3182                            g as i32,
3183                            inter as i32,
3184                            sp(&stream),
3185                        ),
3186                    )?;
3187                    let woff2 = (ex * 3 + 1) * wbytes;
3188                    let soff2 = (ex * 3 + 1) * sbytes;
3189                    ck(
3190                        "fp4_gemm w2",
3191                        k::memra_dsv4_fp4_gemm(
3192                            dp!(hq, &stream),
3193                            dpf!(hs, &stream),
3194                            (layer.experts_w.device_ptr(&stream).0 as usize + woff2)
3195                                as *const c_void,
3196                            (layer.experts_sc.device_ptr(&stream).0 as usize + soff2)
3197                                as *const c_void,
3198                            layer.experts_s2[ex * 3 + 1],
3199                            kind,
3200                            dpm!(contrib, &stream),
3201                            g as i32,
3202                            hidden as i32,
3203                            inter as i32,
3204                            sp(&stream),
3205                        ),
3206                    )?;
3207                    ck(
3208                        "scatter",
3209                        k::memra_dsv4_scatter_add(
3210                            dpm!(y, &stream),
3211                            dpf!(contrib, &stream),
3212                            rows_dev.device_ptr(&stream).0 as *const i32,
3213                            g as i32,
3214                            hidden as i32,
3215                            sp(&stream),
3216                        ),
3217                    )?;
3218                }
3219            }
3220            return self.moe_shared_and_finish(st, layer, &xb, s, y);
3221        }
3222        // reusable per-expert buffers sized for the worst case (all tokens on one expert)
3223        let mut xg = stream.alloc_zeros::<u8>(s * hidden * 2).map_err(e("xg"))?;
3224        let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3225        let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3226        let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3227        let mut hb = stream.alloc_zeros::<u8>(s * inter * 2).map_err(e("hb"))?;
3228        let mut contrib = stream
3229            .alloc_zeros::<f32>(s * hidden)
3230            .map_err(e("contrib"))?;
3231        for &ex in &uniq {
3232            let toks: Vec<(usize, usize)> = (0..s * topk)
3233                .filter(|i| indices[*i] == ex)
3234                .map(|i| (i / topk, i % topk))
3235                .collect();
3236            let g = toks.len();
3237            let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3238            let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3239            let rows_dev = upload_i32(&stream, &tok_rows)?;
3240            let wrow_dev = upload_f32(&stream, &wrow)?;
3241            unsafe {
3242                ck(
3243                    "gather",
3244                    k::memra_dsv4_gather_bf16(
3245                        dp!(xb, &stream),
3246                        rows_dev.device_ptr(&stream).0 as *const i32,
3247                        xg.device_ptr_mut(&stream).0 as *mut c_void,
3248                        g as i32,
3249                        hidden as i32,
3250                        sp(&stream),
3251                    ),
3252                )?;
3253                // dequant w1 (rows=inter, cols=hidden), w2 (rows=hidden, cols=inter), w3
3254                for (pi, (rows, cols)) in [(inter, hidden), (hidden, inter), (inter, hidden)]
3255                    .iter()
3256                    .enumerate()
3257                {
3258                    let woff = (ex * 3 + pi) * wbytes;
3259                    let soff = (ex * 3 + pi) * sbytes;
3260                    let wp =
3261                        (layer.experts_w.device_ptr(&stream).0 as usize + woff) as *const c_void;
3262                    let scp =
3263                        (layer.experts_sc.device_ptr(&stream).0 as usize + soff) as *const c_void;
3264                    let dst = st.deq[pi].device_ptr(&stream).0 as *mut c_void;
3265                    match layer.expert_kind {
3266                        ExpertKind::Nvfp4 => ck(
3267                            "nvfp4 deq",
3268                            k::memra_dsv4_nvfp4_deq_bf16(
3269                                wp,
3270                                scp,
3271                                layer.experts_s2[ex * 3 + pi],
3272                                *rows as i32,
3273                                *cols as i32,
3274                                dst,
3275                                sp(&stream),
3276                            ),
3277                        )?,
3278                        ExpertKind::Mxfp4 => ck(
3279                            "mxfp4 deq",
3280                            k::memra_dsv4_mxfp4_deq_bf16(
3281                                wp,
3282                                scp,
3283                                *rows as i32,
3284                                *cols as i32,
3285                                dst,
3286                                sp(&stream),
3287                            ),
3288                        )?,
3289                    }
3290                }
3291                ck(
3292                    "gemm w1",
3293                    k::memra_dsv4_gemm_bf16(
3294                        st.deq[0].device_ptr(&stream).0 as *const c_void,
3295                        dp!(xg, &stream),
3296                        dpm!(g1, &stream),
3297                        g as i32,
3298                        inter as i32,
3299                        hidden as i32,
3300                        st.dev as i32,
3301                        st.ws.device_ptr(&stream).0 as *mut c_void,
3302                        st.ws.len(),
3303                        sp(&stream),
3304                    ),
3305                )?;
3306                ck(
3307                    "gemm w3",
3308                    k::memra_dsv4_gemm_bf16(
3309                        st.deq[2].device_ptr(&stream).0 as *const c_void,
3310                        dp!(xg, &stream),
3311                        dpm!(g3, &stream),
3312                        g as i32,
3313                        inter as i32,
3314                        hidden as i32,
3315                        st.dev as i32,
3316                        st.ws.device_ptr(&stream).0 as *mut c_void,
3317                        st.ws.len(),
3318                        sp(&stream),
3319                    ),
3320                )?;
3321                ck(
3322                    "swiglu",
3323                    k::memra_dsv4_swiglu(
3324                        dpf!(g1, &stream),
3325                        dpf!(g3, &stream),
3326                        dpm!(hbuf, &stream),
3327                        g as i32,
3328                        inter as i32,
3329                        limit,
3330                        wrow_dev.device_ptr(&stream).0 as *const f32,
3331                        sp(&stream),
3332                    ),
3333                )?;
3334                ck(
3335                    "cvt h",
3336                    k::memra_dsv4_cvt_bf16(
3337                        dpf!(hbuf, &stream),
3338                        hb.device_ptr_mut(&stream).0 as *mut c_void,
3339                        (g * inter) as i64,
3340                        sp(&stream),
3341                    ),
3342                )?;
3343                ck(
3344                    "gemm w2",
3345                    k::memra_dsv4_gemm_bf16(
3346                        st.deq[1].device_ptr(&stream).0 as *const c_void,
3347                        dp!(hb, &stream),
3348                        dpm!(contrib, &stream),
3349                        g as i32,
3350                        hidden as i32,
3351                        inter as i32,
3352                        st.dev as i32,
3353                        st.ws.device_ptr(&stream).0 as *mut c_void,
3354                        st.ws.len(),
3355                        sp(&stream),
3356                    ),
3357                )?;
3358                ck(
3359                    "scatter",
3360                    k::memra_dsv4_scatter_add(
3361                        dpm!(y, &stream),
3362                        dpf!(contrib, &stream),
3363                        rows_dev.device_ptr(&stream).0 as *const i32,
3364                        g as i32,
3365                        hidden as i32,
3366                        sp(&stream),
3367                    ),
3368                )?;
3369            }
3370        }
3371        self.moe_shared_and_finish(st, layer, &xb, s, y)
3372    }
3373
3374    /// Shared expert (unweighted, added last — oracle order) + return. Stays on the
3375    /// lane-4 bf16 rung under BOTH expert arms (lane-7 banked deviation: shared experts
3376    /// are FP8-blk weights — the FP8-linear stay-bf16 decision).
3377    fn moe_shared_and_finish(
3378        &self,
3379        st: &Stage,
3380        layer: &LayerDev,
3381        xb: &CudaSlice<u8>,
3382        s: usize,
3383        mut y: CudaSlice<f32>,
3384    ) -> Res<CudaSlice<f32>> {
3385        let d = self.model.cfg();
3386        let hidden = self.model.mc.n_embd as usize;
3387        let limit = d.swiglu_limit;
3388        let stream = st.gpu.stream();
3389        let sh_inter = {
3390            // width derived from the tensor itself (n_shared_experts * inter)
3391            let (shape, _) = self
3392                .model
3393                .st
3394                .raw("layers.0.ffn.shared_experts.w1.weight")
3395                .map(|(i, _)| (i.shape.clone(), ()))
3396                .expect("shared w1");
3397            shape[0] as usize
3398        };
3399        let mut sg1 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg1"))?;
3400        let mut sg3 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg3"))?;
3401        let mut shbuf = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("shb"))?;
3402        let mut shb16 = stream
3403            .alloc_zeros::<u8>(s * sh_inter * 2)
3404            .map_err(e("shb16"))?;
3405        let mut sh_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("sh_out"))?;
3406        // item 3: staged views (transient upload under the fp8 arm, borrow otherwise)
3407        let sw = [
3408            layer.shared_w[0].staged(&stream)?,
3409            layer.shared_w[1].staged(&stream)?,
3410            layer.shared_w[2].staged(&stream)?,
3411        ];
3412        Self::gemm_pre(
3413            st,
3414            xb,
3415            sw[0].slab().device_ptr(&stream).0 as *const c_void,
3416            s,
3417            sh_inter,
3418            hidden,
3419            &mut sg1,
3420        )?;
3421        Self::gemm_pre(
3422            st,
3423            xb,
3424            sw[2].slab().device_ptr(&stream).0 as *const c_void,
3425            s,
3426            sh_inter,
3427            hidden,
3428            &mut sg3,
3429        )?;
3430        unsafe {
3431            ck(
3432                "swiglu sh",
3433                k::memra_dsv4_swiglu(
3434                    dpf!(sg1, &stream),
3435                    dpf!(sg3, &stream),
3436                    dpm!(shbuf, &stream),
3437                    s as i32,
3438                    sh_inter as i32,
3439                    limit,
3440                    std::ptr::null(),
3441                    sp(&stream),
3442                ),
3443            )?;
3444            ck(
3445                "cvt sh",
3446                k::memra_dsv4_cvt_bf16(
3447                    dpf!(shbuf, &stream),
3448                    shb16.device_ptr_mut(&stream).0 as *mut c_void,
3449                    (s * sh_inter) as i64,
3450                    sp(&stream),
3451                ),
3452            )?;
3453        }
3454        Self::gemm_pre(
3455            st,
3456            &shb16,
3457            sw[1].slab().device_ptr(&stream).0 as *const c_void,
3458            s,
3459            hidden,
3460            sh_inter,
3461            &mut sh_out,
3462        )?;
3463        unsafe {
3464            ck(
3465                "add shared",
3466                k::memra_dsv4_add_inplace(
3467                    dpm!(y, &stream),
3468                    dpf!(sh_out, &stream),
3469                    (s * hidden) as i64,
3470                    sp(&stream),
3471                ),
3472            )?;
3473        }
3474        Ok(y)
3475    }
3476
3477    /// Full trunk prefill. Returns last-position logits, or None on early exit.
3478    /// `early_exit_after` stops after that layer (fixture Input B replays layers 0..=3).
3479    pub fn forward(
3480        &self,
3481        ids: &[u32],
3482        capture: Option<&mut GpuCapture>,
3483        early_exit_after: Option<u32>,
3484    ) -> Res<Option<ForwardOut>> {
3485        self.forward_impl(ids, capture, early_exit_after, None)
3486    }
3487
3488    /// Lane 6: prefill the prompt with the lane-4 path while POPULATING the decode
3489    /// caches, so decode_step can continue incrementally from ids.len().
3490    pub fn prefill_with_cache(&self, ids: &[u32], state: &mut DecodeState) -> Res<ForwardOut> {
3491        assert_eq!(state.pos, 0, "prefill_with_cache needs a fresh DecodeState");
3492        assert!(!ids.is_empty(), "empty prompt");
3493        let out = self
3494            .forward_impl(ids, None, None, Some(state))?
3495            .expect("prefill logits");
3496        state.pos = ids.len();
3497        Ok(out)
3498    }
3499
3500    fn forward_impl(
3501        &self,
3502        ids: &[u32],
3503        mut capture: Option<&mut GpuCapture>,
3504        early_exit_after: Option<u32>,
3505        mut state: Option<&mut DecodeState>,
3506    ) -> Res<Option<ForwardOut>> {
3507        let mc = &self.model.mc;
3508        let d = self.model.cfg();
3509        let s = ids.len();
3510        assert!(s <= self.max_seq, "seq {s} > max_seq {}", self.max_seq);
3511        let hidden = mc.n_embd as usize;
3512        let hc = d.hc_mult as usize;
3513        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3514
3515        // stage 0: embed -> hc state
3516        let st0 = &self.stages[0];
3517        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
3518        let stream0 = st0.gpu.stream();
3519        let ids_i32: Vec<i32> = ids.iter().map(|&x| x as i32).collect();
3520        let ids_dev = upload_i32(&stream0, &ids_i32)?;
3521        let mut emb = stream0.alloc_zeros::<f32>(s * hidden).map_err(e("emb"))?;
3522        unsafe {
3523            ck(
3524                "embed_rows",
3525                k::memra_dsv4_embed_rows(
3526                    st0.embed
3527                        .as_ref()
3528                        .expect("embed on stage 0")
3529                        .device_ptr(&stream0)
3530                        .0 as *const c_void,
3531                    ids_dev.device_ptr(&stream0).0 as *const i32,
3532                    dpm!(emb, &stream0),
3533                    s as i32,
3534                    hidden as i32,
3535                    sp(&stream0),
3536                ),
3537            )?;
3538        }
3539        if let Some(c) = capture.as_deref_mut() {
3540            if c.embed_out.is_none() {
3541                c.embed_out = Some(dtoh_f32(&stream0, &emb)?);
3542            }
3543        }
3544        let mut h = stream0
3545            .alloc_zeros::<f32>(s * hc * hidden)
3546            .map_err(e("h0"))?;
3547        unsafe {
3548            ck(
3549                "repeat_hc",
3550                k::memra_dsv4_repeat_hc(
3551                    dpf!(emb, &stream0),
3552                    dpm!(h, &stream0),
3553                    s as i32,
3554                    hc as i32,
3555                    hidden as i32,
3556                    sp(&stream0),
3557                ),
3558            )?;
3559        }
3560
3561        // layers, stage by stage; ONE host-bounce boundary copy at the split
3562        let mut cur_stage = 0usize;
3563        for il in 0..n_trunk {
3564            let stage = self.layer_stage[il as usize];
3565            if stage != cur_stage {
3566                let src_stream = self.stages[cur_stage].gpu.stream();
3567                let host = dtoh_f32(&src_stream, &h)?;
3568                let dst_stream = self.stages[stage].gpu.stream();
3569                self.stages[stage]
3570                    .gpu
3571                    .ctx
3572                    .bind_to_thread()
3573                    .map_err(e("bind"))?;
3574                h = upload_f32(&dst_stream, &host)?;
3575                cur_stage = stage;
3576            }
3577            let st = &self.stages[stage];
3578            let lidx = st
3579                .layers
3580                .iter()
3581                .position(|l| l.il == il)
3582                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
3583            let layer_cache = state.as_deref_mut().map(|ds| &mut ds.caches[il as usize]);
3584            h = self.block_forward(
3585                st,
3586                &st.layers[lidx],
3587                &h,
3588                s,
3589                ids,
3590                capture.as_deref_mut(),
3591                layer_cache,
3592            )?;
3593            if early_exit_after == Some(il) {
3594                self.stages[cur_stage]
3595                    .gpu
3596                    .stream()
3597                    .synchronize()
3598                    .map_err(e("sync"))?;
3599                return Ok(None);
3600            }
3601        }
3602
3603        // head (last stage): hc_head collapse (host sigmoid gates) -> norm -> logits
3604        let last = self.stages.len() - 1;
3605        if cur_stage != last {
3606            let src_stream = self.stages[cur_stage].gpu.stream();
3607            let host = dtoh_f32(&src_stream, &h)?;
3608            let dst_stream = self.stages[last].gpu.stream();
3609            h = upload_f32(&dst_stream, &host)?;
3610        }
3611        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3612        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3613        let logits = self.head_logits_from(
3614            &h,
3615            s,
3616            hc_head_fn,
3617            &self.hc_head_base,
3618            &self.hc_head_scale,
3619            trunk_norm,
3620        )?;
3621        Ok(Some(ForwardOut { logits, h_last: h }))
3622    }
3623
3624    /// ParallelHead (model.py:713-735): hc_head collapse (mix GEMM f32 island + host
3625    /// sigmoid gates, the oracle's own arithmetic) -> final RMSNorm -> last-position
3626    /// logits over the SHARED bf16 head. Used by the trunk head and the MTP head.
3627    fn head_logits_from(
3628        &self,
3629        h: &CudaSlice<f32>,
3630        s: usize,
3631        fn_w: &CudaSlice<f32>,
3632        base: &[f32],
3633        scale: &[f32],
3634        norm: &CudaSlice<f32>,
3635    ) -> Res<Vec<f32>> {
3636        self.head_logits_row(h, s, s - 1, fn_w, base, scale, norm)
3637    }
3638
3639    /// Same head, logits at an arbitrary position row (lane-6 m-sensitivity probe:
3640    /// the reference's own realization noise is measured by comparing the SAME row
3641    /// under two prefill lengths).
3642    #[allow(clippy::too_many_arguments)]
3643    fn head_logits_row(
3644        &self,
3645        h: &CudaSlice<f32>,
3646        s: usize,
3647        row: usize,
3648        fn_w: &CudaSlice<f32>,
3649        base: &[f32],
3650        scale: &[f32],
3651        norm: &CudaSlice<f32>,
3652    ) -> Res<Vec<f32>> {
3653        let d = self.model.cfg();
3654        let mc = &self.model.mc;
3655        let hc = d.hc_mult as usize;
3656        let hidden = mc.n_embd as usize;
3657        let eps = mc.rms_eps;
3658        let last = self.stages.len() - 1;
3659        let st = &self.stages[last];
3660        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx head"))?;
3661        let stream = st.gpu.stream();
3662        let w = hc * hidden;
3663        let mut mixes = stream.alloc_zeros::<f32>(s * hc).map_err(e("hm"))?;
3664        Self::dots(st, h, fn_w, s, w, hc, &mut mixes)?;
3665        unsafe {
3666            ck(
3667                "rowsq head",
3668                k::memra_dsv4_rowsq_scale(
3669                    dpf!(h, &stream),
3670                    dpm!(mixes, &stream),
3671                    s as i32,
3672                    w as i32,
3673                    hc as i32,
3674                    eps,
3675                    sp(&stream),
3676                ),
3677            )?;
3678        }
3679        // oracle hc_head: pre = sigmoid(mix*scale + base) + hc_eps (note: RMS eps is the
3680        // model rms_eps inside the mean, hc_eps only in the gate — mirrored exactly)
3681        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
3682        for t in 0..s {
3683            for c in 0..hc {
3684                let m = mixes_h[t * hc + c];
3685                mixes_h[t * hc + c] = sigmoid_f32(m * scale[0] + base[c]) + d.hc_eps;
3686            }
3687        }
3688        let pre_d = upload_f32(&stream, &mixes_h)?;
3689        let mut collapsed = stream.alloc_zeros::<f32>(s * hidden).map_err(e("col"))?;
3690        unsafe {
3691            ck(
3692                "hc_collapse head",
3693                k::memra_dsv4_hc_collapse(
3694                    dpf!(h, &stream),
3695                    dpf!(pre_d, &stream),
3696                    dpm!(collapsed, &stream),
3697                    s as i32,
3698                    hc as i32,
3699                    hidden as i32,
3700                    sp(&stream),
3701                ),
3702            )?;
3703            ck(
3704                "rmsnorm head",
3705                k::memra_dsv4_rmsnorm(
3706                    dpf!(collapsed, &stream),
3707                    dpf!(norm, &stream),
3708                    dpm!(collapsed, &stream),
3709                    s as i32,
3710                    hidden as i32,
3711                    eps,
3712                    sp(&stream),
3713                ),
3714            )?;
3715        }
3716        // logits for the selected position (f32 island GEMM over bf16 head rows)
3717        assert!(row < s, "logits row {row} out of range (s = {s})");
3718        let vocab = {
3719            let (info, _) = self.model.st.raw("head.weight").expect("head");
3720            info.shape[0] as usize
3721        };
3722        let last_row = collapsed.slice(row * hidden..(row + 1) * hidden);
3723        let mut logits = stream.alloc_zeros::<f32>(vocab).map_err(e("logits"))?;
3724        unsafe {
3725            ck(
3726                "head dots",
3727                k::memra_dsv4_dots_f32(
3728                    last_row.device_ptr(&stream).0 as *const f32,
3729                    st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
3730                    1,
3731                    dpm!(logits, &stream),
3732                    1,
3733                    hidden as i32,
3734                    vocab as i32,
3735                    sp(&stream),
3736                ),
3737            )?;
3738        }
3739        dtoh_f32(&stream, &logits)
3740    }
3741
3742    /// MTP logits at the fixture call shape (model.py:826 — same ids to trunk and MTP;
3743    /// the V3 NextN drafter shift is the spec-decode lane's wiring, not claimed here).
3744    /// `h_trunk` = the trunk's final hc state on the LAST stage (ForwardOut::h_last).
3745    pub fn mtp_logits_last(&self, h_trunk: &CudaSlice<f32>, ids: &[u32]) -> Res<Vec<f32>> {
3746        self.mtp_logits_last_cap(h_trunk, ids, None)
3747    }
3748
3749    /// [`Self::mtp_logits_last`] with a capture pass-through (lane 7: the native-GEMM
3750    /// kernel gate captures the MTP block's moe_x under want = {n_trunk}).
3751    pub fn mtp_logits_last_cap(
3752        &self,
3753        h_trunk: &CudaSlice<f32>,
3754        ids: &[u32],
3755        capture: Option<&mut GpuCapture>,
3756    ) -> Res<Vec<f32>> {
3757        let mtp = self.mtp.as_ref().expect("MTP not loaded");
3758        let d = self.model.cfg();
3759        let mc = &self.model.mc;
3760        let hc = d.hc_mult as usize;
3761        let hidden = mc.n_embd as usize;
3762        let eps = mc.rms_eps;
3763        let s = ids.len();
3764        let last = self.stages.len() - 1;
3765        let st = &self.stages[last];
3766        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx mtp"))?;
3767        let stream = st.gpu.stream();
3768
3769        // e = rmsnorm(embed(ids), enorm): embed rows gathered HOST-side (bit-exact bf16
3770        // decode, same as the oracle's embed_rows) — the embed table lives on stage 0.
3771        let e_host = self.model.embed_rows(ids);
3772        let mut e_dev = upload_f32(&stream, &e_host)?;
3773        unsafe {
3774            ck(
3775                "rmsnorm enorm",
3776                k::memra_dsv4_rmsnorm(
3777                    dpf!(e_dev, &stream),
3778                    dpf!(mtp.enorm, &stream),
3779                    dpm!(e_dev, &stream),
3780                    s as i32,
3781                    hidden as i32,
3782                    eps,
3783                    sp(&stream),
3784                ),
3785            )?;
3786        }
3787        // x = hnorm(h_trunk) per hc copy
3788        let mut xh = stream
3789            .alloc_zeros::<f32>(s * hc * hidden)
3790            .map_err(e("mtp xh"))?;
3791        unsafe {
3792            ck(
3793                "rmsnorm hnorm",
3794                k::memra_dsv4_rmsnorm(
3795                    dpf!(h_trunk, &stream),
3796                    dpf!(mtp.hnorm, &stream),
3797                    dpm!(xh, &stream),
3798                    (s * hc) as i32,
3799                    hidden as i32,
3800                    eps,
3801                    sp(&stream),
3802                ),
3803            )?;
3804        }
3805        // ep = e_proj(e) [s, hidden]; hp = h_proj(xh) per copy [s*hc, hidden]
3806        let mut ep = stream.alloc_zeros::<f32>(s * hidden).map_err(e("mtp ep"))?;
3807        Self::gemm(st, &e_dev, &mtp.e_proj, 0, s, hidden, hidden, &mut ep)?;
3808        let mut hp = stream
3809            .alloc_zeros::<f32>(s * hc * hidden)
3810            .map_err(e("mtp hp"))?;
3811        Self::gemm(st, &xh, &mtp.h_proj, 0, s * hc, hidden, hidden, &mut hp)?;
3812        // xm[t, c, :] = ep[t, :] + hp[t, c, :]  (e broadcast over the hc copies)
3813        let mut xm = stream
3814            .alloc_zeros::<f32>(s * hc * hidden)
3815            .map_err(e("mtp xm"))?;
3816        unsafe {
3817            ck(
3818                "repeat ep",
3819                k::memra_dsv4_repeat_hc(
3820                    dpf!(ep, &stream),
3821                    dpm!(xm, &stream),
3822                    s as i32,
3823                    hc as i32,
3824                    hidden as i32,
3825                    sp(&stream),
3826                ),
3827            )?;
3828            ck(
3829                "add hp",
3830                k::memra_dsv4_add_inplace(
3831                    dpm!(xm, &stream),
3832                    dpf!(hp, &stream),
3833                    (s * hc * hidden) as i64,
3834                    sp(&stream),
3835                ),
3836            )?;
3837        }
3838        let xm = self.block_forward(st, &mtp.layer, &xm, s, ids, capture, None)?;
3839        self.head_logits_from(
3840            &xm,
3841            s,
3842            &mtp.hc_head_fn,
3843            &mtp.hc_head_base,
3844            &mtp.hc_head_scale,
3845            &mtp.norm,
3846        )
3847    }
3848
3849    /// Trunk-head logits at position `row` of a ForwardOut hc state (m-sensitivity probe).
3850    pub fn trunk_logits_row(&self, h: &CudaSlice<f32>, s: usize, row: usize) -> Res<Vec<f32>> {
3851        let last = self.stages.len() - 1;
3852        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3853        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3854        self.head_logits_row(
3855            h,
3856            s,
3857            row,
3858            hc_head_fn,
3859            &self.hc_head_base,
3860            &self.hc_head_scale,
3861            trunk_norm,
3862        )
3863    }
3864
3865    // ---------------------------------------------------------------- lane 6: decode
3866
3867    /// Allocate the per-layer decode caches (capacity = max_seq, the reference
3868    /// register_buffer shape) on each layer's owning stage. Returns a fresh state
3869    /// (pos = 0) ready for [`Self::prefill_with_cache`].
3870    pub fn alloc_decode_state(&self) -> Res<DecodeState> {
3871        let d = self.model.cfg();
3872        let mc = &self.model.mc;
3873        let win = d.sliding_window as usize;
3874        let hd = d.head_dim as usize;
3875        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3876        let mut caches = Vec::with_capacity(n_trunk as usize);
3877        let mut cache_bytes = vec![0u64; self.stages.len()];
3878        // iteration 3, rung 4: reserve T_max TRANSIENT window-kv rows per layer at
3879        // kvc rows [win + cap_blocks, win + cap_blocks + T_max) — where a batched verify
3880        // round's kv lands so the persistent ring stays read-only until commit (§3.1).
3881        // Zero rows when the drafter is not loaded: today's exact allocation, byte for byte.
3882        let trans_rows = self.verify_tmax();
3883        for il in 0..n_trunk {
3884            let stage_i = self.layer_stage[il as usize];
3885            let st = &self.stages[stage_i];
3886            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx cache"))?;
3887            let stream = st.gpu.stream();
3888            let lidx = st
3889                .layers
3890                .iter()
3891                .position(|l| l.il == il)
3892                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
3893            let layer = &st.layers[lidx];
3894            let ratio = layer.ratio;
3895            let cap_blocks = if ratio != 0 { self.max_seq / ratio } else { 0 };
3896            let kvc_rows = win + cap_blocks + trans_rows;
3897            let mut bytes = (kvc_rows * hd * 4) as u64;
3898            let kvc = stream
3899                .alloc_zeros::<f32>(kvc_rows * hd)
3900                .map_err(e("kvc alloc"))?;
3901            // pending pair: kv zeros, score -inf (block-0-at-decode masking, receipts)
3902            let mk_pend = |latent: usize, slots: usize| -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
3903                let kv = stream
3904                    .alloc_zeros::<f32>(slots * latent)
3905                    .map_err(e("pend kv alloc"))?;
3906                let sc = upload_f32(&stream, &vec![f32::NEG_INFINITY; slots * latent])?;
3907                Ok((kv, sc))
3908            };
3909            let (pend_kv, pend_score) = if let Some(cmp) = &layer.cmp {
3910                let slots = if cmp.overlap {
3911                    2 * cmp.ratio
3912                } else {
3913                    cmp.ratio
3914                };
3915                bytes += (2 * slots * cmp.latent * 4) as u64;
3916                let (a, b) = mk_pend(cmp.latent, slots)?;
3917                (Some(a), Some(b))
3918            } else {
3919                (None, None)
3920            };
3921            let (ikvc, ipend_kv, ipend_score) = if let Some(ix) = &layer.idx {
3922                bytes += (cap_blocks * ix.cmp.d * 4) as u64;
3923                let store = stream
3924                    .alloc_zeros::<f32>(cap_blocks * ix.cmp.d)
3925                    .map_err(e("ikvc alloc"))?;
3926                let slots = if ix.cmp.overlap {
3927                    2 * ix.cmp.ratio
3928                } else {
3929                    ix.cmp.ratio
3930                };
3931                bytes += (2 * slots * ix.cmp.latent * 4) as u64;
3932                let (a, b) = mk_pend(ix.cmp.latent, slots)?;
3933                (Some(store), Some(a), Some(b))
3934            } else {
3935                (None, None, None)
3936            };
3937            cache_bytes[stage_i] += bytes;
3938            caches.push(LayerCache {
3939                kvc,
3940                n_blocks: 0,
3941                pend_kv,
3942                pend_score,
3943                ikvc,
3944                i_blocks: 0,
3945                ipend_kv,
3946                ipend_score,
3947            });
3948        }
3949        let ws = if matches!(self.decode_path, DecodePath::Device { .. }) {
3950            Some(self.alloc_step_ws()?)
3951        } else {
3952            None
3953        };
3954        for st in &self.stages {
3955            st.gpu.stream().synchronize().map_err(e("cache sync"))?;
3956        }
3957        Ok(DecodeState {
3958            caches,
3959            pos: 0,
3960            cache_bytes,
3961            ws,
3962        })
3963    }
3964
3965    /// Lane 8: allocate the per-stage step workspace (device decode path only).
3966    fn alloc_step_ws(&self) -> Res<Vec<StepWs>> {
3967        let d = self.model.cfg();
3968        let mc = &self.model.mc;
3969        let moe = mc.moe.as_ref().expect("moe");
3970        let hc = d.hc_mult as usize;
3971        let hidden = mc.n_embd as usize;
3972        let heads = mc.n_head as usize;
3973        let hd = d.head_dim as usize;
3974        let q_lora = d.q_lora_rank as usize;
3975        let win = d.sliding_window as usize;
3976        let o_groups = d.o_groups as usize;
3977        let o_lora = d.o_lora_rank as usize;
3978        let iheads = d.index_n_heads as usize;
3979        let ihd = d.index_head_dim as usize;
3980        let topk = moe.expert_used_count as usize;
3981        let ne = moe.expert_count as usize;
3982        let inter = moe.expert_ff_length as usize;
3983        let itopk = d.index_topk as usize;
3984        let vocab = {
3985            let (info, _) = self.model.st.raw("head.weight").expect("head");
3986            info.shape[0] as usize
3987        };
3988        let sh_inter = {
3989            let (info, _) = self
3990                .model
3991                .st
3992                .raw("layers.0.ffn.shared_experts.w1.weight")
3993                .expect("shared w1");
3994            info.shape[0] as usize
3995        };
3996        // fine ratio (indexer-carrying) and per-class compressor maxima, config-derived
3997        let mut max_latent = 0usize;
3998        let mut max_d = 0usize;
3999        let mut max_shift = 0usize;
4000        let mut min_ratio = usize::MAX;
4001        for st in &self.stages {
4002            for l in &st.layers {
4003                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
4004                    max_latent = max_latent.max(cmp.latent);
4005                    max_d = max_d.max(cmp.d);
4006                    if cmp.overlap {
4007                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
4008                    }
4009                    min_ratio = min_ratio.min(cmp.ratio);
4010                }
4011            }
4012        }
4013        assert!(min_ratio != usize::MAX, "no compressor layers?");
4014        let score_cap = self.max_seq / min_ratio + 1;
4015        let idx_tail = itopk.max(self.max_seq / 128 + 1);
4016        // the largest bf16 cvt any device-path gemm() performs (activation side, m=1):
4017        // wo_b consumes o_groups*o_lora, the o cvt covers heads*hd separately.
4018        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
4019        let mut out = Vec::with_capacity(self.stages.len());
4020        for st in &self.stages {
4021            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ws"))?;
4022            let s = st.gpu.stream();
4023            let f = |n: usize| s.alloc_zeros::<f32>(n).map_err(e("ws f32"));
4024            let b = |n: usize| s.alloc_zeros::<u8>(n).map_err(e("ws u8"));
4025            let i = |n: usize| s.alloc_zeros::<i32>(n).map_err(e("ws i32"));
4026            out.push(StepWs {
4027                h_a: f(hc * hidden)?,
4028                h_b: f(hc * hidden)?,
4029                h_rx: f(hc * hidden)?,
4030                emb: f(hidden)?,
4031                mixes: f((2 + hc) * hc)?,
4032                pre: f(hc)?,
4033                post: f(hc)?,
4034                comb: f(hc * hc)?,
4035                y_hc: f(hidden)?,
4036                x: f(hidden)?,
4037                xf: f(hidden)?,
4038                qr: f(q_lora)?,
4039                qr_b: b(q_lora * 2)?,
4040                q: f(heads * hd)?,
4041                kv: f(hd)?,
4042                qi: f(iheads * ihd)?,
4043                wproj: f(iheads)?,
4044                score: f(score_cap)?,
4045                idx: i(win + idx_tail)?,
4046                o: f(heads * hd)?,
4047                o_b: b(heads * hd * 2)?,
4048                og: f(o_groups * o_lora)?,
4049                attn_out: f(hidden)?,
4050                gemm_xb: b(max_gemm_k * 2)?,
4051                raw: f(ne)?,
4052                sel: i(topk)?,
4053                selw: f(topk)?,
4054                order: i(topk)?,
4055                xq: b(hidden)?,
4056                xs: f(hidden / 128)?,
4057                g1: f(topk * inter)?,
4058                g3: f(topk * inter)?,
4059                hbuf: f(topk * inter)?,
4060                hq: b(topk * inter)?,
4061                hs: f(topk * inter / 128)?,
4062                contrib: f(topk * hidden)?,
4063                y: f(hidden)?,
4064                xb: b(hidden * 2)?,
4065                sg1: f(sh_inter)?,
4066                sg3: f(sh_inter)?,
4067                shbuf: f(sh_inter)?,
4068                shb16: b(sh_inter * 2)?,
4069                sh_out: f(hidden)?,
4070                cmp_kv_row: f(max_latent)?,
4071                cmp_sc_row: f(max_latent)?,
4072                cmp_emit: f(2 * max_d)?,
4073                cmp_shift: f(max_shift.max(1))?,
4074                sink_scores: f(heads * (win + idx_tail))?,
4075                sink_evals: f(heads * (win + idx_tail))?,
4076                sink_den: s.alloc_zeros::<f64>(heads).map_err(e("ws f64"))?,
4077                head_mixes: f(hc)?,
4078                head_pre: f(hc)?,
4079                collapsed: f(hidden)?,
4080                logits: f(vocab)?,
4081                argmax: i(1)?,
4082                tok: i(1)?,
4083            });
4084        }
4085        Ok(out)
4086    }
4087
4088    /// Incremental compressor step (reference decode state machine, M:344-377): append
4089    /// this position's RAW wkv/wgate rows to the pending state; when the block
4090    /// completes ((pos+1) % ratio == 0), emit block pos/ratio into `store` row
4091    /// row0 + j via the SAME pooling kernel prefill uses (overlap rides a 2-block
4092    /// launch whose block 1 reads prev rows [0,ratio) through dims [0,d) and cur rows
4093    /// [ratio,2ratio) through dims [d,2d) — the emission pooling verbatim), then
4094    /// norm→rope(j·ratio)→QAT, and shift cur→prev.
4095    #[allow(clippy::too_many_arguments)]
4096    fn cmp_decode(
4097        &self,
4098        st: &Stage,
4099        cmp: &CmpDev,
4100        x: &CudaSlice<f32>, // [1, hidden] post-attn-norm
4101        pos: usize,
4102        hidden: usize,
4103        fc_dev: &CudaSlice<f32>,
4104        rd: usize,
4105        eps: f32,
4106        pend_kv: &mut CudaSlice<f32>,
4107        pend_score: &mut CudaSlice<f32>,
4108        store: &mut CudaSlice<f32>,
4109        row0: usize,
4110        blocks: &mut usize,
4111    ) -> Res<()> {
4112        let stream = st.gpu.stream();
4113        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
4114        let mut kv_row = stream.alloc_zeros::<f32>(latent).map_err(e("dkv"))?;
4115        let mut sc_row = stream.alloc_zeros::<f32>(latent).map_err(e("dsc"))?;
4116        Self::dots(st, x, &cmp.wkv, 1, hidden, latent, &mut kv_row)?;
4117        Self::dots(st, x, &cmp.wgate, 1, hidden, latent, &mut sc_row)?;
4118        let slot = if cmp.overlap {
4119            ratio + pos % ratio
4120        } else {
4121            pos % ratio
4122        };
4123        {
4124            let src = kv_row.slice(0..latent);
4125            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
4126            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
4127            let src = sc_row.slice(0..latent);
4128            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
4129            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
4130        }
4131        if (pos + 1) % ratio != 0 {
4132            return Ok(());
4133        }
4134        let j = pos / ratio;
4135        let nb_launch = if cmp.overlap { 2usize } else { 1 };
4136        let row_off = if cmp.overlap { d } else { 0 };
4137        let mut out = stream
4138            .alloc_zeros::<f32>(nb_launch * d)
4139            .map_err(e("emit"))?;
4140        unsafe {
4141            ck(
4142                "compressor_pool dec",
4143                k::memra_dsv4_compressor_pool(
4144                    dpf!(pend_kv, &stream),
4145                    dpf!(pend_score, &stream),
4146                    dpf!(cmp.ape, &stream),
4147                    dpm!(out, &stream),
4148                    nb_launch as i32,
4149                    ratio as i32,
4150                    d as i32,
4151                    latent as i32,
4152                    cmp.overlap as i32,
4153                    sp(&stream),
4154                ),
4155            )?;
4156            // in-place row ops at the emitted row (base + row_off), lane-4 ptr idiom
4157            let row_c = (out.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
4158            let row_m = (out.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
4159            ck(
4160                "rmsnorm dec cmp",
4161                k::memra_dsv4_rmsnorm(
4162                    row_c,
4163                    dpf!(cmp.norm, &stream),
4164                    row_m,
4165                    1,
4166                    d as i32,
4167                    eps,
4168                    sp(&stream),
4169                ),
4170            )?;
4171            let pos_dev = upload_i32(&stream, &[(j * ratio) as i32])?;
4172            ck(
4173                "rope dec cmp",
4174                k::memra_dsv4_rope(
4175                    row_m,
4176                    1,
4177                    1,
4178                    d as i32,
4179                    rd as i32,
4180                    dpf!(fc_dev, &stream),
4181                    pos_dev.device_ptr(&stream).0 as *const i32,
4182                    0,
4183                    sp(&stream),
4184                ),
4185            )?;
4186            if cmp.rotate {
4187                let scale = (d as f32).powf(-0.5);
4188                ck(
4189                    "hadamard dec cmp",
4190                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
4191                )?;
4192                ck(
4193                    "fp4 dec cmp",
4194                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
4195                )?;
4196            } else {
4197                ck(
4198                    "act_quant dec cmp",
4199                    k::memra_dsv4_act_quant(
4200                        row_m,
4201                        1,
4202                        d as i64,
4203                        (d - rd) as i32,
4204                        64,
4205                        (self.variant == ActQuantVariant::ClampOnly) as i32,
4206                        sp(&stream),
4207                    ),
4208                )?;
4209            }
4210        }
4211        {
4212            let src = out.slice(row_off..row_off + d);
4213            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
4214            stream
4215                .memcpy_dtod(&src, &mut dst)
4216                .map_err(e("emit store"))?;
4217        }
4218        if cmp.overlap {
4219            // shift cur -> prev through a bounce (same-buffer D2D ranges must not alias)
4220            let mut tmp = stream
4221                .alloc_zeros::<f32>(ratio * latent)
4222                .map_err(e("shift tmp"))?;
4223            {
4224                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
4225                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift1"))?;
4226            }
4227            {
4228                let mut dst = pend_kv.slice_mut(0..ratio * latent);
4229                stream
4230                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4231                    .map_err(e("shift2"))?;
4232            }
4233            {
4234                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
4235                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift3"))?;
4236            }
4237            {
4238                let mut dst = pend_score.slice_mut(0..ratio * latent);
4239                stream
4240                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4241                    .map_err(e("shift4"))?;
4242            }
4243        }
4244        *blocks = j + 1;
4245        Ok(())
4246    }
4247
4248    /// One trunk block, single-token decode. h is [1, hc, hidden] f32 on the stage.
4249    /// Mirrors the reference decode branches: ring write (M:530), indexer with its
4250    /// compressor BEFORE scoring (M:415), attention compressor before sparse_attn
4251    /// (M:531), window/compressed index law (M:255-276). `dump` (diagnostic only)
4252    /// collects named intermediates for the bisect probe.
4253    #[allow(clippy::too_many_arguments)]
4254    fn block_decode(
4255        &self,
4256        st: &Stage,
4257        layer: &LayerDev,
4258        cache: &mut LayerCache,
4259        h: &CudaSlice<f32>,
4260        pos: usize,
4261        tok: u32,
4262        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
4263    ) -> Res<CudaSlice<f32>> {
4264        let d = self.model.cfg();
4265        let mc = &self.model.mc;
4266        let hc = d.hc_mult as usize;
4267        let hidden = mc.n_embd as usize;
4268        let heads = mc.n_head as usize;
4269        let hd = d.head_dim as usize;
4270        let rd = d.qk_rope_head_dim as usize;
4271        let q_lora = d.q_lora_rank as usize;
4272        let win = d.sliding_window as usize;
4273        let o_groups = d.o_groups as usize;
4274        let o_lora = d.o_lora_rank as usize;
4275        let eps = mc.rms_eps;
4276        let iters = d.hc_sinkhorn_iters;
4277        let hc_eps = d.hc_eps;
4278        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
4279        let stream = st.gpu.stream();
4280        let fc_dev = if layer.ratio != 0 {
4281            &st.fc_yarn
4282        } else {
4283            &st.fc_plain
4284        };
4285        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
4286        let LayerCache {
4287            kvc,
4288            n_blocks,
4289            pend_kv,
4290            pend_score,
4291            ikvc,
4292            i_blocks,
4293            ipend_kv,
4294            ipend_score,
4295        } = cache;
4296
4297        // ---- attention sub-block
4298        let (y, post, comb) = Self::hc_pre(
4299            st,
4300            h,
4301            &layer.hc_attn_fn,
4302            &layer.hc_attn_base,
4303            &layer.hc_attn_scale,
4304            1,
4305            hc,
4306            hidden,
4307            iters,
4308            hc_eps,
4309        )?;
4310        let mut x = stream.alloc_zeros::<f32>(hidden).map_err(e("x"))?;
4311        unsafe {
4312            ck(
4313                "rmsnorm attn",
4314                k::memra_dsv4_rmsnorm(
4315                    dpf!(y, &stream),
4316                    dpf!(layer.attn_norm, &stream),
4317                    dpm!(x, &stream),
4318                    1,
4319                    hidden as i32,
4320                    eps,
4321                    sp(&stream),
4322                ),
4323            )?;
4324        }
4325        if let Some(dm) = dump.as_deref_mut() {
4326            dm.push((format!("layer{}.x", layer.il), dtoh_f32(&stream, &x)?));
4327        }
4328
4329        // q path (item 3: `.dev()` is lawful here — the legacy path with the fp8
4330        // dense arm is a BOOT refusal, so these slabs are always device-resident)
4331        let mut qr = stream.alloc_zeros::<f32>(q_lora).map_err(e("qr"))?;
4332        Self::gemm(st, &x, layer.wq_a.dev(), 0, 1, q_lora, hidden, &mut qr)?;
4333        unsafe {
4334            ck(
4335                "rmsnorm q",
4336                k::memra_dsv4_rmsnorm(
4337                    dpf!(qr, &stream),
4338                    dpf!(layer.q_norm, &stream),
4339                    dpm!(qr, &stream),
4340                    1,
4341                    q_lora as i32,
4342                    eps,
4343                    sp(&stream),
4344                ),
4345            )?;
4346        }
4347        let mut qr_b = stream.alloc_zeros::<u8>(q_lora * 2).map_err(e("qr_b"))?;
4348        unsafe {
4349            ck(
4350                "cvt qr",
4351                k::memra_dsv4_cvt_bf16(
4352                    dpf!(qr, &stream),
4353                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
4354                    q_lora as i64,
4355                    sp(&stream),
4356                ),
4357            )?;
4358        }
4359        let mut q = stream.alloc_zeros::<f32>(heads * hd).map_err(e("q"))?;
4360        Self::gemm_pre(
4361            st,
4362            &qr_b,
4363            layer.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4364            1,
4365            heads * hd,
4366            q_lora,
4367            &mut q,
4368        )?;
4369        let pos_dev = upload_i32(&stream, &[pos as i32])?;
4370        unsafe {
4371            ck(
4372                "headrms",
4373                k::memra_dsv4_headrms(dpm!(q, &stream), heads as i32, hd as i32, eps, sp(&stream)),
4374            )?;
4375            ck(
4376                "rope q",
4377                k::memra_dsv4_rope(
4378                    dpm!(q, &stream),
4379                    1,
4380                    heads as i32,
4381                    hd as i32,
4382                    rd as i32,
4383                    dpf!(fc_dev, &stream),
4384                    pos_dev.device_ptr(&stream).0 as *const i32,
4385                    0,
4386                    sp(&stream),
4387                ),
4388            )?;
4389        }
4390
4391        if let Some(dm) = dump.as_deref_mut() {
4392            dm.push((format!("layer{}.q", layer.il), dtoh_f32(&stream, &q)?));
4393        }
4394        // shared K==V latent row + window QAT, written into the ring at pos % win
4395        let mut kv = stream.alloc_zeros::<f32>(hd).map_err(e("kv"))?;
4396        Self::gemm(st, &x, layer.wkv.dev(), 0, 1, hd, hidden, &mut kv)?;
4397        unsafe {
4398            ck(
4399                "rmsnorm kv",
4400                k::memra_dsv4_rmsnorm(
4401                    dpf!(kv, &stream),
4402                    dpf!(layer.kv_norm, &stream),
4403                    dpm!(kv, &stream),
4404                    1,
4405                    hd as i32,
4406                    eps,
4407                    sp(&stream),
4408                ),
4409            )?;
4410            ck(
4411                "rope kv",
4412                k::memra_dsv4_rope(
4413                    dpm!(kv, &stream),
4414                    1,
4415                    1,
4416                    hd as i32,
4417                    rd as i32,
4418                    dpf!(fc_dev, &stream),
4419                    pos_dev.device_ptr(&stream).0 as *const i32,
4420                    0,
4421                    sp(&stream),
4422                ),
4423            )?;
4424            ck(
4425                "act_quant kv",
4426                k::memra_dsv4_act_quant(
4427                    dpm!(kv, &stream),
4428                    1,
4429                    hd as i64,
4430                    (hd - rd) as i32,
4431                    64,
4432                    clamp_only,
4433                    sp(&stream),
4434                ),
4435            )?;
4436        }
4437        {
4438            let slot = pos % win;
4439            let src = kv.slice(0..hd);
4440            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
4441            stream
4442                .memcpy_dtod(&src, &mut dst)
4443                .map_err(e("ring write"))?;
4444        }
4445        if let Some(dm) = dump.as_deref_mut() {
4446            dm.push((format!("layer{}.kv", layer.il), dtoh_f32(&stream, &kv)?));
4447        }
4448
4449        // index assembly: window part (M:255-262 decode branches), fixed width win
4450        let mut idxs: Vec<i64> = vec![-1; win];
4451        if pos >= win - 1 {
4452            let sp_ = pos % win;
4453            let mut k_ = 0usize;
4454            for s_ in (sp_ + 1)..win {
4455                idxs[k_] = s_ as i64;
4456                k_ += 1;
4457            }
4458            for s_ in 0..=sp_ {
4459                idxs[k_] = s_ as i64;
4460                k_ += 1;
4461            }
4462        } else {
4463            for (p, v) in idxs.iter_mut().enumerate().take(pos + 1) {
4464                *v = p as i64;
4465            }
4466        }
4467
4468        if layer.ratio != 0 {
4469            let cidx: Vec<i64> = if let Some(ix) = &layer.idx {
4470                // indexer q
4471                let mut qi = stream
4472                    .alloc_zeros::<f32>(ix.heads * ix.hd)
4473                    .map_err(e("qi"))?;
4474                Self::gemm_pre(
4475                    st,
4476                    &qr_b,
4477                    ix.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4478                    1,
4479                    ix.heads * ix.hd,
4480                    q_lora,
4481                    &mut qi,
4482                )?;
4483                unsafe {
4484                    ck(
4485                        "rope qi",
4486                        k::memra_dsv4_rope(
4487                            dpm!(qi, &stream),
4488                            1,
4489                            ix.heads as i32,
4490                            ix.hd as i32,
4491                            rd as i32,
4492                            dpf!(fc_dev, &stream),
4493                            pos_dev.device_ptr(&stream).0 as *const i32,
4494                            0,
4495                            sp(&stream),
4496                        ),
4497                    )?;
4498                    let scale = (ix.hd as f32).powf(-0.5);
4499                    ck(
4500                        "hadamard qi",
4501                        k::memra_dsv4_hadamard(
4502                            dpm!(qi, &stream),
4503                            ix.heads as i32,
4504                            ix.hd as i32,
4505                            scale,
4506                            sp(&stream),
4507                        ),
4508                    )?;
4509                    ck(
4510                        "fp4 qi",
4511                        k::memra_dsv4_fp4_act_quant(
4512                            dpm!(qi, &stream),
4513                            ix.heads as i32,
4514                            ix.hd as i64,
4515                            ix.hd as i32,
4516                            sp(&stream),
4517                        ),
4518                    )?;
4519                }
4520                // indexer compressor BEFORE scoring (M:415): this step's block is scored
4521                self.cmp_decode(
4522                    st,
4523                    &ix.cmp,
4524                    &x,
4525                    pos,
4526                    hidden,
4527                    fc_dev,
4528                    rd,
4529                    eps,
4530                    ipend_kv.as_mut().expect("ipend"),
4531                    ipend_score.as_mut().expect("ipend"),
4532                    ikvc.as_mut().expect("ikvc"),
4533                    0,
4534                    i_blocks,
4535                )?;
4536                let nb = *i_blocks;
4537                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
4538                if nb > 0 {
4539                    let mut wproj = stream.alloc_zeros::<f32>(ix.heads).map_err(e("wp"))?;
4540                    Self::gemm(
4541                        st,
4542                        &x,
4543                        ix.weights_proj.dev(),
4544                        0,
4545                        1,
4546                        ix.heads,
4547                        hidden,
4548                        &mut wproj,
4549                    )?;
4550                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
4551                    let mut score = stream.alloc_zeros::<f32>(nb).map_err(e("iscore"))?;
4552                    unsafe {
4553                        ck(
4554                            "indexer_score dec",
4555                            k::memra_dsv4_indexer_score(
4556                                dpf!(qi, &stream),
4557                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
4558                                dpf!(wproj, &stream),
4559                                wscale,
4560                                dpm!(score, &stream),
4561                                1,
4562                                ix.heads as i32,
4563                                ix.hd as i32,
4564                                nb as i32,
4565                                layer.ratio as i32,
4566                                nb as i32, // decode law: store is causal, lim = nb
4567                                sp(&stream),
4568                            ),
4569                        )?;
4570                    }
4571                    let score_h = dtoh_f32(&stream, &score)?;
4572                    // host topk, oracle ordering (value desc, index asc), offset = win
4573                    let kk = ix.topk.min(nb);
4574                    let mut order: Vec<usize> = (0..nb).collect();
4575                    order.sort_by(|&a, &b| {
4576                        score_h[b]
4577                            .partial_cmp(&score_h[a])
4578                            .unwrap_or(std::cmp::Ordering::Equal)
4579                            .then(a.cmp(&b))
4580                    });
4581                    order
4582                        .into_iter()
4583                        .take(kk)
4584                        .map(|j| (j + win) as i64)
4585                        .collect()
4586                } else {
4587                    Vec::new()
4588                }
4589            } else {
4590                // coarse: all blocks incl. the one emitted this step (M:268-271 decode)
4591                let nb = (pos + 1) / layer.ratio;
4592                (0..nb).map(|j| (j + win) as i64).collect()
4593            };
4594            // attention compressor before sparse_attn (M:531)
4595            self.cmp_decode(
4596                st,
4597                layer.cmp.as_ref().expect("ratio!=0 has compressor"),
4598                &x,
4599                pos,
4600                hidden,
4601                fc_dev,
4602                rd,
4603                eps,
4604                pend_kv.as_mut().expect("pend"),
4605                pend_score.as_mut().expect("pend"),
4606                kvc,
4607                win,
4608                n_blocks,
4609            )?;
4610            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
4611            idxs.extend_from_slice(&cidx);
4612        }
4613        let slots = idxs.len();
4614        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
4615        let idx_dev = upload_i32(&stream, &idxs_i32)?;
4616
4617        // sparse sink attention over the layer cache + query-position de-rotation
4618        let mut o = stream.alloc_zeros::<f32>(heads * hd).map_err(e("o"))?;
4619        let scale = (hd as f64).powf(-0.5) as f32;
4620        unsafe {
4621            ck(
4622                "sink_attn dec",
4623                k::memra_dsv4_sink_attn(
4624                    dpf!(q, &stream),
4625                    dpf!(kvc, &stream),
4626                    idx_dev.device_ptr(&stream).0 as *const i32,
4627                    dpf!(layer.sink, &stream),
4628                    dpm!(o, &stream),
4629                    1,
4630                    heads as i32,
4631                    hd as i32,
4632                    slots as i32,
4633                    scale,
4634                    sp(&stream),
4635                ),
4636            )?;
4637            ck(
4638                "rope o inv",
4639                k::memra_dsv4_rope(
4640                    dpm!(o, &stream),
4641                    1,
4642                    heads as i32,
4643                    hd as i32,
4644                    rd as i32,
4645                    dpf!(fc_dev, &stream),
4646                    pos_dev.device_ptr(&stream).0 as *const i32,
4647                    1,
4648                    sp(&stream),
4649                ),
4650            )?;
4651        }
4652
4653        if let Some(dm) = dump.as_deref_mut() {
4654            dm.push((format!("layer{}.o", layer.il), dtoh_f32(&stream, &o)?));
4655        }
4656        // grouped wo (identical to prefill at s=1)
4657        let gw = heads / o_groups * hd;
4658        let mut og = stream
4659            .alloc_zeros::<f32>(o_groups * o_lora)
4660            .map_err(e("og"))?;
4661        let mut o_grp = stream.alloc_zeros::<f32>(gw).map_err(e("o_grp"))?;
4662        let mut y_grp = stream.alloc_zeros::<f32>(o_lora).map_err(e("y_grp"))?;
4663        for g in 0..o_groups {
4664            unsafe {
4665                ck(
4666                    "take_cols",
4667                    k::memra_dsv4_take_cols(
4668                        dpf!(o, &stream),
4669                        dpm!(o_grp, &stream),
4670                        1,
4671                        gw as i32,
4672                        (heads * hd) as i64,
4673                        (g * gw) as i64,
4674                        sp(&stream),
4675                    ),
4676                )?;
4677            }
4678            Self::gemm(
4679                st,
4680                &o_grp,
4681                layer.wo_a.dev(),
4682                g * o_lora * gw,
4683                1,
4684                o_lora,
4685                gw,
4686                &mut y_grp,
4687            )?;
4688            unsafe {
4689                ck(
4690                    "place_cols",
4691                    k::memra_dsv4_place_cols(
4692                        dpf!(y_grp, &stream),
4693                        dpm!(og, &stream),
4694                        1,
4695                        o_lora as i32,
4696                        (o_groups * o_lora) as i64,
4697                        (g * o_lora) as i64,
4698                        sp(&stream),
4699                    ),
4700                )?;
4701            }
4702        }
4703        let mut attn_out = stream.alloc_zeros::<f32>(hidden).map_err(e("ao"))?;
4704        Self::gemm(
4705            st,
4706            &og,
4707            layer.wo_b.dev(),
4708            0,
4709            1,
4710            hidden,
4711            o_groups * o_lora,
4712            &mut attn_out,
4713        )?;
4714
4715        if let Some(dm) = dump.as_deref_mut() {
4716            dm.push((
4717                format!("layer{}.attn_out", layer.il),
4718                dtoh_f32(&stream, &attn_out)?,
4719            ));
4720        }
4721        // hc_post (attention)
4722        let mut h2 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h2"))?;
4723        unsafe {
4724            ck(
4725                "hc_post attn",
4726                k::memra_dsv4_hc_post(
4727                    dpf!(attn_out, &stream),
4728                    dpf!(h, &stream),
4729                    dpf!(post, &stream),
4730                    dpf!(comb, &stream),
4731                    dpm!(h2, &stream),
4732                    1,
4733                    hc as i32,
4734                    hidden as i32,
4735                    sp(&stream),
4736                ),
4737            )?;
4738        }
4739
4740        // ---- ffn sub-block
4741        let (y2, post2, comb2) = Self::hc_pre(
4742            st,
4743            &h2,
4744            &layer.hc_ffn_fn,
4745            &layer.hc_ffn_base,
4746            &layer.hc_ffn_scale,
4747            1,
4748            hc,
4749            hidden,
4750            iters,
4751            hc_eps,
4752        )?;
4753        let mut xf = stream.alloc_zeros::<f32>(hidden).map_err(e("xf"))?;
4754        unsafe {
4755            ck(
4756                "rmsnorm ffn",
4757                k::memra_dsv4_rmsnorm(
4758                    dpf!(y2, &stream),
4759                    dpf!(layer.ffn_norm, &stream),
4760                    dpm!(xf, &stream),
4761                    1,
4762                    hidden as i32,
4763                    eps,
4764                    sp(&stream),
4765                ),
4766            )?;
4767        }
4768        let moe_out = self.moe_forward(st, layer, &xf, 1, &[tok])?;
4769        if let Some(dm) = dump.as_deref_mut() {
4770            dm.push((
4771                format!("layer{}.moe_out", layer.il),
4772                dtoh_f32(&stream, &moe_out)?,
4773            ));
4774        }
4775        let mut h3 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h3"))?;
4776        unsafe {
4777            ck(
4778                "hc_post ffn",
4779                k::memra_dsv4_hc_post(
4780                    dpf!(moe_out, &stream),
4781                    dpf!(h2, &stream),
4782                    dpf!(post2, &stream),
4783                    dpf!(comb2, &stream),
4784                    dpm!(h3, &stream),
4785                    1,
4786                    hc as i32,
4787                    hidden as i32,
4788                    sp(&stream),
4789                ),
4790            )?;
4791        }
4792        if let Some(dm) = dump.as_deref_mut() {
4793            dm.push((format!("layer{}.h3", layer.il), dtoh_f32(&stream, &h3)?));
4794        }
4795        Ok(h3)
4796    }
4797
4798    /// One incremental decode step: consume `tok` at position state.pos through all
4799    /// trunk layers + head using the caches (hc state carried across the PP boundary
4800    /// by host bounce, one copy per step). Returns the full logits row predicting
4801    /// position state.pos + 1.
4802    pub fn decode_step(&self, tok: u32, state: &mut DecodeState) -> Res<Vec<f32>> {
4803        self.decode_step_impl(tok, state, None)
4804    }
4805
4806    /// Diagnostic twin: returns (logits, named per-layer intermediates).
4807    pub fn decode_step_probe(
4808        &self,
4809        tok: u32,
4810        state: &mut DecodeState,
4811    ) -> Res<(Vec<f32>, Vec<(String, Vec<f32>)>)> {
4812        let mut dump = Vec::new();
4813        let logits = self.decode_step_impl(tok, state, Some(&mut dump))?;
4814        Ok((logits, dump))
4815    }
4816
4817    // ------------------------------------------------------------ lane 8: device path
4818
4819    /// bf16 GEMV with the arena cvt scratch and raw pointers (device decode path,
4820    /// m = 1): cvt_bf16 then the deterministic fixed-tree memra_dsv4_gemv_bf16 —
4821    /// the lane-8 class-II realization of the cuBLASLt m=1 GEMMs (gated).
4822    #[allow(clippy::too_many_arguments)]
4823    fn gemm_dev(
4824        st: &Stage,
4825        x_f32: *const f32,
4826        xb: &mut CudaSlice<u8>,
4827        w: DW,
4828        m: usize,
4829        n: usize,
4830        kdim: usize,
4831        y_ptr: *mut f32,
4832    ) -> Res<()> {
4833        assert_eq!(m, 1, "gemm_dev is the m=1 decode path");
4834        let stream = st.gpu.stream();
4835        unsafe {
4836            ck(
4837                "cvt_bf16 dev",
4838                k::memra_dsv4_cvt_bf16(
4839                    x_f32,
4840                    xb.device_ptr_mut(&stream).0 as *mut c_void,
4841                    kdim as i64,
4842                    sp(&stream),
4843                ),
4844            )?;
4845        }
4846        let xb_ptr = xb.device_ptr(&stream).0 as *const c_void;
4847        Self::gemv_pre_dev(st, xb_ptr, w, n, kdim, y_ptr)
4848    }
4849
4850    /// GEMV from an already-bf16 activation buffer (device decode path, m = 1).
4851    /// Dispatches on the dense-weight realization: bf16 slab, or the iteration-5 FP8
4852    /// pair through the bit-identical twin.
4853    fn gemv_pre_dev(
4854        st: &Stage,
4855        xb_ptr: *const c_void,
4856        w: DW,
4857        n: usize,
4858        kdim: usize,
4859        y_ptr: *mut f32,
4860    ) -> Res<()> {
4861        let stream = st.gpu.stream();
4862        unsafe {
4863            match w {
4864                DW::Bf16(w_ptr) => ck(
4865                    "gemv_bf16 pre dev",
4866                    k::memra_dsv4_gemv_bf16(
4867                        w_ptr,
4868                        xb_ptr,
4869                        y_ptr,
4870                        n as i32,
4871                        kdim as i32,
4872                        sp(&stream),
4873                    ),
4874                )?,
4875                DW::Fp8 {
4876                    codes,
4877                    scales,
4878                    sc_cols,
4879                } => ck(
4880                    "gemv_fp8 pre dev",
4881                    k::memra_dsv4_gemv_fp8(
4882                        codes,
4883                        scales,
4884                        sc_cols,
4885                        xb_ptr,
4886                        y_ptr,
4887                        n as i32,
4888                        kdim as i32,
4889                        sp(&stream),
4890                    ),
4891                )?,
4892            }
4893        }
4894        Ok(())
4895    }
4896
4897    /// hc_pre on the device path: dots + rowsq (unchanged kernels) then Sinkhorn either
4898    /// on the HOST (byte-identity arm — hc_split_sinkhorn verbatim, results uploaded
4899    /// into the arena) or as the single-thread device kernel (realization fork, class
4900    /// gated). Writes ws {mixes, pre, post, comb, y_hc}.
4901    #[allow(clippy::too_many_arguments)]
4902    // ── 0731 re-gate extension rung dispatch (MEMRA_DSV4_DOTS_ARM=f32x): each helper
4903    // picks the f64 kernel (default — the pinned oracle-truth bytes, also the lane-9
4904    // `f32` arm's bytes) or its f32acc twin. DEVICE decode path only; prefill and the
4905    // legacy path never route through these.
4906    #[allow(clippy::too_many_arguments)]
4907    unsafe fn rmsnorm_arm(
4908        &self,
4909        x: *const f32,
4910        w: *const f32,
4911        dst: *mut f32,
4912        rows: i32,
4913        ncols: i32,
4914        eps: f32,
4915        sv: *mut c_void,
4916    ) -> i32 {
4917        unsafe {
4918            if self.chains_f32 {
4919                k::memra_dsv4_rmsnorm_f32acc(x, w, dst, rows, ncols, eps, sv)
4920            } else {
4921                k::memra_dsv4_rmsnorm(x, w, dst, rows, ncols, eps, sv)
4922            }
4923        }
4924    }
4925
4926    unsafe fn headrms_arm(&self, x: *mut f32, rows: i32, d: i32, eps: f32, sv: *mut c_void) -> i32 {
4927        unsafe {
4928            if self.chains_f32 {
4929                k::memra_dsv4_headrms_f32acc(x, rows, d, eps, sv)
4930            } else {
4931                k::memra_dsv4_headrms(x, rows, d, eps, sv)
4932            }
4933        }
4934    }
4935
4936    #[allow(clippy::too_many_arguments)]
4937    unsafe fn rowsq_scale_arm(
4938        &self,
4939        x: *const f32,
4940        mixes: *mut f32,
4941        s: i32,
4942        w: i32,
4943        rows: i32,
4944        eps: f32,
4945        sv: *mut c_void,
4946    ) -> i32 {
4947        unsafe {
4948            if self.chains_f32 {
4949                k::memra_dsv4_rowsq_scale_f32acc(x, mixes, s, w, rows, eps, sv)
4950            } else {
4951                k::memra_dsv4_rowsq_scale(x, mixes, s, w, rows, eps, sv)
4952            }
4953        }
4954    }
4955
4956    #[allow(clippy::too_many_arguments)]
4957    unsafe fn indexer_score_arm(
4958        &self,
4959        q: *const f32,
4960        ckv: *const f32,
4961        w: *const f32,
4962        wscale: f32,
4963        score: *mut f32,
4964        s: i32,
4965        heads: i32,
4966        hd: i32,
4967        nb: i32,
4968        ratio: i32,
4969        lim0: i32,
4970        sv: *mut c_void,
4971    ) -> i32 {
4972        unsafe {
4973            if self.chains_f32 {
4974                k::memra_dsv4_indexer_score_f32acc(
4975                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
4976                )
4977            } else {
4978                k::memra_dsv4_indexer_score(
4979                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
4980                )
4981            }
4982        }
4983    }
4984
4985    /// `den` is the f64 workspace either way; the f32acc twin rides a FLOAT view of the
4986    /// same allocation (K2 writes it, K3 reads it, within the one FFI entry).
4987    #[allow(clippy::too_many_arguments)]
4988    unsafe fn sink_attn_dec_arm(
4989        &self,
4990        q: *const f32,
4991        kv: *const f32,
4992        idxs: *const i32,
4993        sink: *const f32,
4994        scores: *mut f32,
4995        evals: *mut f32,
4996        den: *mut f64,
4997        o: *mut f32,
4998        heads: i32,
4999        hd: i32,
5000        slots: i32,
5001        scale: f32,
5002        sv: *mut c_void,
5003    ) -> i32 {
5004        unsafe {
5005            if self.chains_f32 {
5006                k::memra_dsv4_sink_attn_dec_f32acc(
5007                    q,
5008                    kv,
5009                    idxs,
5010                    sink,
5011                    scores,
5012                    evals,
5013                    den as *mut f32,
5014                    o,
5015                    heads,
5016                    hd,
5017                    slots,
5018                    scale,
5019                    sv,
5020                )
5021            } else {
5022                k::memra_dsv4_sink_attn_dec(
5023                    q, kv, idxs, sink, scores, evals, den, o, heads, hd, slots, scale, sv,
5024                )
5025            }
5026        }
5027    }
5028
5029    fn hc_pre_dev(
5030        &self,
5031        st: &Stage,
5032        h: &CudaSlice<f32>,
5033        fn_w: &CudaSlice<f32>,
5034        base_host: &[f32],
5035        scale_host: &[f32],
5036        base_dev: &CudaSlice<f32>,
5037        scale_dev: &CudaSlice<f32>,
5038        mixes: &mut CudaSlice<f32>,
5039        pre: &mut CudaSlice<f32>,
5040        post: &mut CudaSlice<f32>,
5041        comb: &mut CudaSlice<f32>,
5042        y_hc: &mut CudaSlice<f32>,
5043        hc: usize,
5044        hidden: usize,
5045        iters: u32,
5046        hc_eps: f32,
5047        host_math: bool,
5048    ) -> Res<()> {
5049        let stream = st.gpu.stream();
5050        let w = hc * hidden;
5051        let rows = (2 + hc) * hc;
5052        self.dots_dev(st, h, fn_w, 1, w, rows, mixes)?;
5053        unsafe {
5054            ck(
5055                "rowsq_scale dev",
5056                self.rowsq_scale_arm(
5057                    dpf!(h, &stream),
5058                    dpm!(*mixes, &stream),
5059                    1,
5060                    w as i32,
5061                    rows as i32,
5062                    hc_eps,
5063                    sp(&stream),
5064                ),
5065            )?;
5066        }
5067        if host_math {
5068            let mixes_h = dtoh_f32(&stream, mixes)?;
5069            let (pre_h, post_h, comb_h) =
5070                hc_split_sinkhorn(&mixes_h, 1, hc, scale_host, base_host, iters, hc_eps);
5071            stream.memcpy_htod(&pre_h, pre).map_err(e("htod pre"))?;
5072            stream.memcpy_htod(&post_h, post).map_err(e("htod post"))?;
5073            stream.memcpy_htod(&comb_h, comb).map_err(e("htod comb"))?;
5074        } else {
5075            unsafe {
5076                ck(
5077                    "hc_sinkhorn",
5078                    k::memra_dsv4_hc_sinkhorn(
5079                        dpf!(*mixes, &stream),
5080                        dpf!(scale_dev, &stream),
5081                        dpf!(base_dev, &stream),
5082                        dpm!(*pre, &stream),
5083                        dpm!(*post, &stream),
5084                        dpm!(*comb, &stream),
5085                        hc as i32,
5086                        iters as i32,
5087                        hc_eps,
5088                        sp(&stream),
5089                    ),
5090                )?;
5091            }
5092        }
5093        unsafe {
5094            ck(
5095                "hc_collapse dev",
5096                k::memra_dsv4_hc_collapse(
5097                    dpf!(h, &stream),
5098                    dpf!(*pre, &stream),
5099                    dpm!(*y_hc, &stream),
5100                    1,
5101                    hc as i32,
5102                    hidden as i32,
5103                    sp(&stream),
5104                ),
5105            )?;
5106        }
5107        Ok(())
5108    }
5109
5110    /// Incremental compressor step on the arena (cmp_decode's arithmetic verbatim:
5111    /// same kernels, same D2D moves; rope via the scalar-position launcher — identical
5112    /// kernel body). No allocations.
5113    #[allow(clippy::too_many_arguments)]
5114    fn cmp_decode_dev(
5115        &self,
5116        st: &Stage,
5117        cmp: &CmpDev,
5118        x: &CudaSlice<f32>,
5119        pos: usize,
5120        hidden: usize,
5121        fc_dev: &CudaSlice<f32>,
5122        rd: usize,
5123        eps: f32,
5124        kv_row: &mut CudaSlice<f32>,
5125        sc_row: &mut CudaSlice<f32>,
5126        emit: &mut CudaSlice<f32>,
5127        shift: &mut CudaSlice<f32>,
5128        pend_kv: &mut CudaSlice<f32>,
5129        pend_score: &mut CudaSlice<f32>,
5130        store: &mut CudaSlice<f32>,
5131        row0: usize,
5132        blocks: &mut usize,
5133    ) -> Res<()> {
5134        let stream = st.gpu.stream();
5135        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
5136        self.dots_dev(st, x, &cmp.wkv, 1, hidden, latent, kv_row)?;
5137        self.dots_dev(st, x, &cmp.wgate, 1, hidden, latent, sc_row)?;
5138        let slot = if cmp.overlap {
5139            ratio + pos % ratio
5140        } else {
5141            pos % ratio
5142        };
5143        {
5144            let src = kv_row.slice(0..latent);
5145            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
5146            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
5147            let src = sc_row.slice(0..latent);
5148            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
5149            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
5150        }
5151        if (pos + 1) % ratio != 0 {
5152            return Ok(());
5153        }
5154        let j = pos / ratio;
5155        let nb_launch = if cmp.overlap { 2usize } else { 1 };
5156        let row_off = if cmp.overlap { d } else { 0 };
5157        unsafe {
5158            ck(
5159                "compressor_pool dec",
5160                k::memra_dsv4_compressor_pool(
5161                    dpf!(*pend_kv, &stream),
5162                    dpf!(*pend_score, &stream),
5163                    dpf!(cmp.ape, &stream),
5164                    dpm!(*emit, &stream),
5165                    nb_launch as i32,
5166                    ratio as i32,
5167                    d as i32,
5168                    latent as i32,
5169                    cmp.overlap as i32,
5170                    sp(&stream),
5171                ),
5172            )?;
5173            let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
5174            let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
5175            ck(
5176                "rmsnorm dec cmp",
5177                self.rmsnorm_arm(
5178                    row_c,
5179                    dpf!(cmp.norm, &stream),
5180                    row_m,
5181                    1,
5182                    d as i32,
5183                    eps,
5184                    sp(&stream),
5185                ),
5186            )?;
5187            ck(
5188                "rope_at dec cmp",
5189                k::memra_dsv4_rope_at(
5190                    row_m,
5191                    1,
5192                    d as i32,
5193                    rd as i32,
5194                    dpf!(fc_dev, &stream),
5195                    (j * ratio) as i32,
5196                    0,
5197                    sp(&stream),
5198                ),
5199            )?;
5200            if cmp.rotate {
5201                let scale = (d as f32).powf(-0.5);
5202                ck(
5203                    "hadamard dec cmp",
5204                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
5205                )?;
5206                ck(
5207                    "fp4 dec cmp",
5208                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
5209                )?;
5210            } else {
5211                ck(
5212                    "act_quant dec cmp",
5213                    k::memra_dsv4_act_quant(
5214                        row_m,
5215                        1,
5216                        d as i64,
5217                        (d - rd) as i32,
5218                        64,
5219                        (self.variant == ActQuantVariant::ClampOnly) as i32,
5220                        sp(&stream),
5221                    ),
5222                )?;
5223            }
5224        }
5225        {
5226            let src = emit.slice(row_off..row_off + d);
5227            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
5228            stream
5229                .memcpy_dtod(&src, &mut dst)
5230                .map_err(e("emit store"))?;
5231        }
5232        if cmp.overlap {
5233            {
5234                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
5235                let mut dst = shift.slice_mut(0..ratio * latent);
5236                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift1"))?;
5237            }
5238            {
5239                let src = shift.slice(0..ratio * latent);
5240                let mut dst = pend_kv.slice_mut(0..ratio * latent);
5241                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift2"))?;
5242            }
5243            {
5244                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
5245                let mut dst = shift.slice_mut(0..ratio * latent);
5246                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift3"))?;
5247            }
5248            {
5249                let src = shift.slice(0..ratio * latent);
5250                let mut dst = pend_score.slice_mut(0..ratio * latent);
5251                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift4"))?;
5252            }
5253        }
5254        *blocks = j + 1;
5255        Ok(())
5256    }
5257
5258    /// One trunk block, single-token decode, device path (block_decode's flow on the
5259    /// arena; per-value arithmetic identical under host_math — deviations under device
5260    /// math are the banked Sinkhorn/router realization forks). Input h is ws.h_a
5261    /// (or ws.h_rx right after the boundary); output lands in ws.h_a.
5262    #[allow(clippy::too_many_arguments)]
5263    fn block_decode_dev(
5264        &self,
5265        st: &Stage,
5266        layer: &LayerDev,
5267        cache: &mut LayerCache,
5268        ws: &mut StepWs,
5269        input_rx: bool,
5270        pos: usize,
5271        tok: u32,
5272        host_math: bool,
5273    ) -> Res<()> {
5274        let d = self.model.cfg();
5275        let mc = &self.model.mc;
5276        let hc = d.hc_mult as usize;
5277        let hidden = mc.n_embd as usize;
5278        let heads = mc.n_head as usize;
5279        let hd = d.head_dim as usize;
5280        let rd = d.qk_rope_head_dim as usize;
5281        let q_lora = d.q_lora_rank as usize;
5282        let win = d.sliding_window as usize;
5283        let o_groups = d.o_groups as usize;
5284        let o_lora = d.o_lora_rank as usize;
5285        let eps = mc.rms_eps;
5286        let iters = d.hc_sinkhorn_iters;
5287        let hc_eps = d.hc_eps;
5288        let stream = st.gpu.stream();
5289        let fc_dev: *const f32 = if layer.ratio != 0 {
5290            st.fc_yarn.device_ptr(&stream).0 as *const f32
5291        } else {
5292            st.fc_plain.device_ptr(&stream).0 as *const f32
5293        };
5294        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
5295        let LayerCache {
5296            kvc,
5297            n_blocks,
5298            pend_kv,
5299            pend_score,
5300            ikvc,
5301            i_blocks,
5302            ipend_kv,
5303            ipend_score,
5304        } = cache;
5305
5306        // ---- attention sub-block
5307        {
5308            // split-borrow the arena fields we need for hc_pre
5309            let StepWs {
5310                h_a,
5311                h_rx,
5312                mixes,
5313                pre,
5314                post,
5315                comb,
5316                y_hc,
5317                ..
5318            } = ws;
5319            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5320            self.hc_pre_dev(
5321                st,
5322                h_in,
5323                &layer.hc_attn_fn,
5324                &layer.hc_attn_base,
5325                &layer.hc_attn_scale,
5326                &layer.hc_attn_base_dev,
5327                &layer.hc_attn_scale_dev,
5328                mixes,
5329                pre,
5330                post,
5331                comb,
5332                y_hc,
5333                hc,
5334                hidden,
5335                iters,
5336                hc_eps,
5337                host_math,
5338            )?;
5339        }
5340        unsafe {
5341            ck(
5342                "rmsnorm attn dev",
5343                self.rmsnorm_arm(
5344                    dpf!(ws.y_hc, &stream),
5345                    dpf!(layer.attn_norm, &stream),
5346                    dpm!(ws.x, &stream),
5347                    1,
5348                    hidden as i32,
5349                    eps,
5350                    sp(&stream),
5351                ),
5352            )?;
5353        }
5354
5355        // q path
5356        Self::gemm_dev(
5357            st,
5358            ws.x.device_ptr(&stream).0 as *const f32,
5359            &mut ws.gemm_xb,
5360            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
5361            1,
5362            q_lora,
5363            hidden,
5364            ws.qr.device_ptr_mut(&stream).0 as *mut f32,
5365        )?;
5366        unsafe {
5367            ck(
5368                "rmsnorm q dev",
5369                self.rmsnorm_arm(
5370                    dpf!(ws.qr, &stream),
5371                    dpf!(layer.q_norm, &stream),
5372                    dpm!(ws.qr, &stream),
5373                    1,
5374                    q_lora as i32,
5375                    eps,
5376                    sp(&stream),
5377                ),
5378            )?;
5379            ck(
5380                "cvt qr dev",
5381                k::memra_dsv4_cvt_bf16(
5382                    dpf!(ws.qr, &stream),
5383                    ws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
5384                    q_lora as i64,
5385                    sp(&stream),
5386                ),
5387            )?;
5388        }
5389        Self::gemv_pre_dev(
5390            st,
5391            ws.qr_b.device_ptr(&stream).0 as *const c_void,
5392            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
5393            heads * hd,
5394            q_lora,
5395            ws.q.device_ptr_mut(&stream).0 as *mut f32,
5396        )?;
5397        unsafe {
5398            ck(
5399                "headrms dev",
5400                self.headrms_arm(
5401                    dpm!(ws.q, &stream),
5402                    heads as i32,
5403                    hd as i32,
5404                    eps,
5405                    sp(&stream),
5406                ),
5407            )?;
5408            ck(
5409                "rope_at q dev",
5410                k::memra_dsv4_rope_at(
5411                    dpm!(ws.q, &stream),
5412                    heads as i32,
5413                    hd as i32,
5414                    rd as i32,
5415                    fc_dev,
5416                    pos as i32,
5417                    0,
5418                    sp(&stream),
5419                ),
5420            )?;
5421        }
5422
5423        // shared K==V latent row + window QAT + ring write
5424        Self::gemm_dev(
5425            st,
5426            ws.x.device_ptr(&stream).0 as *const f32,
5427            &mut ws.gemm_xb,
5428            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
5429            1,
5430            hd,
5431            hidden,
5432            ws.kv.device_ptr_mut(&stream).0 as *mut f32,
5433        )?;
5434        unsafe {
5435            ck(
5436                "rmsnorm kv dev",
5437                self.rmsnorm_arm(
5438                    dpf!(ws.kv, &stream),
5439                    dpf!(layer.kv_norm, &stream),
5440                    dpm!(ws.kv, &stream),
5441                    1,
5442                    hd as i32,
5443                    eps,
5444                    sp(&stream),
5445                ),
5446            )?;
5447            ck(
5448                "rope_at kv dev",
5449                k::memra_dsv4_rope_at(
5450                    dpm!(ws.kv, &stream),
5451                    1,
5452                    hd as i32,
5453                    rd as i32,
5454                    fc_dev,
5455                    pos as i32,
5456                    0,
5457                    sp(&stream),
5458                ),
5459            )?;
5460            ck(
5461                "act_quant kv dev",
5462                k::memra_dsv4_act_quant(
5463                    dpm!(ws.kv, &stream),
5464                    1,
5465                    hd as i64,
5466                    (hd - rd) as i32,
5467                    64,
5468                    clamp_only,
5469                    sp(&stream),
5470                ),
5471            )?;
5472        }
5473        {
5474            let slot = pos % win;
5475            let src = ws.kv.slice(0..hd);
5476            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
5477            stream
5478                .memcpy_dtod(&src, &mut dst)
5479                .map_err(e("ring write"))?;
5480        }
5481
5482        // index list: window part on device (block_decode's builder verbatim)
5483        let mut slots = win;
5484        if layer.ratio != 0 {
5485            if let Some(ix) = &layer.idx {
5486                // indexer q
5487                Self::gemv_pre_dev(
5488                    st,
5489                    ws.qr_b.device_ptr(&stream).0 as *const c_void,
5490                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
5491                    ix.heads * ix.hd,
5492                    q_lora,
5493                    ws.qi.device_ptr_mut(&stream).0 as *mut f32,
5494                )?;
5495                unsafe {
5496                    ck(
5497                        "rope_at qi dev",
5498                        k::memra_dsv4_rope_at(
5499                            dpm!(ws.qi, &stream),
5500                            ix.heads as i32,
5501                            ix.hd as i32,
5502                            rd as i32,
5503                            fc_dev,
5504                            pos as i32,
5505                            0,
5506                            sp(&stream),
5507                        ),
5508                    )?;
5509                    let scale = (ix.hd as f32).powf(-0.5);
5510                    ck(
5511                        "hadamard qi dev",
5512                        k::memra_dsv4_hadamard(
5513                            dpm!(ws.qi, &stream),
5514                            ix.heads as i32,
5515                            ix.hd as i32,
5516                            scale,
5517                            sp(&stream),
5518                        ),
5519                    )?;
5520                    ck(
5521                        "fp4 qi dev",
5522                        k::memra_dsv4_fp4_act_quant(
5523                            dpm!(ws.qi, &stream),
5524                            ix.heads as i32,
5525                            ix.hd as i64,
5526                            ix.hd as i32,
5527                            sp(&stream),
5528                        ),
5529                    )?;
5530                }
5531                // indexer compressor BEFORE scoring (M:415)
5532                {
5533                    let StepWs {
5534                        x,
5535                        cmp_kv_row,
5536                        cmp_sc_row,
5537                        cmp_emit,
5538                        cmp_shift,
5539                        ..
5540                    } = ws;
5541                    self.cmp_decode_dev(
5542                        st,
5543                        &ix.cmp,
5544                        x,
5545                        pos,
5546                        hidden,
5547                        if layer.ratio != 0 {
5548                            &st.fc_yarn
5549                        } else {
5550                            &st.fc_plain
5551                        },
5552                        rd,
5553                        eps,
5554                        cmp_kv_row,
5555                        cmp_sc_row,
5556                        cmp_emit,
5557                        cmp_shift,
5558                        ipend_kv.as_mut().expect("ipend"),
5559                        ipend_score.as_mut().expect("ipend"),
5560                        ikvc.as_mut().expect("ikvc"),
5561                        0,
5562                        i_blocks,
5563                    )?;
5564                }
5565                let nb = *i_blocks;
5566                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
5567                // window part (fills [0, win)); fine tail written by the top-k below
5568                unsafe {
5569                    ck(
5570                        "build_idx win",
5571                        k::memra_dsv4_build_idx(
5572                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5573                            pos as i32,
5574                            win as i32,
5575                            -1,
5576                            win as i32,
5577                            sp(&stream),
5578                        ),
5579                    )?;
5580                }
5581                if nb > 0 {
5582                    Self::gemm_dev(
5583                        st,
5584                        ws.x.device_ptr(&stream).0 as *const f32,
5585                        &mut ws.gemm_xb,
5586                        dwsel(
5587                            self.dense_fp8,
5588                            &stream,
5589                            &ix.weights_proj,
5590                            &ix.weights_proj_fp8,
5591                        ),
5592                        1,
5593                        ix.heads,
5594                        hidden,
5595                        ws.wproj.device_ptr_mut(&stream).0 as *mut f32,
5596                    )?;
5597                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
5598                    unsafe {
5599                        ck(
5600                            "indexer_score dev",
5601                            self.indexer_score_arm(
5602                                dpf!(ws.qi, &stream),
5603                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
5604                                dpf!(ws.wproj, &stream),
5605                                wscale,
5606                                dpm!(ws.score, &stream),
5607                                1,
5608                                ix.heads as i32,
5609                                ix.hd as i32,
5610                                nb as i32,
5611                                layer.ratio as i32,
5612                                nb as i32,
5613                                sp(&stream),
5614                            ),
5615                        )?;
5616                    }
5617                    let kk = ix.topk.min(nb);
5618                    if host_math {
5619                        // byte-identity arm: the legacy host sort verbatim, uploaded
5620                        // into the arena index tail
5621                        let score_h = {
5622                            let view = ws.score.slice(0..nb);
5623                            let mut v = vec![0f32; nb];
5624                            stream
5625                                .memcpy_dtoh(&view, &mut v[..])
5626                                .map_err(e("dtoh sc"))?;
5627                            stream.synchronize().map_err(e("sync sc"))?;
5628                            v
5629                        };
5630                        let mut order: Vec<usize> = (0..nb).collect();
5631                        order.sort_by(|&a, &b| {
5632                            score_h[b]
5633                                .partial_cmp(&score_h[a])
5634                                .unwrap_or(std::cmp::Ordering::Equal)
5635                                .then(a.cmp(&b))
5636                        });
5637                        let cidx: Vec<i32> = order
5638                            .into_iter()
5639                            .take(kk)
5640                            .map(|j| (j + win) as i32)
5641                            .collect();
5642                        let mut dst = ws.idx.slice_mut(win..win + kk);
5643                        stream.memcpy_htod(&cidx, &mut dst).map_err(e("htod idx"))?;
5644                    } else {
5645                        unsafe {
5646                            let idx_tail =
5647                                (ws.idx.device_ptr_mut(&stream).0 as usize + win * 4) as *mut i32;
5648                            ck(
5649                                "topk_idx dev",
5650                                k::memra_dsv4_topk_idx(
5651                                    dpf!(ws.score, &stream),
5652                                    nb as i32,
5653                                    kk as i32,
5654                                    win as i32,
5655                                    idx_tail,
5656                                    sp(&stream),
5657                                ),
5658                            )?;
5659                        }
5660                    }
5661                    slots = win + kk;
5662                }
5663            } else {
5664                // coarse: all blocks incl. the one emitted this step — but the ATTENTION
5665                // compressor below is what emits it, so the count is (pos+1)/ratio
5666                let nb = (pos + 1) / layer.ratio;
5667                unsafe {
5668                    ck(
5669                        "build_idx coarse",
5670                        k::memra_dsv4_build_idx(
5671                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5672                            pos as i32,
5673                            win as i32,
5674                            nb as i32,
5675                            (win + nb) as i32,
5676                            sp(&stream),
5677                        ),
5678                    )?;
5679                }
5680                slots = win + nb;
5681            }
5682            // attention compressor before sparse_attn (M:531)
5683            {
5684                let StepWs {
5685                    x,
5686                    cmp_kv_row,
5687                    cmp_sc_row,
5688                    cmp_emit,
5689                    cmp_shift,
5690                    ..
5691                } = ws;
5692                self.cmp_decode_dev(
5693                    st,
5694                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
5695                    x,
5696                    pos,
5697                    hidden,
5698                    &st.fc_yarn,
5699                    rd,
5700                    eps,
5701                    cmp_kv_row,
5702                    cmp_sc_row,
5703                    cmp_emit,
5704                    cmp_shift,
5705                    pend_kv.as_mut().expect("pend"),
5706                    pend_score.as_mut().expect("pend"),
5707                    kvc,
5708                    win,
5709                    n_blocks,
5710                )?;
5711            }
5712            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
5713        } else {
5714            // window-only layer: fixed-width window part with -1 pads (legacy widths)
5715            unsafe {
5716                ck(
5717                    "build_idx window-only",
5718                    k::memra_dsv4_build_idx(
5719                        ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5720                        pos as i32,
5721                        win as i32,
5722                        -1,
5723                        win as i32,
5724                        sp(&stream),
5725                    ),
5726                )?;
5727            }
5728        }
5729
5730        // sparse sink attention (lane-8 three-kernel split, bit-exact — see the .cu
5731        // notes) + query-position de-rotation
5732        let scale = (hd as f64).powf(-0.5) as f32;
5733        unsafe {
5734            ck(
5735                "sink_attn_dec dev",
5736                self.sink_attn_dec_arm(
5737                    dpf!(ws.q, &stream),
5738                    dpf!(kvc, &stream),
5739                    ws.idx.device_ptr(&stream).0 as *const i32,
5740                    dpf!(layer.sink, &stream),
5741                    dpm!(ws.sink_scores, &stream),
5742                    dpm!(ws.sink_evals, &stream),
5743                    ws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
5744                    dpm!(ws.o, &stream),
5745                    heads as i32,
5746                    hd as i32,
5747                    slots as i32,
5748                    scale,
5749                    sp(&stream),
5750                ),
5751            )?;
5752            ck(
5753                "rope_at o inv dev",
5754                k::memra_dsv4_rope_at(
5755                    dpm!(ws.o, &stream),
5756                    heads as i32,
5757                    hd as i32,
5758                    rd as i32,
5759                    fc_dev,
5760                    pos as i32,
5761                    1,
5762                    sp(&stream),
5763                ),
5764            )?;
5765        }
5766
5767        // grouped wo: cvt o ONCE (elementwise — bit-equal to the legacy per-group cvt),
5768        // then per-group offset GEMMs straight into og slices (take/place_cols are pure
5769        // offsets at s=1), then wo_b.
5770        let gw = heads / o_groups * hd;
5771        unsafe {
5772            ck(
5773                "cvt o dev",
5774                k::memra_dsv4_cvt_bf16(
5775                    dpf!(ws.o, &stream),
5776                    ws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
5777                    (heads * hd) as i64,
5778                    sp(&stream),
5779                ),
5780            )?;
5781        }
5782        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
5783        for g in 0..o_groups {
5784            Self::gemv_pre_dev(
5785                st,
5786                (ws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
5787                wo_a_dw.offset_rows(g * o_lora, gw),
5788                o_lora,
5789                gw,
5790                (ws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
5791            )?;
5792        }
5793        Self::gemm_dev(
5794            st,
5795            ws.og.device_ptr(&stream).0 as *const f32,
5796            &mut ws.gemm_xb,
5797            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
5798            1,
5799            hidden,
5800            o_groups * o_lora,
5801            ws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
5802        )?;
5803
5804        // hc_post (attention): h2 = ws.h_b from residual h_in
5805        {
5806            let StepWs {
5807                h_a,
5808                h_b,
5809                h_rx,
5810                attn_out,
5811                post,
5812                comb,
5813                ..
5814            } = ws;
5815            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5816            unsafe {
5817                ck(
5818                    "hc_post attn dev",
5819                    k::memra_dsv4_hc_post(
5820                        dpf!(attn_out, &stream),
5821                        dpf!(h_in, &stream),
5822                        dpf!(post, &stream),
5823                        dpf!(comb, &stream),
5824                        dpm!(*h_b, &stream),
5825                        1,
5826                        hc as i32,
5827                        hidden as i32,
5828                        sp(&stream),
5829                    ),
5830                )?;
5831            }
5832        }
5833
5834        // ---- ffn sub-block (input h2 = ws.h_b, output h3 = ws.h_a)
5835        {
5836            let StepWs {
5837                h_b,
5838                mixes,
5839                pre,
5840                post,
5841                comb,
5842                y_hc,
5843                ..
5844            } = ws;
5845            self.hc_pre_dev(
5846                st,
5847                h_b,
5848                &layer.hc_ffn_fn,
5849                &layer.hc_ffn_base,
5850                &layer.hc_ffn_scale,
5851                &layer.hc_ffn_base_dev,
5852                &layer.hc_ffn_scale_dev,
5853                mixes,
5854                pre,
5855                post,
5856                comb,
5857                y_hc,
5858                hc,
5859                hidden,
5860                iters,
5861                hc_eps,
5862                host_math,
5863            )?;
5864        }
5865        unsafe {
5866            ck(
5867                "rmsnorm ffn dev",
5868                self.rmsnorm_arm(
5869                    dpf!(ws.y_hc, &stream),
5870                    dpf!(layer.ffn_norm, &stream),
5871                    dpm!(ws.xf, &stream),
5872                    1,
5873                    hidden as i32,
5874                    eps,
5875                    sp(&stream),
5876                ),
5877            )?;
5878        }
5879        self.moe_forward_dev(st, layer, ws, tok, host_math)?;
5880        {
5881            let StepWs {
5882                h_a,
5883                h_b,
5884                y,
5885                post,
5886                comb,
5887                ..
5888            } = ws;
5889            unsafe {
5890                ck(
5891                    "hc_post ffn dev",
5892                    k::memra_dsv4_hc_post(
5893                        dpf!(y, &stream),
5894                        dpf!(h_b, &stream),
5895                        dpf!(post, &stream),
5896                        dpf!(comb, &stream),
5897                        dpm!(*h_a, &stream),
5898                        1,
5899                        hc as i32,
5900                        hidden as i32,
5901                        sp(&stream),
5902                    ),
5903                )?;
5904            }
5905        }
5906        Ok(())
5907    }
5908
5909    /// MoE on the device path (native fp4 arm only, asserted at load): routing via the
5910    /// device kernel (or route_host under host_math), then ONE launch per projection
5911    /// over all active-expert slots (indirect fused dispatch — attack #3 at s=1),
5912    /// combine in ascending-expert-id order (the legacy scatter sequence), shared
5913    /// expert on the lane-4 bf16 rung. Writes ws.y.
5914    fn moe_forward_dev(
5915        &self,
5916        st: &Stage,
5917        layer: &LayerDev,
5918        ws: &mut StepWs,
5919        tok: u32,
5920        host_math: bool,
5921    ) -> Res<()> {
5922        let mc = &self.model.mc;
5923        let d = self.model.cfg();
5924        let moe = mc.moe.as_ref().expect("moe");
5925        let hidden = mc.n_embd as usize;
5926        let ne = moe.expert_count as usize;
5927        let topk = moe.expert_used_count as usize;
5928        let inter = moe.expert_ff_length as usize;
5929        let limit = d.swiglu_limit;
5930        let stream = st.gpu.stream();
5931        let kind = match layer.expert_kind {
5932            ExpertKind::Nvfp4 => 0i32,
5933            ExpertKind::Mxfp4 => 1i32,
5934        };
5935        let wstride = (inter * hidden / 2) as i64;
5936        let sstride = match layer.expert_kind {
5937            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
5938            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
5939        };
5940
5941        self.dots_dev(st, &ws.xf, &layer.gate_w, 1, hidden, ne, &mut ws.raw)?;
5942        if host_math {
5943            let raw_h = dtoh_f32(&stream, &ws.raw)?;
5944            let (indices, weights) =
5945                Self::route_host(layer, &raw_h, &[tok], 1, ne, topk, d.routed_scaling_factor);
5946            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
5947            let mut order: Vec<i32> = (0..topk as i32).collect();
5948            order.sort_by_key(|&s| indices[s as usize]);
5949            stream
5950                .memcpy_htod(&sel, &mut ws.sel)
5951                .map_err(e("htod sel"))?;
5952            stream
5953                .memcpy_htod(&weights, &mut ws.selw)
5954                .map_err(e("htod selw"))?;
5955            stream
5956                .memcpy_htod(&order, &mut ws.order)
5957                .map_err(e("htod order"))?;
5958        } else {
5959            unsafe {
5960                ck(
5961                    "route dev",
5962                    k::memra_dsv4_route(
5963                        dpf!(ws.raw, &stream),
5964                        layer
5965                            .gate_bias_dev
5966                            .as_ref()
5967                            .map(|b| b.device_ptr(&stream).0 as *const f32)
5968                            .unwrap_or(std::ptr::null()),
5969                        layer
5970                            .tid2eid_dev
5971                            .as_ref()
5972                            .map(|t| t.device_ptr(&stream).0 as *const i32)
5973                            .unwrap_or(std::ptr::null()),
5974                        ws.tok.device_ptr(&stream).0 as *const i32,
5975                        ne as i32,
5976                        topk as i32,
5977                        d.routed_scaling_factor,
5978                        ws.sel.device_ptr_mut(&stream).0 as *mut i32,
5979                        ws.selw.device_ptr_mut(&stream).0 as *mut f32,
5980                        ws.order.device_ptr_mut(&stream).0 as *mut i32,
5981                        sp(&stream),
5982                    ),
5983                )?;
5984            }
5985        }
5986
5987        unsafe {
5988            ck(
5989                "act_quant_fp8 x dev",
5990                k::memra_dsv4_act_quant_fp8(
5991                    dpf!(ws.xf, &stream),
5992                    ws.xq.device_ptr_mut(&stream).0 as *mut c_void,
5993                    dpm!(ws.xs, &stream),
5994                    1,
5995                    hidden as i32,
5996                    sp(&stream),
5997                ),
5998            )?;
5999            for (proj, dst) in [(0i32, &mut ws.g1), (2i32, &mut ws.g3)] {
6000                ck(
6001                    "fp4_gemm_sel w1/w3",
6002                    k::memra_dsv4_fp4_gemm_sel(
6003                        dp!(ws.xq, &stream),
6004                        dpf!(ws.xs, &stream),
6005                        dp!(layer.experts_w, &stream),
6006                        dp!(layer.experts_sc, &stream),
6007                        dpf!(layer.experts_s2_dev, &stream),
6008                        ws.sel.device_ptr(&stream).0 as *const i32,
6009                        proj,
6010                        0,
6011                        kind,
6012                        dpm!(*dst, &stream),
6013                        topk as i32,
6014                        inter as i32,
6015                        hidden as i32,
6016                        wstride,
6017                        sstride,
6018                        sp(&stream),
6019                    ),
6020                )?;
6021            }
6022            ck(
6023                "swiglu dev",
6024                k::memra_dsv4_swiglu(
6025                    dpf!(ws.g1, &stream),
6026                    dpf!(ws.g3, &stream),
6027                    dpm!(ws.hbuf, &stream),
6028                    topk as i32,
6029                    inter as i32,
6030                    limit,
6031                    ws.selw.device_ptr(&stream).0 as *const f32,
6032                    sp(&stream),
6033                ),
6034            )?;
6035            ck(
6036                "act_quant_fp8 h dev",
6037                k::memra_dsv4_act_quant_fp8(
6038                    dpf!(ws.hbuf, &stream),
6039                    ws.hq.device_ptr_mut(&stream).0 as *mut c_void,
6040                    dpm!(ws.hs, &stream),
6041                    topk as i32,
6042                    inter as i32,
6043                    sp(&stream),
6044                ),
6045            )?;
6046            ck(
6047                "fp4_gemm_sel w2",
6048                k::memra_dsv4_fp4_gemm_sel(
6049                    dp!(ws.hq, &stream),
6050                    dpf!(ws.hs, &stream),
6051                    dp!(layer.experts_w, &stream),
6052                    dp!(layer.experts_sc, &stream),
6053                    dpf!(layer.experts_s2_dev, &stream),
6054                    ws.sel.device_ptr(&stream).0 as *const i32,
6055                    1,
6056                    1,
6057                    kind,
6058                    dpm!(ws.contrib, &stream),
6059                    topk as i32,
6060                    hidden as i32,
6061                    inter as i32,
6062                    wstride,
6063                    sstride,
6064                    sp(&stream),
6065                ),
6066            )?;
6067            ck(
6068                "combine dev",
6069                k::memra_dsv4_combine_rows(
6070                    dpf!(ws.contrib, &stream),
6071                    ws.order.device_ptr(&stream).0 as *const i32,
6072                    topk as i32,
6073                    dpm!(ws.y, &stream),
6074                    hidden as i64,
6075                    sp(&stream),
6076                ),
6077            )?;
6078            // shared expert (lane-4 bf16 rung — the lane-7 FP8-linear decision)
6079            ck(
6080                "cvt xb dev",
6081                k::memra_dsv4_cvt_bf16(
6082                    dpf!(ws.xf, &stream),
6083                    ws.xb.device_ptr_mut(&stream).0 as *mut c_void,
6084                    hidden as i64,
6085                    sp(&stream),
6086                ),
6087            )?;
6088        }
6089        let sh_inter = ws.sg1.len();
6090        Self::gemv_pre_dev(
6091            st,
6092            ws.xb.device_ptr(&stream).0 as *const c_void,
6093            dwsel(
6094                self.dense_fp8,
6095                &stream,
6096                &layer.shared_w[0],
6097                &layer.shared_fp8[0],
6098            ),
6099            sh_inter,
6100            hidden,
6101            ws.sg1.device_ptr_mut(&stream).0 as *mut f32,
6102        )?;
6103        Self::gemv_pre_dev(
6104            st,
6105            ws.xb.device_ptr(&stream).0 as *const c_void,
6106            dwsel(
6107                self.dense_fp8,
6108                &stream,
6109                &layer.shared_w[2],
6110                &layer.shared_fp8[2],
6111            ),
6112            sh_inter,
6113            hidden,
6114            ws.sg3.device_ptr_mut(&stream).0 as *mut f32,
6115        )?;
6116        unsafe {
6117            ck(
6118                "swiglu sh dev",
6119                k::memra_dsv4_swiglu(
6120                    dpf!(ws.sg1, &stream),
6121                    dpf!(ws.sg3, &stream),
6122                    dpm!(ws.shbuf, &stream),
6123                    1,
6124                    sh_inter as i32,
6125                    limit,
6126                    std::ptr::null(),
6127                    sp(&stream),
6128                ),
6129            )?;
6130            ck(
6131                "cvt sh dev",
6132                k::memra_dsv4_cvt_bf16(
6133                    dpf!(ws.shbuf, &stream),
6134                    ws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
6135                    sh_inter as i64,
6136                    sp(&stream),
6137                ),
6138            )?;
6139        }
6140        Self::gemv_pre_dev(
6141            st,
6142            ws.shb16.device_ptr(&stream).0 as *const c_void,
6143            dwsel(
6144                self.dense_fp8,
6145                &stream,
6146                &layer.shared_w[1],
6147                &layer.shared_fp8[1],
6148            ),
6149            hidden,
6150            sh_inter,
6151            ws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
6152        )?;
6153        unsafe {
6154            ck(
6155                "add shared dev",
6156                k::memra_dsv4_add_inplace(
6157                    dpm!(ws.y, &stream),
6158                    dpf!(ws.sh_out, &stream),
6159                    hidden as i64,
6160                    sp(&stream),
6161                ),
6162            )?;
6163        }
6164        Ok(())
6165    }
6166
6167    /// Head on the device path: hc_head gate + collapse + trunk norm + vocab dots into
6168    /// ws.logits (dtoh'd by the caller when wanted). head_logits_row's arithmetic with
6169    /// the host sigmoid either kept (host_math) or run as the tiny gate kernel.
6170    fn head_logits_dev(&self, ws: &mut StepWs, host_math: bool) -> Res<()> {
6171        let d = self.model.cfg();
6172        let mc = &self.model.mc;
6173        let hc = d.hc_mult as usize;
6174        let hidden = mc.n_embd as usize;
6175        let eps = mc.rms_eps;
6176        let last = self.stages.len() - 1;
6177        let st = &self.stages[last];
6178        let stream = st.gpu.stream();
6179        let w = hc * hidden;
6180        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
6181        let norm = st.trunk_norm.as_ref().expect("trunk norm");
6182        self.dots_dev(st, &ws.h_a, fn_w, 1, w, hc, &mut ws.head_mixes)?;
6183        unsafe {
6184            ck(
6185                "rowsq head dev",
6186                self.rowsq_scale_arm(
6187                    dpf!(ws.h_a, &stream),
6188                    dpm!(ws.head_mixes, &stream),
6189                    1,
6190                    w as i32,
6191                    hc as i32,
6192                    eps,
6193                    sp(&stream),
6194                ),
6195            )?;
6196        }
6197        if host_math {
6198            let mut mixes_h = dtoh_f32(&stream, &ws.head_mixes)?;
6199            for c in 0..hc {
6200                let m = mixes_h[c];
6201                mixes_h[c] =
6202                    sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
6203            }
6204            stream
6205                .memcpy_htod(&mixes_h, &mut ws.head_pre)
6206                .map_err(e("htod head pre"))?;
6207        } else {
6208            unsafe {
6209                ck(
6210                    "hc_head_pre dev",
6211                    k::memra_dsv4_hc_head_pre(
6212                        dpf!(ws.head_mixes, &stream),
6213                        st.hc_head_scale_dev
6214                            .as_ref()
6215                            .expect("head scale dev")
6216                            .device_ptr(&stream)
6217                            .0 as *const f32,
6218                        st.hc_head_base_dev
6219                            .as_ref()
6220                            .expect("head base dev")
6221                            .device_ptr(&stream)
6222                            .0 as *const f32,
6223                        dpm!(ws.head_pre, &stream),
6224                        hc as i32,
6225                        d.hc_eps,
6226                        sp(&stream),
6227                    ),
6228                )?;
6229            }
6230        }
6231        unsafe {
6232            ck(
6233                "hc_collapse head dev",
6234                k::memra_dsv4_hc_collapse(
6235                    dpf!(ws.h_a, &stream),
6236                    dpf!(ws.head_pre, &stream),
6237                    dpm!(ws.collapsed, &stream),
6238                    1,
6239                    hc as i32,
6240                    hidden as i32,
6241                    sp(&stream),
6242                ),
6243            )?;
6244            ck(
6245                "rmsnorm head dev",
6246                self.rmsnorm_arm(
6247                    dpf!(ws.collapsed, &stream),
6248                    dpf!(norm, &stream),
6249                    dpm!(ws.collapsed, &stream),
6250                    1,
6251                    hidden as i32,
6252                    eps,
6253                    sp(&stream),
6254                ),
6255            )?;
6256            let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
6257            if self.dots_f32 {
6258                ck(
6259                    "head dots f32acc dev",
6260                    k::memra_dsv4_dots_f32acc(
6261                        dpf!(ws.collapsed, &stream),
6262                        head_ptr,
6263                        1,
6264                        dpm!(ws.logits, &stream),
6265                        1,
6266                        hidden as i32,
6267                        ws.logits.len() as i32,
6268                        sp(&stream),
6269                    ),
6270                )?;
6271            } else {
6272                ck(
6273                    "head dots dev",
6274                    k::memra_dsv4_dots_f32(
6275                        dpf!(ws.collapsed, &stream),
6276                        head_ptr,
6277                        1,
6278                        dpm!(ws.logits, &stream),
6279                        1,
6280                        hidden as i32,
6281                        ws.logits.len() as i32,
6282                        sp(&stream),
6283                    ),
6284                )?;
6285            }
6286        }
6287        Ok(())
6288    }
6289
6290    /// One device-path decode step. `want_logits` = dtoh the full row (the gates'
6291    /// contract); otherwise the greedy token comes back through the device argmax
6292    /// (4-byte D2H). Exactly one boundary peer copy per crossed stage boundary.
6293    fn decode_step_fast(
6294        &self,
6295        tok: u32,
6296        state: &mut DecodeState,
6297        want_logits: bool,
6298        host_math: bool,
6299    ) -> Res<(Option<Vec<f32>>, u32)> {
6300        self.decode_step_fast_tap(tok, state, want_logits, host_math, None)
6301    }
6302
6303    /// [`Self::decode_step_fast`] with the iteration-3 DSpark trunk tap: when `taps`
6304    /// is Some((buffer, base)), the hc-mean of the post-block hc state at each drafter
6305    /// target layer (40/41/42) is written at buffer[base + k*hidden ..] (concat in
6306    /// target order, M:917-925) — a pure capture; no kernel computes anything
6307    /// differently.
6308    fn decode_step_fast_tap(
6309        &self,
6310        tok: u32,
6311        state: &mut DecodeState,
6312        want_logits: bool,
6313        host_math: bool,
6314        mut taps: Option<(&mut CudaSlice<f32>, usize)>,
6315    ) -> Res<(Option<Vec<f32>>, u32)> {
6316        let mc = &self.model.mc;
6317        let d = self.model.cfg();
6318        let pos = state.pos;
6319        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6320        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6321        let hidden = mc.n_embd as usize;
6322        let hc = d.hc_mult as usize;
6323        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6324        let ws_all = state.ws.as_mut().expect("device path needs StepWs");
6325
6326        // stage 0: token -> embed -> hc state
6327        let st0 = &self.stages[0];
6328        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6329        let stream0 = st0.gpu.stream();
6330        {
6331            let ws0 = &mut ws_all[0];
6332            stream0
6333                .memcpy_htod(&[tok as i32], &mut ws0.tok)
6334                .map_err(e("htod tok"))?;
6335            unsafe {
6336                ck(
6337                    "embed_rows dev",
6338                    k::memra_dsv4_embed_rows(
6339                        st0.embed
6340                            .as_ref()
6341                            .expect("embed on stage 0")
6342                            .device_ptr(&stream0)
6343                            .0 as *const c_void,
6344                        ws0.tok.device_ptr(&stream0).0 as *const i32,
6345                        dpm!(ws0.emb, &stream0),
6346                        1,
6347                        hidden as i32,
6348                        sp(&stream0),
6349                    ),
6350                )?;
6351                ck(
6352                    "repeat_hc dev",
6353                    k::memra_dsv4_repeat_hc(
6354                        dpf!(ws0.emb, &stream0),
6355                        dpm!(ws0.h_a, &stream0),
6356                        1,
6357                        hc as i32,
6358                        hidden as i32,
6359                        sp(&stream0),
6360                    ),
6361                )?;
6362            }
6363        }
6364
6365        let mut cur_stage = 0usize;
6366        let mut input_rx = false;
6367        for il in 0..n_trunk {
6368            let stage = self.layer_stage[il as usize];
6369            if stage != cur_stage {
6370                // boundary: peer-copy h (TX stream) + event; tok for the hash layers
6371                // never crosses (they live on stage 0)
6372                let bytes = hc * hidden * std::mem::size_of::<f32>();
6373                let src_stream = self.stages[cur_stage].gpu.stream();
6374                let dst_stream = self.stages[stage].gpu.stream();
6375                let (ws_src, ws_dst) = ws_all.split_at_mut(stage);
6376                let src_ws = &ws_src[cur_stage];
6377                let dst_ws = &mut ws_dst[0];
6378                self.stages[cur_stage]
6379                    .gpu
6380                    .ctx
6381                    .bind_to_thread()
6382                    .map_err(e("bind tx"))?;
6383                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
6384                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
6385                unsafe {
6386                    cudarc::driver::result::memcpy_peer_async(
6387                        self.stages[stage].gpu.ctx.cu_ctx(),
6388                        dp_,
6389                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
6390                        sp_,
6391                        bytes,
6392                        src_stream.cu_stream(),
6393                    )
6394                    .map_err(e("peer copy h"))?;
6395                }
6396                let bnd = stage - 1;
6397                self.boundary_ev[bnd]
6398                    .record(&src_stream)
6399                    .map_err(e("ev record"))?;
6400                dst_stream
6401                    .wait(&self.boundary_ev[bnd])
6402                    .map_err(e("ev wait"))?;
6403                self.stages[stage]
6404                    .gpu
6405                    .ctx
6406                    .bind_to_thread()
6407                    .map_err(e("bind rx"))?;
6408                cur_stage = stage;
6409                input_rx = true;
6410            }
6411            let st = &self.stages[stage];
6412            let lidx = st
6413                .layers
6414                .iter()
6415                .position(|l| l.il == il)
6416                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6417            self.block_decode_dev(
6418                st,
6419                &st.layers[lidx],
6420                &mut state.caches[il as usize],
6421                &mut ws_all[stage],
6422                input_rx,
6423                pos,
6424                tok,
6425                host_math,
6426            )?;
6427            input_rx = false;
6428            // iteration-3 DSpark tap (capture-only): hc-mean of this layer's output
6429            // hc state into the tap row at the target's concat offset.
6430            if let Some((t, base)) = taps.as_mut() {
6431                if let Some(ds) = &self.dspark {
6432                    if let Some(k) = ds.targets.iter().position(|&tl| tl == il as usize) {
6433                        let stream = self.stages[stage].gpu.stream();
6434                        let hidden_i = hidden as i32;
6435                        unsafe {
6436                            ck(
6437                                "hc_mean tap dev",
6438                                k::memra_dsv4_hc_mean(
6439                                    dpf!(ws_all[stage].h_a, &stream),
6440                                    (t.device_ptr_mut(&stream).0 as usize
6441                                        + (*base + k * hidden) * 4)
6442                                        as *mut f32,
6443                                    1,
6444                                    hc as i32,
6445                                    hidden_i,
6446                                    sp(&stream),
6447                                ),
6448                            )?;
6449                        }
6450                    }
6451                }
6452            }
6453        }
6454
6455        let last = self.stages.len() - 1;
6456        assert_eq!(cur_stage, last, "device path expects the head stage last");
6457        self.head_logits_dev(&mut ws_all[last], host_math)?;
6458        let stream_last = self.stages[last].gpu.stream();
6459        state.pos += 1;
6460        if want_logits {
6461            let logits = dtoh_f32(&stream_last, &ws_all[last].logits)?;
6462            let mut best = 0usize;
6463            for i in 1..logits.len() {
6464                if logits[i] > logits[best] {
6465                    best = i;
6466                }
6467            }
6468            Ok((Some(logits), best as u32))
6469        } else {
6470            unsafe {
6471                ck(
6472                    "argmax dev",
6473                    k::memra_dsv4_argmax(
6474                        dpf!(ws_all[last].logits, &stream_last),
6475                        ws_all[last].logits.len() as i64,
6476                        ws_all[last].argmax.device_ptr_mut(&stream_last).0 as *mut i32,
6477                        sp(&stream_last),
6478                    ),
6479                )?;
6480            }
6481            let mut out = [0i32; 1];
6482            stream_last
6483                .memcpy_dtoh(&ws_all[last].argmax, &mut out[..])
6484                .map_err(e("dtoh argmax"))?;
6485            stream_last.synchronize().map_err(e("sync argmax"))?;
6486            Ok((None, out[0] as u32))
6487        }
6488    }
6489
6490    /// Greedy decode step (bench serving shape): returns ONLY the next token; on the
6491    /// device path the argmax runs on-device and 4 bytes cross back. Legacy path
6492    /// falls back to the full-logits step + host argmax (same value by the argmax
6493    /// tie-rule equivalence).
6494    pub fn decode_step_greedy(&self, tok: u32, state: &mut DecodeState) -> Res<u32> {
6495        match self.decode_path {
6496            DecodePath::Legacy => {
6497                let logits = self.decode_step_impl(tok, state, None)?;
6498                let mut best = 0usize;
6499                for i in 1..logits.len() {
6500                    if logits[i] > logits[best] {
6501                        best = i;
6502                    }
6503                }
6504                Ok(best as u32)
6505            }
6506            DecodePath::Device { host_math } => {
6507                Ok(self.decode_step_fast(tok, state, false, host_math)?.1)
6508            }
6509        }
6510    }
6511
6512    fn decode_step_impl(
6513        &self,
6514        tok: u32,
6515        state: &mut DecodeState,
6516        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
6517    ) -> Res<Vec<f32>> {
6518        if let DecodePath::Device { host_math } = self.decode_path {
6519            assert!(
6520                dump.is_none(),
6521                "decode_step_probe is a legacy-path diagnostic (set MEMRA_DSV4_DECODE_PATH=legacy)"
6522            );
6523            let (logits, _) = self.decode_step_fast(tok, state, true, host_math)?;
6524            return Ok(logits.expect("want_logits"));
6525        }
6526        let mc = &self.model.mc;
6527        let d = self.model.cfg();
6528        let pos = state.pos;
6529        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6530        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6531        let hidden = mc.n_embd as usize;
6532        let hc = d.hc_mult as usize;
6533        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6534
6535        // stage 0: embed row -> hc state
6536        let st0 = &self.stages[0];
6537        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6538        let stream0 = st0.gpu.stream();
6539        let ids_dev = upload_i32(&stream0, &[tok as i32])?;
6540        let mut emb = stream0.alloc_zeros::<f32>(hidden).map_err(e("emb"))?;
6541        unsafe {
6542            ck(
6543                "embed_rows",
6544                k::memra_dsv4_embed_rows(
6545                    st0.embed
6546                        .as_ref()
6547                        .expect("embed on stage 0")
6548                        .device_ptr(&stream0)
6549                        .0 as *const c_void,
6550                    ids_dev.device_ptr(&stream0).0 as *const i32,
6551                    dpm!(emb, &stream0),
6552                    1,
6553                    hidden as i32,
6554                    sp(&stream0),
6555                ),
6556            )?;
6557        }
6558        let mut h = stream0.alloc_zeros::<f32>(hc * hidden).map_err(e("h0"))?;
6559        unsafe {
6560            ck(
6561                "repeat_hc",
6562                k::memra_dsv4_repeat_hc(
6563                    dpf!(emb, &stream0),
6564                    dpm!(h, &stream0),
6565                    1,
6566                    hc as i32,
6567                    hidden as i32,
6568                    sp(&stream0),
6569                ),
6570            )?;
6571        }
6572
6573        let mut cur_stage = 0usize;
6574        for il in 0..n_trunk {
6575            let stage = self.layer_stage[il as usize];
6576            if stage != cur_stage {
6577                let src_stream = self.stages[cur_stage].gpu.stream();
6578                let host = dtoh_f32(&src_stream, &h)?;
6579                let dst_stream = self.stages[stage].gpu.stream();
6580                self.stages[stage]
6581                    .gpu
6582                    .ctx
6583                    .bind_to_thread()
6584                    .map_err(e("bind"))?;
6585                h = upload_f32(&dst_stream, &host)?;
6586                cur_stage = stage;
6587            }
6588            let st = &self.stages[stage];
6589            let lidx = st
6590                .layers
6591                .iter()
6592                .position(|l| l.il == il)
6593                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6594            h = self.block_decode(
6595                st,
6596                &st.layers[lidx],
6597                &mut state.caches[il as usize],
6598                &h,
6599                pos,
6600                tok,
6601                dump.as_deref_mut(),
6602            )?;
6603        }
6604
6605        let last = self.stages.len() - 1;
6606        if cur_stage != last {
6607            let src_stream = self.stages[cur_stage].gpu.stream();
6608            let host = dtoh_f32(&src_stream, &h)?;
6609            let dst_stream = self.stages[last].gpu.stream();
6610            h = upload_f32(&dst_stream, &host)?;
6611        }
6612        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
6613        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
6614        let logits = self.head_logits_from(
6615            &h,
6616            1,
6617            hc_head_fn,
6618            &self.hc_head_base,
6619            &self.hc_head_scale,
6620            trunk_norm,
6621        )?;
6622        state.pos += 1;
6623        Ok(logits)
6624    }
6625}
6626
6627// ================================================================ iteration 3: DSpark drafter (device)
6628//
6629// Semantic law: DSPARK-SEMANTICS.md (M-cites); numeric truth: the lane-10 CPU oracle
6630// (memra_gguf::dsv4_dspark) — every gate compares against its fixtures/trajectory.
6631// Realization: the PREFILL-class helpers (Self::hc_pre host-Sinkhorn, cuBLASLt bf16
6632// gemm, moe_forward bf16-dequant experts, prefill sink_attn) at s = block_size —
6633// the lane-4-gated numeric class; the drafter's arena/native-expert perf rungs are
6634// banked follow-ups, never correctness requirements.
6635impl Dsv4Gpu {
6636    fn dspark(&self) -> &DsparkDev {
6637        self.dspark
6638            .as_ref()
6639            .expect("MEMRA_DSV4_DRAFTER=dspark not loaded")
6640    }
6641
6642    /// The drafter's exit-head island dots, HOISTED across the block's rows (weight row
6643    /// read once instead of once per row) with the rung-4c arm selection. The f64 branch
6644    /// is BIT-EXACT vs the pinned `Self::dots` — identical per-(t, j) element order and
6645    /// reduction tree — so the default arm's bytes are unchanged by the hoist; the f32x
6646    /// branch is the measured fork (`MEMRA_DSV4_DSPARK_HEAD_ARM=f32x`) offered for owner
6647    /// ratification. Either way this touches only WHICH tokens are drafted: verification
6648    /// always emits the trunk's own argmax, so the emitted stream cannot depend on it.
6649    #[allow(clippy::too_many_arguments)]
6650    fn dspark_head_dots(
6651        &self,
6652        st: &Stage,
6653        x: *const f32,
6654        w: *const c_void,
6655        w_is_bf16: i32,
6656        s: usize,
6657        kdim: usize,
6658        n: usize,
6659        y: *mut f32,
6660    ) -> Res<()> {
6661        let stream = st.gpu.stream();
6662        unsafe {
6663            if self.dspark_head_f32 {
6664                ck(
6665                    "dspark head dots f32acc_mrow",
6666                    k::memra_dsv4_dots_f32acc_mrow(
6667                        x,
6668                        w,
6669                        w_is_bf16,
6670                        y,
6671                        s as i32,
6672                        kdim as i32,
6673                        n as i32,
6674                        sp(&stream),
6675                    ),
6676                )
6677            } else {
6678                ck(
6679                    "dspark head dots f32_mrow",
6680                    k::memra_dsv4_dots_f32_mrow(
6681                        x,
6682                        w,
6683                        w_is_bf16,
6684                        y,
6685                        s as i32,
6686                        kdim as i32,
6687                        n as i32,
6688                        sp(&stream),
6689                    ),
6690                )
6691            }
6692        }
6693    }
6694
6695    /// Allocate the drafter decode state on the last stage: 3 rings [win + block, hd]
6696    /// (ring + transient draft rows, struct doc) + the tap rows [block+1, n_t*hidden].
6697    pub fn dspark_alloc_state(&self) -> Res<DsparkState> {
6698        let ds = self.dspark();
6699        let d = self.model.cfg();
6700        let hd = d.head_dim as usize;
6701        let win = d.sliding_window as usize;
6702        let hidden = self.model.mc.n_embd as usize;
6703        let last = self.stages.len() - 1;
6704        let stream = self.stages[last].gpu.stream();
6705        let mut rings = Vec::with_capacity(ds.blocks.len());
6706        for _ in 0..ds.blocks.len() {
6707            rings.push(
6708                stream
6709                    .alloc_zeros::<f32>((win + ds.block_size) * hd)
6710                    .map_err(e("dspark ring"))?,
6711            );
6712        }
6713        let taps = stream
6714            .alloc_zeros::<f32>((ds.block_size + 1) * ds.targets.len() * hidden)
6715            .map_err(e("dspark taps"))?;
6716        Ok(DsparkState { rings, taps })
6717    }
6718
6719    /// main_x = main_norm(main_proj(main_hidden)) (M:853), s rows on the last stage.
6720    fn dspark_main_x(&self, main_hidden: &CudaSlice<f32>, s: usize) -> Res<CudaSlice<f32>> {
6721        let ds = self.dspark();
6722        let hidden = self.model.mc.n_embd as usize;
6723        let k = ds.targets.len() * hidden;
6724        let last = self.stages.len() - 1;
6725        let st = &self.stages[last];
6726        let stream = st.gpu.stream();
6727        let mut mx = stream.alloc_zeros::<f32>(s * hidden).map_err(e("main_x"))?;
6728        Self::gemm(st, main_hidden, &ds.main_proj, 0, s, hidden, k, &mut mx)?;
6729        unsafe {
6730            ck(
6731                "rmsnorm main_x",
6732                k::memra_dsv4_rmsnorm(
6733                    dpf!(mx, &stream),
6734                    dpf!(ds.main_norm, &stream),
6735                    dpm!(mx, &stream),
6736                    s as i32,
6737                    hidden as i32,
6738                    self.model.mc.rms_eps,
6739                    sp(&stream),
6740                ),
6741            )?;
6742        }
6743        Ok(mx)
6744    }
6745
6746    /// Per-block main_kv rows (M:758-761): kv_norm(wkv(main_x)) + rope(REAL positions)
6747    /// + group-64 FP8 QAT on the nope dims. Returns [s, hd] on the last stage.
6748    fn dspark_main_kv(
6749        &self,
6750        blk: &LayerDev,
6751        main_x: &CudaSlice<f32>,
6752        s: usize,
6753        positions: &[i32],
6754    ) -> Res<CudaSlice<f32>> {
6755        let d = self.model.cfg();
6756        let hd = d.head_dim as usize;
6757        let rd = d.qk_rope_head_dim as usize;
6758        let hidden = self.model.mc.n_embd as usize;
6759        let eps = self.model.mc.rms_eps;
6760        let last = self.stages.len() - 1;
6761        let st = &self.stages[last];
6762        let stream = st.gpu.stream();
6763        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
6764        // item 3: `.dev()` — the drafter blocks carry no fp8 twins this rung, so
6765        // their bf16 slabs are always device-resident; if a future rung stages them,
6766        // this must fail loudly rather than pay a per-round upload silently.
6767        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("dspark kv"))?;
6768        Self::gemm(st, main_x, blk.wkv.dev(), 0, s, hd, hidden, &mut kv)?;
6769        let pos_dev = upload_i32(&stream, positions)?;
6770        unsafe {
6771            ck(
6772                "rmsnorm dspark kv",
6773                k::memra_dsv4_rmsnorm(
6774                    dpf!(kv, &stream),
6775                    dpf!(blk.kv_norm, &stream),
6776                    dpm!(kv, &stream),
6777                    s as i32,
6778                    hd as i32,
6779                    eps,
6780                    sp(&stream),
6781                ),
6782            )?;
6783            ck(
6784                "rope dspark kv",
6785                k::memra_dsv4_rope(
6786                    dpm!(kv, &stream),
6787                    s as i32,
6788                    1,
6789                    hd as i32,
6790                    rd as i32,
6791                    dpf!(st.fc_plain, &stream),
6792                    pos_dev.device_ptr(&stream).0 as *const i32,
6793                    0,
6794                    sp(&stream),
6795                ),
6796            )?;
6797            ck(
6798                "act_quant dspark kv",
6799                k::memra_dsv4_act_quant(
6800                    dpm!(kv, &stream),
6801                    s as i32,
6802                    hd as i64,
6803                    (hd - rd) as i32,
6804                    64,
6805                    clamp_only,
6806                    sp(&stream),
6807                ),
6808            )?;
6809        }
6810        Ok(kv)
6811    }
6812
6813    /// Prefill ring priming (M:763-769): last min(s, win) positions land at slot
6814    /// p % win. `main_hidden` = [s, n_t*hidden] tap rows from the prefill.
6815    pub fn dspark_prime_prefill(
6816        &self,
6817        state: &mut DsparkState,
6818        main_hidden: &CudaSlice<f32>,
6819        s: usize,
6820    ) -> Res<()> {
6821        let d = self.model.cfg();
6822        let hd = d.head_dim as usize;
6823        let win = d.sliding_window as usize;
6824        let last = self.stages.len() - 1;
6825        let stream = self.stages[last].gpu.stream();
6826        let mx = self.dspark_main_x(main_hidden, s)?;
6827        let positions: Vec<i32> = (0..s as i32).collect();
6828        let n_blocks = self.dspark().blocks.len();
6829        for bi in 0..n_blocks {
6830            let blk = &self.dspark().blocks[bi];
6831            let kv = self.dspark_main_kv(blk, &mx, s, &positions)?;
6832            for p in s.saturating_sub(win)..s {
6833                let slot = p % win;
6834                let src = kv.slice(p * hd..(p + 1) * hd);
6835                let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
6836                stream
6837                    .memcpy_dtod(&src, &mut dst)
6838                    .map_err(e("prime ring"))?;
6839            }
6840        }
6841        Ok(())
6842    }
6843
6844    /// Trunk prefill + DSpark ring prime in ONE pass — the device twin of the CPU
6845    /// oracle's `trunk.forward(&seq[..p0], 0)` + `dspark.prime_prefill(&pre.main_hidden,
6846    /// p0)` pair (dsv4_dspark_gate components mode).
6847    ///
6848    /// The prefill taps come from the existing `GpuCapture::layer_out` hook (the target
6849    /// layers' full hc state `[s, hc, hidden]`), then run through the SAME
6850    /// `memra_dsv4_hc_mean` kernel the decode tap uses — prefill and decode taps must
6851    /// not be two numeric realizations of one tap — and are placed at the target's
6852    /// concat stride with `place_cols`, reproducing the oracle's
6853    /// `main_hidden[(p*n_t + k)*hidden ..]` layout exactly.
6854    pub fn dspark_prefill_prime(
6855        &self,
6856        ids: &[u32],
6857        state: &mut DecodeState,
6858        dstate: &mut DsparkState,
6859    ) -> Res<ForwardOut> {
6860        assert_eq!(
6861            state.pos, 0,
6862            "dspark_prefill_prime needs a fresh DecodeState"
6863        );
6864        assert!(!ids.is_empty(), "empty prompt");
6865        let hidden = self.model.mc.n_embd as usize;
6866        let hc = self.model.cfg().hc_mult as usize;
6867        let s = ids.len();
6868        let targets = self.dspark().targets.clone();
6869        let n_t = targets.len();
6870        let mut cap = GpuCapture {
6871            want: targets.iter().map(|&t| t as u32).collect(),
6872            ..Default::default()
6873        };
6874        let out = self
6875            .forward_impl(ids, Some(&mut cap), None, Some(state))?
6876            .expect("prefill logits");
6877        state.pos = s;
6878
6879        let last = self.stages.len() - 1;
6880        let stream = self.stages[last].gpu.stream();
6881        self.stages[last]
6882            .gpu
6883            .ctx
6884            .bind_to_thread()
6885            .map_err(e("bind ctx prime"))?;
6886        let mut main_hidden = stream
6887            .alloc_zeros::<f32>(s * n_t * hidden)
6888            .map_err(e("prefill main_hidden"))?;
6889        let mut tmp = stream
6890            .alloc_zeros::<f32>(s * hidden)
6891            .map_err(e("tap tmp"))?;
6892        for (k, &il) in targets.iter().enumerate() {
6893            let h = cap
6894                .layer_out
6895                .get(&(il as u32))
6896                .unwrap_or_else(|| panic!("prefill capture missing target layer {il}"));
6897            assert_eq!(
6898                h.len(),
6899                s * hc * hidden,
6900                "target layer {il} capture is not [s, hc, hidden]"
6901            );
6902            let h_dev = upload_f32(&stream, h)?;
6903            unsafe {
6904                ck(
6905                    "hc_mean prefill tap",
6906                    k::memra_dsv4_hc_mean(
6907                        dpf!(h_dev, &stream),
6908                        dpm!(tmp, &stream),
6909                        s as i32,
6910                        hc as i32,
6911                        hidden as i32,
6912                        sp(&stream),
6913                    ),
6914                )?;
6915                ck(
6916                    "place_cols prefill tap",
6917                    k::memra_dsv4_place_cols(
6918                        dpf!(tmp, &stream),
6919                        dpm!(main_hidden, &stream),
6920                        s as i32,
6921                        hidden as i32,
6922                        (n_t * hidden) as i64,
6923                        (k * hidden) as i64,
6924                        sp(&stream),
6925                    ),
6926                )?;
6927            }
6928        }
6929        self.dspark_prime_prefill(dstate, &main_hidden, s)?;
6930        // Seed taps row 0 with the LAST prefill position's tap: the generic spec loop's
6931        // first proposal is `propose(t, mh_last, p0-1)` with mh_last = pre_taps row
6932        // p0-1 (spec_oracle::run_spec_greedy) — without this the first round would draft
6933        // off a zeroed tap.
6934        {
6935            let src = main_hidden.slice((s - 1) * n_t * hidden..s * n_t * hidden);
6936            let mut dst = dstate.taps.slice_mut(0..n_t * hidden);
6937            stream
6938                .memcpy_dtod(&src, &mut dst)
6939                .map_err(e("seed tap row"))?;
6940        }
6941        stream.synchronize().map_err(e("prime sync"))?;
6942        Ok(out)
6943    }
6944
6945    /// Ring advance for ONE committed position (§3.1 drafter rule: accepted positions
6946    /// only). `tap_row` indexes into `state.taps` (the row that holds position `pos`'s
6947    /// hc-mean concat).
6948    pub fn dspark_write_rings(
6949        &self,
6950        state: &mut DsparkState,
6951        tap_row: usize,
6952        pos: usize,
6953    ) -> Res<()> {
6954        let d = self.model.cfg();
6955        let hd = d.head_dim as usize;
6956        let win = d.sliding_window as usize;
6957        let hidden = self.model.mc.n_embd as usize;
6958        let n_t = self.dspark().targets.len();
6959        let last = self.stages.len() - 1;
6960        let stream = self.stages[last].gpu.stream();
6961        let tap = {
6962            // one-row view as an owned slice copy (gemm wants a base slice)
6963            let mut row = stream
6964                .alloc_zeros::<f32>(n_t * hidden)
6965                .map_err(e("tap row"))?;
6966            let src = state
6967                .taps
6968                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
6969            stream.memcpy_dtod(&src, &mut row).map_err(e("tap copy"))?;
6970            row
6971        };
6972        let mx = self.dspark_main_x(&tap, 1)?;
6973        let n_blocks = self.dspark().blocks.len();
6974        for bi in 0..n_blocks {
6975            let blk = &self.dspark().blocks[bi];
6976            let kv = self.dspark_main_kv(blk, &mx, 1, &[pos as i32])?;
6977            let slot = pos % win;
6978            let src = kv.slice(0..hd);
6979            let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
6980            stream
6981                .memcpy_dtod(&src, &mut dst)
6982                .map_err(e("ring write"))?;
6983        }
6984        Ok(())
6985    }
6986
6987    /// One DSpark draft-block forward (M:695-707 body with DSparkAttention M:771-792):
6988    /// h [block, hc, hidden] -> same shape. Reads the ring; writes ONLY the transient
6989    /// draft-kv rows [win, win+block) of `ring` (never persistent ring slots).
6990    #[allow(clippy::too_many_arguments)]
6991    fn dspark_block_forward(
6992        &self,
6993        blk: &LayerDev,
6994        ring: &mut CudaSlice<f32>,
6995        h: &CudaSlice<f32>,
6996        block: usize,
6997        pos: usize,
6998    ) -> Res<CudaSlice<f32>> {
6999        let d = self.model.cfg();
7000        let mc = &self.model.mc;
7001        let hc = d.hc_mult as usize;
7002        let hidden = mc.n_embd as usize;
7003        let heads = mc.n_head as usize;
7004        let hd = d.head_dim as usize;
7005        let rd = d.qk_rope_head_dim as usize;
7006        let q_lora = d.q_lora_rank as usize;
7007        let win = d.sliding_window as usize;
7008        let o_groups = d.o_groups as usize;
7009        let o_lora = d.o_lora_rank as usize;
7010        let eps = mc.rms_eps;
7011        let iters = d.hc_sinkhorn_iters;
7012        let hc_eps = d.hc_eps;
7013        let last = self.stages.len() - 1;
7014        let st = &self.stages[last];
7015        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx dspark"))?;
7016        let stream = st.gpu.stream();
7017        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
7018        // draft positions pos+1 .. pos+block (M:772)
7019        let positions: Vec<i32> = (1..=block as i32).map(|j| pos as i32 + j).collect();
7020        let pos_dev = upload_i32(&stream, &positions)?;
7021
7022        // ---- attention sub-block
7023        let (y, post, comb) = Self::hc_pre(
7024            st,
7025            h,
7026            &blk.hc_attn_fn,
7027            &blk.hc_attn_base,
7028            &blk.hc_attn_scale,
7029            block,
7030            hc,
7031            hidden,
7032            iters,
7033            hc_eps,
7034        )?;
7035        let mut x = stream.alloc_zeros::<f32>(block * hidden).map_err(e("x"))?;
7036        unsafe {
7037            ck(
7038                "rmsnorm dspark attn",
7039                k::memra_dsv4_rmsnorm(
7040                    dpf!(y, &stream),
7041                    dpf!(blk.attn_norm, &stream),
7042                    dpm!(x, &stream),
7043                    block as i32,
7044                    hidden as i32,
7045                    eps,
7046                    sp(&stream),
7047                ),
7048            )?;
7049        }
7050        // q path (trunk-identical, M:774-777)
7051        let mut qr = stream.alloc_zeros::<f32>(block * q_lora).map_err(e("qr"))?;
7052        Self::gemm(st, &x, blk.wq_a.dev(), 0, block, q_lora, hidden, &mut qr)?;
7053        unsafe {
7054            ck(
7055                "rmsnorm dspark q",
7056                k::memra_dsv4_rmsnorm(
7057                    dpf!(qr, &stream),
7058                    dpf!(blk.q_norm, &stream),
7059                    dpm!(qr, &stream),
7060                    block as i32,
7061                    q_lora as i32,
7062                    eps,
7063                    sp(&stream),
7064                ),
7065            )?;
7066        }
7067        let mut qr_b = stream
7068            .alloc_zeros::<u8>(block * q_lora * 2)
7069            .map_err(e("qr_b"))?;
7070        unsafe {
7071            ck(
7072                "cvt dspark qr",
7073                k::memra_dsv4_cvt_bf16(
7074                    dpf!(qr, &stream),
7075                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
7076                    (block * q_lora) as i64,
7077                    sp(&stream),
7078                ),
7079            )?;
7080        }
7081        let mut q = stream
7082            .alloc_zeros::<f32>(block * heads * hd)
7083            .map_err(e("q"))?;
7084        Self::gemm_pre(
7085            st,
7086            &qr_b,
7087            blk.wq_b.dev().device_ptr(&stream).0 as *const c_void,
7088            block,
7089            heads * hd,
7090            q_lora,
7091            &mut q,
7092        )?;
7093        unsafe {
7094            ck(
7095                "headrms dspark",
7096                k::memra_dsv4_headrms(
7097                    dpm!(q, &stream),
7098                    (block * heads) as i32,
7099                    hd as i32,
7100                    eps,
7101                    sp(&stream),
7102                ),
7103            )?;
7104            ck(
7105                "rope dspark q",
7106                k::memra_dsv4_rope(
7107                    dpm!(q, &stream),
7108                    block as i32,
7109                    heads as i32,
7110                    hd as i32,
7111                    rd as i32,
7112                    dpf!(st.fc_plain, &stream),
7113                    pos_dev.device_ptr(&stream).0 as *const i32,
7114                    0,
7115                    sp(&stream),
7116                ),
7117            )?;
7118        }
7119        // draft kv (M:778-780) -> transient rows [win, win+block) of the ring buffer
7120        {
7121            let mut kv = stream.alloc_zeros::<f32>(block * hd).map_err(e("dkv"))?;
7122            Self::gemm(st, &x, blk.wkv.dev(), 0, block, hd, hidden, &mut kv)?;
7123            unsafe {
7124                ck(
7125                    "rmsnorm dspark dkv",
7126                    k::memra_dsv4_rmsnorm(
7127                        dpf!(kv, &stream),
7128                        dpf!(blk.kv_norm, &stream),
7129                        dpm!(kv, &stream),
7130                        block as i32,
7131                        hd as i32,
7132                        eps,
7133                        sp(&stream),
7134                    ),
7135                )?;
7136                ck(
7137                    "rope dspark dkv",
7138                    k::memra_dsv4_rope(
7139                        dpm!(kv, &stream),
7140                        block as i32,
7141                        1,
7142                        hd as i32,
7143                        rd as i32,
7144                        dpf!(st.fc_plain, &stream),
7145                        pos_dev.device_ptr(&stream).0 as *const i32,
7146                        0,
7147                        sp(&stream),
7148                    ),
7149                )?;
7150                ck(
7151                    "act_quant dspark dkv",
7152                    k::memra_dsv4_act_quant(
7153                        dpm!(kv, &stream),
7154                        block as i32,
7155                        hd as i64,
7156                        (hd - rd) as i32,
7157                        64,
7158                        clamp_only,
7159                        sp(&stream),
7160                    ),
7161                )?;
7162            }
7163            let src = kv.slice(0..block * hd);
7164            let mut dst = ring.slice_mut(win * hd..(win + block) * hd);
7165            stream.memcpy_dtod(&src, &mut dst).map_err(e("draft kv"))?;
7166        }
7167        // attention set (M:743-747): ring slots 0..min(win, pos+1) then the block's
7168        // transient rows — ONE shared row for every draft query (bidirectional
7169        // intra-block attention), replicated per query for the prefill kernel.
7170        let n_ring = win.min(pos + 1);
7171        let mut idx_row: Vec<i32> = (0..n_ring as i32).collect();
7172        idx_row.extend((0..block as i32).map(|j| win as i32 + j));
7173        let slots = idx_row.len();
7174        let mut idxs = Vec::with_capacity(block * slots);
7175        for _ in 0..block {
7176            idxs.extend_from_slice(&idx_row);
7177        }
7178        let idx_dev = upload_i32(&stream, &idxs)?;
7179        let mut o = stream
7180            .alloc_zeros::<f32>(block * heads * hd)
7181            .map_err(e("o"))?;
7182        let scale = (hd as f64).powf(-0.5) as f32;
7183        unsafe {
7184            ck(
7185                "sink_attn dspark",
7186                k::memra_dsv4_sink_attn(
7187                    dpf!(q, &stream),
7188                    dpf!(ring, &stream),
7189                    idx_dev.device_ptr(&stream).0 as *const i32,
7190                    dpf!(blk.sink, &stream),
7191                    dpm!(o, &stream),
7192                    block as i32,
7193                    heads as i32,
7194                    hd as i32,
7195                    slots as i32,
7196                    scale,
7197                    sp(&stream),
7198                ),
7199            )?;
7200            ck(
7201                "rope dspark o inv",
7202                k::memra_dsv4_rope(
7203                    dpm!(o, &stream),
7204                    block as i32,
7205                    heads as i32,
7206                    hd as i32,
7207                    rd as i32,
7208                    dpf!(st.fc_plain, &stream),
7209                    pos_dev.device_ptr(&stream).0 as *const i32,
7210                    1,
7211                    sp(&stream),
7212                ),
7213            )?;
7214        }
7215        // grouped wo (trunk-identical)
7216        let gw = heads / o_groups * hd;
7217        let mut og = stream
7218            .alloc_zeros::<f32>(block * o_groups * o_lora)
7219            .map_err(e("og"))?;
7220        let mut o_grp = stream.alloc_zeros::<f32>(block * gw).map_err(e("o_grp"))?;
7221        let mut y_grp = stream
7222            .alloc_zeros::<f32>(block * o_lora)
7223            .map_err(e("y_grp"))?;
7224        for g in 0..o_groups {
7225            unsafe {
7226                ck(
7227                    "take_cols dspark",
7228                    k::memra_dsv4_take_cols(
7229                        dpf!(o, &stream),
7230                        dpm!(o_grp, &stream),
7231                        block as i32,
7232                        gw as i32,
7233                        (heads * hd) as i64,
7234                        (g * gw) as i64,
7235                        sp(&stream),
7236                    ),
7237                )?;
7238            }
7239            Self::gemm(
7240                st,
7241                &o_grp,
7242                blk.wo_a.dev(),
7243                g * o_lora * gw,
7244                block,
7245                o_lora,
7246                gw,
7247                &mut y_grp,
7248            )?;
7249            unsafe {
7250                ck(
7251                    "place_cols dspark",
7252                    k::memra_dsv4_place_cols(
7253                        dpf!(y_grp, &stream),
7254                        dpm!(og, &stream),
7255                        block as i32,
7256                        o_lora as i32,
7257                        (o_groups * o_lora) as i64,
7258                        (g * o_lora) as i64,
7259                        sp(&stream),
7260                    ),
7261                )?;
7262            }
7263        }
7264        let mut attn_out = stream.alloc_zeros::<f32>(block * hidden).map_err(e("ao"))?;
7265        Self::gemm(
7266            st,
7267            &og,
7268            blk.wo_b.dev(),
7269            0,
7270            block,
7271            hidden,
7272            o_groups * o_lora,
7273            &mut attn_out,
7274        )?;
7275        let mut h2 = stream
7276            .alloc_zeros::<f32>(block * hc * hidden)
7277            .map_err(e("h2"))?;
7278        unsafe {
7279            ck(
7280                "hc_post dspark attn",
7281                k::memra_dsv4_hc_post(
7282                    dpf!(attn_out, &stream),
7283                    dpf!(h, &stream),
7284                    dpf!(post, &stream),
7285                    dpf!(comb, &stream),
7286                    dpm!(h2, &stream),
7287                    block as i32,
7288                    hc as i32,
7289                    hidden as i32,
7290                    sp(&stream),
7291                ),
7292            )?;
7293        }
7294        // ---- ffn sub-block (score-routed MoE; ids unused by a non-hash gate)
7295        let (y2, post2, comb2) = Self::hc_pre(
7296            st,
7297            &h2,
7298            &blk.hc_ffn_fn,
7299            &blk.hc_ffn_base,
7300            &blk.hc_ffn_scale,
7301            block,
7302            hc,
7303            hidden,
7304            iters,
7305            hc_eps,
7306        )?;
7307        let mut xf = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xf"))?;
7308        unsafe {
7309            ck(
7310                "rmsnorm dspark ffn",
7311                k::memra_dsv4_rmsnorm(
7312                    dpf!(y2, &stream),
7313                    dpf!(blk.ffn_norm, &stream),
7314                    dpm!(xf, &stream),
7315                    block as i32,
7316                    hidden as i32,
7317                    eps,
7318                    sp(&stream),
7319                ),
7320            )?;
7321        }
7322        let ids = vec![0u32; block];
7323        let moe_out = self.moe_forward(st, blk, &xf, block, &ids)?;
7324        let mut h3 = stream
7325            .alloc_zeros::<f32>(block * hc * hidden)
7326            .map_err(e("h3"))?;
7327        unsafe {
7328            ck(
7329                "hc_post dspark ffn",
7330                k::memra_dsv4_hc_post(
7331                    dpf!(moe_out, &stream),
7332                    dpf!(h2, &stream),
7333                    dpf!(post2, &stream),
7334                    dpf!(comb2, &stream),
7335                    dpm!(h3, &stream),
7336                    block as i32,
7337                    hc as i32,
7338                    hidden as i32,
7339                    sp(&stream),
7340                ),
7341            )?;
7342        }
7343        Ok(h3)
7344    }
7345
7346    /// forward_spec (M:928-936) + forward_head (M:860-874) on the device: ONE parallel
7347    /// noise-block draft through the 3 blocks, shared trunk head over all block rows,
7348    /// sequential rank-256 markov chaining (greedy), fp32 confidence. Mutates ONLY the
7349    /// rings' transient rows (drafting is side-effect-free on trunk + persistent ring
7350    /// state — §3.1). `tap_row` = the taps row holding position `pos`'s hc-mean concat.
7351    pub fn dspark_forward_spec(
7352        &self,
7353        state: &mut DsparkState,
7354        input_token: u32,
7355        tap_row: usize,
7356        pos: usize,
7357        capture: bool,
7358    ) -> Res<DsparkProposal> {
7359        let ds = self.dspark();
7360        let mc = &self.model.mc;
7361        let d = self.model.cfg();
7362        let hc = d.hc_mult as usize;
7363        let hidden = mc.n_embd as usize;
7364        let eps = mc.rms_eps;
7365        let block = ds.block_size;
7366        let rank = ds.rank;
7367        let vocab = ds.vocab;
7368        let n_t = ds.targets.len();
7369        let last = self.stages.len() - 1;
7370        let st = &self.stages[last];
7371        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx spec"))?;
7372        let stream = st.gpu.stream();
7373
7374        let prof = if dsv4_prof_on() {
7375            Some(stream.clone())
7376        } else {
7377            None
7378        };
7379        // main_x from the tap row (computed once per call, M:930-932)
7380        let tap = {
7381            let _p = phase!("1a.tap_copy", prof.as_ref());
7382            let mut row = stream.alloc_zeros::<f32>(n_t * hidden).map_err(e("tapr"))?;
7383            let src = state
7384                .taps
7385                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
7386            stream.memcpy_dtod(&src, &mut row).map_err(e("tap cp"))?;
7387            row
7388        };
7389        let mx = {
7390            let _p = phase!("1b.main_x", prof.as_ref());
7391            self.dspark_main_x(&tap, 1)?
7392        };
7393        let (cap_main_hidden, cap_main_x) = if capture {
7394            (
7395                Some(dtoh_f32(&stream, &tap)?),
7396                Some(dtoh_f32(&stream, &mx)?),
7397            )
7398        } else {
7399            (None, None)
7400        };
7401        // draft block ids: [input token, noise ×(block-1)] via the SHARED trunk embed
7402        // (host-gathered — the MtpDev precedent)
7403        let _p_embed = phase!("1c.embed_h2d_repeat", prof.as_ref());
7404        let mut draft_ids = vec![ds.noise_token; block];
7405        draft_ids[0] = input_token;
7406        let e_rows = self.model.embed_rows(&draft_ids);
7407        let e_dev = upload_f32(&stream, &e_rows)?;
7408        let mut h = stream
7409            .alloc_zeros::<f32>(block * hc * hidden)
7410            .map_err(e("h0"))?;
7411        unsafe {
7412            ck(
7413                "repeat_hc dspark",
7414                k::memra_dsv4_repeat_hc(
7415                    dpf!(e_dev, &stream),
7416                    dpm!(h, &stream),
7417                    block as i32,
7418                    hc as i32,
7419                    hidden as i32,
7420                    sp(&stream),
7421                ),
7422            )?;
7423        }
7424        drop(_p_embed);
7425        let mut block_outs: Vec<Vec<f32>> = Vec::new();
7426        let _p_blocks = phase!("1d.drafter_blocks", prof.as_ref());
7427        let n_blocks = ds.blocks.len();
7428        for bi in 0..n_blocks {
7429            // rings[bi] transient rows are rewritten; persistent slots untouched
7430            let mut ring = std::mem::replace(
7431                &mut state.rings[bi],
7432                stream.alloc_zeros::<f32>(0).map_err(e("swap"))?,
7433            );
7434            let out =
7435                self.dspark_block_forward(&self.dspark().blocks[bi], &mut ring, &h, block, pos);
7436            state.rings[bi] = ring;
7437            h = out?;
7438            if capture {
7439                block_outs.push(dtoh_f32(&stream, &h)?);
7440            }
7441        }
7442        drop(_p_blocks);
7443        // exit head (mtp.2): pre-only hc collapse -> xc (pre-norm, feeds confidence),
7444        // norm, shared trunk head over ALL block rows
7445        let w = hc * hidden;
7446        let _p_mix = phase!("1e.exit_mix_dots", prof.as_ref());
7447        let mut mixes = stream.alloc_zeros::<f32>(block * hc).map_err(e("mx"))?;
7448        // hoisted (weight row read once across the block's rows). The f64 twin is
7449        // BIT-EXACT vs `Self::dots` — same per-(t, j) element order and reduction tree —
7450        // so the default arm's values are untouched by the hoist.
7451        self.dspark_head_dots(
7452            st,
7453            h.device_ptr(&stream).0 as *const f32,
7454            ds.hc_head_fn.device_ptr(&stream).0 as *const c_void,
7455            0,
7456            block,
7457            w,
7458            hc,
7459            mixes.device_ptr_mut(&stream).0 as *mut f32,
7460        )?;
7461        unsafe {
7462            ck(
7463                "rowsq dspark head",
7464                k::memra_dsv4_rowsq_scale(
7465                    dpf!(h, &stream),
7466                    dpm!(mixes, &stream),
7467                    block as i32,
7468                    w as i32,
7469                    hc as i32,
7470                    eps,
7471                    sp(&stream),
7472                ),
7473            )?;
7474        }
7475        drop(_p_mix);
7476        let _p_mixrt = phase!("1f.mix_D2H_host_H2D", prof.as_ref());
7477        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
7478        for t in 0..block {
7479            for c in 0..hc {
7480                let m = mixes_h[t * hc + c];
7481                mixes_h[t * hc + c] =
7482                    sigmoid_f32(m * ds.hc_head_scale[0] + ds.hc_head_base[c]) + d.hc_eps;
7483            }
7484        }
7485        let pre_d = upload_f32(&stream, &mixes_h)?;
7486        drop(_p_mixrt);
7487        let _p_cn = phase!("1g.collapse_norm", prof.as_ref());
7488        let mut xc = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xc"))?;
7489        unsafe {
7490            ck(
7491                "hc_collapse dspark",
7492                k::memra_dsv4_hc_collapse(
7493                    dpf!(h, &stream),
7494                    dpf!(pre_d, &stream),
7495                    dpm!(xc, &stream),
7496                    block as i32,
7497                    hc as i32,
7498                    hidden as i32,
7499                    sp(&stream),
7500                ),
7501            )?;
7502        }
7503        let mut normed = stream.alloc_zeros::<f32>(block * hidden).map_err(e("nr"))?;
7504        unsafe {
7505            ck(
7506                "rmsnorm dspark head",
7507                k::memra_dsv4_rmsnorm(
7508                    dpf!(xc, &stream),
7509                    dpf!(ds.norm, &stream),
7510                    dpm!(normed, &stream),
7511                    block as i32,
7512                    hidden as i32,
7513                    eps,
7514                    sp(&stream),
7515                ),
7516            )?;
7517        }
7518        drop(_p_cn);
7519        let _p_head = phase!("1h.exit_head_dots", prof.as_ref());
7520        let mut logits = stream.alloc_zeros::<f32>(block * vocab).map_err(e("lg"))?;
7521        // THE 21%-of-a-round instance (nsys, rung 4c): vocab x block over the 1.06 GiB
7522        // shared head. f64 default (gated bytes, hoisted bit-exactly);
7523        // MEMRA_DSV4_DSPARK_HEAD_ARM=f32x switches it to the ratified accumulation class.
7524        self.dspark_head_dots(
7525            st,
7526            normed.device_ptr(&stream).0 as *const f32,
7527            st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
7528            1,
7529            block,
7530            hidden,
7531            vocab,
7532            logits.device_ptr_mut(&stream).0 as *mut f32,
7533        )?;
7534        drop(_p_head);
7535        // pre-markov head logits (the gate's logits_pre array) — captured BEFORE the
7536        // chaining loop adds any bias row in place.
7537        let cap_logits_pre = if capture {
7538            Some(dtoh_f32(&stream, &logits)?)
7539        } else {
7540            None
7541        };
7542        // sequential markov chaining (M:866-871), greedy (temperature 0).
7543        //
7544        // ITERATION-5 (F itemisation, rung 2): the chain is inherently sequential -- draft i+1's
7545        // markov row is indexed by draft i -- but the DEPENDENCY never needed a HOST round trip.
7546        // The shipped loop reads each argmax back (4 B D2H + `stream.synchronize()`) and each
7547        // confidence back the same way, so a block_size-5 chain DRAINS the only stream TEN times
7548        // per round. Those drains are pure F: T-independent, all latency, no work.
7549        // `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the chain resident -- the argmax lands in
7550        // `am_dev[i + 1]`, the next markov row is gathered BY DEVICE INDEX, confidences
7551        // accumulate into `conf_out[i]`, and ONE D2H at the end of the loop returns every id and
7552        // confidence together. Same kernels, same operands, same reduction order: the arm is
7553        // bit-identical BY CONSTRUCTION rather than by tolerance, because only transport moved.
7554        let chain_device = dsv4_dspark_chain_device();
7555        let markov_rowblk = dsv4_dspark_markov_rowblk();
7556        let mut w1_row = stream.alloc_zeros::<f32>(rank).map_err(e("w1r"))?;
7557        let mut bias = stream.alloc_zeros::<f32>(vocab).map_err(e("bias"))?;
7558        // Slot 0 carries the round's input token so even the FIRST gather is device-indexed and
7559        // the two arms share one code path.
7560        let mut am_dev = stream.alloc_zeros::<i32>(block + 1).map_err(e("am"))?;
7561        {
7562            let mut dst = am_dev.slice_mut(0..1);
7563            stream
7564                .memcpy_htod(&[input_token as i32][..], &mut dst)
7565                .map_err(e("htod am0"))?;
7566        }
7567        let mut out_ids = vec![input_token];
7568        let mut margins = Vec::with_capacity(block);
7569        let mut top1_logits = Vec::with_capacity(block);
7570        let mut conf_in = stream.alloc_zeros::<f32>(hidden + rank).map_err(e("cin"))?;
7571        let mut conf_out = stream.alloc_zeros::<f32>(block).map_err(e("cout"))?;
7572        let mut confidence = Vec::with_capacity(block);
7573        let _p_mk = phase!("1i.markov_chain", prof.as_ref());
7574        for i in 0..block {
7575            {
7576                let _p = phase!("1i1.markov_w1_gather", prof.as_ref());
7577                if chain_device {
7578                    unsafe {
7579                        ck(
7580                            "markov w1 gather dev",
7581                            k::memra_dsv4_gather_row_by_idx(
7582                                dpf!(ds.markov_w1, &stream),
7583                                am_dev.device_ptr(&stream).0 as *const i32,
7584                                i as i32,
7585                                dpm!(w1_row, &stream),
7586                                rank as i32,
7587                                sp(&stream),
7588                            ),
7589                        )?;
7590                    }
7591                } else {
7592                    let prev = out_ids[i] as usize;
7593                    let src = ds.markov_w1.slice(prev * rank..(prev + 1) * rank);
7594                    stream.memcpy_dtod(&src, &mut w1_row).map_err(e("w1 cp"))?;
7595                }
7596            }
7597            {
7598                let _p = phase!("1i2.markov_bias_gemv", prof.as_ref());
7599                if markov_rowblk {
7600                    unsafe {
7601                        ck(
7602                            "dots_f32 markov rowblk",
7603                            k::memra_dsv4_dots_f32_rowblk(
7604                                dpf!(w1_row, &stream),
7605                                dp!(ds.markov_w2, &stream),
7606                                0,
7607                                dpm!(bias, &stream),
7608                                1,
7609                                rank as i32,
7610                                vocab as i32,
7611                                sp(&stream),
7612                            ),
7613                        )?;
7614                    }
7615                } else {
7616                    Self::dots(st, &w1_row, &ds.markov_w2, 1, rank, vocab, &mut bias)?;
7617                }
7618            }
7619            let _p_aa = phase!("1i3.markov_add_argmax", prof.as_ref());
7620            unsafe {
7621                ck(
7622                    "markov add dspark",
7623                    k::memra_dsv4_add_inplace(
7624                        (logits.device_ptr_mut(&stream).0 as usize + i * vocab * 4) as *mut f32,
7625                        dpf!(bias, &stream),
7626                        vocab as i64,
7627                        sp(&stream),
7628                    ),
7629                )?;
7630                ck(
7631                    "argmax dspark",
7632                    k::memra_dsv4_argmax(
7633                        (logits.device_ptr(&stream).0 as usize + i * vocab * 4) as *const f32,
7634                        vocab as i64,
7635                        (am_dev.device_ptr_mut(&stream).0 as usize + (i + 1) * 4) as *mut i32,
7636                        sp(&stream),
7637                    ),
7638                )?;
7639            }
7640            drop(_p_aa);
7641            if !chain_device {
7642                let _p_d2h = phase!("1i4.markov_argmax_D2H_SYNC", None);
7643                let mut am = [0i32; 1];
7644                let view = am_dev.slice(i + 1..i + 2);
7645                stream
7646                    .memcpy_dtoh(&view, &mut am[..])
7647                    .map_err(e("dtoh am"))?;
7648                stream.synchronize().map_err(e("sync am"))?;
7649                out_ids.push(am[0] as u32);
7650            }
7651            // confidence (M:807-815): fp32 proj of concat(PRE-norm xc row, markov_embed)
7652            {
7653                let _p = phase!("1i5.conf_in_copies", prof.as_ref());
7654                let src = xc.slice(i * hidden..(i + 1) * hidden);
7655                let mut dst = conf_in.slice_mut(0..hidden);
7656                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin x"))?;
7657                let src = w1_row.slice(0..rank);
7658                let mut dst = conf_in.slice_mut(hidden..hidden + rank);
7659                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin m"))?;
7660            }
7661            {
7662                // `Self::dots` writes y[0]; the confidence now lands in slot i of a
7663                // block-wide buffer, so the launcher is called with the offset directly
7664                // (the same pointer-arithmetic pattern the add/argmax above use). Kernel,
7665                // f64 accumulation and operand order are untouched.
7666                let _p = phase!("1i6.conf_dots", prof.as_ref());
7667                unsafe {
7668                    ck(
7669                        "dots_f32 conf dspark",
7670                        k::memra_dsv4_dots_f32(
7671                            dpf!(conf_in, &stream),
7672                            dp!(ds.conf_w, &stream),
7673                            0,
7674                            (conf_out.device_ptr_mut(&stream).0 as usize + i * 4) as *mut f32,
7675                            1,
7676                            (hidden + rank) as i32,
7677                            1,
7678                            sp(&stream),
7679                        ),
7680                    )?;
7681                }
7682            }
7683            if !chain_device {
7684                let _p = phase!("1i7.conf_D2H_SYNC", None);
7685                let mut c = [0f32; 1];
7686                let view = conf_out.slice(i..i + 1);
7687                stream
7688                    .memcpy_dtoh(&view, &mut c[..])
7689                    .map_err(e("dtoh cf"))?;
7690                stream.synchronize().map_err(e("sync cf"))?;
7691                confidence.push(c[0]);
7692            }
7693        }
7694        if chain_device {
7695            // ONE drain for the whole chain: block ids + block confidences.
7696            let _p = phase!("1i8.chain_D2H_SYNC_once", None);
7697            let mut ids = vec![0i32; block];
7698            let view = am_dev.slice(1..block + 1);
7699            stream
7700                .memcpy_dtoh(&view, &mut ids[..])
7701                .map_err(e("dtoh chain ids"))?;
7702            let mut cf = vec![0f32; block];
7703            stream
7704                .memcpy_dtoh(&conf_out, &mut cf[..])
7705                .map_err(e("dtoh chain conf"))?;
7706            stream.synchronize().map_err(e("sync chain"))?;
7707            out_ids.extend(ids.iter().map(|&x| x as u32));
7708            confidence.extend_from_slice(&cf);
7709        }
7710        drop(_p_mk);
7711        // `markov_embed`, `margins` and `top1_logits` are CAPTURE-ONLY observables, and wanting
7712        // them mid-chain was the other reason the shipped loop had to know each id on the host.
7713        // `add_inplace` touches logits row i only at step i, so every row is final once the loop
7714        // ends and one post-loop read is bit-identical to the per-step reads it replaces.
7715        let membeds: Vec<f32> = if capture {
7716            let mut m = Vec::with_capacity(block * rank);
7717            for i in 0..block {
7718                let prev = out_ids[i] as usize;
7719                m.extend_from_slice(&ds.markov_w1_host[prev * rank..(prev + 1) * rank]);
7720            }
7721            m
7722        } else {
7723            Vec::new()
7724        };
7725        let cap = if capture {
7726            let logits_post = dtoh_f32(&stream, &logits)?;
7727            for i in 0..block {
7728                let row = &logits_post[i * vocab..(i + 1) * vocab];
7729                let top = out_ids[i + 1];
7730                let mut second = f32::NEG_INFINITY;
7731                for (vv, &val) in row.iter().enumerate() {
7732                    if vv as u32 != top && val > second {
7733                        second = val;
7734                    }
7735                }
7736                margins.push(row[top as usize] - second);
7737                top1_logits.push(row[top as usize]);
7738            }
7739            Some(DsparkCaptureOut {
7740                main_hidden: cap_main_hidden.unwrap(),
7741                main_x: cap_main_x.unwrap(),
7742                block_outs,
7743                x_collapsed: dtoh_f32(&stream, &xc)?,
7744                logits_pre: cap_logits_pre.unwrap(),
7745                logits_post,
7746                markov_embed: membeds,
7747            })
7748        } else {
7749            None
7750        };
7751        Ok(DsparkProposal {
7752            out_ids,
7753            confidence,
7754            margins,
7755            top1_logits,
7756            capture: cap,
7757        })
7758    }
7759
7760    /// Device decode step + the DSpark tap into `dspark_state.taps` row `tap_row`
7761    /// (full logits — the gates' contract).
7762    pub fn decode_step_tap(
7763        &self,
7764        tok: u32,
7765        state: &mut DecodeState,
7766        dspark_state: &mut DsparkState,
7767        tap_row: usize,
7768    ) -> Res<Vec<f32>> {
7769        let DecodePath::Device { host_math } = self.decode_path else {
7770            return Err("decode_step_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7771        };
7772        let n_t = self.dspark().targets.len();
7773        let hidden = self.model.mc.n_embd as usize;
7774        let (logits, _) = self.decode_step_fast_tap(
7775            tok,
7776            state,
7777            true,
7778            host_math,
7779            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7780        )?;
7781        Ok(logits.expect("want_logits"))
7782    }
7783
7784    /// Greedy twin of [`Self::decode_step_tap`] (device argmax, 4-byte D2H).
7785    pub fn decode_step_greedy_tap(
7786        &self,
7787        tok: u32,
7788        state: &mut DecodeState,
7789        dspark_state: &mut DsparkState,
7790        tap_row: usize,
7791    ) -> Res<u32> {
7792        let DecodePath::Device { host_math } = self.decode_path else {
7793            return Err("decode_step_greedy_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7794        };
7795        let ds = self.dspark();
7796        let hidden = self.model.mc.n_embd as usize;
7797        let n_t = ds.targets.len();
7798        let (_, tok_next) = self.decode_step_fast_tap(
7799            tok,
7800            state,
7801            false,
7802            host_math,
7803            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7804        )?;
7805        Ok(tok_next)
7806    }
7807}
7808
7809// ================================================================ iteration 3, rung 4: batched T=k+1 device verify
7810//
7811// The rung that makes drafted decode pay. Design law (banked in the iteration-3 receipts
7812// before this code was written, and restated in cu/dsv4_gpu.cu's batched section):
7813//
7814//   1. BIT-EXACT against T sequential single-position steps. The greedy spec==plain
7815//      identity law is this lane's verdict instrument; if the verify pass computed
7816//      different logits than the plain pass, identity would break silently at every
7817//      near-tie and no gate could tell a port bug from a rounding fork. Achievable
7818//      because the device decode path's dense projections are OUR kernels: the batched
7819//      twins hoist the WEIGHT load across T activation rows without touching any
7820//      accumulation order. cuBLASLt is deliberately absent from this path (its m-order
7821//      changes split-K plans and shifts logits 0.18-3.08 — banked).
7822//   2. §3.1 ring hazard, exactly as GATED on the CPU oracle: window-ring writes go to
7823//      TRANSIENT rows (kvc rows [win+cap_blocks, win+cap_blocks+T)) and reads of
7824//      in-round positions are redirected there (`dsv4_build_idx_redirect`); the
7825//      compressor/indexer pending state advances in place with a snapshot + replay
7826//      payload; the append-only stores roll back by high-water mark; the drafter rings
7827//      advance for ACCEPTED positions only.
7828//   3. Where a kernel cannot batch (per-position compressor state machine, per-position
7829//      indexer top-k), the loop runs t = 0..T-1 in POSITION ORDER — the sequential
7830//      program's order, so in-round block emissions are visible to later queries exactly
7831//      as they would be sequentially.
7832//
7833// The one place uniformity is imposed: the batched sink attention takes ONE `slots`
7834// width for all T queries (the max over the round) and shorter queries' index tails are
7835// -1 pads. That is bit-inert by the pinned kernels' own pad contract (score -inf ->
7836// eval +0.0 -> skipped in both the denominator and the output chain), which is why it is
7837// legal rather than merely convenient.
7838
7839/// Per-stage batched-verify workspace: the lane-8 arena widened to `tmax` rows. Held
7840/// separately from [`StepWs`] so the gated single-position path's allocations, launches
7841/// and bytes are literally untouched by this rung.
7842pub struct VerifyWs {
7843    pub tmax: usize,
7844    h_a: CudaSlice<f32>,
7845    h_b: CudaSlice<f32>,
7846    h_rx: CudaSlice<f32>,
7847    emb: CudaSlice<f32>,
7848    mixes: CudaSlice<f32>,
7849    pre: CudaSlice<f32>,
7850    post: CudaSlice<f32>,
7851    comb: CudaSlice<f32>,
7852    y_hc: CudaSlice<f32>,
7853    x: CudaSlice<f32>,
7854    xf: CudaSlice<f32>,
7855    qr: CudaSlice<f32>,
7856    qr_b: CudaSlice<u8>,
7857    q: CudaSlice<f32>,
7858    kv: CudaSlice<f32>,
7859    qi: CudaSlice<f32>,
7860    wproj: CudaSlice<f32>,
7861    score: CudaSlice<f32>,
7862    idx: CudaSlice<i32>,
7863    idx_stride: usize,
7864    o: CudaSlice<f32>,
7865    o_b: CudaSlice<u8>,
7866    og: CudaSlice<f32>,
7867    attn_out: CudaSlice<f32>,
7868    gemm_xb: CudaSlice<u8>,
7869    raw: CudaSlice<f32>,
7870    sel: CudaSlice<i32>,
7871    selw: CudaSlice<f32>,
7872    order: CudaSlice<i32>,
7873    xq: CudaSlice<u8>,
7874    xs: CudaSlice<f32>,
7875    g1: CudaSlice<f32>,
7876    g3: CudaSlice<f32>,
7877    hbuf: CudaSlice<f32>,
7878    hq: CudaSlice<u8>,
7879    hs: CudaSlice<f32>,
7880    contrib: CudaSlice<f32>,
7881    y: CudaSlice<f32>,
7882    xb: CudaSlice<u8>,
7883    sg1: CudaSlice<f32>,
7884    sg3: CudaSlice<f32>,
7885    shbuf: CudaSlice<f32>,
7886    shb16: CudaSlice<u8>,
7887    sh_out: CudaSlice<f32>,
7888    cmp_emit: CudaSlice<f32>,
7889    cmp_shift: CudaSlice<f32>,
7890    sink_scores: CudaSlice<f32>,
7891    sink_evals: CudaSlice<f32>,
7892    sink_den: CudaSlice<f64>,
7893    head_mixes: CudaSlice<f32>,
7894    head_pre: CudaSlice<f32>,
7895    collapsed: CudaSlice<f32>,
7896    logits: CudaSlice<f32>,
7897    tok: CudaSlice<i32>,
7898    pos_dev: CudaSlice<i32>,
7899    argmax: CudaSlice<i32>,
7900    /// ring-commit staging: transient rows copied out, then scattered to ring slots
7901    /// (source and destination live in the same `kvc` allocation, so the bounce is a
7902    /// borrow requirement, not a numeric one).
7903    bounce: CudaSlice<f32>,
7904    slot_rows: CudaSlice<i32>,
7905    /// hc-mean staging for the DSpark tap (one target at a time, then `place_cols`)
7906    tap_tmp: CudaSlice<f32>,
7907}
7908
7909/// One compressor's verify-round checkpoint on device — the CPU oracle's `CompCkpt`,
7910/// device-realized: full pending snapshot + the per-position RAW (kv, score) rows that
7911/// were written, plus the store high-water mark. `dst` and `emitted` are pure functions
7912/// of the position, so nothing has to come back to the host to replay.
7913struct CmpCkptDev {
7914    kv_snap: CudaSlice<f32>,
7915    sc_snap: CudaSlice<f32>,
7916    rows_kv: CudaSlice<f32>,
7917    rows_sc: CudaSlice<f32>,
7918    latent: usize,
7919    ratio: usize,
7920    overlap: bool,
7921    n_blocks0: usize,
7922}
7923
7924/// One trunk layer's verify-round checkpoint: the two compressor payloads. The window
7925/// ring needs no payload at all — the round never wrote it (transient rows instead).
7926struct LayerCkptDev {
7927    cmp: Option<CmpCkptDev>,
7928    idx: Option<CmpCkptDev>,
7929    /// first transient row id in this layer's `kvc` (== win + cap_blocks)
7930    trans_base: usize,
7931}
7932
7933/// Whole-round verify state: the per-stage arenas + the per-layer §3.1 checkpoints.
7934pub struct VerifyState {
7935    ws: Vec<VerifyWs>,
7936    layers: Vec<LayerCkptDev>,
7937    pub tmax: usize,
7938    /// (pos0, t) of the open round; `None` between rounds. `commit_verify_dev` closes it.
7939    open: Option<(usize, usize)>,
7940    /// allocated bytes per device index (reported next to the drafter VRAM plan)
7941    pub bytes: Vec<u64>,
7942}
7943
7944impl Dsv4Gpu {
7945    /// Verify-round depth ceiling: block_size + 1 with the drafter loaded, else 0 (and
7946    /// then no transient rows are reserved anywhere — today's exact allocation).
7947    pub fn verify_tmax(&self) -> usize {
7948        self.dspark.as_ref().map(|d| d.block_size + 1).unwrap_or(0)
7949    }
7950
7951    /// Allocate the batched-verify state (arenas + §3.1 checkpoints). Requires the
7952    /// drafter (the only producer of rounds) and the device decode path.
7953    pub fn alloc_verify_state(&self) -> Res<VerifyState> {
7954        let tmax = self.verify_tmax();
7955        if tmax == 0 {
7956            return Err("alloc_verify_state needs MEMRA_DSV4_DRAFTER=dspark".into());
7957        }
7958        if !matches!(self.decode_path, DecodePath::Device { .. }) {
7959            return Err(
7960                "batched verify is a device-path rung (MEMRA_DSV4_DECODE_PATH=device)".into(),
7961            );
7962        }
7963        let d = self.model.cfg();
7964        let mc = &self.model.mc;
7965        let moe = mc.moe.as_ref().expect("moe");
7966        let hc = d.hc_mult as usize;
7967        let hidden = mc.n_embd as usize;
7968        let heads = mc.n_head as usize;
7969        let hd = d.head_dim as usize;
7970        let q_lora = d.q_lora_rank as usize;
7971        let win = d.sliding_window as usize;
7972        let o_groups = d.o_groups as usize;
7973        let o_lora = d.o_lora_rank as usize;
7974        let iheads = d.index_n_heads as usize;
7975        let ihd = d.index_head_dim as usize;
7976        let topk = moe.expert_used_count as usize;
7977        let ne = moe.expert_count as usize;
7978        let inter = moe.expert_ff_length as usize;
7979        let itopk = d.index_topk as usize;
7980        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
7981        let vocab = {
7982            let (info, _) = self.model.st.raw("head.weight").expect("head");
7983            info.shape[0] as usize
7984        };
7985        let sh_inter = {
7986            let (info, _) = self
7987                .model
7988                .st
7989                .raw("layers.0.ffn.shared_experts.w1.weight")
7990                .expect("shared w1");
7991            info.shape[0] as usize
7992        };
7993        let mut max_d = 0usize;
7994        let mut max_shift = 0usize;
7995        let mut min_ratio = usize::MAX;
7996        for st in &self.stages {
7997            for l in &st.layers {
7998                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
7999                    max_d = max_d.max(cmp.d);
8000                    if cmp.overlap {
8001                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
8002                    }
8003                    min_ratio = min_ratio.min(cmp.ratio);
8004                }
8005            }
8006        }
8007        assert!(min_ratio != usize::MAX, "no compressor layers?");
8008        let score_cap = self.max_seq / min_ratio + 1;
8009        let idx_tail = itopk.max(self.max_seq / 128 + 1);
8010        let idx_stride = win + idx_tail;
8011        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
8012        let mut bytes = vec![0u64; self.stages.len()];
8013        let mut ws = Vec::with_capacity(self.stages.len());
8014        for st in &self.stages {
8015            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx vws"))?;
8016            let s = st.gpu.stream();
8017            let acc = std::cell::Cell::new(0u64);
8018            let f = |n: usize| {
8019                acc.set(acc.get() + (n * 4) as u64);
8020                s.alloc_zeros::<f32>(n).map_err(e("vws f32"))
8021            };
8022            let b = |n: usize| {
8023                acc.set(acc.get() + n as u64);
8024                s.alloc_zeros::<u8>(n).map_err(e("vws u8"))
8025            };
8026            let i = |n: usize| {
8027                acc.set(acc.get() + (n * 4) as u64);
8028                s.alloc_zeros::<i32>(n).map_err(e("vws i32"))
8029            };
8030            let w = VerifyWs {
8031                tmax,
8032                h_a: f(tmax * hc * hidden)?,
8033                h_b: f(tmax * hc * hidden)?,
8034                h_rx: f(tmax * hc * hidden)?,
8035                emb: f(tmax * hidden)?,
8036                mixes: f(tmax * (2 + hc) * hc)?,
8037                pre: f(tmax * hc)?,
8038                post: f(tmax * hc)?,
8039                comb: f(tmax * hc * hc)?,
8040                y_hc: f(tmax * hidden)?,
8041                x: f(tmax * hidden)?,
8042                xf: f(tmax * hidden)?,
8043                qr: f(tmax * q_lora)?,
8044                qr_b: b(tmax * q_lora * 2)?,
8045                q: f(tmax * heads * hd)?,
8046                kv: f(tmax * hd)?,
8047                qi: f(tmax * iheads * ihd)?,
8048                wproj: f(tmax * iheads)?,
8049                score: f(score_cap)?,
8050                idx: i(tmax * idx_stride)?,
8051                idx_stride,
8052                o: f(tmax * heads * hd)?,
8053                o_b: b(tmax * heads * hd * 2)?,
8054                og: f(tmax * o_groups * o_lora)?,
8055                attn_out: f(tmax * hidden)?,
8056                gemm_xb: b(tmax * max_gemm_k * 2)?,
8057                raw: f(tmax * ne)?,
8058                sel: i(tmax * topk)?,
8059                selw: f(tmax * topk)?,
8060                order: i(tmax * topk)?,
8061                xq: b(tmax * hidden)?,
8062                xs: f(tmax * hidden / 128)?,
8063                g1: f(tmax * topk * inter)?,
8064                g3: f(tmax * topk * inter)?,
8065                hbuf: f(tmax * topk * inter)?,
8066                hq: b(tmax * topk * inter)?,
8067                hs: f(tmax * topk * inter / 128)?,
8068                contrib: f(tmax * topk * hidden)?,
8069                y: f(tmax * hidden)?,
8070                xb: b(tmax * hidden * 2)?,
8071                sg1: f(tmax * sh_inter)?,
8072                sg3: f(tmax * sh_inter)?,
8073                shbuf: f(tmax * sh_inter)?,
8074                shb16: b(tmax * sh_inter * 2)?,
8075                sh_out: f(tmax * hidden)?,
8076                cmp_emit: f(2 * max_d)?,
8077                cmp_shift: f(max_shift.max(1))?,
8078                sink_scores: f(tmax * heads * idx_stride)?,
8079                sink_evals: f(tmax * heads * idx_stride)?,
8080                sink_den: {
8081                    acc.set(acc.get() + (tmax * heads * 8) as u64);
8082                    s.alloc_zeros::<f64>(tmax * heads).map_err(e("vws f64"))?
8083                },
8084                head_mixes: f(tmax * hc)?,
8085                head_pre: f(tmax * hc)?,
8086                collapsed: f(tmax * hidden)?,
8087                logits: f(tmax * vocab)?,
8088                tok: i(tmax)?,
8089                pos_dev: i(tmax)?,
8090                argmax: i(tmax)?,
8091                bounce: f(tmax * hd)?,
8092                slot_rows: i(tmax)?,
8093                tap_tmp: f(tmax * hidden)?,
8094            };
8095            bytes[st.dev] += acc.get();
8096            ws.push(w);
8097        }
8098        // per-layer §3.1 checkpoints, each on the layer's own device
8099        let mut layers = Vec::with_capacity(n_trunk);
8100        for il in 0..n_trunk {
8101            let stage_i = self.layer_stage[il];
8102            let st = &self.stages[stage_i];
8103            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ckpt"))?;
8104            let stream = st.gpu.stream();
8105            let lidx = st
8106                .layers
8107                .iter()
8108                .position(|l| l.il == il as u32)
8109                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
8110            let layer = &st.layers[lidx];
8111            let cap_blocks = self.max_seq.checked_div(layer.ratio).unwrap_or(0);
8112            let mk = |cmp: &CmpDev| -> Res<CmpCkptDev> {
8113                let slots = if cmp.overlap {
8114                    2 * cmp.ratio
8115                } else {
8116                    cmp.ratio
8117                };
8118                Ok(CmpCkptDev {
8119                    kv_snap: stream
8120                        .alloc_zeros::<f32>(slots * cmp.latent)
8121                        .map_err(e("ckpt kv snap"))?,
8122                    sc_snap: stream
8123                        .alloc_zeros::<f32>(slots * cmp.latent)
8124                        .map_err(e("ckpt sc snap"))?,
8125                    rows_kv: stream
8126                        .alloc_zeros::<f32>(tmax * cmp.latent)
8127                        .map_err(e("ckpt rows kv"))?,
8128                    rows_sc: stream
8129                        .alloc_zeros::<f32>(tmax * cmp.latent)
8130                        .map_err(e("ckpt rows sc"))?,
8131                    latent: cmp.latent,
8132                    ratio: cmp.ratio,
8133                    overlap: cmp.overlap,
8134                    n_blocks0: 0,
8135                })
8136            };
8137            let cmp = match &layer.cmp {
8138                Some(c) => Some(mk(c)?),
8139                None => None,
8140            };
8141            let idxc = match &layer.idx {
8142                Some(ix) => Some(mk(&ix.cmp)?),
8143                None => None,
8144            };
8145            for c in cmp.iter().chain(idxc.iter()) {
8146                let slots = if c.overlap { 2 * c.ratio } else { c.ratio };
8147                bytes[st.dev] += ((2 * slots * c.latent + 2 * tmax * c.latent) * 4) as u64;
8148            }
8149            layers.push(LayerCkptDev {
8150                cmp,
8151                idx: idxc,
8152                trans_base: d.sliding_window as usize + cap_blocks,
8153            });
8154        }
8155        for st in &self.stages {
8156            st.gpu.stream().synchronize().map_err(e("vws sync"))?;
8157        }
8158        Ok(VerifyState {
8159            ws,
8160            layers,
8161            tmax,
8162            open: None,
8163            bytes,
8164        })
8165    }
8166
8167    /// Batched bf16 GEMV: y[m, n] = x[m, k] @ W[n, k]^T with the weight row read once.
8168    /// `xstride`/`ystride` in elements (0 == packed) — the grouped output projection is
8169    /// the only caller that needs them.
8170    #[allow(clippy::too_many_arguments)]
8171    fn gemv_m_dev(
8172        st: &Stage,
8173        w: DW,
8174        x_ptr: *const c_void,
8175        y_ptr: *mut f32,
8176        m: usize,
8177        n: usize,
8178        kdim: usize,
8179        xstride: usize,
8180        ystride: usize,
8181    ) -> Res<()> {
8182        let stream = st.gpu.stream();
8183        unsafe {
8184            match w {
8185                DW::Bf16(w_ptr) => ck(
8186                    "gemv_bf16_m dev",
8187                    k::memra_dsv4_gemv_bf16_m(
8188                        w_ptr,
8189                        x_ptr,
8190                        y_ptr,
8191                        m as i32,
8192                        n as i32,
8193                        kdim as i32,
8194                        xstride as i32,
8195                        ystride as i32,
8196                        sp(&stream),
8197                    ),
8198                ),
8199                DW::Fp8 {
8200                    codes,
8201                    scales,
8202                    sc_cols,
8203                } => ck(
8204                    "gemv_fp8_m dev",
8205                    k::memra_dsv4_gemv_fp8_m(
8206                        codes,
8207                        scales,
8208                        sc_cols,
8209                        x_ptr,
8210                        y_ptr,
8211                        m as i32,
8212                        n as i32,
8213                        kdim as i32,
8214                        xstride as i32,
8215                        ystride as i32,
8216                        sp(&stream),
8217                    ),
8218                ),
8219            }
8220        }
8221    }
8222
8223    /// f32 cvt + batched GEMV (the m=T twin of `gemm_dev`).
8224    #[allow(clippy::too_many_arguments)]
8225    fn gemm_m_dev(
8226        st: &Stage,
8227        x_f32: *const f32,
8228        xb: &mut CudaSlice<u8>,
8229        w: DW,
8230        m: usize,
8231        n: usize,
8232        kdim: usize,
8233        y_ptr: *mut f32,
8234    ) -> Res<()> {
8235        let stream = st.gpu.stream();
8236        unsafe {
8237            ck(
8238                "cvt_bf16 m dev",
8239                k::memra_dsv4_cvt_bf16(
8240                    x_f32,
8241                    xb.device_ptr_mut(&stream).0 as *mut c_void,
8242                    (m * kdim) as i64,
8243                    sp(&stream),
8244                ),
8245            )?;
8246        }
8247        Self::gemv_m_dev(
8248            st,
8249            w,
8250            xb.device_ptr(&stream).0 as *const c_void,
8251            y_ptr,
8252            m,
8253            n,
8254            kdim,
8255            0,
8256            0,
8257        )
8258    }
8259
8260    /// Island dots, batched rows, weight row hoisted. Same arm selection as `dots_dev`.
8261    #[allow(clippy::too_many_arguments)]
8262    fn dots_m_dev(
8263        &self,
8264        st: &Stage,
8265        x: *const f32,
8266        w_f32: *const c_void,
8267        w_is_bf16: i32,
8268        s: usize,
8269        kdim: usize,
8270        n: usize,
8271        y: *mut f32,
8272    ) -> Res<()> {
8273        let stream = st.gpu.stream();
8274        unsafe {
8275            if self.dots_f32 {
8276                ck(
8277                    "dots_f32acc_mrow",
8278                    k::memra_dsv4_dots_f32acc_mrow(
8279                        x,
8280                        w_f32,
8281                        w_is_bf16,
8282                        y,
8283                        s as i32,
8284                        kdim as i32,
8285                        n as i32,
8286                        sp(&stream),
8287                    ),
8288                )
8289            } else {
8290                ck(
8291                    "dots_f32_mrow",
8292                    k::memra_dsv4_dots_f32_mrow(
8293                        x,
8294                        w_f32,
8295                        w_is_bf16,
8296                        y,
8297                        s as i32,
8298                        kdim as i32,
8299                        n as i32,
8300                        sp(&stream),
8301                    ),
8302                )
8303            }
8304        }
8305    }
8306}
8307
8308impl Dsv4Gpu {
8309    /// hc_pre for T rows: the `hc_pre_dev` program with every kernel taking the row
8310    /// count (Sinkhorn either the host closure per row — byte-identity arm — or the
8311    /// one-block-per-position device twin).
8312    #[allow(clippy::too_many_arguments)]
8313    fn hc_pre_batch_dev(
8314        &self,
8315        st: &Stage,
8316        h_ptr: *const f32,
8317        fn_w: &CudaSlice<f32>,
8318        base_host: &[f32],
8319        scale_host: &[f32],
8320        base_dev: &CudaSlice<f32>,
8321        scale_dev: &CudaSlice<f32>,
8322        vws: &mut VerifyWs,
8323        t: usize,
8324        hc: usize,
8325        hidden: usize,
8326        iters: u32,
8327        hc_eps: f32,
8328        host_math: bool,
8329    ) -> Res<()> {
8330        let stream = st.gpu.stream();
8331        let w = hc * hidden;
8332        let rows = (2 + hc) * hc;
8333        self.dots_m_dev(
8334            st,
8335            h_ptr,
8336            fn_w.device_ptr(&stream).0 as *const c_void,
8337            0,
8338            t,
8339            w,
8340            rows,
8341            vws.mixes.device_ptr_mut(&stream).0 as *mut f32,
8342        )?;
8343        unsafe {
8344            ck(
8345                "rowsq_scale batch",
8346                self.rowsq_scale_arm(
8347                    h_ptr,
8348                    dpm!(vws.mixes, &stream),
8349                    t as i32,
8350                    w as i32,
8351                    rows as i32,
8352                    hc_eps,
8353                    sp(&stream),
8354                ),
8355            )?;
8356        }
8357        if host_math {
8358            let mut mixes_h = vec![0f32; t * rows];
8359            let view = vws.mixes.slice(0..t * rows);
8360            stream
8361                .memcpy_dtoh(&view, &mut mixes_h[..])
8362                .map_err(e("dtoh mixes batch"))?;
8363            stream.synchronize().map_err(e("sync mixes batch"))?;
8364            let (pre_h, post_h, comb_h) =
8365                hc_split_sinkhorn(&mixes_h, t, hc, scale_host, base_host, iters, hc_eps);
8366            let mut dp = vws.pre.slice_mut(0..t * hc);
8367            stream
8368                .memcpy_htod(&pre_h, &mut dp)
8369                .map_err(e("htod pre b"))?;
8370            let mut dp = vws.post.slice_mut(0..t * hc);
8371            stream
8372                .memcpy_htod(&post_h, &mut dp)
8373                .map_err(e("htod post b"))?;
8374            let mut dp = vws.comb.slice_mut(0..t * hc * hc);
8375            stream
8376                .memcpy_htod(&comb_h, &mut dp)
8377                .map_err(e("htod comb b"))?;
8378        } else {
8379            unsafe {
8380                ck(
8381                    "hc_sinkhorn_m",
8382                    k::memra_dsv4_hc_sinkhorn_m(
8383                        dpf!(vws.mixes, &stream),
8384                        dpf!(scale_dev, &stream),
8385                        dpf!(base_dev, &stream),
8386                        dpm!(vws.pre, &stream),
8387                        dpm!(vws.post, &stream),
8388                        dpm!(vws.comb, &stream),
8389                        t as i32,
8390                        hc as i32,
8391                        iters as i32,
8392                        hc_eps,
8393                        sp(&stream),
8394                    ),
8395                )?;
8396            }
8397        }
8398        unsafe {
8399            ck(
8400                "hc_collapse batch",
8401                k::memra_dsv4_hc_collapse(
8402                    h_ptr,
8403                    dpf!(vws.pre, &stream),
8404                    dpm!(vws.y_hc, &stream),
8405                    t as i32,
8406                    hc as i32,
8407                    hidden as i32,
8408                    sp(&stream),
8409                ),
8410            )?;
8411        }
8412        Ok(())
8413    }
8414
8415    /// Compressor advance for a whole verify round (§3.1): the two projection GEMMs run
8416    /// batched STRAIGHT INTO the checkpoint's row payload (which is both the record and
8417    /// the source of the pending writes — one copy, not two), then the pending state
8418    /// machine + emissions run t = 0..T-1 in POSITION ORDER, exactly the sequential
8419    /// program. The snapshot is taken before the first write.
8420    #[allow(clippy::too_many_arguments)]
8421    fn cmp_decode_batch_dev(
8422        &self,
8423        st: &Stage,
8424        cmp: &CmpDev,
8425        x_ptr: *const f32,
8426        t: usize,
8427        pos0: usize,
8428        hidden: usize,
8429        fc_dev: &CudaSlice<f32>,
8430        rd: usize,
8431        eps: f32,
8432        ck_dev: &mut CmpCkptDev,
8433        emit: &mut CudaSlice<f32>,
8434        shift: &mut CudaSlice<f32>,
8435        pend_kv: &mut CudaSlice<f32>,
8436        pend_score: &mut CudaSlice<f32>,
8437        store: &mut CudaSlice<f32>,
8438        row0: usize,
8439        blocks: &mut usize,
8440    ) -> Res<()> {
8441        let stream = st.gpu.stream();
8442        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
8443        // snapshot + high-water mark BEFORE anything is written
8444        stream
8445            .memcpy_dtod(pend_kv, &mut ck_dev.kv_snap)
8446            .map_err(e("ckpt snap kv"))?;
8447        stream
8448            .memcpy_dtod(pend_score, &mut ck_dev.sc_snap)
8449            .map_err(e("ckpt snap sc"))?;
8450        ck_dev.n_blocks0 = *blocks;
8451        self.dots_m_dev(
8452            st,
8453            x_ptr,
8454            cmp.wkv.device_ptr(&stream).0 as *const c_void,
8455            0,
8456            t,
8457            hidden,
8458            latent,
8459            ck_dev.rows_kv.device_ptr_mut(&stream).0 as *mut f32,
8460        )?;
8461        self.dots_m_dev(
8462            st,
8463            x_ptr,
8464            cmp.wgate.device_ptr(&stream).0 as *const c_void,
8465            0,
8466            t,
8467            hidden,
8468            latent,
8469            ck_dev.rows_sc.device_ptr_mut(&stream).0 as *mut f32,
8470        )?;
8471        for i in 0..t {
8472            let pos = pos0 + i;
8473            let slot = if cmp.overlap {
8474                ratio + pos % ratio
8475            } else {
8476                pos % ratio
8477            };
8478            {
8479                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8480                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8481                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv b"))?;
8482                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8483                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8484                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc b"))?;
8485            }
8486            if (pos + 1) % ratio != 0 {
8487                continue;
8488            }
8489            let j = pos / ratio;
8490            let nb_launch = if cmp.overlap { 2usize } else { 1 };
8491            let row_off = if cmp.overlap { d } else { 0 };
8492            unsafe {
8493                ck(
8494                    "compressor_pool batch",
8495                    k::memra_dsv4_compressor_pool(
8496                        dpf!(*pend_kv, &stream),
8497                        dpf!(*pend_score, &stream),
8498                        dpf!(cmp.ape, &stream),
8499                        dpm!(*emit, &stream),
8500                        nb_launch as i32,
8501                        ratio as i32,
8502                        d as i32,
8503                        latent as i32,
8504                        cmp.overlap as i32,
8505                        sp(&stream),
8506                    ),
8507                )?;
8508                let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
8509                let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
8510                ck(
8511                    "rmsnorm batch cmp",
8512                    self.rmsnorm_arm(
8513                        row_c,
8514                        dpf!(cmp.norm, &stream),
8515                        row_m,
8516                        1,
8517                        d as i32,
8518                        eps,
8519                        sp(&stream),
8520                    ),
8521                )?;
8522                ck(
8523                    "rope_at batch cmp",
8524                    k::memra_dsv4_rope_at(
8525                        row_m,
8526                        1,
8527                        d as i32,
8528                        rd as i32,
8529                        dpf!(fc_dev, &stream),
8530                        (j * ratio) as i32,
8531                        0,
8532                        sp(&stream),
8533                    ),
8534                )?;
8535                if cmp.rotate {
8536                    let scale = (d as f32).powf(-0.5);
8537                    ck(
8538                        "hadamard batch cmp",
8539                        k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
8540                    )?;
8541                    ck(
8542                        "fp4 batch cmp",
8543                        k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
8544                    )?;
8545                } else {
8546                    ck(
8547                        "act_quant batch cmp",
8548                        k::memra_dsv4_act_quant(
8549                            row_m,
8550                            1,
8551                            d as i64,
8552                            (d - rd) as i32,
8553                            64,
8554                            (self.variant == ActQuantVariant::ClampOnly) as i32,
8555                            sp(&stream),
8556                        ),
8557                    )?;
8558                }
8559            }
8560            {
8561                let src = emit.slice(row_off..row_off + d);
8562                let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
8563                stream
8564                    .memcpy_dtod(&src, &mut dst)
8565                    .map_err(e("emit store b"))?;
8566            }
8567            if cmp.overlap {
8568                {
8569                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8570                    let mut dst = shift.slice_mut(0..ratio * latent);
8571                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift1"))?;
8572                }
8573                {
8574                    let src = shift.slice(0..ratio * latent);
8575                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8576                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift2"))?;
8577                }
8578                {
8579                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8580                    let mut dst = shift.slice_mut(0..ratio * latent);
8581                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift3"))?;
8582                }
8583                {
8584                    let src = shift.slice(0..ratio * latent);
8585                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8586                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift4"))?;
8587                }
8588            }
8589            *blocks = j + 1;
8590        }
8591        Ok(())
8592    }
8593
8594    /// §3.1 compressor rollback: restore the snapshot, then REPLAY the committed
8595    /// positions' row writes + cur->prev shifts + block accounting. Emitted store rows
8596    /// of the committed prefix are kept as the round wrote them (bit-identical to the
8597    /// sequential twin — the batch advanced the pending in position order, so every
8598    /// emission pooled the same inputs). The CPU oracle's `rollback_replay`, verbatim.
8599    #[allow(clippy::too_many_arguments)]
8600    fn cmp_rollback_replay_dev(
8601        &self,
8602        st: &Stage,
8603        ck_dev: &CmpCkptDev,
8604        n_commit: usize,
8605        t: usize,
8606        pos0: usize,
8607        shift: &mut CudaSlice<f32>,
8608        pend_kv: &mut CudaSlice<f32>,
8609        pend_score: &mut CudaSlice<f32>,
8610        blocks: &mut usize,
8611    ) -> Res<()> {
8612        if n_commit == t {
8613            return Ok(()); // fully committed: the in-place batch state is already exact
8614        }
8615        let stream = st.gpu.stream();
8616        let (ratio, latent, overlap) = (ck_dev.ratio, ck_dev.latent, ck_dev.overlap);
8617        stream
8618            .memcpy_dtod(&ck_dev.kv_snap, pend_kv)
8619            .map_err(e("rb kv snap"))?;
8620        stream
8621            .memcpy_dtod(&ck_dev.sc_snap, pend_score)
8622            .map_err(e("rb sc snap"))?;
8623        *blocks = ck_dev.n_blocks0;
8624        for i in 0..n_commit {
8625            let pos = pos0 + i;
8626            let slot = if overlap {
8627                ratio + pos % ratio
8628            } else {
8629                pos % ratio
8630            };
8631            {
8632                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8633                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8634                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row kv"))?;
8635                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8636                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8637                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row sc"))?;
8638            }
8639            if (pos + 1) % ratio != 0 {
8640                continue;
8641            }
8642            if overlap {
8643                {
8644                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8645                    let mut dst = shift.slice_mut(0..ratio * latent);
8646                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift1"))?;
8647                }
8648                {
8649                    let src = shift.slice(0..ratio * latent);
8650                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8651                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift2"))?;
8652                }
8653                {
8654                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8655                    let mut dst = shift.slice_mut(0..ratio * latent);
8656                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift3"))?;
8657                }
8658                {
8659                    let src = shift.slice(0..ratio * latent);
8660                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8661                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift4"))?;
8662                }
8663            }
8664            *blocks += 1;
8665        }
8666        Ok(())
8667    }
8668}
8669
8670impl Dsv4Gpu {
8671    /// One trunk block, BATCHED T-position verify (§3.1). Positions pos0..pos0+t-1,
8672    /// tokens `toks`. Input h is vws.h_a (or vws.h_rx right after a stage boundary);
8673    /// output lands in vws.h_a. Window-ring writes go to the layer's TRANSIENT kvc rows
8674    /// and every query's index list is built with the redirect, so the persistent ring
8675    /// is read-only for the whole round.
8676    #[allow(clippy::too_many_arguments)]
8677    fn block_verify_dev(
8678        &self,
8679        st: &Stage,
8680        layer: &LayerDev,
8681        cache: &mut LayerCache,
8682        lck: &mut LayerCkptDev,
8683        vws: &mut VerifyWs,
8684        input_rx: bool,
8685        pos0: usize,
8686        t: usize,
8687        toks: &[u32],
8688        host_math: bool,
8689    ) -> Res<()> {
8690        let d = self.model.cfg();
8691        let mc = &self.model.mc;
8692        let hc = d.hc_mult as usize;
8693        let hidden = mc.n_embd as usize;
8694        let heads = mc.n_head as usize;
8695        let hd = d.head_dim as usize;
8696        let rd = d.qk_rope_head_dim as usize;
8697        let q_lora = d.q_lora_rank as usize;
8698        let win = d.sliding_window as usize;
8699        let o_groups = d.o_groups as usize;
8700        let o_lora = d.o_lora_rank as usize;
8701        let eps = mc.rms_eps;
8702        let iters = d.hc_sinkhorn_iters;
8703        let hc_eps = d.hc_eps;
8704        let stream = st.gpu.stream();
8705        let fc_dev: *const f32 = if layer.ratio != 0 {
8706            st.fc_yarn.device_ptr(&stream).0 as *const f32
8707        } else {
8708            st.fc_plain.device_ptr(&stream).0 as *const f32
8709        };
8710        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
8711        let trans_base = lck.trans_base;
8712        let LayerCache {
8713            kvc,
8714            n_blocks,
8715            pend_kv,
8716            pend_score,
8717            ikvc,
8718            i_blocks,
8719            ipend_kv,
8720            ipend_score,
8721        } = cache;
8722
8723        // ---- attention sub-block
8724        let h_in_ptr: *const f32 = if input_rx {
8725            vws.h_rx.device_ptr(&stream).0 as *const f32
8726        } else {
8727            vws.h_a.device_ptr(&stream).0 as *const f32
8728        };
8729        self.hc_pre_batch_dev(
8730            st,
8731            h_in_ptr,
8732            &layer.hc_attn_fn,
8733            &layer.hc_attn_base,
8734            &layer.hc_attn_scale,
8735            &layer.hc_attn_base_dev,
8736            &layer.hc_attn_scale_dev,
8737            vws,
8738            t,
8739            hc,
8740            hidden,
8741            iters,
8742            hc_eps,
8743            host_math,
8744        )?;
8745        unsafe {
8746            ck(
8747                "rmsnorm attn batch",
8748                self.rmsnorm_arm(
8749                    dpf!(vws.y_hc, &stream),
8750                    dpf!(layer.attn_norm, &stream),
8751                    dpm!(vws.x, &stream),
8752                    t as i32,
8753                    hidden as i32,
8754                    eps,
8755                    sp(&stream),
8756                ),
8757            )?;
8758        }
8759
8760        // q path (weights read once for all t rows)
8761        Self::gemm_m_dev(
8762            st,
8763            vws.x.device_ptr(&stream).0 as *const f32,
8764            &mut vws.gemm_xb,
8765            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
8766            t,
8767            q_lora,
8768            hidden,
8769            vws.qr.device_ptr_mut(&stream).0 as *mut f32,
8770        )?;
8771        unsafe {
8772            ck(
8773                "rmsnorm q batch",
8774                self.rmsnorm_arm(
8775                    dpf!(vws.qr, &stream),
8776                    dpf!(layer.q_norm, &stream),
8777                    dpm!(vws.qr, &stream),
8778                    t as i32,
8779                    q_lora as i32,
8780                    eps,
8781                    sp(&stream),
8782                ),
8783            )?;
8784            ck(
8785                "cvt qr batch",
8786                k::memra_dsv4_cvt_bf16(
8787                    dpf!(vws.qr, &stream),
8788                    vws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
8789                    (t * q_lora) as i64,
8790                    sp(&stream),
8791                ),
8792            )?;
8793        }
8794        Self::gemv_m_dev(
8795            st,
8796            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
8797            vws.qr_b.device_ptr(&stream).0 as *const c_void,
8798            vws.q.device_ptr_mut(&stream).0 as *mut f32,
8799            t,
8800            heads * hd,
8801            q_lora,
8802            0,
8803            0,
8804        )?;
8805        unsafe {
8806            ck(
8807                "headrms batch",
8808                self.headrms_arm(
8809                    dpm!(vws.q, &stream),
8810                    (t * heads) as i32,
8811                    hd as i32,
8812                    eps,
8813                    sp(&stream),
8814                ),
8815            )?;
8816            ck(
8817                "rope q batch",
8818                k::memra_dsv4_rope(
8819                    dpm!(vws.q, &stream),
8820                    t as i32,
8821                    heads as i32,
8822                    hd as i32,
8823                    rd as i32,
8824                    fc_dev,
8825                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8826                    0,
8827                    sp(&stream),
8828                ),
8829            )?;
8830        }
8831
8832        // shared K==V latent rows + window QAT, then the TRANSIENT ring write
8833        Self::gemm_m_dev(
8834            st,
8835            vws.x.device_ptr(&stream).0 as *const f32,
8836            &mut vws.gemm_xb,
8837            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
8838            t,
8839            hd,
8840            hidden,
8841            vws.kv.device_ptr_mut(&stream).0 as *mut f32,
8842        )?;
8843        unsafe {
8844            ck(
8845                "rmsnorm kv batch",
8846                self.rmsnorm_arm(
8847                    dpf!(vws.kv, &stream),
8848                    dpf!(layer.kv_norm, &stream),
8849                    dpm!(vws.kv, &stream),
8850                    t as i32,
8851                    hd as i32,
8852                    eps,
8853                    sp(&stream),
8854                ),
8855            )?;
8856            ck(
8857                "rope kv batch",
8858                k::memra_dsv4_rope(
8859                    dpm!(vws.kv, &stream),
8860                    t as i32,
8861                    1,
8862                    hd as i32,
8863                    rd as i32,
8864                    fc_dev,
8865                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8866                    0,
8867                    sp(&stream),
8868                ),
8869            )?;
8870            ck(
8871                "act_quant kv batch",
8872                k::memra_dsv4_act_quant(
8873                    dpm!(vws.kv, &stream),
8874                    t as i32,
8875                    hd as i64,
8876                    (hd - rd) as i32,
8877                    64,
8878                    clamp_only,
8879                    sp(&stream),
8880                ),
8881            )?;
8882        }
8883        {
8884            let src = vws.kv.slice(0..t * hd);
8885            let mut dst = kvc.slice_mut(trans_base * hd..(trans_base + t) * hd);
8886            stream
8887                .memcpy_dtod(&src, &mut dst)
8888                .map_err(e("transient ring write"))?;
8889        }
8890
8891        // ---- per-position index lists (redirected) + compressor advances
8892        let mut slots = win;
8893        if layer.ratio != 0 {
8894            let ratio = layer.ratio;
8895            // the round's per-position block counts (host arithmetic, exactly the
8896            // sequential program's `(pos+1)/ratio`)
8897            let nbs: Vec<usize> = (0..t).map(|i| (pos0 + i + 1) / ratio).collect();
8898            if let Some(ix) = &layer.idx {
8899                // indexer q, batched
8900                Self::gemv_m_dev(
8901                    st,
8902                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
8903                    vws.qr_b.device_ptr(&stream).0 as *const c_void,
8904                    vws.qi.device_ptr_mut(&stream).0 as *mut f32,
8905                    t,
8906                    ix.heads * ix.hd,
8907                    q_lora,
8908                    0,
8909                    0,
8910                )?;
8911                unsafe {
8912                    ck(
8913                        "rope qi batch",
8914                        k::memra_dsv4_rope(
8915                            dpm!(vws.qi, &stream),
8916                            t as i32,
8917                            ix.heads as i32,
8918                            ix.hd as i32,
8919                            rd as i32,
8920                            fc_dev,
8921                            vws.pos_dev.device_ptr(&stream).0 as *const i32,
8922                            0,
8923                            sp(&stream),
8924                        ),
8925                    )?;
8926                    let scale = (ix.hd as f32).powf(-0.5);
8927                    ck(
8928                        "hadamard qi batch",
8929                        k::memra_dsv4_hadamard(
8930                            dpm!(vws.qi, &stream),
8931                            (t * ix.heads) as i32,
8932                            ix.hd as i32,
8933                            scale,
8934                            sp(&stream),
8935                        ),
8936                    )?;
8937                    ck(
8938                        "fp4 qi batch",
8939                        k::memra_dsv4_fp4_act_quant(
8940                            dpm!(vws.qi, &stream),
8941                            (t * ix.heads) as i32,
8942                            ix.hd as i64,
8943                            ix.hd as i32,
8944                            sp(&stream),
8945                        ),
8946                    )?;
8947                }
8948                // indexer weights projection, batched
8949                Self::gemm_m_dev(
8950                    st,
8951                    vws.x.device_ptr(&stream).0 as *const f32,
8952                    &mut vws.gemm_xb,
8953                    dwsel(
8954                        self.dense_fp8,
8955                        &stream,
8956                        &ix.weights_proj,
8957                        &ix.weights_proj_fp8,
8958                    ),
8959                    t,
8960                    ix.heads,
8961                    hidden,
8962                    vws.wproj.device_ptr_mut(&stream).0 as *mut f32,
8963                )?;
8964                // indexer compressor: batched projections + position-ordered state machine
8965                {
8966                    let VerifyWs {
8967                        x,
8968                        cmp_emit,
8969                        cmp_shift,
8970                        ..
8971                    } = vws;
8972                    self.cmp_decode_batch_dev(
8973                        st,
8974                        &ix.cmp,
8975                        x.device_ptr(&stream).0 as *const f32,
8976                        t,
8977                        pos0,
8978                        hidden,
8979                        &st.fc_yarn,
8980                        rd,
8981                        eps,
8982                        lck.idx.as_mut().expect("idx ckpt"),
8983                        cmp_emit,
8984                        cmp_shift,
8985                        ipend_kv.as_mut().expect("ipend"),
8986                        ipend_score.as_mut().expect("ipend"),
8987                        ikvc.as_mut().expect("ikvc"),
8988                        0,
8989                        i_blocks,
8990                    )?;
8991                }
8992                debug_assert_eq!(*i_blocks, nbs[t - 1], "indexer block count (batch)");
8993                let kks: Vec<usize> = nbs.iter().map(|&nb| ix.topk.min(nb)).collect();
8994                let tail_max = kks.iter().cloned().max().unwrap_or(0);
8995                slots = win + tail_max;
8996                for i in 0..t {
8997                    let pos = pos0 + i;
8998                    let idx_off = i * vws.idx_stride;
8999                    unsafe {
9000                        ck(
9001                            "build_idx_redirect fine",
9002                            k::memra_dsv4_build_idx_redirect(
9003                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9004                                    as *mut i32,
9005                                pos as i32,
9006                                win as i32,
9007                                0, // fine layers: -1 pads over the whole tail; top-k overwrites
9008                                slots as i32,
9009                                pos0 as i32,
9010                                trans_base as i32,
9011                                sp(&stream),
9012                            ),
9013                        )?;
9014                    }
9015                    let nb = nbs[i];
9016                    if nb == 0 {
9017                        continue;
9018                    }
9019                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
9020                    unsafe {
9021                        ck(
9022                            "indexer_score batch",
9023                            self.indexer_score_arm(
9024                                (vws.qi.device_ptr(&stream).0 as usize + i * ix.heads * ix.hd * 4)
9025                                    as *const f32,
9026                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
9027                                (vws.wproj.device_ptr(&stream).0 as usize + i * ix.heads * 4)
9028                                    as *const f32,
9029                                wscale,
9030                                dpm!(vws.score, &stream),
9031                                1,
9032                                ix.heads as i32,
9033                                ix.hd as i32,
9034                                nb as i32,
9035                                ratio as i32,
9036                                nb as i32,
9037                                sp(&stream),
9038                            ),
9039                        )?;
9040                    }
9041                    let kk = kks[i];
9042                    if host_math {
9043                        let score_h = {
9044                            let view = vws.score.slice(0..nb);
9045                            let mut v = vec![0f32; nb];
9046                            stream
9047                                .memcpy_dtoh(&view, &mut v[..])
9048                                .map_err(e("dtoh sc b"))?;
9049                            stream.synchronize().map_err(e("sync sc b"))?;
9050                            v
9051                        };
9052                        let mut order: Vec<usize> = (0..nb).collect();
9053                        order.sort_by(|&a, &b| {
9054                            score_h[b]
9055                                .partial_cmp(&score_h[a])
9056                                .unwrap_or(std::cmp::Ordering::Equal)
9057                                .then(a.cmp(&b))
9058                        });
9059                        let cidx: Vec<i32> = order
9060                            .into_iter()
9061                            .take(kk)
9062                            .map(|j| (j + win) as i32)
9063                            .collect();
9064                        let mut dst = vws.idx.slice_mut(idx_off + win..idx_off + win + kk);
9065                        stream
9066                            .memcpy_htod(&cidx, &mut dst)
9067                            .map_err(e("htod idx b"))?;
9068                    } else {
9069                        unsafe {
9070                            let idx_tail_ptr = (vws.idx.device_ptr_mut(&stream).0 as usize
9071                                + (idx_off + win) * 4)
9072                                as *mut i32;
9073                            ck(
9074                                "topk_idx batch",
9075                                k::memra_dsv4_topk_idx(
9076                                    dpf!(vws.score, &stream),
9077                                    nb as i32,
9078                                    kk as i32,
9079                                    win as i32,
9080                                    idx_tail_ptr,
9081                                    sp(&stream),
9082                                ),
9083                            )?;
9084                        }
9085                    }
9086                }
9087            } else {
9088                let tail_max = nbs.iter().cloned().max().unwrap_or(0);
9089                slots = win + tail_max;
9090                for (i, &nb_i) in nbs.iter().enumerate() {
9091                    let pos = pos0 + i;
9092                    let idx_off = i * vws.idx_stride;
9093                    unsafe {
9094                        ck(
9095                            "build_idx_redirect coarse",
9096                            k::memra_dsv4_build_idx_redirect(
9097                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9098                                    as *mut i32,
9099                                pos as i32,
9100                                win as i32,
9101                                nb_i as i32,
9102                                slots as i32,
9103                                pos0 as i32,
9104                                trans_base as i32,
9105                                sp(&stream),
9106                            ),
9107                        )?;
9108                    }
9109                }
9110            }
9111            // attention compressor: batched projections + position-ordered state machine
9112            {
9113                let VerifyWs {
9114                    x,
9115                    cmp_emit,
9116                    cmp_shift,
9117                    ..
9118                } = vws;
9119                self.cmp_decode_batch_dev(
9120                    st,
9121                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
9122                    x.device_ptr(&stream).0 as *const f32,
9123                    t,
9124                    pos0,
9125                    hidden,
9126                    &st.fc_yarn,
9127                    rd,
9128                    eps,
9129                    lck.cmp.as_mut().expect("cmp ckpt"),
9130                    cmp_emit,
9131                    cmp_shift,
9132                    pend_kv.as_mut().expect("pend"),
9133                    pend_score.as_mut().expect("pend"),
9134                    kvc,
9135                    win,
9136                    n_blocks,
9137                )?;
9138            }
9139            debug_assert_eq!(*n_blocks, nbs[t - 1], "attn block count (batch)");
9140        } else {
9141            for i in 0..t {
9142                let pos = pos0 + i;
9143                let idx_off = i * vws.idx_stride;
9144                unsafe {
9145                    ck(
9146                        "build_idx_redirect window-only",
9147                        k::memra_dsv4_build_idx_redirect(
9148                            (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4) as *mut i32,
9149                            pos as i32,
9150                            win as i32,
9151                            -1,
9152                            win as i32,
9153                            pos0 as i32,
9154                            trans_base as i32,
9155                            sp(&stream),
9156                        ),
9157                    )?;
9158                }
9159            }
9160        }
9161
9162        // sparse sink attention, T queries in one launch (uniform `slots`, -1 pads —
9163        // bit-inert by the pinned pad contract) + per-position de-rotation
9164        let scale = (hd as f64).powf(-0.5) as f32;
9165        unsafe {
9166            if self.chains_f32 {
9167                ck(
9168                    "sink_attn_dec_mq_f32acc",
9169                    k::memra_dsv4_sink_attn_dec_mq_f32acc(
9170                        dpf!(vws.q, &stream),
9171                        dpf!(kvc, &stream),
9172                        vws.idx.device_ptr(&stream).0 as *const i32,
9173                        dpf!(layer.sink, &stream),
9174                        dpm!(vws.sink_scores, &stream),
9175                        dpm!(vws.sink_evals, &stream),
9176                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f32,
9177                        dpm!(vws.o, &stream),
9178                        t as i32,
9179                        heads as i32,
9180                        hd as i32,
9181                        slots as i32,
9182                        vws.idx_stride as i32,
9183                        scale,
9184                        sp(&stream),
9185                    ),
9186                )?;
9187            } else {
9188                ck(
9189                    "sink_attn_dec_mq",
9190                    k::memra_dsv4_sink_attn_dec_mq(
9191                        dpf!(vws.q, &stream),
9192                        dpf!(kvc, &stream),
9193                        vws.idx.device_ptr(&stream).0 as *const i32,
9194                        dpf!(layer.sink, &stream),
9195                        dpm!(vws.sink_scores, &stream),
9196                        dpm!(vws.sink_evals, &stream),
9197                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
9198                        dpm!(vws.o, &stream),
9199                        t as i32,
9200                        heads as i32,
9201                        hd as i32,
9202                        slots as i32,
9203                        vws.idx_stride as i32,
9204                        scale,
9205                        sp(&stream),
9206                    ),
9207                )?;
9208            }
9209            ck(
9210                "rope o inv batch",
9211                k::memra_dsv4_rope(
9212                    dpm!(vws.o, &stream),
9213                    t as i32,
9214                    heads as i32,
9215                    hd as i32,
9216                    rd as i32,
9217                    fc_dev,
9218                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
9219                    1,
9220                    sp(&stream),
9221                ),
9222            )?;
9223        }
9224
9225        // grouped output projection: cvt o once, then per-group strided batched GEMVs
9226        let gw = heads / o_groups * hd;
9227        unsafe {
9228            ck(
9229                "cvt o batch",
9230                k::memra_dsv4_cvt_bf16(
9231                    dpf!(vws.o, &stream),
9232                    vws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
9233                    (t * heads * hd) as i64,
9234                    sp(&stream),
9235                ),
9236            )?;
9237        }
9238        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
9239        for g in 0..o_groups {
9240            Self::gemv_m_dev(
9241                st,
9242                wo_a_dw.offset_rows(g * o_lora, gw),
9243                (vws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
9244                (vws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
9245                t,
9246                o_lora,
9247                gw,
9248                heads * hd,
9249                o_groups * o_lora,
9250            )?;
9251        }
9252        Self::gemm_m_dev(
9253            st,
9254            vws.og.device_ptr(&stream).0 as *const f32,
9255            &mut vws.gemm_xb,
9256            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
9257            t,
9258            hidden,
9259            o_groups * o_lora,
9260            vws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
9261        )?;
9262
9263        // hc_post (attention) -> vws.h_b
9264        unsafe {
9265            ck(
9266                "hc_post attn batch",
9267                k::memra_dsv4_hc_post(
9268                    dpf!(vws.attn_out, &stream),
9269                    h_in_ptr,
9270                    dpf!(vws.post, &stream),
9271                    dpf!(vws.comb, &stream),
9272                    dpm!(vws.h_b, &stream),
9273                    t as i32,
9274                    hc as i32,
9275                    hidden as i32,
9276                    sp(&stream),
9277                ),
9278            )?;
9279        }
9280
9281        // ---- ffn sub-block (input vws.h_b, output vws.h_a)
9282        let h_b_ptr = vws.h_b.device_ptr(&stream).0 as *const f32;
9283        self.hc_pre_batch_dev(
9284            st,
9285            h_b_ptr,
9286            &layer.hc_ffn_fn,
9287            &layer.hc_ffn_base,
9288            &layer.hc_ffn_scale,
9289            &layer.hc_ffn_base_dev,
9290            &layer.hc_ffn_scale_dev,
9291            vws,
9292            t,
9293            hc,
9294            hidden,
9295            iters,
9296            hc_eps,
9297            host_math,
9298        )?;
9299        unsafe {
9300            ck(
9301                "rmsnorm ffn batch",
9302                self.rmsnorm_arm(
9303                    dpf!(vws.y_hc, &stream),
9304                    dpf!(layer.ffn_norm, &stream),
9305                    dpm!(vws.xf, &stream),
9306                    t as i32,
9307                    hidden as i32,
9308                    eps,
9309                    sp(&stream),
9310                ),
9311            )?;
9312        }
9313        self.moe_verify_dev(st, layer, vws, t, toks, host_math)?;
9314        unsafe {
9315            ck(
9316                "hc_post ffn batch",
9317                k::memra_dsv4_hc_post(
9318                    dpf!(vws.y, &stream),
9319                    dpf!(vws.h_b, &stream),
9320                    dpf!(vws.post, &stream),
9321                    dpf!(vws.comb, &stream),
9322                    dpm!(vws.h_a, &stream),
9323                    t as i32,
9324                    hc as i32,
9325                    hidden as i32,
9326                    sp(&stream),
9327                ),
9328            )?;
9329        }
9330        Ok(())
9331    }
9332
9333    /// MoE for T rows: per-position routing (the hash layers need the per-position TOKEN,
9334    /// which is why a round carries a token array), then ONE launch per projection over
9335    /// the whole T x topk slot set — routed-expert weight traffic scales with T (each
9336    /// position's experts are its own) while the shared expert and the gate amortize.
9337    fn moe_verify_dev(
9338        &self,
9339        st: &Stage,
9340        layer: &LayerDev,
9341        vws: &mut VerifyWs,
9342        t: usize,
9343        toks: &[u32],
9344        host_math: bool,
9345    ) -> Res<()> {
9346        let mc = &self.model.mc;
9347        let d = self.model.cfg();
9348        let moe = mc.moe.as_ref().expect("moe");
9349        let hidden = mc.n_embd as usize;
9350        let ne = moe.expert_count as usize;
9351        let topk = moe.expert_used_count as usize;
9352        let inter = moe.expert_ff_length as usize;
9353        let limit = d.swiglu_limit;
9354        let stream = st.gpu.stream();
9355        let kind = match layer.expert_kind {
9356            ExpertKind::Nvfp4 => 0i32,
9357            ExpertKind::Mxfp4 => 1i32,
9358        };
9359        let wstride = (inter * hidden / 2) as i64;
9360        let sstride = match layer.expert_kind {
9361            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
9362            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
9363        };
9364        let slots = t * topk;
9365
9366        self.dots_m_dev(
9367            st,
9368            vws.xf.device_ptr(&stream).0 as *const f32,
9369            layer.gate_w.device_ptr(&stream).0 as *const c_void,
9370            0,
9371            t,
9372            hidden,
9373            ne,
9374            vws.raw.device_ptr_mut(&stream).0 as *mut f32,
9375        )?;
9376        if host_math {
9377            let raw_h = {
9378                let view = vws.raw.slice(0..t * ne);
9379                let mut v = vec![0f32; t * ne];
9380                stream
9381                    .memcpy_dtoh(&view, &mut v[..])
9382                    .map_err(e("dtoh raw b"))?;
9383                stream.synchronize().map_err(e("sync raw b"))?;
9384                v
9385            };
9386            let (indices, weights) =
9387                Self::route_host(layer, &raw_h, toks, t, ne, topk, d.routed_scaling_factor);
9388            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
9389            let mut order = vec![0i32; t * topk];
9390            for p in 0..t {
9391                let mut o: Vec<i32> = (0..topk as i32).collect();
9392                o.sort_by_key(|&s| indices[p * topk + s as usize]);
9393                order[p * topk..(p + 1) * topk].copy_from_slice(&o);
9394            }
9395            let mut dst = vws.sel.slice_mut(0..t * topk);
9396            stream
9397                .memcpy_htod(&sel, &mut dst)
9398                .map_err(e("htod sel b"))?;
9399            let mut dst = vws.selw.slice_mut(0..t * topk);
9400            stream
9401                .memcpy_htod(&weights, &mut dst)
9402                .map_err(e("htod selw b"))?;
9403            let mut dst = vws.order.slice_mut(0..t * topk);
9404            stream
9405                .memcpy_htod(&order, &mut dst)
9406                .map_err(e("htod order b"))?;
9407        } else {
9408            unsafe {
9409                ck(
9410                    "route_m",
9411                    k::memra_dsv4_route_m(
9412                        dpf!(vws.raw, &stream),
9413                        layer
9414                            .gate_bias_dev
9415                            .as_ref()
9416                            .map(|b| b.device_ptr(&stream).0 as *const f32)
9417                            .unwrap_or(std::ptr::null()),
9418                        layer
9419                            .tid2eid_dev
9420                            .as_ref()
9421                            .map(|x| x.device_ptr(&stream).0 as *const i32)
9422                            .unwrap_or(std::ptr::null()),
9423                        vws.tok.device_ptr(&stream).0 as *const i32,
9424                        t as i32,
9425                        ne as i32,
9426                        topk as i32,
9427                        d.routed_scaling_factor,
9428                        vws.sel.device_ptr_mut(&stream).0 as *mut i32,
9429                        vws.selw.device_ptr_mut(&stream).0 as *mut f32,
9430                        vws.order.device_ptr_mut(&stream).0 as *mut i32,
9431                        sp(&stream),
9432                    ),
9433                )?;
9434            }
9435        }
9436
9437        unsafe {
9438            ck(
9439                "act_quant_fp8 x batch",
9440                k::memra_dsv4_act_quant_fp8(
9441                    dpf!(vws.xf, &stream),
9442                    vws.xq.device_ptr_mut(&stream).0 as *mut c_void,
9443                    dpm!(vws.xs, &stream),
9444                    t as i32,
9445                    hidden as i32,
9446                    sp(&stream),
9447                ),
9448            )?;
9449            for (proj, dst) in [(0i32, &mut vws.g1), (2i32, &mut vws.g3)] {
9450                ck(
9451                    "fp4_gemm_sel_g w1/w3",
9452                    k::memra_dsv4_fp4_gemm_sel_g(
9453                        dp!(vws.xq, &stream),
9454                        dpf!(vws.xs, &stream),
9455                        dp!(layer.experts_w, &stream),
9456                        dp!(layer.experts_sc, &stream),
9457                        dpf!(layer.experts_s2_dev, &stream),
9458                        vws.sel.device_ptr(&stream).0 as *const i32,
9459                        proj,
9460                        0,
9461                        kind,
9462                        dpm!(*dst, &stream),
9463                        slots as i32,
9464                        inter as i32,
9465                        hidden as i32,
9466                        wstride,
9467                        sstride,
9468                        topk as i32,
9469                        sp(&stream),
9470                    ),
9471                )?;
9472            }
9473            ck(
9474                "swiglu batch",
9475                k::memra_dsv4_swiglu(
9476                    dpf!(vws.g1, &stream),
9477                    dpf!(vws.g3, &stream),
9478                    dpm!(vws.hbuf, &stream),
9479                    slots as i32,
9480                    inter as i32,
9481                    limit,
9482                    vws.selw.device_ptr(&stream).0 as *const f32,
9483                    sp(&stream),
9484                ),
9485            )?;
9486            ck(
9487                "act_quant_fp8 h batch",
9488                k::memra_dsv4_act_quant_fp8(
9489                    dpf!(vws.hbuf, &stream),
9490                    vws.hq.device_ptr_mut(&stream).0 as *mut c_void,
9491                    dpm!(vws.hs, &stream),
9492                    slots as i32,
9493                    inter as i32,
9494                    sp(&stream),
9495                ),
9496            )?;
9497            ck(
9498                "fp4_gemm_sel_g w2",
9499                k::memra_dsv4_fp4_gemm_sel_g(
9500                    dp!(vws.hq, &stream),
9501                    dpf!(vws.hs, &stream),
9502                    dp!(layer.experts_w, &stream),
9503                    dp!(layer.experts_sc, &stream),
9504                    dpf!(layer.experts_s2_dev, &stream),
9505                    vws.sel.device_ptr(&stream).0 as *const i32,
9506                    1,
9507                    1,
9508                    kind,
9509                    dpm!(vws.contrib, &stream),
9510                    slots as i32,
9511                    hidden as i32,
9512                    inter as i32,
9513                    wstride,
9514                    sstride,
9515                    0,
9516                    sp(&stream),
9517                ),
9518            )?;
9519            ck(
9520                "combine_rows_m",
9521                k::memra_dsv4_combine_rows_m(
9522                    dpf!(vws.contrib, &stream),
9523                    vws.order.device_ptr(&stream).0 as *const i32,
9524                    topk as i32,
9525                    dpm!(vws.y, &stream),
9526                    hidden as i64,
9527                    t as i32,
9528                    sp(&stream),
9529                ),
9530            )?;
9531            ck(
9532                "cvt xb batch",
9533                k::memra_dsv4_cvt_bf16(
9534                    dpf!(vws.xf, &stream),
9535                    vws.xb.device_ptr_mut(&stream).0 as *mut c_void,
9536                    (t * hidden) as i64,
9537                    sp(&stream),
9538                ),
9539            )?;
9540        }
9541        let sh_inter = vws.sg1.len() / vws.tmax;
9542        Self::gemv_m_dev(
9543            st,
9544            dwsel(
9545                self.dense_fp8,
9546                &stream,
9547                &layer.shared_w[0],
9548                &layer.shared_fp8[0],
9549            ),
9550            vws.xb.device_ptr(&stream).0 as *const c_void,
9551            vws.sg1.device_ptr_mut(&stream).0 as *mut f32,
9552            t,
9553            sh_inter,
9554            hidden,
9555            0,
9556            0,
9557        )?;
9558        Self::gemv_m_dev(
9559            st,
9560            dwsel(
9561                self.dense_fp8,
9562                &stream,
9563                &layer.shared_w[2],
9564                &layer.shared_fp8[2],
9565            ),
9566            vws.xb.device_ptr(&stream).0 as *const c_void,
9567            vws.sg3.device_ptr_mut(&stream).0 as *mut f32,
9568            t,
9569            sh_inter,
9570            hidden,
9571            0,
9572            0,
9573        )?;
9574        unsafe {
9575            ck(
9576                "swiglu sh batch",
9577                k::memra_dsv4_swiglu(
9578                    dpf!(vws.sg1, &stream),
9579                    dpf!(vws.sg3, &stream),
9580                    dpm!(vws.shbuf, &stream),
9581                    t as i32,
9582                    sh_inter as i32,
9583                    limit,
9584                    std::ptr::null(),
9585                    sp(&stream),
9586                ),
9587            )?;
9588            ck(
9589                "cvt sh batch",
9590                k::memra_dsv4_cvt_bf16(
9591                    dpf!(vws.shbuf, &stream),
9592                    vws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
9593                    (t * sh_inter) as i64,
9594                    sp(&stream),
9595                ),
9596            )?;
9597        }
9598        Self::gemv_m_dev(
9599            st,
9600            dwsel(
9601                self.dense_fp8,
9602                &stream,
9603                &layer.shared_w[1],
9604                &layer.shared_fp8[1],
9605            ),
9606            vws.shb16.device_ptr(&stream).0 as *const c_void,
9607            vws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
9608            t,
9609            hidden,
9610            sh_inter,
9611            0,
9612            0,
9613        )?;
9614        unsafe {
9615            ck(
9616                "add shared batch",
9617                k::memra_dsv4_add_inplace(
9618                    dpm!(vws.y, &stream),
9619                    dpf!(vws.sh_out, &stream),
9620                    (t * hidden) as i64,
9621                    sp(&stream),
9622                ),
9623            )?;
9624        }
9625        Ok(())
9626    }
9627
9628    /// Head for T rows: the `head_logits_dev` program with the row count, and the vocab
9629    /// dots on the batched island kernel so the 1.06 GiB head slab is read ONCE per round
9630    /// instead of once per verified position.
9631    fn head_logits_batch_dev(&self, vws: &mut VerifyWs, t: usize, host_math: bool) -> Res<()> {
9632        let d = self.model.cfg();
9633        let mc = &self.model.mc;
9634        let hc = d.hc_mult as usize;
9635        let hidden = mc.n_embd as usize;
9636        let eps = mc.rms_eps;
9637        let last = self.stages.len() - 1;
9638        let st = &self.stages[last];
9639        let stream = st.gpu.stream();
9640        let w = hc * hidden;
9641        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
9642        let norm = st.trunk_norm.as_ref().expect("trunk norm");
9643        let vocab = vws.logits.len() / vws.tmax;
9644        self.dots_m_dev(
9645            st,
9646            vws.h_a.device_ptr(&stream).0 as *const f32,
9647            fn_w.device_ptr(&stream).0 as *const c_void,
9648            0,
9649            t,
9650            w,
9651            hc,
9652            vws.head_mixes.device_ptr_mut(&stream).0 as *mut f32,
9653        )?;
9654        unsafe {
9655            ck(
9656                "rowsq head batch",
9657                self.rowsq_scale_arm(
9658                    dpf!(vws.h_a, &stream),
9659                    dpm!(vws.head_mixes, &stream),
9660                    t as i32,
9661                    w as i32,
9662                    hc as i32,
9663                    eps,
9664                    sp(&stream),
9665                ),
9666            )?;
9667        }
9668        if host_math {
9669            let mut mixes_h = vec![0f32; t * hc];
9670            let view = vws.head_mixes.slice(0..t * hc);
9671            stream
9672                .memcpy_dtoh(&view, &mut mixes_h[..])
9673                .map_err(e("dtoh head mixes b"))?;
9674            stream.synchronize().map_err(e("sync head mixes b"))?;
9675            for p in 0..t {
9676                for c in 0..hc {
9677                    let m = mixes_h[p * hc + c];
9678                    mixes_h[p * hc + c] =
9679                        sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
9680                }
9681            }
9682            let mut dst = vws.head_pre.slice_mut(0..t * hc);
9683            stream
9684                .memcpy_htod(&mixes_h, &mut dst)
9685                .map_err(e("htod head pre b"))?;
9686        } else {
9687            unsafe {
9688                ck(
9689                    "hc_head_pre_m",
9690                    k::memra_dsv4_hc_head_pre_m(
9691                        dpf!(vws.head_mixes, &stream),
9692                        st.hc_head_scale_dev
9693                            .as_ref()
9694                            .expect("head scale dev")
9695                            .device_ptr(&stream)
9696                            .0 as *const f32,
9697                        st.hc_head_base_dev
9698                            .as_ref()
9699                            .expect("head base dev")
9700                            .device_ptr(&stream)
9701                            .0 as *const f32,
9702                        dpm!(vws.head_pre, &stream),
9703                        t as i32,
9704                        hc as i32,
9705                        d.hc_eps,
9706                        sp(&stream),
9707                    ),
9708                )?;
9709            }
9710        }
9711        unsafe {
9712            ck(
9713                "hc_collapse head batch",
9714                k::memra_dsv4_hc_collapse(
9715                    dpf!(vws.h_a, &stream),
9716                    dpf!(vws.head_pre, &stream),
9717                    dpm!(vws.collapsed, &stream),
9718                    t as i32,
9719                    hc as i32,
9720                    hidden as i32,
9721                    sp(&stream),
9722                ),
9723            )?;
9724            ck(
9725                "rmsnorm head batch",
9726                self.rmsnorm_arm(
9727                    dpf!(vws.collapsed, &stream),
9728                    dpf!(norm, &stream),
9729                    dpm!(vws.collapsed, &stream),
9730                    t as i32,
9731                    hidden as i32,
9732                    eps,
9733                    sp(&stream),
9734                ),
9735            )?;
9736        }
9737        let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
9738        self.dots_m_dev(
9739            st,
9740            vws.collapsed.device_ptr(&stream).0 as *const f32,
9741            head_ptr,
9742            1,
9743            t,
9744            hidden,
9745            vocab,
9746            vws.logits.device_ptr_mut(&stream).0 as *mut f32,
9747        )?;
9748        Ok(())
9749    }
9750}
9751
9752/// One verify round's bookkeeping (the device twin of `spec_oracle::SpecRound`).
9753pub struct SpecRoundGpu {
9754    pub start_pos: usize,
9755    pub drafts: Vec<u32>,
9756    pub accepts: usize,
9757    pub verified: usize,
9758    /// batch depth actually forwarded this round (T = 1 + verifiable drafts)
9759    pub t_batch: usize,
9760    /// STRUCTURAL depth ceiling for this round: min(k_drafts + 1, MEMRA_DSV4_SPEC_DEPTH,
9761    /// vstate.tmax) -- i.e. `t_batch` before the n_new budget is applied. `t_batch < t_cap`
9762    /// is exactly "the budget truncated this round", which is what `carry_pending` keys on.
9763    pub t_cap: usize,
9764    /// The drafter's fp32 per-slot confidence for this round's proposal (pre-sigmoid
9765    /// logits; the head is supervised on c* = 1 - TV, i.e. conditional acceptance
9766    /// probability). Banked per round so the DSpark Algorithm-1 scheduler can be scored
9767    /// offline against measured round costs -- never consumed by the round itself.
9768    pub confidence: Vec<f32>,
9769    /// tokens this round contributed to the output stream (head + accepted drafts)
9770    pub emitted: usize,
9771    /// wall time of the whole round — proposal, batched verify, commit/rollback, drafter
9772    /// ring advance — with the drafter stream synchronized at the round boundary so no
9773    /// work leaks into the next round's measurement. The A/B instrument.
9774    pub round_us: u64,
9775}
9776
9777pub struct SpecRunGpu {
9778    pub tokens: Vec<u32>,
9779    pub rounds: Vec<SpecRoundGpu>,
9780}
9781
9782impl Dsv4Gpu {
9783    /// Batched T=k+1 verify forward (§3.1): ONE trunk pass over `toks` at positions
9784    /// state.pos .. state.pos+T-1, logits for EVERY position (the accept walk needs them
9785    /// all), state advanced PROVISIONALLY for all T. Exactly one
9786    /// [`Self::commit_verify_dev`] must follow, which makes the accepted prefix permanent
9787    /// and rolls the rest back. The DSpark trunk tap is written for all T rows when
9788    /// `taps` is Some (rows 0..T-1 of the drafter's taps buffer).
9789    ///
9790    /// Returns (logits `[T, vocab]` when `want_logits`, per-position argmax `[T]`).
9791    pub fn verify_batch_dev(
9792        &self,
9793        toks: &[u32],
9794        state: &mut DecodeState,
9795        vstate: &mut VerifyState,
9796        taps: Option<&mut CudaSlice<f32>>,
9797        want_logits: bool,
9798    ) -> Res<(Option<Vec<f32>>, Vec<u32>)> {
9799        let DecodePath::Device { host_math } = self.decode_path else {
9800            return Err("verify_batch_dev requires MEMRA_DSV4_DECODE_PATH=device".into());
9801        };
9802        let mc = &self.model.mc;
9803        let d = self.model.cfg();
9804        let t = toks.len();
9805        assert!(
9806            t >= 1 && t <= vstate.tmax,
9807            "round depth {t} > tmax {}",
9808            vstate.tmax
9809        );
9810        assert!(vstate.open.is_none(), "verify_batch_dev with an open round");
9811        let pos0 = state.pos;
9812        assert!(pos0 > 0, "batched verify needs prefill_with_cache first");
9813        assert!(
9814            pos0 + t <= self.max_seq,
9815            "round [{pos0}, {}) exceeds max_seq {}",
9816            pos0 + t,
9817            self.max_seq
9818        );
9819        let hidden = mc.n_embd as usize;
9820        let hc = d.hc_mult as usize;
9821        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
9822        let tok_i32: Vec<i32> = toks.iter().map(|&x| x as i32).collect();
9823        let pos_i32: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
9824
9825        // per-stage round constants (the hash layers read the token array; every layer's
9826        // ropes read the position array — both live on whichever stage the layer does)
9827        for (si, st) in self.stages.iter().enumerate() {
9828            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx round"))?;
9829            let stream = st.gpu.stream();
9830            let vws = &mut vstate.ws[si];
9831            let mut dst = vws.tok.slice_mut(0..t);
9832            stream
9833                .memcpy_htod(&tok_i32, &mut dst)
9834                .map_err(e("htod tok round"))?;
9835            let mut dst = vws.pos_dev.slice_mut(0..t);
9836            stream
9837                .memcpy_htod(&pos_i32, &mut dst)
9838                .map_err(e("htod pos round"))?;
9839        }
9840
9841        // stage 0: tokens -> embed rows -> hc state
9842        {
9843            let st0 = &self.stages[0];
9844            st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0 round"))?;
9845            let stream0 = st0.gpu.stream();
9846            let vws0 = &mut vstate.ws[0];
9847            unsafe {
9848                ck(
9849                    "embed_rows batch",
9850                    k::memra_dsv4_embed_rows(
9851                        st0.embed
9852                            .as_ref()
9853                            .expect("embed on stage 0")
9854                            .device_ptr(&stream0)
9855                            .0 as *const c_void,
9856                        vws0.tok.device_ptr(&stream0).0 as *const i32,
9857                        dpm!(vws0.emb, &stream0),
9858                        t as i32,
9859                        hidden as i32,
9860                        sp(&stream0),
9861                    ),
9862                )?;
9863                ck(
9864                    "repeat_hc batch",
9865                    k::memra_dsv4_repeat_hc(
9866                        dpf!(vws0.emb, &stream0),
9867                        dpm!(vws0.h_a, &stream0),
9868                        t as i32,
9869                        hc as i32,
9870                        hidden as i32,
9871                        sp(&stream0),
9872                    ),
9873                )?;
9874            }
9875        }
9876
9877        let targets = self.dspark.as_ref().map(|ds| ds.targets.clone());
9878        let n_t = targets.as_ref().map(|x| x.len()).unwrap_or(0);
9879        let mut taps = taps;
9880        let mut cur_stage = 0usize;
9881        let mut input_rx = false;
9882        for il in 0..n_trunk {
9883            let stage = self.layer_stage[il];
9884            if stage != cur_stage {
9885                let bytes = t * hc * hidden * std::mem::size_of::<f32>();
9886                let src_stream = self.stages[cur_stage].gpu.stream();
9887                let dst_stream = self.stages[stage].gpu.stream();
9888                let (ws_src, ws_dst) = vstate.ws.split_at_mut(stage);
9889                let src_ws = &ws_src[cur_stage];
9890                let dst_ws = &mut ws_dst[0];
9891                self.stages[cur_stage]
9892                    .gpu
9893                    .ctx
9894                    .bind_to_thread()
9895                    .map_err(e("bind tx round"))?;
9896                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
9897                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
9898                unsafe {
9899                    cudarc::driver::result::memcpy_peer_async(
9900                        self.stages[stage].gpu.ctx.cu_ctx(),
9901                        dp_,
9902                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
9903                        sp_,
9904                        bytes,
9905                        src_stream.cu_stream(),
9906                    )
9907                    .map_err(e("peer copy h round"))?;
9908                }
9909                let bnd = stage - 1;
9910                self.boundary_ev[bnd]
9911                    .record(&src_stream)
9912                    .map_err(e("ev record round"))?;
9913                dst_stream
9914                    .wait(&self.boundary_ev[bnd])
9915                    .map_err(e("ev wait round"))?;
9916                self.stages[stage]
9917                    .gpu
9918                    .ctx
9919                    .bind_to_thread()
9920                    .map_err(e("bind rx round"))?;
9921                cur_stage = stage;
9922                input_rx = true;
9923            }
9924            let st = &self.stages[stage];
9925            let lidx = st
9926                .layers
9927                .iter()
9928                .position(|l| l.il == il as u32)
9929                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
9930            self.block_verify_dev(
9931                st,
9932                &st.layers[lidx],
9933                &mut state.caches[il],
9934                &mut vstate.layers[il],
9935                &mut vstate.ws[stage],
9936                input_rx,
9937                pos0,
9938                t,
9939                toks,
9940                host_math,
9941            )?;
9942            input_rx = false;
9943            // DSpark trunk tap for all T rows (capture only)
9944            if let (Some(tp), Some(tg)) = (taps.as_mut(), targets.as_ref()) {
9945                if let Some(kk) = tg.iter().position(|&tl| tl == il) {
9946                    let stream = self.stages[stage].gpu.stream();
9947                    let vws = &mut vstate.ws[stage];
9948                    unsafe {
9949                        ck(
9950                            "hc_mean tap batch",
9951                            k::memra_dsv4_hc_mean(
9952                                dpf!(vws.h_a, &stream),
9953                                dpm!(vws.tap_tmp, &stream),
9954                                t as i32,
9955                                hc as i32,
9956                                hidden as i32,
9957                                sp(&stream),
9958                            ),
9959                        )?;
9960                        ck(
9961                            "place_cols tap batch",
9962                            k::memra_dsv4_place_cols(
9963                                dpf!(vws.tap_tmp, &stream),
9964                                dpm!(**tp, &stream),
9965                                t as i32,
9966                                hidden as i32,
9967                                (n_t * hidden) as i64,
9968                                (kk * hidden) as i64,
9969                                sp(&stream),
9970                            ),
9971                        )?;
9972                    }
9973                }
9974            }
9975        }
9976
9977        let last = self.stages.len() - 1;
9978        assert_eq!(cur_stage, last, "device path expects the head stage last");
9979        self.head_logits_batch_dev(&mut vstate.ws[last], t, host_math)?;
9980        let stream_last = self.stages[last].gpu.stream();
9981        let vws = &mut vstate.ws[last];
9982        let vocab = vws.logits.len() / vws.tmax;
9983        let logits = if want_logits {
9984            let mut v = vec![0f32; t * vocab];
9985            let view = vws.logits.slice(0..t * vocab);
9986            stream_last
9987                .memcpy_dtoh(&view, &mut v[..])
9988                .map_err(e("dtoh logits batch"))?;
9989            stream_last.synchronize().map_err(e("sync logits batch"))?;
9990            Some(v)
9991        } else {
9992            None
9993        };
9994        let mut am = vec![0i32; t];
9995        if let Some(lg) = &logits {
9996            for (i, slot) in am.iter_mut().enumerate() {
9997                let row = &lg[i * vocab..(i + 1) * vocab];
9998                let mut best = 0usize;
9999                for j in 1..vocab {
10000                    if row[j] > row[best] {
10001                        best = j;
10002                    }
10003                }
10004                *slot = best as i32;
10005            }
10006        } else {
10007            unsafe {
10008                for i in 0..t {
10009                    ck(
10010                        "argmax batch",
10011                        k::memra_dsv4_argmax(
10012                            (vws.logits.device_ptr(&stream_last).0 as usize + i * vocab * 4)
10013                                as *const f32,
10014                            vocab as i64,
10015                            (vws.argmax.device_ptr_mut(&stream_last).0 as usize + i * 4)
10016                                as *mut i32,
10017                            sp(&stream_last),
10018                        ),
10019                    )?;
10020                }
10021            }
10022            let view = vws.argmax.slice(0..t);
10023            stream_last
10024                .memcpy_dtoh(&view, &mut am[..])
10025                .map_err(e("dtoh argmax batch"))?;
10026            stream_last.synchronize().map_err(e("sync argmax batch"))?;
10027        }
10028        vstate.open = Some((pos0, t));
10029        Ok((logits, am.into_iter().map(|x| x as u32).collect()))
10030    }
10031
10032    /// Commit the first `n_commit` positions of the open round and roll the rest back
10033    /// (§3.1 invariant: every trunk cache class ends bit-identical to plain sequential
10034    /// decode of exactly the committed positions). Ring slots take their transient rows;
10035    /// the compressors replay; the append-only stores fall back to their high-water mark.
10036    pub fn commit_verify_dev(
10037        &self,
10038        state: &mut DecodeState,
10039        vstate: &mut VerifyState,
10040        n_commit: usize,
10041    ) -> Res<()> {
10042        let (pos0, t) = vstate
10043            .open
10044            .take()
10045            .ok_or_else(|| "commit_verify_dev without an open round".to_string())?;
10046        assert!(
10047            n_commit >= 1 && n_commit <= t,
10048            "commit {n_commit} outside round width {t}"
10049        );
10050        let d = self.model.cfg();
10051        let mc = &self.model.mc;
10052        let hd = d.head_dim as usize;
10053        let win = d.sliding_window as usize;
10054        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
10055        let slot_rows: Vec<i32> = (0..n_commit).map(|j| ((pos0 + j) % win) as i32).collect();
10056        for il in 0..n_trunk {
10057            let stage = self.layer_stage[il];
10058            let st = &self.stages[stage];
10059            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx commit"))?;
10060            let stream = st.gpu.stream();
10061            let lck = &mut vstate.layers[il];
10062            let vws = &mut vstate.ws[stage];
10063            let cache = &mut state.caches[il];
10064            let trans_base = lck.trans_base;
10065            // ring commit: bounce out the transient rows (same allocation as the ring),
10066            // then scatter to slot (pos0+j) % win in one launch
10067            {
10068                let src = cache
10069                    .kvc
10070                    .slice(trans_base * hd..(trans_base + n_commit) * hd);
10071                let mut dst = vws.bounce.slice_mut(0..n_commit * hd);
10072                stream
10073                    .memcpy_dtod(&src, &mut dst)
10074                    .map_err(e("commit bounce"))?;
10075            }
10076            {
10077                let mut dst = vws.slot_rows.slice_mut(0..n_commit);
10078                stream
10079                    .memcpy_htod(&slot_rows, &mut dst)
10080                    .map_err(e("htod slot rows"))?;
10081            }
10082            unsafe {
10083                ck(
10084                    "scatter_rows commit",
10085                    k::memra_dsv4_scatter_rows(
10086                        dpf!(vws.bounce, &stream),
10087                        dpm!(cache.kvc, &stream),
10088                        vws.slot_rows.device_ptr(&stream).0 as *const i32,
10089                        n_commit as i32,
10090                        hd as i32,
10091                        sp(&stream),
10092                    ),
10093                )?;
10094            }
10095            if let Some(ckd) = &lck.cmp {
10096                self.cmp_rollback_replay_dev(
10097                    st,
10098                    ckd,
10099                    n_commit,
10100                    t,
10101                    pos0,
10102                    &mut vws.cmp_shift,
10103                    cache.pend_kv.as_mut().expect("pend kv"),
10104                    cache.pend_score.as_mut().expect("pend sc"),
10105                    &mut cache.n_blocks,
10106                )?;
10107            }
10108            if let Some(ckd) = &lck.idx {
10109                self.cmp_rollback_replay_dev(
10110                    st,
10111                    ckd,
10112                    n_commit,
10113                    t,
10114                    pos0,
10115                    &mut vws.cmp_shift,
10116                    cache.ipend_kv.as_mut().expect("ipend kv"),
10117                    cache.ipend_score.as_mut().expect("ipend sc"),
10118                    &mut cache.i_blocks,
10119                )?;
10120            }
10121        }
10122        for st in &self.stages {
10123            st.gpu
10124                .ctx
10125                .bind_to_thread()
10126                .map_err(e("bind ctx commit sync"))?;
10127            st.gpu.stream().synchronize().map_err(e("commit sync"))?;
10128        }
10129        state.pos = pos0 + n_commit;
10130        Ok(())
10131    }
10132
10133    /// The device propose-then-verify greedy loop with BATCHED verification — the
10134    /// engine-side twin of `spec_oracle::run_spec_greedy_batched`, including its
10135    /// round/budget accounting (the budget-truncated final round and its pending-carry
10136    /// no-propose tail), so proposal streams and token streams are comparable
10137    /// item-for-item with the CPU oracle's.
10138    ///
10139    /// Greedy law: the trunk's own argmax is ALWAYS the emitted token, so the output
10140    /// stream is plain greedy by construction — and because every batched kernel on this
10141    /// path is bit-exact against its single-position twin, that identity is byte-exact on
10142    /// device too, not merely mathematical.
10143    /// Reads the `MEMRA_DSV4_SPEC_DEPTH` knob and delegates to
10144    /// [`Self::spec_greedy_batched_depth`]. Every existing gate and bench calls this form,
10145    /// so their behaviour is decided by the environment exactly as before.
10146    pub fn spec_greedy_batched_with(
10147        &self,
10148        prompt: &[u32],
10149        n_new: usize,
10150        state: &mut DecodeState,
10151        dstate: &mut DsparkState,
10152        vstate: &mut VerifyState,
10153    ) -> Res<SpecRunGpu> {
10154        // MEMRA_DSV4_SPEC_DEPTH=T: structural cap on the batched verify depth (T rows =
10155        // 1 head + T-1 verified drafts). Unset or 0 => no cap, which reproduces the
10156        // pre-knob driver exactly. Clamped to >= 1 so a typo cannot ask for a zero-row
10157        // verify.
10158        let depth_cap = std::env::var("MEMRA_DSV4_SPEC_DEPTH")
10159            .ok()
10160            .and_then(|v| v.trim().parse::<usize>().ok())
10161            .filter(|t| *t > 0)
10162            .unwrap_or(usize::MAX)
10163            .max(1);
10164        if depth_cap != usize::MAX {
10165            println!("[spec] verify depth capped at T={depth_cap} (MEMRA_DSV4_SPEC_DEPTH)");
10166        }
10167        self.spec_greedy_batched_depth(prompt, n_new, state, dstate, vstate, depth_cap)
10168    }
10169
10170    /// [`Self::spec_greedy_batched_with`] with the verify-depth ceiling passed explicitly.
10171    /// `usize::MAX` means "no cap" (the drafter's own `block_size + 1`).
10172    ///
10173    /// Greedy identity is preserved at every cap by construction: truncating the proposal
10174    /// only shortens the accepted prefix, and the head token of every round is the trunk's
10175    /// own argmax. That is what makes a depth sweep measurable without re-earning the
10176    /// identity law at each rung -- though the sweep still asserts it per arm.
10177    pub fn spec_greedy_batched_depth(
10178        &self,
10179        prompt: &[u32],
10180        n_new: usize,
10181        state: &mut DecodeState,
10182        dstate: &mut DsparkState,
10183        vstate: &mut VerifyState,
10184        depth_cap: usize,
10185    ) -> Res<SpecRunGpu> {
10186        // ds4f rung 1: confidence-window policy, read once per run (see resolve_vt).
10187        // Off reproduces the pre-policy t_cap expression exactly (vt_drafts == k_drafts).
10188        let vt = resolve_vt(
10189            std::env::var("MEMRA_DSV4_VT").ok().as_deref(),
10190            std::env::var("MEMRA_DSV4_VT_TAU").ok().as_deref(),
10191            std::env::var("MEMRA_DSV4_VT_FLOOR").ok().as_deref(),
10192        )?;
10193        self.spec_greedy_batched_policy(prompt, n_new, state, dstate, vstate, depth_cap, vt)
10194    }
10195
10196    /// [`Self::spec_greedy_batched_depth`] with the vt policy passed EXPLICITLY — the
10197    /// in-process multi-arm sweep entry (one load, one thermal window; the env seam
10198    /// stays the serving/gate path). `Dsv4Vt::Off` + the same depth_cap is
10199    /// byte-identical to the env path with `MEMRA_DSV4_VT` unset.
10200    #[allow(clippy::too_many_arguments)]
10201    #[allow(clippy::too_many_arguments)]
10202    pub fn spec_greedy_batched_policy(
10203        &self,
10204        prompt: &[u32],
10205        n_new: usize,
10206        state: &mut DecodeState,
10207        dstate: &mut DsparkState,
10208        vstate: &mut VerifyState,
10209        depth_cap: usize,
10210        vt: Dsv4Vt,
10211    ) -> Res<SpecRunGpu> {
10212        self.spec_greedy_batched_stream(prompt, n_new, state, dstate, vstate, depth_cap, vt, None)
10213    }
10214
10215    /// ds4f rung 3 — [`Self::spec_greedy_batched_policy`] with a per-round COMMIT
10216    /// callback: `round_cb` receives every newly committed token slice after the
10217    /// round's ring writes + close sync (i.e. the tokens are final), and returning
10218    /// `false` stops generation at that round boundary — the serve door's streaming,
10219    /// EOS/stop-string, and client-disconnect cancel all ride this one seam. `None`
10220    /// is byte-identical to the gated driver (the closure is never constructed).
10221    #[allow(clippy::too_many_arguments)]
10222    pub fn spec_greedy_batched_stream(
10223        &self,
10224        prompt: &[u32],
10225        n_new: usize,
10226        state: &mut DecodeState,
10227        dstate: &mut DsparkState,
10228        vstate: &mut VerifyState,
10229        depth_cap: usize,
10230        vt: Dsv4Vt,
10231        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10232    ) -> Res<SpecRunGpu> {
10233        let p0 = prompt.len();
10234        assert!(n_new >= 1, "n_new must be positive");
10235        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10236        let mut t_tok = {
10237            let lg = &pre.logits;
10238            let mut best = 0usize;
10239            for i in 1..lg.len() {
10240                if lg[i] > lg[best] {
10241                    best = i;
10242                }
10243            }
10244            best as u32
10245        };
10246        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10247        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10248        let mut mh_row = 0usize; // taps row holding the tap of the position behind `t_tok`
10249        let mut carry_pending = false;
10250        // MEMRA_DSV4_BENCH_PROFILE=1: bracket steady-state ROUNDS [4, 12) with
10251        // cudaProfilerStart/Stop so `nsys profile -c cudaProfilerApi` captures only
10252        // rounds — no load, no prefill/prime, no warmup. Read ONCE (never per round).
10253        // Profiling runs are rung-0 instruments, never A/B observations.
10254        let profile_bracket = std::env::var("MEMRA_DSV4_BENCH_PROFILE").as_deref() == Ok("1");
10255        let depth_cap = depth_cap.max(1);
10256        if let Dsv4Vt::Slot { tau_logit, floor } = vt {
10257            println!(
10258                "[spec] vt policy: slot (tau_logit {tau_logit:.6}, floor {floor}) — \
10259                 per-round verify window from the confidence head"
10260            );
10261        }
10262        while tokens.len() < n_new {
10263            if profile_bracket && rounds.len() == 4 {
10264                cudarc::driver::safe::profiler_start().map_err(e("profiler_start"))?;
10265            }
10266            if profile_bracket && rounds.len() == 12 {
10267                cudarc::driver::safe::profiler_stop().map_err(e("profiler_stop"))?;
10268            }
10269            let cb_from = tokens.len();
10270            if carry_pending {
10271                tokens.push(t_tok);
10272                if let Some(cb) = round_cb.as_deref_mut() {
10273                    cb(&tokens[cb_from..]);
10274                }
10275                break;
10276            }
10277            let round_t0 = std::time::Instant::now();
10278            let prof_stream = if dsv4_prof_on() {
10279                Some(self.stages[self.stages.len() - 1].gpu.stream())
10280            } else {
10281                None
10282            };
10283            let _p_round = phase!("round", prof_stream.as_ref());
10284            let m0 = p0 + tokens.len();
10285            let prop = {
10286                let _p = phase!("1.drafter_forward", prof_stream.as_ref());
10287                self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?
10288            };
10289            let k_drafts = prop.out_ids.len() - 1;
10290            tokens.push(t_tok);
10291            if tokens.len() == n_new {
10292                rounds.push(SpecRoundGpu {
10293                    start_pos: m0 - 1,
10294                    drafts: prop.out_ids[1..].to_vec(),
10295                    accepts: 0,
10296                    verified: 0,
10297                    t_batch: 0,
10298                    t_cap: 0,
10299                    confidence: prop.confidence.clone(),
10300                    emitted: 1,
10301                    round_us: round_t0.elapsed().as_micros() as u64,
10302                });
10303                if let Some(cb) = round_cb.as_deref_mut() {
10304                    cb(&tokens[cb_from..]);
10305                }
10306                break;
10307            }
10308            let forwards_left = n_new - tokens.len();
10309            // STRUCTURAL ceiling (drafts available / depth knob / vt window /
10310            // verify-state capacity), then the n_new BUDGET on top. Keeping them
10311            // separate is what lets the depth knob (and the vt window, which is a
10312            // per-round depth) shorten a round without it looking like "we ran out of
10313            // tokens" — carry_pending below fires on the BUDGET only.
10314            let vt_drafts = match vt {
10315                Dsv4Vt::Off => k_drafts,
10316                Dsv4Vt::Slot { tau_logit, floor } => {
10317                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10318                }
10319            };
10320            let t_cap = (vt_drafts + 1)
10321                .min(k_drafts + 1)
10322                .min(depth_cap)
10323                .min(vstate.tmax);
10324            let t_batch = t_cap.min(forwards_left);
10325            let kv = t_batch - 1;
10326            let mut batch_ids = Vec::with_capacity(t_batch);
10327            batch_ids.push(t_tok);
10328            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10329            let (_, am) = {
10330                let _p = phase!("2.verify_batch", prof_stream.as_ref());
10331                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), false)?
10332            };
10333            // accept walk: row i (position m0+i) arbitrates draft i+1
10334            let mut c_d = 0usize;
10335            let mut t_next = 0u32;
10336            for i in 0..t_batch {
10337                let a = am[i];
10338                if i < kv && a == batch_ids[i + 1] {
10339                    c_d += 1;
10340                    continue;
10341                }
10342                t_next = a;
10343                break;
10344            }
10345            let n_commit = c_d + 1;
10346            {
10347                let _p = phase!("3.commit_rollback", prof_stream.as_ref());
10348                self.commit_verify_dev(state, vstate, n_commit)?;
10349            }
10350            // drafter rings advance for EVERY accepted position and no rejected one
10351            {
10352                let _p = phase!("4.ring_writes", prof_stream.as_ref());
10353                for i in 0..n_commit {
10354                    self.dspark_write_rings(dstate, i, m0 + i)?;
10355                }
10356            }
10357            {
10358                let _p = phase!("5.round_close_sync", None);
10359                // close the round on device too, so the ring advance is inside THIS
10360                // round's measurement and not the next one's
10361                let last = self.stages.len() - 1;
10362                self.stages[last]
10363                    .gpu
10364                    .stream()
10365                    .synchronize()
10366                    .map_err(e("round close sync"))?;
10367            }
10368            mh_row = c_d;
10369            for i in 0..c_d {
10370                tokens.push(batch_ids[i + 1]);
10371            }
10372            // Carry (= stop after emitting the bonus token) only when the n_new BUDGET
10373            // truncated this round -- never when the depth knob did. Identical to the old
10374            // `kv < k_drafts` whenever the knob is unset and vstate.tmax >= k_drafts + 1.
10375            carry_pending = c_d == kv && t_batch < t_cap;
10376            rounds.push(SpecRoundGpu {
10377                start_pos: m0 - 1,
10378                drafts: prop.out_ids[1..].to_vec(),
10379                accepts: c_d,
10380                verified: (c_d + 1).min(kv),
10381                t_batch,
10382                t_cap,
10383                confidence: prop.confidence.clone(),
10384                emitted: 1 + c_d,
10385                round_us: round_t0.elapsed().as_micros() as u64,
10386            });
10387            t_tok = t_next;
10388            if let Some(cb) = round_cb.as_deref_mut() {
10389                if !cb(&tokens[cb_from..]) {
10390                    break;
10391                }
10392            }
10393        }
10394        Ok(SpecRunGpu { tokens, rounds })
10395    }
10396
10397    /// [`Self::spec_greedy_batched_with`] with freshly allocated state (gate shape).
10398    pub fn spec_greedy_batched(&self, prompt: &[u32], n_new: usize) -> Res<SpecRunGpu> {
10399        let mut state = self.alloc_decode_state()?;
10400        let mut dstate = self.dspark_alloc_state()?;
10401        let mut vstate = self.alloc_verify_state()?;
10402        self.spec_greedy_batched_with(prompt, n_new, &mut state, &mut dstate, &mut vstate)
10403    }
10404
10405    /// ds4f rung 2 (slice 1) — the SAMPLED propose-then-verify loop (it5 item 8).
10406    ///
10407    /// A deliberate near-copy of [`Self::spec_greedy_batched_policy`] with the accept
10408    /// walk arbitrated by POSITION-KEYED seeded target draws instead of argmax — the
10409    /// gated greedy driver's bytes are not touched (its accept-sha receipts stay the
10410    /// witness; a shared parameterized loop would put those bytes at refactor risk for
10411    /// zero measurement gain). Identity law: the emitted stream equals the plain
10412    /// sampled stream at the same seed BY CONSTRUCTION — row i of the batched verify
10413    /// is bit-exact against the sequential step's row at the same position (the it3
10414    /// gate (c) proof) and [`dsv4_sample_row`] is a pure function of (row, pos, seed).
10415    /// The drafter proposes greedily (deterministic one-hot proposal); a draft is
10416    /// accepted iff it EQUALS the target draw at its position — the correct
10417    /// arbitration for a one-hot proposal (full min(1, p/q) rejection sampling
10418    /// degenerates to exactly this when q is one-hot). Penalties are slice 2 and NOT
10419    /// claimed here.
10420    #[allow(clippy::too_many_arguments)]
10421    pub fn spec_sampled_batched_policy(
10422        &self,
10423        prompt: &[u32],
10424        n_new: usize,
10425        state: &mut DecodeState,
10426        dstate: &mut DsparkState,
10427        vstate: &mut VerifyState,
10428        depth_cap: usize,
10429        vt: Dsv4Vt,
10430        sample: &Dsv4SampleCfg,
10431    ) -> Res<SpecRunGpu> {
10432        self.spec_sampled_batched_stream(
10433            prompt, n_new, state, dstate, vstate, depth_cap, vt, sample, None,
10434        )
10435    }
10436
10437    /// [`Self::spec_sampled_batched_policy`] with the rung-3 per-round commit callback
10438    /// (see [`Self::spec_greedy_batched_stream`] — same seam, same None-is-byte-identical
10439    /// contract).
10440    #[allow(clippy::too_many_arguments)]
10441    pub fn spec_sampled_batched_stream(
10442        &self,
10443        prompt: &[u32],
10444        n_new: usize,
10445        state: &mut DecodeState,
10446        dstate: &mut DsparkState,
10447        vstate: &mut VerifyState,
10448        depth_cap: usize,
10449        vt: Dsv4Vt,
10450        sample: &Dsv4SampleCfg,
10451        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10452    ) -> Res<SpecRunGpu> {
10453        self.spec_sampled_batched_pen(
10454            prompt,
10455            n_new,
10456            state,
10457            dstate,
10458            vstate,
10459            depth_cap,
10460            vt,
10461            sample,
10462            None,
10463            round_cb.take(),
10464        )
10465    }
10466
10467    /// ds4f rung-2 slice 2 — the sampled driver with PENALTIES over the true
10468    /// per-state window (row-incremental: row r penalizes over prompt ++ committed
10469    /// ++ this round's accepts before r — the q38 penalized-sampled law). `None` is
10470    /// byte-identical to the unpenalized driver. Identity vs the plain penalized
10471    /// loop is structural for the same reason as the unpenalized path: the window at
10472    /// a given position is a pure function of the shared committed prefix.
10473    #[allow(clippy::too_many_arguments)]
10474    pub fn spec_sampled_batched_pen(
10475        &self,
10476        prompt: &[u32],
10477        n_new: usize,
10478        state: &mut DecodeState,
10479        dstate: &mut DsparkState,
10480        vstate: &mut VerifyState,
10481        depth_cap: usize,
10482        vt: Dsv4Vt,
10483        sample: &Dsv4SampleCfg,
10484        pen: Option<&Dsv4PenaltyCfg>,
10485        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10486    ) -> Res<SpecRunGpu> {
10487        let p0 = prompt.len();
10488        assert!(n_new >= 1, "n_new must be positive");
10489        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10490        // token at absolute position p0 (output index 0): the seeded draw, keyed p0
10491        let mut t_tok = if let Some(pc) = pen {
10492            let mut row = pre.logits.clone();
10493            dsv4_penalize_row(&mut row, prompt, pc);
10494            dsv4_sample_row(&row, p0, sample)?
10495        } else {
10496            dsv4_sample_row(&pre.logits, p0, sample)?
10497        };
10498        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10499        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10500        let mut mh_row = 0usize;
10501        let mut carry_pending = false;
10502        let depth_cap = depth_cap.max(1);
10503        while tokens.len() < n_new {
10504            let cb_from = tokens.len();
10505            if carry_pending {
10506                tokens.push(t_tok);
10507                if let Some(cb) = round_cb.as_deref_mut() {
10508                    cb(&tokens[cb_from..]);
10509                }
10510                break;
10511            }
10512            let round_t0 = std::time::Instant::now();
10513            let m0 = p0 + tokens.len();
10514            let prop = self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?;
10515            let k_drafts = prop.out_ids.len() - 1;
10516            tokens.push(t_tok);
10517            if tokens.len() == n_new {
10518                rounds.push(SpecRoundGpu {
10519                    start_pos: m0 - 1,
10520                    drafts: prop.out_ids[1..].to_vec(),
10521                    accepts: 0,
10522                    verified: 0,
10523                    t_batch: 0,
10524                    t_cap: 0,
10525                    confidence: prop.confidence.clone(),
10526                    emitted: 1,
10527                    round_us: round_t0.elapsed().as_micros() as u64,
10528                });
10529                if let Some(cb) = round_cb.as_deref_mut() {
10530                    cb(&tokens[cb_from..]);
10531                }
10532                break;
10533            }
10534            let forwards_left = n_new - tokens.len();
10535            let vt_drafts = match vt {
10536                Dsv4Vt::Off => k_drafts,
10537                Dsv4Vt::Slot { tau_logit, floor } => {
10538                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10539                }
10540            };
10541            let t_cap = (vt_drafts + 1)
10542                .min(k_drafts + 1)
10543                .min(depth_cap)
10544                .min(vstate.tmax);
10545            let t_batch = t_cap.min(forwards_left);
10546            let kv = t_batch - 1;
10547            let mut batch_ids = Vec::with_capacity(t_batch);
10548            batch_ids.push(t_tok);
10549            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10550            let (rows, _am) =
10551                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), true)?;
10552            let rows = rows.expect("verify_batch_dev(want_logits=true) returned rows");
10553            let vocab = rows.len() / t_batch;
10554            // sampled accept walk: row i's input token sits at position m0 + i, so the
10555            // row PREDICTS the token at position m0 + i + 1 — that predicted position
10556            // is the draw key (the plain loop keys every token by its own absolute
10557            // position; misaligning this by one would silently break the identity law
10558            // at every accepted draft). Draft i+1 is accepted iff it equals the draw.
10559            let mut c_d = 0usize;
10560            let mut t_next = 0u32;
10561            // row-incremental penalty window: prompt ++ tokens (head included) ++ the
10562            // accepts of rows < i in THIS round (batch_ids[1..=c_d] at walk time).
10563            let mut wround: Vec<u32> = Vec::new();
10564            for i in 0..t_batch {
10565                let s = if let Some(pc) = pen {
10566                    let mut row = rows[i * vocab..(i + 1) * vocab].to_vec();
10567                    let mut window = Vec::with_capacity(prompt.len() + tokens.len() + wround.len());
10568                    window.extend_from_slice(prompt);
10569                    window.extend_from_slice(&tokens);
10570                    window.extend_from_slice(&wround);
10571                    dsv4_penalize_row(&mut row, &window, pc);
10572                    dsv4_sample_row(&row, m0 + i + 1, sample)?
10573                } else {
10574                    dsv4_sample_row(&rows[i * vocab..(i + 1) * vocab], m0 + i + 1, sample)?
10575                };
10576                if i < kv && s == batch_ids[i + 1] {
10577                    c_d += 1;
10578                    wround.push(batch_ids[i + 1]);
10579                    continue;
10580                }
10581                t_next = s;
10582                break;
10583            }
10584            let n_commit = c_d + 1;
10585            self.commit_verify_dev(state, vstate, n_commit)?;
10586            for i in 0..n_commit {
10587                self.dspark_write_rings(dstate, i, m0 + i)?;
10588            }
10589            {
10590                let last = self.stages.len() - 1;
10591                self.stages[last]
10592                    .gpu
10593                    .stream()
10594                    .synchronize()
10595                    .map_err(e("round close sync"))?;
10596            }
10597            mh_row = c_d;
10598            for i in 0..c_d {
10599                tokens.push(batch_ids[i + 1]);
10600            }
10601            carry_pending = c_d == kv && t_batch < t_cap;
10602            rounds.push(SpecRoundGpu {
10603                start_pos: m0 - 1,
10604                drafts: prop.out_ids[1..].to_vec(),
10605                accepts: c_d,
10606                verified: (c_d + 1).min(kv),
10607                t_batch,
10608                t_cap,
10609                confidence: prop.confidence.clone(),
10610                emitted: 1 + c_d,
10611                round_us: round_t0.elapsed().as_micros() as u64,
10612            });
10613            t_tok = t_next;
10614            if let Some(cb) = round_cb.as_deref_mut() {
10615                if !cb(&tokens[cb_from..]) {
10616                    break;
10617                }
10618            }
10619        }
10620        Ok(SpecRunGpu { tokens, rounds })
10621    }
10622}
10623
10624impl Dsv4Gpu {
10625    /// Every LIVE trunk cache class, per layer, as host f32 arrays — the instrument for
10626    /// the §3.1 device state gate (batched round + commit vs plain sequential decode of
10627    /// the committed tokens, bit for bit). "Live" is load-bearing: bytes past `n_blocks`
10628    /// in an append-only store, and the TRANSIENT verify rows, are dead scratch and are
10629    /// deliberately excluded (the CPU-oracle gate draws the same line).
10630    pub fn cache_classes(&self, state: &DecodeState) -> Res<Vec<(String, Vec<f32>)>> {
10631        let d = self.model.cfg();
10632        let hd = d.head_dim as usize;
10633        let win = d.sliding_window as usize;
10634        let mut out = Vec::new();
10635        for (il, cache) in state.caches.iter().enumerate() {
10636            let stage_i = self.layer_stage[il];
10637            let st = &self.stages[stage_i];
10638            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx classes"))?;
10639            let stream = st.gpu.stream();
10640            let lidx = st
10641                .layers
10642                .iter()
10643                .position(|l| l.il == il as u32)
10644                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
10645            let layer = &st.layers[lidx];
10646            let read = |sl: cudarc::driver::CudaView<'_, f32>| -> Res<Vec<f32>> {
10647                let mut v = vec![0f32; sl.len()];
10648                stream
10649                    .memcpy_dtoh(&sl, &mut v[..])
10650                    .map_err(e("dtoh class"))?;
10651                stream.synchronize().map_err(e("sync class"))?;
10652                Ok(v)
10653            };
10654            out.push((format!("l{il}.ring"), read(cache.kvc.slice(0..win * hd))?));
10655            if let Some(cmp) = &layer.cmp {
10656                out.push((
10657                    format!("l{il}.cmp_store"),
10658                    read(cache.kvc.slice(win * hd..(win + cache.n_blocks) * cmp.d))?,
10659                ));
10660                out.push((
10661                    format!("l{il}.cmp_pend_kv"),
10662                    read(cache.pend_kv.as_ref().expect("pend kv").slice(..))?,
10663                ));
10664                out.push((
10665                    format!("l{il}.cmp_pend_score"),
10666                    read(cache.pend_score.as_ref().expect("pend sc").slice(..))?,
10667                ));
10668            }
10669            if let Some(ix) = &layer.idx {
10670                let ikvc = cache.ikvc.as_ref().expect("ikvc");
10671                out.push((
10672                    format!("l{il}.idx_store"),
10673                    read(ikvc.slice(0..cache.i_blocks * ix.cmp.d))?,
10674                ));
10675                out.push((
10676                    format!("l{il}.idx_pend_kv"),
10677                    read(cache.ipend_kv.as_ref().expect("ipend kv").slice(..))?,
10678                ));
10679                out.push((
10680                    format!("l{il}.idx_pend_score"),
10681                    read(cache.ipend_score.as_ref().expect("ipend sc").slice(..))?,
10682                ));
10683            }
10684        }
10685        Ok(out)
10686    }
10687
10688    /// The DSpark drafter's main_kv rings as host f32 arrays (accepted-position-only
10689    /// ring-write rule gate: the batched drafted arm's rings must end bit-identical to a
10690    /// plain greedy run that wrote a ring row at EVERY decoded position).
10691    pub fn dspark_ring_classes(&self, dstate: &DsparkState) -> Res<Vec<(String, Vec<f32>)>> {
10692        let last = self.stages.len() - 1;
10693        let st = &self.stages[last];
10694        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx rings"))?;
10695        let stream = st.gpu.stream();
10696        let d = self.model.cfg();
10697        let hd = d.head_dim as usize;
10698        let win = d.sliding_window as usize;
10699        let mut out = Vec::new();
10700        for (bi, ring) in dstate.rings.iter().enumerate() {
10701            // persistent ring only — rows [win, win+block) are the drafter's transient
10702            // draft-kv scratch, rewritten by every propose and never state.
10703            let view = ring.slice(0..win * hd);
10704            let mut v = vec![0f32; view.len()];
10705            stream
10706                .memcpy_dtoh(&view, &mut v[..])
10707                .map_err(e("dtoh ring class"))?;
10708            stream.synchronize().map_err(e("sync ring class"))?;
10709            out.push((format!("dspark.ring{bi}"), v));
10710        }
10711        Ok(out)
10712    }
10713}
10714
10715/// The dense-arm resolution, pure for the flip's toothed tests (owner ratification
10716/// 2026-08-20, executed v0.98): unset = `fp8` on the DEVICE decode path, `bf16` on
10717/// legacy (device-scoped default, the 82a754fbec dots-default shape); explicit values
10718/// keep their exact prior semantics including the legacy+fp8 refusal and the
10719/// unknown-value refusal.
10720pub fn resolve_dense_arm(v: Option<&str>, on_device: bool) -> Result<bool, String> {
10721    match v {
10722        None | Some("") => Ok(on_device),
10723        Some("bf16") => Ok(false),
10724        Some("fp8") if !on_device => Err(
10725            "MEMRA_DSV4_DENSE_ARM=fp8 requires MEMRA_DSV4_DECODE_PATH=device (the \
10726             fp8 GEMV twins exist on the device decode/verify paths only; prefill \
10727             and the legacy path consume the bf16 slabs)"
10728                .to_string(),
10729        ),
10730        Some("fp8") => Ok(true),
10731        Some(other) => Err(format!(
10732            "MEMRA_DSV4_DENSE_ARM '{other}' unknown (bf16 | fp8)"
10733        )),
10734    }
10735}
10736
10737/// ds4f rung 1 — per-round verify-window policy from the drafter's OWN confidence head
10738/// (`MEMRA_DSV4_VT={off|slot}`, unset = off = the byte-identical round driver).
10739///
10740/// `slot` is the owner-directive per-slot reading. The q38 H4 verdict transfers as a
10741/// MECHANISM, never as receipts (no-generic-support): their head emits MARGINAL accept
10742/// probabilities, so cumprod-survival double-counts depth decay — and dsv4's own head
10743/// was independently measured discriminative per-slot (AUC 0.871–0.918, it5 rung 4,
10744/// where STS recalibration was the measured NEGATIVE — the policy consumes RAW
10745/// sigmoids by design). Verification still arbitrates every forwarded draft, so the
10746/// policy moves acceptance ECONOMICS only; greedy identity holds at any window (the
10747/// `MEMRA_DSV4_SPEC_DEPTH` argument, verbatim — this is a per-round depth).
10748///
10749/// Knobs: `MEMRA_DSV4_VT_TAU` in (0,1) exclusive, default 0.5; `MEMRA_DSV4_VT_FLOOR`
10750/// = minimum drafts forwarded, default 0, max `DSPARK_BLOCK-1` (0 is legal: a
10751/// fully-unconfident proposal degenerates to a 1-row verify — the it5 Algorithm-1
10752/// scans price exactly that round shape). Unknown values, out-of-range tau/floor, and
10753/// orphan knobs (tau/floor set without `slot`) REFUSE BY NAME.
10754#[derive(Clone, Copy, Debug, PartialEq)]
10755pub enum Dsv4Vt {
10756    Off,
10757    /// tau stored in LOGIT space (sigmoid(c) >= tau  <=>  c >= tau_logit, exact for
10758    /// tau = 0.5 -> 0.0); floor = minimum number of drafts forwarded per round.
10759    Slot {
10760        tau_logit: f32,
10761        floor: usize,
10762    },
10763}
10764
10765pub fn resolve_vt(
10766    policy: Option<&str>,
10767    tau: Option<&str>,
10768    floor: Option<&str>,
10769) -> Result<Dsv4Vt, String> {
10770    match policy {
10771        None | Some("") | Some("off") => {
10772            if let Some(t) = tau {
10773                return Err(format!(
10774                    "MEMRA_DSV4_VT_TAU='{t}' set without MEMRA_DSV4_VT=slot (orphan knob \
10775                     would be silently inert — refuse instead)"
10776                ));
10777            }
10778            if let Some(f) = floor {
10779                return Err(format!(
10780                    "MEMRA_DSV4_VT_FLOOR='{f}' set without MEMRA_DSV4_VT=slot (orphan \
10781                     knob would be silently inert — refuse instead)"
10782                ));
10783            }
10784            Ok(Dsv4Vt::Off)
10785        }
10786        Some("slot") => {
10787            let tau_v: f32 = match tau {
10788                None => 0.5,
10789                Some(s) => s
10790                    .trim()
10791                    .parse::<f32>()
10792                    .map_err(|_| format!("MEMRA_DSV4_VT_TAU '{s}' is not a float in (0,1)"))?,
10793            };
10794            if !(tau_v > 0.0 && tau_v < 1.0) {
10795                return Err(format!(
10796                    "MEMRA_DSV4_VT_TAU {tau_v} out of range: need 0 < tau < 1 \
10797                     (a probability threshold on the per-slot sigmoid)"
10798                ));
10799            }
10800            let floor_v: usize = match floor {
10801                None => 0,
10802                Some(s) => s.trim().parse::<usize>().map_err(|_| {
10803                    format!("MEMRA_DSV4_VT_FLOOR '{s}' is not a non-negative integer")
10804                })?,
10805            };
10806            // block_size is baked into the weights at 5 (DSPARK-SEMANTICS §1.5); a
10807            // floor >= block would pin the window fully open, i.e. silently disable
10808            // the policy while claiming to run it.
10809            if floor_v >= 5 {
10810                return Err(format!(
10811                    "MEMRA_DSV4_VT_FLOOR {floor_v} >= dspark block size 5 would pin the \
10812                     window fully open (use MEMRA_DSV4_VT=off to disable)"
10813                ));
10814            }
10815            Ok(Dsv4Vt::Slot {
10816                tau_logit: (tau_v / (1.0 - tau_v)).ln(),
10817                floor: floor_v,
10818            })
10819        }
10820        Some(other) => Err(format!("MEMRA_DSV4_VT '{other}' unknown (off | slot)")),
10821    }
10822}
10823
10824/// ds4f rung 2 (slice 1) — the dsv4 SAMPLED path's sampler: deterministic,
10825/// POSITION-KEYED seeded draws over a temperature/top-k/top-p-filtered target row.
10826///
10827/// Position keying is the identity law's load-bearing choice: the uniform draw for
10828/// absolute position `pos` is a pure function of (seed, pos), never of how many draws
10829/// happened before — so the plain sampled loop and the sampled-leader verify walk
10830/// consume IDENTICAL randomness at every position, and (because the batched verify's
10831/// logits rows are bit-exact against the sequential step's — the it3 gate (c) proof)
10832/// **sampled spec == sampled plain identity is structural, per seed**, exactly like
10833/// greedy. The drafter keeps proposing greedily (its chain is a deterministic
10834/// proposal policy); arbitration is sample-match against the target draw — the
10835/// correct accept rule for a one-hot proposal (the q38 "cold sampled leader" shape).
10836///
10837/// Filter semantics (vendor-posture defaults live at the call sites: temperature 1.0,
10838/// top_p 0.95, top_k off): logits/T -> softmax -> top-k by (value desc, index asc)
10839/// -> smallest prefix of that order with cumulative mass >= top_p (always >= 1 token)
10840/// -> renormalize -> inverse-CDF draw at u(seed, pos). temperature <= 0 REFUSES BY
10841/// NAME (greedy is the greedy driver's job; a silent argmax fallback here would be
10842/// the q38 penalized-greedy footgun).
10843#[derive(Clone, Copy, Debug)]
10844pub struct Dsv4SampleCfg {
10845    pub temperature: f32,
10846    pub top_p: f32,
10847    pub top_k: usize,
10848    pub seed: u64,
10849}
10850
10851fn splitmix64(mut x: u64) -> u64 {
10852    x = x.wrapping_add(0x9e3779b97f4a7c15);
10853    let mut z = x;
10854    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
10855    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
10856    z ^ (z >> 31)
10857}
10858
10859/// The uniform draw for absolute position `pos` under `seed` — in [0, 1).
10860pub fn dsv4_pos_uniform(seed: u64, pos: usize) -> f64 {
10861    let h = splitmix64(seed ^ (pos as u64).wrapping_mul(0xa24baed4963ee407));
10862    (h >> 11) as f64 / (1u64 << 53) as f64
10863}
10864
10865/// One sampled token from a full-vocab logits row at absolute position `pos`.
10866pub fn dsv4_sample_row(logits: &[f32], pos: usize, cfg: &Dsv4SampleCfg) -> Result<u32, String> {
10867    if !(cfg.temperature > 0.0) {
10868        return Err(format!(
10869            "dsv4 sampled path: temperature {} refused (need > 0; greedy is served by \
10870             the greedy driver, never a silent argmax fallback)",
10871            cfg.temperature
10872        ));
10873    }
10874    if !(cfg.top_p > 0.0 && cfg.top_p <= 1.0) {
10875        return Err(format!(
10876            "dsv4 sampled path: top_p {} out of (0, 1]",
10877            cfg.top_p
10878        ));
10879    }
10880    // candidate order: value desc, index asc (the house tie ordering)
10881    let k = if cfg.top_k == 0 || cfg.top_k > logits.len() {
10882        logits.len()
10883    } else {
10884        cfg.top_k
10885    };
10886    let mut idx: Vec<u32> = (0..logits.len() as u32).collect();
10887    idx.sort_by(|&a, &b| {
10888        let (va, vb) = (logits[a as usize], logits[b as usize]);
10889        vb.partial_cmp(&va)
10890            .unwrap_or(std::cmp::Ordering::Equal)
10891            .then(a.cmp(&b))
10892    });
10893    idx.truncate(k);
10894    // softmax over the kept set in kept order (f64 accumulation, max-shifted)
10895    let m = logits[idx[0] as usize] as f64;
10896    let t = cfg.temperature as f64;
10897    let mut probs: Vec<f64> = idx
10898        .iter()
10899        .map(|&i| (((logits[i as usize] as f64) - m) / t).exp())
10900        .collect();
10901    let z: f64 = probs.iter().sum();
10902    for p in &mut probs {
10903        *p /= z;
10904    }
10905    // nucleus: smallest prefix with cumulative >= top_p (>= 1 token), renormalize
10906    let mut cum = 0.0f64;
10907    let mut keep = probs.len();
10908    for (i, p) in probs.iter().enumerate() {
10909        cum += p;
10910        if cum >= cfg.top_p as f64 {
10911            keep = i + 1;
10912            break;
10913        }
10914    }
10915    probs.truncate(keep);
10916    idx.truncate(keep);
10917    let z2: f64 = probs.iter().sum();
10918    let u = dsv4_pos_uniform(cfg.seed, pos) * z2;
10919    let mut acc = 0.0f64;
10920    for (i, p) in probs.iter().enumerate() {
10921        acc += p;
10922        if u < acc {
10923            return Ok(idx[i]);
10924        }
10925    }
10926    Ok(idx[keep - 1]) // u landed on the tail boundary (float roundoff)
10927}
10928
10929/// ds4f rung 2 slice 2 — penalties for the dsv4 sampled path, over an EXPLICIT
10930/// window. The rule is `memra-sampling`'s own `Sampler::apply_penalties` (Keskar
10931/// repeat divide/multiply toward 0 + frequency*count + presence), replicated here
10932/// because the dsv4 path needs per-ROW windows (the spec verify's row-incremental
10933/// state: row r penalizes over prompt ++ committed ++ this round's accepts < r),
10934/// and CROSS-PINNED by unit test against a real `Sampler` so the two
10935/// implementations cannot drift apart silently.
10936#[derive(Clone, Copy, Debug)]
10937pub struct Dsv4PenaltyCfg {
10938    pub last_n: usize,
10939    pub repeat: f32,
10940    pub freq: f32,
10941    pub present: f32,
10942}
10943
10944impl Dsv4PenaltyCfg {
10945    pub fn armed(&self) -> bool {
10946        self.last_n > 0 && (self.repeat != 1.0 || self.freq != 0.0 || self.present != 0.0)
10947    }
10948}
10949
10950/// Apply the Keskar penalties in place over `window`'s last `cfg.last_n` entries.
10951pub fn dsv4_penalize_row(logits: &mut [f32], window: &[u32], cfg: &Dsv4PenaltyCfg) {
10952    if !cfg.armed() {
10953        return;
10954    }
10955    let start = window.len().saturating_sub(cfg.last_n);
10956    let win = &window[start..];
10957    if win.is_empty() {
10958        return;
10959    }
10960    let mut counts: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
10961    for &t in win {
10962        *counts.entry(t).or_insert(0) += 1;
10963    }
10964    for (&id, &cnt) in &counts {
10965        let Some(l) = logits.get_mut(id as usize) else {
10966            continue;
10967        };
10968        if cfg.repeat != 1.0 {
10969            if *l > 0.0 {
10970                *l /= cfg.repeat;
10971            } else {
10972                *l *= cfg.repeat;
10973            }
10974        }
10975        *l -= cfg.freq * cnt as f32;
10976        if cnt > 0 {
10977            *l -= cfg.present;
10978        }
10979    }
10980}
10981
10982/// Drafts to forward under the slot policy: the longest LEADING prefix of `conf`
10983/// (the drafter's pre-sigmoid per-slot logits) with `c >= tau_logit`, raised to
10984/// `floor`, clamped to `conf.len()`. A NaN slot compares false = unconfident
10985/// (conservative: it truncates, and verification still owns correctness).
10986pub fn vt_slot_drafts(conf: &[f32], tau_logit: f32, floor: usize) -> usize {
10987    let mut k = 0usize;
10988    for &c in conf {
10989        if c >= tau_logit {
10990            k += 1;
10991        } else {
10992            break;
10993        }
10994    }
10995    k.max(floor).min(conf.len())
10996}
10997
10998#[cfg(test)]
10999mod peer_probe_tests {
11000    use super::{dsv4_peer_probe_mismatches, dsv4_peer_probe_pattern};
11001
11002    /// TOOTH for the lane-8 byte probe (host-side halves; the on-box halves are the boot
11003    /// PASS line and the MEMRA_DSV4_PEER_PROBE_POISON refusal arm): the pattern must be
11004    /// deterministic, non-trivial, and keyed per (bytes, boundary, src, dst) so a stuck or
11005    /// crossed lane cannot alias another probe's expectation; the mismatch count must see
11006    /// single-byte flips, inversion (the poison), and truncation.
11007    #[test]
11008    fn peer_probe_pattern_is_keyed_and_mismatches_are_counted() {
11009        let a = dsv4_peer_probe_pattern(4096, 0, 0, 1);
11010        assert_eq!(a.len(), 4096);
11011        assert_eq!(a, dsv4_peer_probe_pattern(4096, 0, 0, 1), "deterministic");
11012        assert_ne!(a, dsv4_peer_probe_pattern(4096, 0, 1, 0), "direction-keyed");
11013        assert_ne!(a, dsv4_peer_probe_pattern(4096, 1, 0, 1), "boundary-keyed");
11014        assert!(a.iter().any(|&b| b != a[0]), "non-constant pattern");
11015
11016        assert_eq!(dsv4_peer_probe_mismatches(&a, &a), 0);
11017        let mut flipped = a.clone();
11018        flipped[17] ^= 1;
11019        assert_eq!(dsv4_peer_probe_mismatches(&a, &flipped), 1);
11020        let poison: Vec<u8> = a.iter().map(|b| !b).collect();
11021        assert_eq!(dsv4_peer_probe_mismatches(&a, &poison), a.len());
11022        assert_eq!(dsv4_peer_probe_mismatches(&a, &a[..4000]), 96);
11023    }
11024}
11025
11026#[cfg(test)]
11027mod dense_arm_default_tests {
11028    use super::resolve_dense_arm;
11029
11030    /// The owner-ratified flip (2026-08-20): unset env on the device decode path = fp8.
11031    /// Mutating the default back to bf16 fails this with the evidence named.
11032    #[test]
11033    fn ratified_default_dense_arm_is_fp8_on_device() {
11034        assert_eq!(
11035            resolve_dense_arm(None, true),
11036            Ok(true),
11037            "owner-ratified 2026-08-20: unset MEMRA_DSV4_DENSE_ARM defaults the DEVICE \
11038             decode path to fp8 (bit-identical on four boxes, x5 A/B 41.06->47.19, \
11039             item-3 residency green on box7)"
11040        );
11041        assert_eq!(resolve_dense_arm(Some(""), true), Ok(true));
11042        // Legacy path: unset resolves bf16 (no fp8 twins there — must keep booting).
11043        assert_eq!(resolve_dense_arm(None, false), Ok(false));
11044        // Explicit values keep their exact prior semantics.
11045        assert_eq!(resolve_dense_arm(Some("bf16"), true), Ok(false));
11046        assert_eq!(resolve_dense_arm(Some("fp8"), true), Ok(true));
11047        assert!(
11048            resolve_dense_arm(Some("fp8"), false).is_err(),
11049            "legacy+fp8 stays a refusal"
11050        );
11051        assert!(
11052            resolve_dense_arm(Some("q8"), true).is_err(),
11053            "unknown values refuse"
11054        );
11055    }
11056}
11057
11058#[cfg(test)]
11059mod vt_policy_tests {
11060    use super::{Dsv4Vt, resolve_vt, vt_slot_drafts};
11061
11062    /// Unset env = Off = the byte-identical round driver. Mutating the default fails
11063    /// this by name.
11064    #[test]
11065    fn default_vt_is_off_and_byte_inert() {
11066        assert_eq!(resolve_vt(None, None, None), Ok(Dsv4Vt::Off));
11067        assert_eq!(resolve_vt(Some(""), None, None), Ok(Dsv4Vt::Off));
11068        assert_eq!(resolve_vt(Some("off"), None, None), Ok(Dsv4Vt::Off));
11069    }
11070
11071    #[test]
11072    fn slot_defaults_tau_half_floor_zero() {
11073        // tau 0.5 must land on tau_logit 0.0 EXACTLY (ln(0.5/0.5) = ln(1) = 0), so the
11074        // default threshold admits c = 0.0 with no float fuzz.
11075        match resolve_vt(Some("slot"), None, None) {
11076            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11077                assert_eq!(tau_logit, 0.0);
11078                assert_eq!(floor, 0);
11079            }
11080            other => panic!("slot default parse broke: {other:?}"),
11081        }
11082        // explicit tau round-trips through logit space
11083        match resolve_vt(Some("slot"), Some("0.6"), Some("2")) {
11084            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11085                assert!((tau_logit - (0.6f32 / 0.4).ln()).abs() < 1e-6);
11086                assert_eq!(floor, 2);
11087            }
11088            other => panic!("slot tau/floor parse broke: {other:?}"),
11089        }
11090    }
11091
11092    #[test]
11093    fn unknown_and_out_of_range_refuse_by_name() {
11094        for (p, t, f) in [
11095            (Some("banana"), None, None),    // unknown policy
11096            (Some("slot"), Some("0"), None), // tau not in (0,1)
11097            (Some("slot"), Some("1"), None),
11098            (Some("slot"), Some("nan"), None),
11099            (Some("slot"), Some("x"), None),
11100            (Some("slot"), None, Some("5")), // floor pins window open
11101            (Some("slot"), None, Some("-1")),
11102            (None, Some("0.5"), None),      // orphan tau
11103            (Some("off"), None, Some("2")), // orphan floor
11104        ] {
11105            let r = resolve_vt(p, t, f);
11106            assert!(r.is_err(), "({p:?},{t:?},{f:?}) must refuse, got {r:?}");
11107            let msg = r.unwrap_err();
11108            assert!(
11109                msg.contains("MEMRA_DSV4_VT"),
11110                "refusal must name the knob: {msg}"
11111            );
11112        }
11113    }
11114
11115    /// The slot rule is a LEADING-prefix rule: a confident slot after an unconfident
11116    /// one is never forwarded (chained markov ids past a rejected slot are garbage).
11117    #[test]
11118    fn slot_truncation_is_leading_prefix_with_floor() {
11119        let up = 3.0f32; // sigmoid ~0.95
11120        let dn = -3.0f32; // sigmoid ~0.05
11121        assert_eq!(vt_slot_drafts(&[up, up, up, up, up], 0.0, 0), 5);
11122        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 0), 0);
11123        assert_eq!(vt_slot_drafts(&[up, up, dn, up, up], 0.0, 0), 2);
11124        // boundary equality counts as confident (>=): tau 0.5 admits c = 0.0
11125        assert_eq!(vt_slot_drafts(&[0.0, dn, dn, dn, dn], 0.0, 0), 1);
11126        // floor raises a fully-unconfident round; clamped to the block
11127        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 2), 2);
11128        assert_eq!(vt_slot_drafts(&[dn, dn], 0.0, 4), 2);
11129        // NaN slot is unconfident (conservative), never a panic
11130        assert_eq!(vt_slot_drafts(&[f32::NAN, up, up, up, up], 0.0, 0), 0);
11131        assert_eq!(vt_slot_drafts(&[], 0.0, 0), 0);
11132    }
11133
11134    /// Off must reproduce the pre-policy t_cap expression exactly: with
11135    /// vt_drafts == k_drafts, (vt_drafts+1).min(k_drafts+1) == k_drafts+1.
11136    #[test]
11137    fn off_arm_t_cap_expression_is_identity() {
11138        for k_drafts in 0usize..=5 {
11139            let vt_drafts = k_drafts; // the Off branch in the driver
11140            assert_eq!((vt_drafts + 1).min(k_drafts + 1), k_drafts + 1);
11141        }
11142    }
11143}
11144
11145#[cfg(test)]
11146mod penalty_cross_pin_tests {
11147    use super::{Dsv4PenaltyCfg, dsv4_penalize_row};
11148
11149    /// The dsv4 explicit-window penalty rule must equal memra-sampling's own
11150    /// `Sampler::apply_penalties` (the house Keskar law) — pinned by running BOTH on
11151    /// the same rows/windows and comparing the penalized-greedy argmax, plus a direct
11152    /// per-element check through the Sampler's greedy path. Drift in either
11153    /// implementation fails here by name.
11154    #[test]
11155    fn penalize_matches_the_sampling_crate_reference() {
11156        let mk_row = |seed: u32| -> Vec<f32> {
11157            (0..64u32)
11158                .map(|i| {
11159                    let h = i.wrapping_mul(2654435761).wrapping_add(seed);
11160                    ((h % 2000) as f32 / 100.0) - 10.0
11161                })
11162                .collect()
11163        };
11164        for (seed, window, last_n, rep, freq, present) in [
11165            (
11166                1u32,
11167                vec![3u32, 3, 3, 7, 12, 3],
11168                8usize,
11169                1.8f32,
11170                0.4f32,
11171                0.6f32,
11172            ),
11173            (2, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4, 1.3, 0.0, 0.0),
11174            (3, vec![63, 63, 63, 63], 64, 1.0, 1.1, 0.0),
11175            (4, vec![5], 1, 2.5, 0.7, 1.3),
11176        ] {
11177            let row = mk_row(seed);
11178            // ours
11179            let mut ours = row.clone();
11180            dsv4_penalize_row(
11181                &mut ours,
11182                &window,
11183                &Dsv4PenaltyCfg {
11184                    last_n,
11185                    repeat: rep,
11186                    freq,
11187                    present,
11188                },
11189            );
11190            let our_pick = ours
11191                .iter()
11192                .enumerate()
11193                .max_by(|a, b| a.1.total_cmp(b.1))
11194                .unwrap()
11195                .0 as u32;
11196            // the house reference: greedy Sampler with penalties + the window as history
11197            let mut sampler = memra_sampling::Sampler::new(memra_sampling::SamplerConfig {
11198                temperature: 0.0,
11199                top_k: 0,
11200                top_p: 1.0,
11201                min_p: 0.0,
11202                penalty_last_n: last_n,
11203                penalty_repeat: rep,
11204                penalty_freq: freq,
11205                penalty_present: present,
11206                seed: 0,
11207            });
11208            for &t in &window {
11209                sampler.accept(t);
11210            }
11211            let ref_pick = sampler.sample(&row);
11212            assert_eq!(
11213                our_pick, ref_pick,
11214                "penalized argmax diverged from memra-sampling (seed {seed}): \
11215                 ours {our_pick} vs reference {ref_pick}"
11216            );
11217        }
11218    }
11219}
11220
11221#[cfg(test)]
11222mod sampled_path_tests {
11223    use super::{Dsv4SampleCfg, dsv4_pos_uniform, dsv4_sample_row};
11224
11225    fn cfg(seed: u64) -> Dsv4SampleCfg {
11226        Dsv4SampleCfg {
11227            temperature: 1.0,
11228            top_p: 0.95,
11229            top_k: 0,
11230            seed,
11231        }
11232    }
11233
11234    /// The identity law's anchor: the draw is a pure function of (row, pos, seed) —
11235    /// same inputs, same token, always; different positions decouple.
11236    #[test]
11237    fn draws_are_position_keyed_and_deterministic() {
11238        let row = [0.1f32, 2.0, -1.0, 1.9, 0.0];
11239        let a = dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap();
11240        for _ in 0..8 {
11241            assert_eq!(dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap(), a);
11242        }
11243        // uniforms at neighboring positions must not be equal (keying is real)
11244        let u0 = dsv4_pos_uniform(20260822, 40);
11245        let u1 = dsv4_pos_uniform(20260822, 41);
11246        let v0 = dsv4_pos_uniform(7, 40);
11247        assert_ne!(u0, u1);
11248        assert_ne!(u0, v0);
11249        assert!((0.0..1.0).contains(&u0));
11250    }
11251
11252    /// temperature <= 0 refuses BY NAME (the penalized-greedy footgun class);
11253    /// bad top_p refuses too.
11254    #[test]
11255    fn t0_and_bad_topp_refuse_by_name() {
11256        let row = [0.0f32, 1.0];
11257        let mut c = cfg(1);
11258        c.temperature = 0.0;
11259        let e = dsv4_sample_row(&row, 0, &c).unwrap_err();
11260        assert!(e.contains("temperature"), "{e}");
11261        let mut c2 = cfg(1);
11262        c2.top_p = 0.0;
11263        assert!(dsv4_sample_row(&row, 0, &c2).is_err());
11264    }
11265
11266    /// top-k 1 and a tight nucleus both collapse to argmax regardless of the draw;
11267    /// ties break by lowest index (the house ordering).
11268    #[test]
11269    fn filters_collapse_to_argmax_and_ties_break_low_index() {
11270        let row = [0.0f32, 5.0, 5.0, -2.0];
11271        let mut c = cfg(99);
11272        c.top_k = 1;
11273        for pos in 0..64 {
11274            assert_eq!(dsv4_sample_row(&row, pos, &c).unwrap(), 1);
11275        }
11276        let mut c2 = cfg(99);
11277        c2.top_p = 1e-9; // nucleus keeps exactly the top-1
11278        for pos in 0..64 {
11279            assert_eq!(dsv4_sample_row(&row, pos, &c2).unwrap(), 1);
11280        }
11281    }
11282
11283    /// The sampled distribution honors the filtered target: over many positions a
11284    /// dominant token wins the majority, and a token outside top-k never appears.
11285    #[test]
11286    fn draw_frequencies_track_the_filtered_target() {
11287        let row = [3.0f32, 1.0, 0.0, -50.0];
11288        let mut c = cfg(20260822);
11289        c.top_k = 3;
11290        c.top_p = 1.0;
11291        let mut counts = [0usize; 4];
11292        for pos in 0..4096 {
11293            counts[dsv4_sample_row(&row, pos, &c).unwrap() as usize] += 1;
11294        }
11295        assert_eq!(counts[3], 0, "outside top-k must never be drawn");
11296        assert!(counts[0] > 2600, "p(tok0) ~ 0.84, got {}/4096", counts[0]);
11297        assert!(counts[1] > 100, "tail token starved: {}", counts[1]);
11298    }
11299}