Skip to main content

memra_engine/
dsv4_gpu.rs

1//! DeepSeek-V4-Flash GPU trunk forward (lane 4): 2-card layer-split placement,
2//! correctness bring-up gated against the lane-2/3 CPU oracle fixtures.
3//!
4//! Plan of record: wt-dsv4-loader research/dsv4-flash-loader-20260818/RECEIPTS.md
5//! "Lane 4" (placement math, quant rungs, threshold derivation — banked BEFORE this
6//! module was written). Semantic law: darklanes SEMANTICS.md; arithmetic contract: the
7//! lane-3 CPU oracle (memra_gguf::dsv4_forward), whose host-side pieces
8//! (hc_split_sinkhorn, rope tables, index builders, routing math) are REUSED here
9//! verbatim so the CPU/GPU forks share one implementation of every host-side rule.
10//!
11//! Rungs (explicit): trunk routed experts stay AS-STORED NVFP4 on GPU and are
12//! dequantized per activated expert into a reused bf16 scratch (exact in bf16), all
13//! other quantized linears are host-dequantized (lane-1 proven decoders) to bf16 at
14//! load with a bit-level exactness refusal; f32 islands (SEMANTICS §7.2) run in
15//! dedicated f32/f64 kernels or on the host. bf16 enters ONLY at the activation inputs
16//! of the non-island GEMMs (cuBLASLt bf16, f32 accumulate).
17//!
18//! Multi-GPU: PP layer split (the engine's only executing multi-GPU idiom, pp.rs /
19//! Step-3.7-Flash precedent), split point derived from per-layer byte math, ONE hc-state
20//! boundary copy per forward via host bounce (peer copy is a perf-lane step).
21//!
22//! NOT a serving path: prefill-only, greedy continuation by re-prefill per step (the
23//! accepted O(n²) bring-up rung). Decode KV caching, CUDA-graph, batched serving and any
24//! perf claims belong to later lanes.
25
26use std::collections::BTreeMap;
27use std::os::raw::c_void;
28use std::path::Path;
29
30use cudarc::driver::{CudaSlice, CudaStream, DevicePtr, DevicePtrMut};
31use memra_gguf::dsv4_forward::{
32    ActQuantVariant, Dsv4Model, FreqsCis, compress_topk_idxs, hc_split_sinkhorn,
33    precompute_freqs_cis, window_topk_idxs,
34};
35
36use crate::dsv4_ffi as k;
37use crate::dsv4_ffi::ck;
38
39type Res<T> = Result<T, String>;
40
41fn e<E: std::fmt::Display>(what: &str) -> impl FnOnce(E) -> String + '_ {
42    move |err| format!("{what}: {err}")
43}
44
45// ---------------------------------------------------------------- host math (oracle twins)
46
47#[inline]
48fn sigmoid_f32(x: f32) -> f32 {
49    1.0 / (1.0 + (-x).exp())
50}
51
52/// torch softplus (beta=1, threshold=20) — same as the oracle's private softplus_f32.
53#[inline]
54fn softplus_f32(x: f32) -> f32 {
55    if x > 20.0 { x } else { x.exp().ln_1p() }
56}
57
58// ---------------------------------------------------------------- device buffers
59
60/// One stage = one GPU: its runtime handle plus the resident weights of its layer range.
61pub struct Stage {
62    pub dev: usize,
63    pub gpu: memra_runtime::Gpu,
64    pub layers: Vec<LayerDev>,
65    pub embed: Option<CudaSlice<u8>>, // bf16 raw [vocab, hidden] (stage 0)
66    pub head: Option<CudaSlice<u8>>,  // bf16 raw [vocab, hidden] (last stage)
67    pub trunk_norm: Option<CudaSlice<f32>>,
68    pub hc_head_fn: Option<CudaSlice<f32>>, // [hc, hc*hidden]
69    pub fc_yarn: CudaSlice<f32>,            // rope table, compressor layers [max_seq, rd]
70    pub fc_plain: CudaSlice<f32>,           // rope table, ratio-0 layers    [max_seq, rd]
71    pub ws: CudaSlice<u8>,                  // cuBLASLt workspace
72    pub deq: [CudaSlice<u8>; 3],            // expert dequant scratch, bf16 [inter*hidden] each
73    pub loaded_bytes: u64,                  // resident weight bytes uploaded to this device
74    // lane 8: device twins of the trunk hc_head gate constants (last stage)
75    pub hc_head_base_dev: Option<CudaSlice<f32>>,
76    pub hc_head_scale_dev: Option<CudaSlice<f32>>,
77}
78
79pub struct CmpDev {
80    pub ratio: usize,
81    pub d: usize,
82    pub latent: usize,
83    pub overlap: bool,
84    pub rotate: bool,
85    pub wkv: CudaSlice<f32>,   // f32 island
86    pub wgate: CudaSlice<f32>, // f32 island
87    pub norm: CudaSlice<f32>,
88    pub ape: CudaSlice<f32>, // [ratio, latent]
89}
90
91pub struct IdxDev {
92    pub wq_b: DenseBf16,         // bf16 [heads*hd, q_lora]
93    pub weights_proj: DenseBf16, // bf16 [heads, hidden]
94    pub wq_b_fp8: Option<Fp8Dense>,
95    pub weights_proj_fp8: Option<Fp8Dense>,
96    pub cmp: CmpDev,
97    pub heads: usize,
98    pub hd: usize,
99    pub topk: usize,
100}
101
102/// Iteration-5 FP8 dense arm (`MEMRA_DSV4_DENSE_ARM=fp8`): an FP8-blk linear held
103/// AS-STORED — e4m3 codes `[rows, cols]` plus the 128x128 block-scale grid decoded to
104/// f32 on the host (exact: every e8m0 code is a pow2; 0xFF refused at load). The device
105/// GEMV twins decode `e4m3(code) * scale` in-register — the SAME f32 value the bf16
106/// dequant slab holds (the loader's `f32_to_bf16_exact` refusal proves exactness), with
107/// the accumulation order VERBATIM — so the arm is bit-identical to the bf16 arm by
108/// construction and its gate is a no-regression proof. It5 ledger item 3: when this
109/// pair exists, the bf16 twin is NOT device-resident — it drops to [`DenseBf16::Host`]
110/// staged residency (the dual-residency +~2.7 GiB/card is gone).
111pub struct Fp8Dense {
112    pub codes: CudaSlice<u8>,   // e4m3, [rows, cols] row-major as stored
113    pub scales: CudaSlice<f32>, // [ceil(rows/128), sc_cols] host-decoded e8m0
114    pub sc_cols: usize,         // ceil(cols/128)
115    pub rows: usize,
116    pub cols: usize,
117}
118
119/// It5 ledger item 3 — residency of a dense bf16 slab. `Dev` = device-resident, today's
120/// exact bytes: the only realization when the dense arm is bf16, and always the
121/// realization for the drafter/MTP blocks (no fp8 twins this rung). `Host` = the fp8
122/// dense arm's STAGED residency: the same host-dequantized bf16 bytes the loader would
123/// have uploaded, kept host-side; the fp8 pair owns every device decode/verify read
124/// (via [`dwsel`]) and the prefill pass stages these bytes H2D per consuming call,
125/// the transient copy freed stream-ordered when the [`DenseView`] drops. This is the
126/// engine's existing staged-residency idiom (hybrid EDGE-1 `HostExps` / the moe-cache
127/// host-resident expert staging) translated to dsv4; dsv4 has no CUDA-graph capture,
128/// so the "release after capture" boundary degenerates to "never resident outside a
129/// prefill pass". Prefill's bf16 path is byte-identical by construction: the staged
130/// upload is the SAME `f32_to_bf16_exact` byte vector the resident slab held.
131pub enum DenseBf16 {
132    Dev(CudaSlice<u8>),
133    Host(Vec<u8>),
134}
135
136impl DenseBf16 {
137    /// The device-resident slab. Host residency here is an ENGINE bug, never an env
138    /// error: `Host` exists only when the fp8 arm is on, and every path that reaches
139    /// this accessor under fp8 (legacy decode combos, bf16-slab probes) is already a
140    /// boot refusal (hermes a4e3d9a8eab4cf17) or dwsel-routed to the fp8 twin.
141    pub fn dev(&self) -> &CudaSlice<u8> {
142        match self {
143            DenseBf16::Dev(d) => d,
144            DenseBf16::Host(_) => unreachable!(
145                "bf16 dense slab is host-staged (fp8 dense arm): this consumer must \
146                 ride the fp8 twins (dwsel) or the staged prefill view"
147            ),
148        }
149    }
150
151    /// Prefill-class access (block_forward / shared-expert finish): borrow the
152    /// resident slab, or stage the host bytes into a transient device copy freed
153    /// (stream-ordered, after the enqueued consumers) when the view drops.
154    fn staged(&self, stream: &std::sync::Arc<CudaStream>) -> Res<DenseView<'_>> {
155        Ok(match self {
156            DenseBf16::Dev(d) => DenseView::Res(d),
157            DenseBf16::Host(b) => DenseView::Tmp(upload_u8(stream, b)?),
158        })
159    }
160}
161
162/// A borrowed resident slab or a staged transient copy — see [`DenseBf16::staged`].
163pub enum DenseView<'a> {
164    Res(&'a CudaSlice<u8>),
165    Tmp(CudaSlice<u8>),
166}
167
168impl DenseView<'_> {
169    fn slab(&self) -> &CudaSlice<u8> {
170        match self {
171            DenseView::Res(d) => d,
172            DenseView::Tmp(d) => d,
173        }
174    }
175}
176
177/// Dense-weight pointer for the device-path GEMV wrappers: the bf16 dequant slab, or
178/// the as-stored FP8 pair when the dense arm is on. Copy of raw pointers only — built
179/// per call from the owning slabs via [`dwsel`].
180#[derive(Clone, Copy)]
181pub enum DW {
182    Bf16(*const c_void),
183    Fp8 {
184        codes: *const c_void,
185        scales: *const f32,
186        sc_cols: i32,
187    },
188}
189
190impl DW {
191    /// Row-offset view (the grouped wo_a slices): `rows_off` rows into the weight, row
192    /// width `cols`. The fp8 arm requires the offset to land on a scale-grid row
193    /// boundary (o_lora = 1024 = 8x128 — asserted, never assumed).
194    fn offset_rows(self, rows_off: usize, cols: usize) -> DW {
195        match self {
196            DW::Bf16(p) => DW::Bf16((p as usize + rows_off * cols * 2) as *const c_void),
197            DW::Fp8 {
198                codes,
199                scales,
200                sc_cols,
201            } => {
202                assert_eq!(
203                    rows_off % 128,
204                    0,
205                    "fp8 dense arm: grouped row offset {rows_off} not on the 128-row \
206                     scale-grid boundary"
207                );
208                DW::Fp8 {
209                    codes: (codes as usize + rows_off * cols) as *const c_void,
210                    scales: unsafe { scales.add((rows_off / 128) * sc_cols as usize) },
211                    sc_cols,
212                }
213            }
214        }
215    }
216}
217
218/// Select the weight realization for a device-path GEMV: the fp8 pair when the dense
219/// arm is on AND this tensor is FP8-blk stored, else the bf16 slab. `active` is
220/// `self.dense_fp8` — passed explicitly because the wrappers are associated fns.
221fn dwsel(
222    active: bool,
223    stream: &cudarc::driver::CudaStream,
224    w_bf16: &DenseBf16,
225    fp8: &Option<Fp8Dense>,
226) -> DW {
227    match fp8 {
228        Some(f) if active => DW::Fp8 {
229            codes: f.codes.device_ptr(stream).0 as *const c_void,
230            scales: f.scales.device_ptr(stream).0 as *const f32,
231            sc_cols: f.sc_cols as i32,
232        },
233        // item 3: reached only when the fp8 twin is absent or the arm is off, i.e.
234        // exactly when the bf16 slab IS device-resident — .dev() is the invariant.
235        _ => DW::Bf16(w_bf16.dev().device_ptr(stream).0 as *const c_void),
236    }
237}
238
239/// Routed-expert quantization recipe of a layer (lane-1 census: trunk = modelopt NVFP4,
240/// MTP = OCP MXFP4). Never inferred from ancestry — detected from the stored dtypes and
241/// sibling names, refused on any surprise.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum ExpertKind {
244    Nvfp4,
245    Mxfp4,
246}
247
248/// Lane 7: which expert-GEMM realization runs. `Bf16Dequant` = the lane-4 gated rung
249/// (on-the-fly exact dequant + cuBLASLt bf16, the fallback and A/B reference).
250/// `Native` = the reference-law quantized GEMMs (act_quant per-128 FP8 codes ×
251/// as-stored NVFP4/MXFP4 slabs, kernel.py fp4_gemm arithmetic — RECEIPTS.md "Lane 7").
252/// Selected by `MEMRA_DSV4_EXPERT_ARM=native` via [`memra_gguf::dsv4_forward::
253/// expert_arm_native`] — the SAME seam the CPU oracle reads, so one invocation can
254/// never mix numeric classes.
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub enum ExpertArm {
257    Bf16Dequant,
258    Native,
259}
260
261/// Lane 8: which decode-step realization runs (RECEIPTS.md "Lane 8"). `Legacy` = the
262/// lane-6/7 gated host-driven loop, byte-stable. `Device` = the device-resident step:
263/// preallocated workspace arena, device index build / fine top-k / router / Sinkhorn /
264/// head gate, one-launch-per-projection indirect expert dispatch, peer-copy PP
265/// boundary. `host_math: true` (seam `device-hostmath`) keeps Sinkhorn + router +
266/// fine-top-k + head-gate math on the HOST — the byte-identity instrument for the
267/// mechanical rungs; `false` (seam `device`) runs them as kernels (expf/log1pf
268/// realization fork, gated at class bounds per the lane-6/7 doctrine). Selected by
269/// MEMRA_DSV4_DECODE_PATH — read once at load and printed; one binary carries both
270/// arms for the interleaved A/B law.
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum DecodePath {
273    Legacy,
274    Device { host_math: bool },
275}
276
277pub struct LayerDev {
278    pub il: u32,
279    pub ratio: usize,
280    pub expert_kind: ExpertKind,
281    // attention (bf16 unless island; staged host residency under the fp8 dense arm)
282    pub wq_a: DenseBf16,
283    pub wq_b: DenseBf16,
284    pub wkv: DenseBf16,
285    pub wo_a: DenseBf16, // [o_groups*o_lora, hidden-group-width] grouped rows
286    pub wo_b: DenseBf16,
287    pub q_norm: CudaSlice<f32>,
288    pub kv_norm: CudaSlice<f32>,
289    pub attn_norm: CudaSlice<f32>,
290    pub ffn_norm: CudaSlice<f32>,
291    pub sink: CudaSlice<f32>,
292    pub cmp: Option<CmpDev>,
293    pub idx: Option<IdxDev>,
294    // hyper-connections (f32 island; base/scale live host-side)
295    pub hc_attn_fn: CudaSlice<f32>,
296    pub hc_ffn_fn: CudaSlice<f32>,
297    pub hc_attn_base: Vec<f32>,
298    pub hc_attn_scale: Vec<f32>,
299    pub hc_ffn_base: Vec<f32>,
300    pub hc_ffn_scale: Vec<f32>,
301    // lane 8: device twins of the host-side routing/hc constants (tiny; loaded always)
302    pub hc_attn_base_dev: CudaSlice<f32>,
303    pub hc_attn_scale_dev: CudaSlice<f32>,
304    pub hc_ffn_base_dev: CudaSlice<f32>,
305    pub hc_ffn_scale_dev: CudaSlice<f32>,
306    pub gate_bias_dev: Option<CudaSlice<f32>>,
307    /// i32 cast of tid2eid, range- and distinctness-validated at LOAD (the legacy path
308    /// asserts per token at route time; the device route kernel cannot).
309    pub tid2eid_dev: Option<CudaSlice<i32>>,
310    pub experts_s2_dev: CudaSlice<f32>,
311    // MoE
312    pub gate_w: CudaSlice<f32>, // f32 island [ne, hidden]
313    pub gate_bias: Option<Vec<f32>>,
314    pub tid2eid: Option<Vec<i64>>, // host routing table (hash layers)
315    pub experts_w: CudaSlice<u8>,  // expert slab: per (e, proj) nibble-pair bytes
316    pub experts_sc: CudaSlice<u8>, // expert slab: per (e, proj) scales (e4m3/16 or e8m0/32)
317    pub experts_s2: Vec<f32>,      // host [ne*3] scale_2 (NVFP4 only, asserted pow2)
318    pub shared_w: [DenseBf16; 3],  // bf16 shared expert w1/w2/w3
319    // iteration-5 FP8 dense arm: as-stored twins of the FP8-blk linears, Some only when
320    // MEMRA_DSV4_DENSE_ARM=fp8 AND the tensor is F8_E4M3-stored (trunk layers only —
321    // the drafter/MTP blocks ride the prefill helpers and keep bf16 this rung).
322    pub wq_a_fp8: Option<Fp8Dense>,
323    pub wq_b_fp8: Option<Fp8Dense>,
324    pub wkv_fp8: Option<Fp8Dense>,
325    pub wo_a_fp8: Option<Fp8Dense>,
326    pub wo_b_fp8: Option<Fp8Dense>,
327    pub shared_fp8: [Option<Fp8Dense>; 3],
328}
329
330/// Fixture-array capture (GPU twin of the oracle's BlockCapture, gathered host-side).
331#[derive(Default)]
332pub struct GpuCapture {
333    pub embed_out: Option<Vec<f32>>,
334    pub layer_out: BTreeMap<u32, Vec<f32>>,
335    pub attn_out: BTreeMap<u32, Vec<f32>>,
336    /// diagnostic (lane-6 bisect probe): post-attn-norm x, post-rope q, post-QAT kv,
337    /// post-derotation o — full [s, ...] arrays
338    pub x_dbg: BTreeMap<u32, Vec<f32>>,
339    pub q_dbg: BTreeMap<u32, Vec<f32>>,
340    pub kv_dbg: BTreeMap<u32, Vec<f32>>,
341    pub o_dbg: BTreeMap<u32, Vec<f32>>,
342    pub compressor_kv: BTreeMap<u32, (Vec<f32>, usize)>,
343    pub indexer_kv: BTreeMap<u32, (Vec<f32>, usize)>,
344    pub index_score: BTreeMap<u32, (Vec<f32>, usize)>,
345    /// lane 7: post-ffn-norm MoE input rows [s, hidden] (the real activation vectors
346    /// the native-GEMM kernel gate feeds to sampled experts)
347    pub moe_x: BTreeMap<u32, Vec<f32>>,
348    pub want: std::collections::BTreeSet<u32>,
349}
350
351/// MTP (NextN) block on the LAST stage (pp idiom: MTP -> last stage). Shares the trunk
352/// embed (host-gathered) and head; own norms/projections/block/hc_head (SEMANTICS §5).
353pub struct MtpDev {
354    pub layer: LayerDev, // layer id = n_trunk: ratio 0, score-routed, MXFP4 experts
355    pub enorm: CudaSlice<f32>,
356    pub hnorm: CudaSlice<f32>,
357    pub norm: CudaSlice<f32>,
358    pub e_proj: CudaSlice<u8>, // bf16
359    pub h_proj: CudaSlice<u8>, // bf16
360    pub hc_head_fn: CudaSlice<f32>,
361    pub hc_head_base: Vec<f32>,
362    pub hc_head_scale: Vec<f32>,
363}
364
365/// DSpark drafter on the LAST stage (iteration 3; semantics DSPARK-SEMANTICS.md,
366/// numeric truth = the lane-10 CPU oracle `memra_gguf::dsv4_dspark`). Loaded only
367/// under MEMRA_DSV4_DRAFTER=dspark (≈10.7 GiB resident on dev1 — VRAM plan in the
368/// iteration-3 receipts); config census pins ride the oracle's own
369/// `DsparkConfig::load` (refuse-on-drift, NextN refusal included).
370pub struct DsparkDev {
371    /// mtp.0..2 — layer ids n_trunk+k, ratio 0 (window-only), score-routed MXFP4.
372    pub blocks: Vec<LayerDev>,
373    pub main_proj: CudaSlice<u8>, // bf16 [hidden, n_targets*hidden]
374    pub main_norm: CudaSlice<f32>,
375    pub norm: CudaSlice<f32>, // mtp.2.norm (exit head)
376    /// markov factors held f32 at runtime (M:795-804 reference convention); the
377    /// bias GEMV runs the f32-island dots kernel (f64 accumulation, oracle class).
378    pub markov_w1: CudaSlice<f32>, // [vocab, rank]
379    pub markov_w2: CudaSlice<f32>, // [vocab, rank]
380    pub markov_w1_host: Vec<f32>, // host copy (row gather per chained id)
381    pub conf_w: CudaSlice<f32>, // f32 [hidden + rank] (fp32 head, M:810)
382    pub hc_head_fn: CudaSlice<f32>, // mtp.2 hc_head trio
383    pub hc_head_base: Vec<f32>,
384    pub hc_head_scale: Vec<f32>,
385    pub block_size: usize,
386    pub noise_token: u32,
387    pub targets: Vec<usize>, // [40, 41, 42]
388    pub rank: usize,
389    pub vocab: usize,
390}
391
392/// DSpark decode-side state: the 3 per-block main_kv rings, each allocated
393/// [win + block_size, hd] — rows [0, win) are the persistent ring (slot = pos % win,
394/// M:783), rows [win, win+block) hold the CURRENT round's transient draft kv (the
395/// M:784 cat([kv_cache, draft_kv]) gather realized in one buffer; rewritten every
396/// propose, never read as ring). Rings advance ONLY for committed positions
397/// (`dspark_write_rings`) — the §3.1 drafter rule.
398pub struct DsparkState {
399    pub rings: Vec<CudaSlice<f32>>,
400    /// tap rows [t_max, n_targets*hidden] on the last stage: the hc-mean concat of
401    /// layers 40/41/42, written by the decode step (and consumed by write_rings /
402    /// forward_spec).
403    pub taps: CudaSlice<f32>,
404}
405
406/// One drafter proposal (host view). `out_ids[0]` is the input token; margins/top1
407/// are adjudication instruments (populated only under `capture`).
408pub struct DsparkProposal {
409    pub out_ids: Vec<u32>,
410    pub confidence: Vec<f32>,
411    pub margins: Vec<f32>,
412    pub top1_logits: Vec<f32>,
413    /// captured component arrays for the gate (dtoh): main_x, per-block outs,
414    /// x_collapsed (pre-norm), post-markov logits rows, markov_embed.
415    pub capture: Option<DsparkCaptureOut>,
416}
417
418pub struct DsparkCaptureOut {
419    /// the trunk tap row itself (hc-mean concat of layers 40/41/42) — the CPU gate's
420    /// `pos{p}_main_hidden` array; captured here so the GPU gate compares the SAME
421    /// seven arrays the lane-10 CPU components gate does.
422    pub main_hidden: Vec<f32>,
423    pub main_x: Vec<f32>,
424    pub block_outs: Vec<Vec<f32>>,
425    pub x_collapsed: Vec<f32>,
426    /// shared-trunk-head logits BEFORE any markov bias add (`pos{p}_logits_pre`).
427    pub logits_pre: Vec<f32>,
428    pub logits_post: Vec<f32>,
429    pub markov_embed: Vec<f32>,
430}
431
432pub struct Dsv4Gpu {
433    pub model: Dsv4Model,
434    pub stages: Vec<Stage>,
435    pub layer_stage: Vec<usize>, // trunk layer -> stage idx
436    pub split_at: u32,           // first layer of stage 1
437    pub max_seq: usize,
438    pub variant: ActQuantVariant,
439    pub fc_yarn_host: FreqsCis,
440    pub fc_plain_host: FreqsCis,
441    pub mtp: Option<MtpDev>,
442    /// iteration 3: the DSpark drafter (0731 lineage), loaded under
443    /// MEMRA_DSV4_DRAFTER=dspark; None = today's exact behavior everywhere.
444    pub dspark: Option<DsparkDev>,
445    pub expert_arm: ExpertArm,
446    pub decode_path: DecodePath,
447    /// lane 9 (owner ruling 2026-08-19): island dots on the DEVICE decode path run the
448    /// f32-accumulation serving arm when true (fork-gated); false = the f64
449    /// oracle-truth arm (MEMRA_DSV4_DOTS_ARM=f64). Legacy path and prefill NEVER
450    /// consult this (they stay the pinned reference realizations).
451    pub dots_f32: bool,
452    /// 0731 re-gate extension rung — RATIFIED by the owner 2026-08-19 and now the
453    /// DEFAULT (unset env == f32x): the remaining f64 dependency chains on the DEVICE
454    /// decode path (sink scores/soft/out, rmsnorm, headrms, rowsq_scale,
455    /// indexer_score) run f32-accumulation twins when true. false = those chains keep
456    /// the f64 kernels (MEMRA_DSV4_DOTS_ARM=f64|f32 — oracle/debug arms, bytes
457    /// untouched). hc_sinkhorn is NOT in f32x (never authorized). Legacy path and
458    /// prefill NEVER consult this.
459    pub chains_f32: bool,
460    /// iteration-3 rung 4c MEASURED FORK (`MEMRA_DSV4_DSPARK_HEAD_ARM=f32x`, default
461    /// f64 = the lane-10-gated bytes): the DSpark drafter's shared-trunk-head projection
462    /// over block_size rows uses the f32-accumulation hoisted kernel instead of the f64
463    /// one. Affects WHICH tokens are drafted, never the emitted stream (verification
464    /// always emits the trunk's own argmax — the greedy identity law).
465    pub dspark_head_f32: bool,
466    /// iteration-5 FP8 dense arm (`MEMRA_DSV4_DENSE_ARM`; DEFAULT fp8 on the device
467    /// decode path since the 2026-08-20 ratification, bf16 selectable and the legacy
468    /// default): the DEVICE decode/verify paths read the FP8-blk linears as-stored
469    /// (e4m3 + f32 block scales) through the bit-identical GEMV twins, halving the
470    /// dense weight traffic (79.9% of a step's bytes). It5 ledger item 3: the trunk
471    /// bf16 slabs are NOT device-resident under this arm — they hold [`DenseBf16::Host`]
472    /// staged residency (same bytes, staged H2D per prefill pass); the legacy path is a
473    /// boot refusal and the drafter's cuBLASLt linears keep resident bf16 (no twins).
474    pub dense_fp8: bool,
475    /// lane 8: cross-stage boundary events (peer transport), one per boundary,
476    /// created in the TX stage's context (cuEventRecord requires event ctx == stream ctx).
477    boundary_ev: Vec<cudarc::driver::CudaEvent>,
478    hc_head_base: Vec<f32>,
479    hc_head_scale: Vec<f32>,
480}
481
482/// A full-trunk forward's outputs: last-position logits + the final hc state (resident
483/// on the LAST stage — the MTP drafter's input).
484pub struct ForwardOut {
485    pub logits: Vec<f32>,
486    pub h_last: CudaSlice<f32>,
487}
488
489/// Lane-6 decode cache for ONE trunk layer, on the layer's owning stage. Layout mirrors
490/// the reference (model.py:473-474, :491): `kvc` = [win + cap_blocks, hd] f32 with the
491/// 128-slot window ring at rows [0, win) (slot = pos % win, M:530) and compressed block
492/// j at row win + j (decode index offset = win, M:509). Pending state = RAW wkv/wgate
493/// rows (ape added at pool time — see the lane-6 receipts): fine [2·ratio, latent] with
494/// rows [0, ratio) = previous block / [ratio, 2·ratio) = current (M:344-370 state
495/// machine); coarse [ratio, latent]. `pend_score` is initialized to −inf so a block
496/// with no predecessor reproduces the reference j==0 masking bit-exactly.
497pub struct LayerCache {
498    pub kvc: CudaSlice<f32>,
499    pub n_blocks: usize,
500    pub pend_kv: Option<CudaSlice<f32>>,
501    pub pend_score: Option<CudaSlice<f32>>,
502    /// indexer compressed-kv store [cap_blocks, index_head_dim] (FP4-QAT'd values) +
503    /// its own pending pair — fine layers only.
504    pub ikvc: Option<CudaSlice<f32>>,
505    pub i_blocks: usize,
506    pub ipend_kv: Option<CudaSlice<f32>>,
507    pub ipend_score: Option<CudaSlice<f32>>,
508}
509
510/// Lane-8 per-stage decode workspace: every per-step buffer preallocated ONCE (the
511/// legacy path issues ~3,086 allocAsync+memset+free triplets per step — rung-0
512/// profile). Every buffer is fully rewritten before it is read within a step; the
513/// consumers (sink_attn via idx pads, combine via order, top-k via exact nb) read
514/// exactly the regions written this step, so no per-step zeroing exists at all.
515pub struct StepWs {
516    pub h_a: CudaSlice<f32>, // [hc*hidden] layer io (in h_a -> h2 in h_b -> h3 in h_a)
517    pub h_b: CudaSlice<f32>, // [hc*hidden]
518    pub h_rx: CudaSlice<f32>, // [hc*hidden] boundary RX slot (peer TX writes here)
519    pub emb: CudaSlice<f32>, // [hidden]
520    pub mixes: CudaSlice<f32>, // [(2+hc)*hc]
521    pub pre: CudaSlice<f32>, // [hc]
522    pub post: CudaSlice<f32>, // [hc]
523    pub comb: CudaSlice<f32>, // [hc*hc]
524    pub y_hc: CudaSlice<f32>, // [hidden] hc_pre collapse out
525    pub x: CudaSlice<f32>,   // [hidden] post-attn-norm
526    pub xf: CudaSlice<f32>,  // [hidden] post-ffn-norm
527    pub qr: CudaSlice<f32>,  // [q_lora]
528    pub qr_b: CudaSlice<u8>, // [q_lora*2]
529    pub q: CudaSlice<f32>,   // [heads*hd]
530    pub kv: CudaSlice<f32>,  // [hd]
531    pub qi: CudaSlice<f32>,  // [iheads*ihd]
532    pub wproj: CudaSlice<f32>, // [iheads]
533    pub score: CudaSlice<f32>, // [max_seq/ratio_min]
534    pub idx: CudaSlice<i32>, // [win + max(topk, max_seq/128)]
535    pub o: CudaSlice<f32>,   // [heads*hd]
536    pub o_b: CudaSlice<u8>,  // [heads*hd*2] (bf16 cvt of o, once — grouped wo reads slices)
537    pub og: CudaSlice<f32>,  // [o_groups*o_lora]
538    pub attn_out: CudaSlice<f32>, // [hidden]
539    pub gemm_xb: CudaSlice<u8>, // [max_gemm_k*2] per-call cvt scratch
540    // MoE
541    pub raw: CudaSlice<f32>,     // [ne]
542    pub sel: CudaSlice<i32>,     // [topk]
543    pub selw: CudaSlice<f32>,    // [topk]
544    pub order: CudaSlice<i32>,   // [topk]
545    pub xq: CudaSlice<u8>,       // [hidden]
546    pub xs: CudaSlice<f32>,      // [hidden/128]
547    pub g1: CudaSlice<f32>,      // [topk*inter]
548    pub g3: CudaSlice<f32>,      // [topk*inter]
549    pub hbuf: CudaSlice<f32>,    // [topk*inter]
550    pub hq: CudaSlice<u8>,       // [topk*inter]
551    pub hs: CudaSlice<f32>,      // [topk*inter/128]
552    pub contrib: CudaSlice<f32>, // [topk*hidden]
553    pub y: CudaSlice<f32>,       // [hidden]
554    pub xb: CudaSlice<u8>,       // [hidden*2] shared-expert input (bf16 cvt of xf)
555    pub sg1: CudaSlice<f32>,     // [sh_inter]
556    pub sg3: CudaSlice<f32>,
557    pub shbuf: CudaSlice<f32>,
558    pub shb16: CudaSlice<u8>,   // [sh_inter*2]
559    pub sh_out: CudaSlice<f32>, // [hidden]
560    // compressor scratch (max class dims across attn fine/coarse + indexer)
561    pub cmp_kv_row: CudaSlice<f32>, // [max latent]
562    pub cmp_sc_row: CudaSlice<f32>, // [max latent]
563    pub cmp_emit: CudaSlice<f32>,   // [2*max d]
564    pub cmp_shift: CudaSlice<f32>,  // [max overlap ratio*latent]
565    // sink attention (three-kernel split): scores/evals [heads, win+idx_tail], f64 den
566    pub sink_scores: CudaSlice<f32>,
567    pub sink_evals: CudaSlice<f32>,
568    pub sink_den: CudaSlice<f64>,
569    // head (allocated on every stage; consumed on the last)
570    pub head_mixes: CudaSlice<f32>, // [hc]
571    pub head_pre: CudaSlice<f32>,   // [hc]
572    pub collapsed: CudaSlice<f32>,  // [hidden]
573    pub logits: CudaSlice<f32>,     // [vocab]
574    pub argmax: CudaSlice<i32>,     // [1]
575    pub tok: CudaSlice<i32>,        // [1]
576}
577
578/// Whole-trunk decode state: one [`LayerCache`] per trunk layer + the stream position.
579/// `pos` = tokens consumed so far (the next decode_step processes position `pos`).
580pub struct DecodeState {
581    pub caches: Vec<LayerCache>,
582    pub pos: usize,
583    /// allocated cache bytes per device index (gate (e): measured vs design math)
584    pub cache_bytes: Vec<u64>,
585    /// lane 8: per-stage step workspace (Some iff the load-time decode path is Device)
586    pub ws: Option<Vec<StepWs>>,
587}
588
589// ---------------------------------------------------------------- small launch helpers
590
591fn sp(stream: &CudaStream) -> *mut c_void {
592    stream.cu_stream() as *mut c_void
593}
594
595fn upload_f32(stream: &std::sync::Arc<CudaStream>, v: &[f32]) -> Res<CudaSlice<f32>> {
596    let mut d = stream.alloc_zeros::<f32>(v.len()).map_err(e("alloc f32"))?;
597    stream.memcpy_htod(v, &mut d).map_err(e("htod f32"))?;
598    Ok(d)
599}
600
601fn upload_i32(stream: &std::sync::Arc<CudaStream>, v: &[i32]) -> Res<CudaSlice<i32>> {
602    let mut d = stream.alloc_zeros::<i32>(v.len()).map_err(e("alloc i32"))?;
603    stream.memcpy_htod(v, &mut d).map_err(e("htod i32"))?;
604    Ok(d)
605}
606
607fn upload_u8(stream: &std::sync::Arc<CudaStream>, v: &[u8]) -> Res<CudaSlice<u8>> {
608    let mut d = stream.alloc_zeros::<u8>(v.len()).map_err(e("alloc u8"))?;
609    stream.memcpy_htod(v, &mut d).map_err(e("htod u8"))?;
610    Ok(d)
611}
612
613fn dtoh_f32(stream: &std::sync::Arc<CudaStream>, d: &CudaSlice<f32>) -> Res<Vec<f32>> {
614    let mut v = vec![0f32; d.len()];
615    stream.memcpy_dtoh(d, &mut v[..]).map_err(e("dtoh"))?;
616    stream.synchronize().map_err(e("sync dtoh"))?;
617    Ok(v)
618}
619
620// -------------------------------------------------- lane 8: peer byte-integrity probe
621//
622// (lane/hermes-perf-fixes, 2026-08-23 — the "DSv4 device-path PP copies hidden state with no
623// peer byte probe" finding.) The lane-8 setup used to cuCtxEnablePeerAccess +
624// cuMemPoolSetAccess and eprintln success; the pp.rs boot probe exists precisely because a
625// fabric can grant peer access and still corrupt bytes in flight (Pod B: official simpleP2P
626// reproduced it while bandwidth-test returned rc=0). The probe here runs the PRODUCTION
627// program — stream-ordered pool allocations moved by the exact `memcpy_peer_async`-on-the-
628// TX-stream call shape the boundary copy uses (the cx-peerprobe lesson: probing legacy
629// cuMemAlloc buffers validates a different allocation class) — over every cross-device
630// boundary, both directions, on a byte ladder up to the prefill hidden-state payload class.
631// FAIL-CLOSED: dsv4's device PP path has no host-bounce twin, so a mismatch refuses at load.
632
633/// Deterministic per-(bytes, boundary, src, dst) xorshift pattern (pp.rs idiom): a stuck or
634/// crossed lane cannot alias another probe's expected bytes.
635fn dsv4_peer_probe_pattern(
636    bytes: usize,
637    boundary: usize,
638    src_dev: usize,
639    dst_dev: usize,
640) -> Vec<u8> {
641    let mut state = 0xD1B5_4A32_D192_ED03u64
642        ^ (bytes as u64).rotate_left(7)
643        ^ (boundary as u64).rotate_left(19)
644        ^ (src_dev as u64).rotate_left(31)
645        ^ (dst_dev as u64).rotate_left(43);
646    (0..bytes)
647        .map(|_| {
648            state ^= state << 13;
649            state ^= state >> 7;
650            state ^= state << 17;
651            state as u8
652        })
653        .collect()
654}
655
656fn dsv4_peer_probe_mismatches(expected: &[u8], readback: &[u8]) -> usize {
657    expected
658        .iter()
659        .zip(readback)
660        .filter(|(a, b)| a != b)
661        .count()
662        + expected.len().abs_diff(readback.len())
663}
664
665fn dsv4_peer_probe_ladder(hidden: usize, hc: usize) -> Vec<usize> {
666    let hc_state = hidden * hc * std::mem::size_of::<f32>();
667    let mut ladder = vec![
668        16 << 10,
669        hc_state,
670        8 * hc_state,
671        1 << 20,
672        (4096usize * hidden * std::mem::size_of::<f32>()).min(64 << 20),
673    ];
674    ladder.sort_unstable();
675    ladder.dedup();
676    ladder
677}
678
679/// One probed copy src->dst at `bytes`. Destination is poisoned with the inverted pattern
680/// first, so a silently dropped copy reads back as full-length corruption, never as PASS.
681fn dsv4_peer_probe_copy(src: &Stage, dst: &Stage, boundary: usize, bytes: usize) -> Res<()> {
682    let expected = dsv4_peer_probe_pattern(bytes, boundary, src.dev, dst.dev);
683    src.gpu.ctx.bind_to_thread().map_err(e("probe bind src"))?;
684    let src_stream = src.gpu.stream();
685    let src_buf = upload_u8(&src_stream, &expected)?;
686    src_stream.synchronize().map_err(e("probe sync src htod"))?;
687
688    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind dst"))?;
689    let dst_stream = dst.gpu.stream();
690    let poison: Vec<u8> = expected.iter().map(|b| !b).collect();
691    let mut dst_buf = upload_u8(&dst_stream, &poison)?;
692    dst_stream.synchronize().map_err(e("probe sync poison"))?;
693
694    // the production call shape: peer copy issued on the TX (source) stream.
695    src.gpu.ctx.bind_to_thread().map_err(e("probe bind tx"))?;
696    {
697        let (sp, _g0) = src_buf.device_ptr(&src_stream);
698        let (dp, _g1) = dst_buf.device_ptr_mut(&src_stream);
699        unsafe {
700            cudarc::driver::result::memcpy_peer_async(
701                dst.gpu.ctx.cu_ctx(),
702                dp,
703                src.gpu.ctx.cu_ctx(),
704                sp,
705                bytes,
706                src_stream.cu_stream(),
707            )
708            .map_err(e("probe peer copy"))?;
709        }
710    }
711    src_stream.synchronize().map_err(e("probe sync copy"))?;
712
713    dst.gpu.ctx.bind_to_thread().map_err(e("probe bind rx"))?;
714    let mut readback = vec![0u8; bytes];
715    dst_stream
716        .memcpy_dtoh(&dst_buf, &mut readback[..])
717        .map_err(e("probe readback"))?;
718    dst_stream.synchronize().map_err(e("probe sync readback"))?;
719    // TEETH DOOR (diagnostics only, never a tuning knob): MEMRA_DSV4_PEER_PROBE_POISON=1
720    // flips one readback byte so the refusal arm can be proven live on a healthy fabric —
721    // a probe that can only be observed passing proves nothing (serve-stress-gate law).
722    if std::env::var("MEMRA_DSV4_PEER_PROBE_POISON").as_deref() == Ok("1") && !readback.is_empty() {
723        readback[0] ^= 1;
724    }
725    let mismatches = dsv4_peer_probe_mismatches(&expected, &readback);
726    if mismatches == 0 {
727        Ok(())
728    } else {
729        Err(format!("{mismatches} mismatched byte(s) of {bytes}"))
730    }
731}
732
733// ================================================== iteration-5: drafted-round phase instruments
734//
735// WHY: iteration 4 measured `cost(T) = F + 0.272*T` plain steps with F = 1.057 plain steps on
736// the f32x exit head, proved the marginal term is ~65-70% irreducible expert-union traffic, and
737// showed the ENTIRE drafted gap to the bar is F. F cannot be attacked until it is itemised into
738// named components with sizes, which is what these two instruments produce. Both are OFF by
739// default and their env knobs are read ONCE through a `OnceLock` (never per round), so the
740// shipping path is untouched: with both unset `Dsv4Phase::new` returns `None` before any work.
741//
742//   MEMRA_DSV4_ROUND_PROFILE=1 -- sync-bracketed host timers. Every phase boundary
743//       synchronizes the head stage's stream, so per-phase wall times SUM to the round's wall
744//       time and can be quoted in F's own unit (plain steps). It PERTURBS: the added syncs
745//       expose latency a queued round would have overlapped, so the report always prints the
746//       bracketed round total for comparison against the unbracketed A/B baseline. A
747//       sync-bracketed run is a rung-0 instrument, NEVER an A/B observation.
748//
749//   MEMRA_DSV4_NVTX=1 -- NVTX push/pop only, no added syncs, so the round is undisturbed.
750//       `nsys profile -t cuda,nvtx` then gives `nvtx_gpu_proj_sum` (GPU-busy attributed to the
751//       range that launched each op) and `nvtx_sum` (host wall per range). GPU-busy is the real
752//       kernel work; wall minus GPU-busy inside a sync-terminated phase is the exposed stall.
753//
754// The accumulator is thread-local and the phase stack makes nesting exact: each row keeps
755// INCLUSIVE time plus the time its direct children consumed, so `self = inclusive - children`
756// is a true exclusive cost and the leaves partition the round.
757#[derive(Default, Clone)]
758struct Dsv4PhaseAcc {
759    /// (label, inclusive_us, direct_child_us, calls)
760    rows: Vec<(&'static str, u64, u64, u64)>,
761    /// (row index, direct-child us accumulated for the open range)
762    stack: Vec<(usize, u64)>,
763}
764
765thread_local! {
766    static DSV4_PHASES: std::cell::RefCell<Dsv4PhaseAcc> =
767        std::cell::RefCell::new(Dsv4PhaseAcc::default());
768}
769
770fn dsv4_prof_sync() -> bool {
771    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
772    *V.get_or_init(|| std::env::var("MEMRA_DSV4_ROUND_PROFILE").as_deref() == Ok("1"))
773}
774
775fn dsv4_prof_nvtx() -> bool {
776    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
777    *V.get_or_init(|| std::env::var("MEMRA_DSV4_NVTX").as_deref() == Ok("1"))
778}
779
780/// True when either phase instrument is armed. Checked first in `Dsv4Phase::new` so an
781/// unprofiled build pays one relaxed load per bracket and nothing else.
782pub fn dsv4_prof_on() -> bool {
783    dsv4_prof_sync() || dsv4_prof_nvtx()
784}
785
786/// A named, nestable phase bracket. Constructed through the `phase!` macro, which supplies a
787/// NUL-terminated literal so the NVTX push needs no allocation.
788pub struct Dsv4Phase<'a> {
789    stream: Option<&'a std::sync::Arc<CudaStream>>,
790    t0: std::time::Instant,
791    nvtx: bool,
792}
793
794impl<'a> Dsv4Phase<'a> {
795    /// `name` MUST end in `\0` (use the `phase!` macro). `stream` is the stream whose queue
796    /// this phase's work rides; it is synchronized on drop under `MEMRA_DSV4_ROUND_PROFILE=1`
797    /// and ignored otherwise.
798    pub fn new(name: &'static str, stream: Option<&'a std::sync::Arc<CudaStream>>) -> Option<Self> {
799        if !dsv4_prof_on() {
800            return None;
801        }
802        let nvtx = dsv4_prof_nvtx();
803        if nvtx {
804            unsafe {
805                k::memra_dsv4_nvtx_push(name.as_ptr() as *const std::os::raw::c_char);
806            }
807        }
808        let label = &name[..name.len() - 1];
809        DSV4_PHASES.with(|p| {
810            let mut p = p.borrow_mut();
811            let idx = match p.rows.iter().position(|r| r.0 == label) {
812                Some(i) => i,
813                None => {
814                    p.rows.push((label, 0, 0, 0));
815                    p.rows.len() - 1
816                }
817            };
818            p.stack.push((idx, 0));
819        });
820        Some(Dsv4Phase {
821            stream: if dsv4_prof_sync() { stream } else { None },
822            t0: std::time::Instant::now(),
823            nvtx,
824        })
825    }
826}
827
828impl Drop for Dsv4Phase<'_> {
829    fn drop(&mut self) {
830        // sync BEFORE stopping the clock: under the sync-bracketed instrument the phase's cost
831        // includes the GPU work it queued, which is the only way the rows can sum to the round.
832        if let Some(s) = self.stream {
833            let _ = s.synchronize();
834        }
835        let us = self.t0.elapsed().as_micros() as u64;
836        if self.nvtx {
837            unsafe {
838                k::memra_dsv4_nvtx_pop();
839            }
840        }
841        DSV4_PHASES.with(|p| {
842            let mut p = p.borrow_mut();
843            if let Some((idx, child)) = p.stack.pop() {
844                let r = &mut p.rows[idx];
845                r.1 += us;
846                r.2 += child;
847                r.3 += 1;
848                if let Some(top) = p.stack.last_mut() {
849                    top.1 += us;
850                }
851            }
852        });
853    }
854}
855
856/// Bracket a phase. `phase!("name", stream_opt)` -> `Option<Dsv4Phase>`; bind it to a `_p`
857/// local so it drops at the end of the scope.
858macro_rules! phase {
859    ($name:literal, $stream:expr) => {
860        crate::dsv4_gpu::Dsv4Phase::new(concat!($name, "\0"), $stream)
861    };
862}
863
864/// Print the accumulated itemisation. `plain_us` is the measured PLAIN step wall time so each
865/// row can be quoted in plain steps, which is the unit `F` is expressed in; pass 0.0 to omit.
866pub fn dsv4_phase_report(tag: &str, rounds: u64, plain_us: f64) {
867    DSV4_PHASES.with(|p| {
868        let p = p.borrow();
869        if p.rows.is_empty() {
870            return;
871        }
872        let mode = if dsv4_prof_sync() {
873            "sync-bracketed (PERTURBS: compare the round total against the unbracketed A/B)"
874        } else {
875            "nvtx-only (host wall; GPU-busy comes from nsys nvtx_gpu_proj_sum)"
876        };
877        println!("\n[phase] === F ITEMISATION: {tag} ===");
878        println!("[phase] rounds={rounds}  plain step={plain_us:.1} us  mode={mode}");
879        println!(
880            "[phase] {:<26} {:>11} {:>11} {:>9} {:>12} {:>12}",
881            "phase", "incl_us/rd", "self_us/rd", "calls/rd", "self_plainstp", "incl_plainstp"
882        );
883        let mut rows = p.rows.clone();
884        rows.sort_by(|a, b| (b.1.saturating_sub(b.2)).cmp(&(a.1.saturating_sub(a.2))));
885        let r = rounds.max(1) as f64;
886        let mut leaf_sum = 0f64;
887        for (name, incl, child, calls) in rows {
888            let selfus = incl.saturating_sub(child) as f64 / r;
889            let inclus = incl as f64 / r;
890            leaf_sum += selfus;
891            let (sp, ip) = if plain_us > 0.0 {
892                (selfus / plain_us, inclus / plain_us)
893            } else {
894                (0.0, 0.0)
895            };
896            println!(
897                "[phase] {name:<26} {inclus:>11.1} {selfus:>11.1} {:>9.2} {sp:>12.4} {ip:>12.4}",
898                calls as f64 / r
899            );
900        }
901        println!(
902            "[phase] {:<26} {:>11} {:>11.1} {:>9} {:>12.4}",
903            "SUM of self",
904            "",
905            leaf_sum,
906            "",
907            if plain_us > 0.0 {
908                leaf_sum / plain_us
909            } else {
910                0.0
911            }
912        );
913    });
914}
915
916/// `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the DSpark markov chain resident on the device (see
917/// `dspark_forward_spec`). Default (`host`, or unset) reproduces the pre-iteration-5 transport
918/// exactly, including its ten per-round stream drains, so the shipped arm is unchanged until an
919/// A/B and the gate battery say otherwise.
920fn dsv4_dspark_chain_device() -> bool {
921    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
922    *V.get_or_init(|| {
923        let on = std::env::var("MEMRA_DSV4_DSPARK_CHAIN").as_deref() == Ok("device");
924        if on {
925            println!(
926                "[spec] DSpark markov chain RESIDENT ON DEVICE (MEMRA_DSV4_DSPARK_CHAIN=device): \
927                 one D2H per round instead of 2 x block_size"
928            );
929        }
930        on
931    })
932}
933
934/// `MEMRA_DSV4_DSPARK_MARKOV=rowblk` runs the DSpark markov bias GEMV through the row-blocked
935/// twin of the f64 island dots. Bit-identical output (same accumulation order and reduction tree,
936/// only R rows share a block), so this is a pure geometry change; the default `base` keeps the
937/// shipped kernel. Measured defect it addresses: 5 x 318 us/round at 416 GB/s = 26% of roofline,
938/// latency-bound on one 7-level reduction tree per 1 KB of weights read.
939fn dsv4_dspark_markov_rowblk() -> bool {
940    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
941    *V.get_or_init(|| {
942        let on = std::env::var("MEMRA_DSV4_DSPARK_MARKOV").as_deref() == Ok("rowblk");
943        if on {
944            println!(
945                "[spec] DSpark markov bias GEMV on the ROW-BLOCKED dots twin \
946                 (MEMRA_DSV4_DSPARK_MARKOV=rowblk; bit-identical, geometry only)"
947            );
948        }
949        on
950    })
951}
952
953/// Drop everything accumulated so far (used to keep the plain arm's brackets out of the
954/// drafted arm's table).
955pub fn dsv4_phase_reset() {
956    DSV4_PHASES.with(|p| *p.borrow_mut() = Dsv4PhaseAcc::default());
957}
958
959macro_rules! dp {
960    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const c_void }};
961}
962macro_rules! dpf {
963    ($slice:expr, $stream:expr) => {{ $slice.device_ptr($stream).0 as *const f32 }};
964}
965macro_rules! dpm {
966    ($slice:expr, $stream:expr) => {{ $slice.device_ptr_mut($stream).0 as *mut f32 }};
967}
968
969// ---------------------------------------------------------------- loading
970
971/// f32 (already NaN-checked by tensor_f32) -> bf16 with a bit-level exactness REFUSAL:
972/// every value in the lane-4 rungs is exactly representable (see receipts); a non-zero
973/// low half means the exactness proof broke and the load must stop, not round.
974fn f32_to_bf16_exact(name: &str, v: &[f32]) -> Vec<u8> {
975    let mut out = Vec::with_capacity(v.len() * 2);
976    for (i, x) in v.iter().enumerate() {
977        let bits = x.to_bits();
978        assert!(
979            bits & 0xFFFF == 0,
980            "{name}: element {i} = {x} not exactly representable in bf16 — lane-4 rung \
981             exactness violated"
982        );
983        out.extend_from_slice(&((bits >> 16) as u16).to_le_bytes());
984    }
985    out
986}
987
988impl Dsv4Gpu {
989    /// Upload a tensor as bf16: BF16-stored tensors ride raw bytes; FP8-blk tensors are
990    /// host-dequantized (lane-1 decoder) and cast with the exactness refusal.
991    fn tensor_bf16(&mut self, stage: usize, name: &str) -> Res<CudaSlice<u8>> {
992        let raw_name = format!("{name}.weight");
993        let is_bf16_raw = self
994            .model
995            .st
996            .raw(&raw_name)
997            .map(|(i, _)| i.dtype == "BF16")
998            .unwrap_or(false)
999            || self
1000                .model
1001                .st
1002                .raw(name)
1003                .map(|(i, _)| i.dtype == "BF16")
1004                .unwrap_or(false);
1005        let stream = self.stages[stage].gpu.stream();
1006        let bytes: u64;
1007        let out = if is_bf16_raw {
1008            let (_, raw) = self
1009                .model
1010                .st
1011                .raw(&raw_name)
1012                .or_else(|| self.model.st.raw(name))
1013                .unwrap();
1014            bytes = raw.len() as u64;
1015            upload_u8(&stream, raw)?
1016        } else {
1017            let (_, v) = self.model.tensor_f32(name);
1018            let b = f32_to_bf16_exact(name, &v);
1019            bytes = b.len() as u64;
1020            upload_u8(&stream, &b)?
1021        };
1022        self.stages[stage].loaded_bytes += bytes;
1023        Ok(out)
1024    }
1025
1026    /// Iteration-5 FP8 dense arm loader. bf16 arm (or no fp8 twin): the device-resident
1027    /// bf16 dequant slab, today's exact bytes. fp8 arm on an F8_E4M3-stored `fp8_ok`
1028    /// tensor (trunk layers only this rung): the as-stored codes + host-decoded f32
1029    /// scale grid go to the device, and the bf16 slab drops to STAGED residency
1030    /// ([`DenseBf16::Host`], it5 ledger item 3) — the fp8 twins own every device
1031    /// decode/verify read and prefill stages the same bytes per pass, so the
1032    /// +~2.7 GiB/card dual residency is gone. Load-time refusals: missing/mis-shaped
1033    /// scale grid, e8m0 NaN code, cols not a multiple of 8 (the uint2 chunk contract),
1034    /// and a 1,024-element stride-sampled BIT check
1035    /// `e4m3(code[r,c]) * sc[r/128, c/128] == host_dequant[r,c]` — the layout/indexing
1036    /// proof, in the load-refusal tradition of the bf16 slab's own exactness check.
1037    fn tensor_dense(
1038        &mut self,
1039        stage: usize,
1040        name: &str,
1041        fp8_ok: bool,
1042    ) -> Res<(DenseBf16, Option<Fp8Dense>)> {
1043        let raw_name = format!("{name}.weight");
1044        let is_bf16_raw = self
1045            .model
1046            .st
1047            .raw(&raw_name)
1048            .map(|(i, _)| i.dtype == "BF16")
1049            .unwrap_or(false)
1050            || self
1051                .model
1052                .st
1053                .raw(name)
1054                .map(|(i, _)| i.dtype == "BF16")
1055                .unwrap_or(false);
1056        if is_bf16_raw || !fp8_ok || !self.dense_fp8 {
1057            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1058        }
1059        // FP8-blk path: resolve the weight raw + its scale sibling.
1060        let (wi, wraw, stem) = if let Some((i, r)) = self.model.st.raw(&raw_name) {
1061            (i.clone(), r.to_vec(), name.to_string())
1062        } else {
1063            let (i, r) = self
1064                .model
1065                .st
1066                .raw(name)
1067                .unwrap_or_else(|| panic!("missing dense tensor {name}"));
1068            let stem = name.strip_suffix(".weight").unwrap_or(name).to_string();
1069            (i.clone(), r.to_vec(), stem)
1070        };
1071        if wi.dtype != "F8_E4M3" {
1072            // not the FP8-blk class (e.g. a BF16-raw special) — bf16 slab only.
1073            return Ok((DenseBf16::Dev(self.tensor_bf16(stage, name)?), None));
1074        }
1075        assert_eq!(wi.shape.len(), 2, "{name}: fp8 dense tensor must be 2-D");
1076        let rows = wi.shape[0] as usize;
1077        let cols = wi.shape[1] as usize;
1078        assert_eq!(cols % 8, 0, "{name}: fp8 dense cols {cols} % 8 != 0");
1079        assert_eq!(wraw.len(), rows * cols, "{name}: fp8 byte count");
1080        let scale_name = format!("{stem}.scale");
1081        let (si, sraw) = self
1082            .model
1083            .st
1084            .raw(&scale_name)
1085            .unwrap_or_else(|| panic!("{name}: F8_E4M3 weight without {scale_name}"));
1086        assert_eq!(si.dtype, "F8_E8M0", "{scale_name}: dtype");
1087        let sc_rows = rows.div_ceil(128);
1088        let sc_cols = cols.div_ceil(128);
1089        assert_eq!(
1090            (si.shape[0] as usize, si.shape[1] as usize),
1091            (sc_rows, sc_cols),
1092            "{scale_name}: scale grid shape vs [ceil({rows}/128), ceil({cols}/128)]"
1093        );
1094        let sc_f32: Vec<f32> = sraw
1095            .iter()
1096            .map(|&b| {
1097                assert_ne!(b, 0xFF, "{scale_name}: e8m0 NaN code");
1098                memra_gguf::dsv4::e8m0_to_f32(b)
1099            })
1100            .collect();
1101        // host dequant (the bf16 slab's own source) + the sampled layout bit-check.
1102        let (_, v) = self.model.tensor_f32(name);
1103        assert_eq!(v.len(), rows * cols, "{name}: dequant len");
1104        let step = (v.len() / 1024).max(1);
1105        for idx in (0..v.len()).step_by(step) {
1106            let (r, c) = (idx / cols, idx % cols);
1107            let got = memra_gguf::nvfp4_repack::fp8_e4m3_to_f32(wraw[idx])
1108                * sc_f32[(r / 128) * sc_cols + c / 128];
1109            assert_eq!(
1110                got.to_bits(),
1111                v[idx].to_bits(),
1112                "{name}: fp8 arm layout check failed at [{r},{c}] ({got} vs {})",
1113                v[idx]
1114            );
1115        }
1116        let b = f32_to_bf16_exact(name, &v);
1117        let stream = self.stages[stage].gpu.stream();
1118        // item 3: the bf16 slab is NOT uploaded — the fp8 pair owns every device
1119        // decode/verify read (dwsel) and prefill stages `b` per pass. loaded_bytes
1120        // counts DEVICE bytes only, so vram_report stays honest.
1121        let codes = upload_u8(&stream, &wraw)?;
1122        let scales = upload_f32(&stream, &sc_f32)?;
1123        self.stages[stage].loaded_bytes += (wraw.len() + sc_f32.len() * 4) as u64;
1124        Ok((
1125            DenseBf16::Host(b),
1126            Some(Fp8Dense {
1127                codes,
1128                scales,
1129                sc_cols,
1130                rows,
1131                cols,
1132            }),
1133        ))
1134    }
1135
1136    /// Upload a tensor as f32 (islands): any storage dtype goes through the proven
1137    /// tensor_f32 decode.
1138    fn tensor_f32_dev(&mut self, stage: usize, name: &str) -> Res<CudaSlice<f32>> {
1139        let (_, v) = self.model.tensor_f32(name);
1140        let stream = self.stages[stage].gpu.stream();
1141        self.stages[stage].loaded_bytes += (v.len() * 4) as u64;
1142        upload_f32(&stream, &v)
1143    }
1144
1145    fn load_cmp(
1146        &mut self,
1147        stage: usize,
1148        prefix: &str,
1149        ratio: usize,
1150        d: usize,
1151        rotate: bool,
1152    ) -> Res<CmpDev> {
1153        let (wkv_shape, _) = self.model.tensor_f32(&format!("{prefix}.wkv.weight"));
1154        let latent = wkv_shape[0];
1155        let overlap = ratio == 4;
1156        assert_eq!(latent, if overlap { 2 * d } else { d }, "{prefix} latent");
1157        Ok(CmpDev {
1158            ratio,
1159            d,
1160            latent,
1161            overlap,
1162            rotate,
1163            wkv: self.tensor_f32_dev(stage, &format!("{prefix}.wkv.weight"))?,
1164            wgate: self.tensor_f32_dev(stage, &format!("{prefix}.wgate.weight"))?,
1165            norm: self.tensor_f32_dev(stage, &format!("{prefix}.norm.weight"))?,
1166            ape: self.tensor_f32_dev(stage, &format!("{prefix}.ape"))?,
1167        })
1168    }
1169
1170    /// Load one block's device weights. `prefix` is "layers.N" for trunk, "mtp.0" for
1171    /// the MTP block (whose layer id is n_trunk — ratio 0, score-routed, MXFP4 experts).
1172    fn load_layer(&mut self, stage: usize, il: u32, prefix: &str) -> Res<LayerDev> {
1173        let d = self.model.cfg().clone();
1174        let moe = self.model.mc.moe.clone().expect("moe block");
1175        let ratio = d.compress_ratio(il) as usize;
1176        let hd = d.head_dim as usize;
1177        let p = prefix.to_string();
1178        let hash = d.is_hash_layer(il);
1179        let ne = moe.expert_count as usize;
1180        let inter = moe.expert_ff_length as usize;
1181        let hidden = self.model.mc.n_embd as usize;
1182
1183        // hc host params
1184        let hc_load = |m: &Dsv4Model, fam: &str| -> (Vec<f32>, Vec<f32>, Vec<f32>) {
1185            let fn_w = m.tensor_f32(&format!("{p}.hc_{fam}_fn")).1;
1186            let base = m.tensor_f32(&format!("{p}.hc_{fam}_base")).1;
1187            let scale = m.tensor_f32(&format!("{p}.hc_{fam}_scale")).1;
1188            (fn_w, base, scale)
1189        };
1190        let (attn_fn, attn_base, attn_scale) = hc_load(&self.model, "attn");
1191        let (ffn_fn, ffn_base, ffn_scale) = hc_load(&self.model, "ffn");
1192        let stream = self.stages[stage].gpu.stream();
1193        let hc_attn_fn = upload_f32(&stream, &attn_fn)?;
1194        let hc_ffn_fn = upload_f32(&stream, &ffn_fn)?;
1195        self.stages[stage].loaded_bytes += ((attn_fn.len() + ffn_fn.len()) * 4) as u64;
1196
1197        // expert slab (as-stored quant bytes) — geometry derived from config; the recipe
1198        // is DETECTED from the stored dtype (U8+weight_scale+weight_scale_2 = modelopt
1199        // NVFP4; I8+scale = OCP MXFP4, the MTP experts) and refused on any surprise.
1200        let (wi0, _) = self
1201            .model
1202            .st
1203            .raw(&format!("{p}.ffn.experts.0.w1.weight"))
1204            .unwrap_or_else(|| panic!("missing {p}.ffn.experts.0.w1.weight"));
1205        let expert_kind = match wi0.dtype.as_str() {
1206            "U8" => ExpertKind::Nvfp4,
1207            "I8" => ExpertKind::Mxfp4,
1208            other => panic!("{p}: unexpected expert weight dtype {other}"),
1209        };
1210        let wbytes = inter * hidden / 2; // same for w1/w2/w3 (transposed dims)
1211        let sbytes = match expert_kind {
1212            ExpertKind::Nvfp4 => inter * hidden / 16,
1213            ExpertKind::Mxfp4 => inter * hidden / 32,
1214        };
1215        let mut experts_w = stream
1216            .alloc_zeros::<u8>(ne * 3 * wbytes)
1217            .map_err(e("alloc expert slab"))?;
1218        let mut experts_sc = stream
1219            .alloc_zeros::<u8>(ne * 3 * sbytes)
1220            .map_err(e("alloc expert scale slab"))?;
1221        let mut experts_s2 = Vec::with_capacity(ne * 3);
1222        for ex in 0..ne {
1223            for (pi, pname) in ["w1", "w2", "w3"].iter().enumerate() {
1224                let base = format!("{p}.ffn.experts.{ex}.{pname}");
1225                let (wi, wb) = self
1226                    .model
1227                    .st
1228                    .raw(&format!("{base}.weight"))
1229                    .unwrap_or_else(|| panic!("missing {base}.weight"));
1230                assert_eq!(wb.len(), wbytes, "{base}: weight bytes");
1231                let sb = match expert_kind {
1232                    ExpertKind::Nvfp4 => {
1233                        assert_eq!(wi.dtype, "U8", "{base}: expected NVFP4 U8 weight");
1234                        let (_, sb) = self
1235                            .model
1236                            .st
1237                            .raw(&format!("{base}.weight_scale"))
1238                            .unwrap_or_else(|| panic!("missing {base}.weight_scale"));
1239                        let (_, s2b) = self
1240                            .model
1241                            .st
1242                            .raw(&format!("{base}.weight_scale_2"))
1243                            .unwrap_or_else(|| panic!("missing {base}.weight_scale_2"));
1244                        let s2 = f32::from_le_bytes(s2b.try_into().expect("scale_2 4B"));
1245                        // pow2 refusal: the bf16-exactness proof of the on-the-fly dequant
1246                        // rung requires a pow2 scale_2 (receipts, "Quant rungs" §1).
1247                        assert!(
1248                            s2 > 0.0 && s2.to_bits() & 0x007F_FFFF == 0,
1249                            "{base}: scale_2 {s2} not a power of two — rung exactness violated"
1250                        );
1251                        experts_s2.push(s2);
1252                        sb
1253                    }
1254                    ExpertKind::Mxfp4 => {
1255                        assert_eq!(wi.dtype, "I8", "{base}: expected MXFP4 I8 weight");
1256                        let (si, sb) = self
1257                            .model
1258                            .st
1259                            .raw(&format!("{base}.scale"))
1260                            .unwrap_or_else(|| panic!("missing {base}.scale"));
1261                        assert_eq!(si.dtype, "F8_E8M0", "{base}: expected E8M0 scale");
1262                        // e8m0 0xFF is the NaN code — refuse at load, never zero a scale
1263                        assert!(
1264                            !sb.contains(&0xFFu8),
1265                            "{base}: E8M0 NaN scale code — refusing"
1266                        );
1267                        experts_s2.push(1.0);
1268                        sb
1269                    }
1270                };
1271                assert_eq!(sb.len(), sbytes, "{base}: scale bytes");
1272                let off = (ex * 3 + pi) * wbytes;
1273                let mut view = experts_w.slice_mut(off..off + wbytes);
1274                stream
1275                    .memcpy_htod(wb, &mut view)
1276                    .map_err(e("htod expert w"))?;
1277                let soff = (ex * 3 + pi) * sbytes;
1278                let mut sview = experts_sc.slice_mut(soff..soff + sbytes);
1279                stream
1280                    .memcpy_htod(sb, &mut sview)
1281                    .map_err(e("htod expert sc"))?;
1282            }
1283        }
1284        self.stages[stage].loaded_bytes +=
1285            (ne * 3 * (wbytes + sbytes)) as u64 + (ne * 3 * 4) as u64;
1286
1287        let cmp = if ratio != 0 {
1288            Some(self.load_cmp(stage, &format!("{p}.attn.compressor"), ratio, hd, false)?)
1289        } else {
1290            None
1291        };
1292        // iteration-5 FP8 dense arm: trunk layers only this rung (the drafter/MTP
1293        // blocks ride the prefill helpers, which consume the bf16 slabs).
1294        let fp8_ok = p.starts_with("layers.");
1295        let idx = if d.has_indexer(il) {
1296            let heads = d.index_n_heads as usize;
1297            let ihd = d.index_head_dim as usize;
1298            let (iwq_b, iwq_b_fp8) =
1299                self.tensor_dense(stage, &format!("{p}.attn.indexer.wq_b"), fp8_ok)?;
1300            let (iwp, iwp_fp8) = self.tensor_dense(
1301                stage,
1302                &format!("{p}.attn.indexer.weights_proj.weight"),
1303                fp8_ok,
1304            )?;
1305            Some(IdxDev {
1306                wq_b: iwq_b,
1307                weights_proj: iwp,
1308                wq_b_fp8: iwq_b_fp8,
1309                weights_proj_fp8: iwp_fp8,
1310                cmp: self.load_cmp(
1311                    stage,
1312                    &format!("{p}.attn.indexer.compressor"),
1313                    ratio,
1314                    ihd,
1315                    true,
1316                )?,
1317                heads,
1318                hd: ihd,
1319                topk: d.index_topk as usize,
1320            })
1321        } else {
1322            None
1323        };
1324
1325        // lane 8: device twins of the host routing/hc constants. tid2eid is validated
1326        // here ONCE (range + per-row distinctness — the checks the legacy route_host
1327        // asserts per token) because the device route kernel cannot refuse.
1328        let stream = self.stages[stage].gpu.stream();
1329        let hc_attn_base_dev = upload_f32(&stream, &attn_base)?;
1330        let hc_attn_scale_dev = upload_f32(&stream, &attn_scale)?;
1331        let hc_ffn_base_dev = upload_f32(&stream, &ffn_base)?;
1332        let hc_ffn_scale_dev = upload_f32(&stream, &ffn_scale)?;
1333        let gate_bias_host: Option<Vec<f32>> = if hash {
1334            None
1335        } else {
1336            Some(self.model.tensor_f32(&format!("{p}.ffn.gate.bias")).1)
1337        };
1338        let gate_bias_dev = match &gate_bias_host {
1339            Some(b) => Some(upload_f32(&stream, b)?),
1340            None => None,
1341        };
1342        let tid2eid_host: Option<Vec<i64>> = if hash {
1343            Some(self.model.tensor_i64(&format!("{p}.ffn.gate.tid2eid")).1)
1344        } else {
1345            None
1346        };
1347        let tid2eid_dev = match &tid2eid_host {
1348            Some(t) => {
1349                let topk = moe.expert_used_count as usize;
1350                assert_eq!(t.len() % topk, 0, "{p}: tid2eid rows");
1351                let mut t32 = Vec::with_capacity(t.len());
1352                for row in t.chunks(topk) {
1353                    let mut seen = std::collections::BTreeSet::new();
1354                    for &ex in row {
1355                        assert!(
1356                            (0..ne as i64).contains(&ex),
1357                            "{p}: tid2eid out of range at load"
1358                        );
1359                        assert!(seen.insert(ex), "{p}: duplicate expert id in tid2eid row");
1360                        t32.push(ex as i32);
1361                    }
1362                }
1363                Some(upload_i32(&stream, &t32)?)
1364            }
1365            None => None,
1366        };
1367        let experts_s2_dev = upload_f32(&stream, &experts_s2)?;
1368
1369        let (wq_a, wq_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_a"), fp8_ok)?;
1370        let (wq_b, wq_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wq_b"), fp8_ok)?;
1371        let (wkv, wkv_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wkv"), fp8_ok)?;
1372        let (wo_a, wo_a_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_a"), fp8_ok)?;
1373        let (wo_b, wo_b_fp8) = self.tensor_dense(stage, &format!("{p}.attn.wo_b"), fp8_ok)?;
1374        let (sw1, sw1_fp8) =
1375            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w1"), fp8_ok)?;
1376        let (sw2, sw2_fp8) =
1377            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w2"), fp8_ok)?;
1378        let (sw3, sw3_fp8) =
1379            self.tensor_dense(stage, &format!("{p}.ffn.shared_experts.w3"), fp8_ok)?;
1380
1381        Ok(LayerDev {
1382            il,
1383            ratio,
1384            expert_kind,
1385            hc_attn_base_dev,
1386            hc_attn_scale_dev,
1387            hc_ffn_base_dev,
1388            hc_ffn_scale_dev,
1389            gate_bias_dev,
1390            tid2eid_dev,
1391            experts_s2_dev,
1392            wq_a,
1393            wq_b,
1394            wkv,
1395            wo_a,
1396            wo_b,
1397            wq_a_fp8,
1398            wq_b_fp8,
1399            wkv_fp8,
1400            wo_a_fp8,
1401            wo_b_fp8,
1402            q_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.q_norm.weight"))?,
1403            kv_norm: self.tensor_f32_dev(stage, &format!("{p}.attn.kv_norm.weight"))?,
1404            attn_norm: self.tensor_f32_dev(stage, &format!("{p}.attn_norm.weight"))?,
1405            ffn_norm: self.tensor_f32_dev(stage, &format!("{p}.ffn_norm.weight"))?,
1406            sink: self.tensor_f32_dev(stage, &format!("{p}.attn.attn_sink"))?,
1407            cmp,
1408            idx,
1409            hc_attn_fn,
1410            hc_ffn_fn,
1411            hc_attn_base: attn_base,
1412            hc_attn_scale: attn_scale,
1413            hc_ffn_base: ffn_base,
1414            hc_ffn_scale: ffn_scale,
1415            gate_w: self.tensor_f32_dev(stage, &format!("{p}.ffn.gate.weight"))?,
1416            gate_bias: gate_bias_host,
1417            tid2eid: tid2eid_host,
1418            experts_w,
1419            experts_sc,
1420            experts_s2,
1421            shared_w: [sw1, sw2, sw3],
1422            shared_fp8: [sw1_fp8, sw2_fp8, sw3_fp8],
1423        })
1424    }
1425
1426    /// Open the artifact and place the trunk across `devices`. `split_at` = first layer
1427    /// of stage 1, derived from per-layer byte math unless overridden.
1428    pub fn load(
1429        dir: &Path,
1430        devices: &[usize],
1431        variant: ActQuantVariant,
1432        max_seq: usize,
1433    ) -> Res<Self> {
1434        assert_eq!(devices.len(), 2, "lane 4 placement is a 2-card layer split");
1435        let model = Dsv4Model::open(dir);
1436        let d = model.cfg().clone();
1437        let mc = model.mc.clone();
1438        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
1439        let rd = d.qk_rope_head_dim as usize;
1440
1441        // split point: balance per-layer resident bytes (experts uniform; fine layers
1442        // carry the indexer). Computed from config, not hardcoded.
1443        let layer_bytes = |il: u32| -> u64 {
1444            let ratio = d.compress_ratio(il);
1445            let base = 3_875_000_000u64; // experts slab + attn bf16 (measured class math)
1446            match ratio {
1447                4 => base + 66_000_000,
1448                _ => base,
1449            }
1450        };
1451        let total: u64 = (0..n_trunk).map(layer_bytes).sum();
1452        let mut acc = 0u64;
1453        let mut split_at = n_trunk / 2;
1454        for il in 0..n_trunk {
1455            acc += layer_bytes(il);
1456            if acc * 2 >= total {
1457                split_at = il + 1;
1458                break;
1459            }
1460        }
1461
1462        let fc_yarn_host = precompute_freqs_cis(
1463            rd,
1464            max_seq,
1465            d.rope_yarn_orig_ctx,
1466            d.compress_rope_theta,
1467            d.rope_yarn_factor,
1468            d.rope_yarn_beta_fast,
1469            d.rope_yarn_beta_slow,
1470        );
1471        let fc_plain_host = precompute_freqs_cis(
1472            rd,
1473            max_seq,
1474            0,
1475            mc.rope_freq_base,
1476            d.rope_yarn_factor,
1477            d.rope_yarn_beta_fast,
1478            d.rope_yarn_beta_slow,
1479        );
1480        let flat =
1481            |fc: &FreqsCis| -> Vec<f32> { fc.cs.iter().flat_map(|&(c, s)| [c, s]).collect() };
1482
1483        let inter = mc.moe.as_ref().expect("moe").expert_ff_length as usize;
1484        let hidden = mc.n_embd as usize;
1485        let mut stages = Vec::new();
1486        for &dev in devices {
1487            let gpu = memra_runtime::Gpu::new(dev).map_err(e("Gpu::new"))?;
1488            // Engine::new idiom (lib.rs:1172): single stream per stage, explicit syncs at
1489            // the boundary — cudarc per-arg event tracking off.
1490            unsafe { gpu.ctx.disable_event_tracking() };
1491            let stream = gpu.stream();
1492            let fc_yarn = upload_f32(&stream, &flat(&fc_yarn_host))?;
1493            let fc_plain = upload_f32(&stream, &flat(&fc_plain_host))?;
1494            let ws = stream.alloc_zeros::<u8>(64 << 20).map_err(e("ws alloc"))?;
1495            let deq = [
1496                stream
1497                    .alloc_zeros::<u8>(inter * hidden * 2)
1498                    .map_err(e("deq"))?,
1499                stream
1500                    .alloc_zeros::<u8>(inter * hidden * 2)
1501                    .map_err(e("deq"))?,
1502                stream
1503                    .alloc_zeros::<u8>(inter * hidden * 2)
1504                    .map_err(e("deq"))?,
1505            ];
1506            stages.push(Stage {
1507                dev,
1508                gpu,
1509                layers: Vec::new(),
1510                embed: None,
1511                head: None,
1512                trunk_norm: None,
1513                hc_head_fn: None,
1514                fc_yarn,
1515                fc_plain,
1516                ws,
1517                deq,
1518                loaded_bytes: 0,
1519                hc_head_base_dev: None,
1520                hc_head_scale_dev: None,
1521            });
1522        }
1523
1524        // lane 8: decode-path seam (read once, printed; one binary carries both arms)
1525        let decode_path = match std::env::var("MEMRA_DSV4_DECODE_PATH").as_deref() {
1526            Err(_) | Ok("") | Ok("legacy") => DecodePath::Legacy,
1527            Ok("device-hostmath") => DecodePath::Device { host_math: true },
1528            Ok("device") => DecodePath::Device { host_math: false },
1529            Ok(other) => {
1530                return Err(format!(
1531                    "MEMRA_DSV4_DECODE_PATH '{other}' unknown (legacy | device | device-hostmath)"
1532                ));
1533            }
1534        };
1535        // lane 9: island-dots arm seam (owner-gated fork; f64 = the oracle-truth arm).
1536        // 0731 re-gate extension rung: `f32x` = the f32 dots arm PLUS f32-accumulation
1537        // twins for the remaining device-path f64 chains (owner-authorized fork).
1538        // OWNER RATIFICATION 2026-08-19: f32x is the DEFAULT device-decode dots arm
1539        // (quality-stays condition met at the owner bar — 0731 re-gate Task B gates:
1540        // decode 52/52, CPU teacher-forcing 257/260 all-in-band, tf-gate 158/160,
1541        // determinism ×2). f64 stays the selectable oracle-truth arm; hc_sinkhorn is
1542        // NOT part of f32x (never authorized). Legacy path and prefill are untouched.
1543        // The unset default is DEVICE-decode-scoped by the ratification's own words:
1544        // the legacy path never consults the flag, so on Legacy an UNSET env resolves
1545        // to the f64 oracle bytes rather than tripping the f32-requires-device refusal
1546        // (box4 find, 2026-08-20: dsv4-gpu-gate under the flipped default panicked at
1547        // load on the legacy path — the refusal is for EXPLICIT f32/f32x only).
1548        // Illegal combos are BOOT REFUSALS (Err), never post-build aborts — hermes
1549        // review fingerprint a4e3d9a8eab4cf17: an assert! after Dsv4Gpu is built dies
1550        // as a process ABORT, which a serving watchdog restarts in a crash loop; the
1551        // unknown-enum arms already refuse at parse, so the combo checks live here too.
1552        let on_device = matches!(decode_path, DecodePath::Device { .. });
1553        let (dots_f32, chains_f32) = match std::env::var("MEMRA_DSV4_DOTS_ARM").as_deref() {
1554            Err(_) | Ok("") => {
1555                // ratified default, DEVICE-decode-scoped: legacy resolves f64.
1556                if on_device {
1557                    (true, true)
1558                } else {
1559                    (false, false)
1560                }
1561            }
1562            Ok(explicit @ ("f32x" | "f32")) if !on_device => {
1563                return Err(format!(
1564                    "MEMRA_DSV4_DOTS_ARM={explicit} requires MEMRA_DSV4_DECODE_PATH=device \
1565                     (the f32 dots arms exist on the device decode path only)"
1566                ));
1567            }
1568            Ok("f32x") => (true, true),
1569            Ok("f64") => (false, false),
1570            Ok("f32") => (true, false),
1571            Ok(other) => {
1572                return Err(format!(
1573                    "MEMRA_DSV4_DOTS_ARM '{other}' unknown (f64 | f32 | f32x)"
1574                ));
1575            }
1576        };
1577
1578        // Iteration-3 rung 4c, MEASURED FORK (nsys, drafted rounds [4,12)): the DRAFTER's
1579        // shared-trunk-head projection runs `dsv4_dots_f32` — the f64 kernel — over
1580        // block_size rows, and it measured **16.3 ms of a 78 ms drafted round (21%)**, one
1581        // instance at 13-14.7 ms. The trunk's OWN head already runs the ratified f32x arm
1582        // on the SAME weights; the drafter's copy only picks DRAFTS (verification always
1583        // emits the trunk's argmax, so output identity cannot depend on it). This arm makes
1584        // the drafter's exit head follow the ratified class. Default is f64 — today's gated
1585        // bytes, untouched — because the lane-10 components gate ran the drafter at f64 and
1586        // a gated component does not change default without its gate; `f32x` is the
1587        // measured arm offered for owner ratification with the acceptance delta reported.
1588        // OWNER RATIFICATION 2026-08-19 (relayed to the box4 lane 2026-08-20): f32x is
1589        // the DEFAULT drafter exit-head arm — the fork was measured quality-INERT
1590        // (acceptance digest byte-identical across arms on the gate fixture AND 3,321
1591        // corpora rounds, iteration-3 rung 4c) and it only picks DRAFTS (the greedy
1592        // identity law keeps the emitted stream the trunk's own argmax either way).
1593        // f64 stays selectable as the lane-10 oracle-truth arm. hc_sinkhorn remains f64
1594        // in every arm — never authorized.
1595        let dspark_head_f32 = match std::env::var("MEMRA_DSV4_DSPARK_HEAD_ARM").as_deref() {
1596            Err(_) | Ok("") | Ok("f32x") => true,
1597            Ok("f64") => false,
1598            Ok(other) => {
1599                return Err(format!(
1600                    "MEMRA_DSV4_DSPARK_HEAD_ARM '{other}' unknown (f64 | f32x)"
1601                ));
1602            }
1603        };
1604
1605        // iteration-5 FP8 dense arm seam — OWNER RATIFICATION 2026-08-20 (the ratified
1606        // bundle, executed in the v0.98 train once the it5 item-3 cells went green on
1607        // box7): **fp8 is the DEFAULT DEVICE-DECODE dense arm.** Receipts: bit-identical
1608        // to bf16 on four boxes / five binaries (dsgate accept shas
1609        // 150342bae32b38b5/85603e87fadf7876 one bit pattern, tf-gate 158/160 with the
1610        // banked near-ties at steps 22+134), completed interleaved x5 A/B plain
1611        // 41.06 -> 47.19 median (+14.9%, box5), and the item-3 staged residency turns
1612        // the arm's +2.7 GiB/card dual-residency cost into a saving (box7: -5.56/-5.34
1613        // GiB/card vs the dual-resident builds, every item-3 bit-gate green).
1614        // DEVICE-scoped exactly like the ratified dots default (82a754fbec): unset on
1615        // the LEGACY path resolves bf16 (legacy has no fp8 twins and must keep
1616        // booting); explicit fp8 on legacy still refuses; bf16 stays selectable
1617        // everywhere. Resolution is the pure `resolve_dense_arm` so the flip is
1618        // toothed-testable; the `[load] dense arm:` line below is the boot receipt.
1619        let dense_fp8 = resolve_dense_arm(
1620            std::env::var("MEMRA_DSV4_DENSE_ARM").ok().as_deref(),
1621            on_device,
1622        )?;
1623
1624        let mut me = Dsv4Gpu {
1625            model,
1626            stages,
1627            layer_stage: (0..n_trunk).map(|il| usize::from(il >= split_at)).collect(),
1628            split_at,
1629            max_seq,
1630            variant,
1631            fc_yarn_host,
1632            fc_plain_host,
1633            mtp: None,
1634            expert_arm: if memra_gguf::dsv4_forward::expert_arm_native() {
1635                ExpertArm::Native
1636            } else {
1637                ExpertArm::Bf16Dequant
1638            },
1639            decode_path,
1640            dots_f32,
1641            chains_f32,
1642            dspark_head_f32,
1643            dense_fp8,
1644            dspark: None,
1645            boundary_ev: Vec::new(),
1646            hc_head_base: Vec::new(),
1647            hc_head_scale: Vec::new(),
1648        };
1649        eprintln!(
1650            "[load] expert arm: {:?} | decode path: {:?} | dots arm: {}",
1651            me.expert_arm,
1652            me.decode_path,
1653            if me.chains_f32 {
1654                "f32x (dots + sink/norm/indexer chains)"
1655            } else if me.dots_f32 {
1656                "f32"
1657            } else {
1658                "f64"
1659            }
1660        );
1661        eprintln!(
1662            "[load] dspark exit-head dots arm: {} (rung-4c fork; drafts only, never the \
1663             emitted stream)",
1664            if me.dspark_head_f32 { "f32x" } else { "f64" }
1665        );
1666        eprintln!(
1667            "[load] dense arm: {} (iteration-5; fp8 = FP8-blk linears as-stored on the \
1668             device decode/verify paths, bit-identical twins)",
1669            if me.dense_fp8 { "fp8" } else { "bf16" }
1670        );
1671        if matches!(me.decode_path, DecodePath::Device { .. }) && me.expert_arm != ExpertArm::Native
1672        {
1673            // the indirect fused dispatch is an fp4-slab program — the bf16-dequant arm
1674            // has no device-indirect twin. Boot refusal, not a post-build abort
1675            // (hermes fingerprint a4e3d9a8eab4cf17); the dots/dense combos refuse at
1676            // env-parse above for the same reason.
1677            return Err(
1678                "MEMRA_DSV4_DECODE_PATH=device requires MEMRA_DSV4_EXPERT_ARM=native".to_string(),
1679            );
1680        }
1681
1682        // lane 8: peer transport for the PP boundary (pp.rs idiom: cuCtxEnablePeerAccess
1683        // both directions + default-mempool access grants — cudarc buffers are
1684        // stream-ordered-pool allocations, unmapped by EnablePeerAccess alone).
1685        if matches!(me.decode_path, DecodePath::Device { .. }) && me.stages.len() > 1 {
1686            use cudarc::driver::sys as cus;
1687            for a in 0..me.stages.len() {
1688                for b in 0..me.stages.len() {
1689                    if a == b || me.stages[a].dev == me.stages[b].dev {
1690                        continue;
1691                    }
1692                    me.stages[a]
1693                        .gpu
1694                        .ctx
1695                        .bind_to_thread()
1696                        .map_err(e("peer bind"))?;
1697                    let rc =
1698                        unsafe { cus::cuCtxEnablePeerAccess(me.stages[b].gpu.ctx.cu_ctx(), 0) };
1699                    if rc != cus::cudaError_enum::CUDA_SUCCESS
1700                        && rc != cus::cudaError_enum::CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED
1701                    {
1702                        return Err(format!(
1703                            "cuCtxEnablePeerAccess(dev{} -> dev{}) failed: {rc:?}",
1704                            me.stages[a].dev, me.stages[b].dev
1705                        ));
1706                    }
1707                    let dev = cudarc::driver::result::device::get(me.stages[a].dev as i32)
1708                        .map_err(e("device get"))?;
1709                    let mut pool: cus::CUmemoryPool = std::ptr::null_mut();
1710                    unsafe {
1711                        cus::cuDeviceGetDefaultMemPool(&mut pool, dev)
1712                            .result()
1713                            .map_err(e("default pool"))?;
1714                    }
1715                    let desc = cus::CUmemAccessDesc {
1716                        location: cus::CUmemLocation {
1717                            type_: cus::CUmemLocationType::CU_MEM_LOCATION_TYPE_DEVICE,
1718                            id: me.stages[b].dev as i32,
1719                        },
1720                        flags: cus::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
1721                    };
1722                    let rc = unsafe { cus::cuMemPoolSetAccess(pool, &desc, 1) };
1723                    if rc != cus::cudaError_enum::CUDA_SUCCESS {
1724                        return Err(format!(
1725                            "cuMemPoolSetAccess(dev{} pool -> dev{}) failed: {rc:?}",
1726                            me.stages[a].dev, me.stages[b].dev
1727                        ));
1728                    }
1729                }
1730            }
1731            for bnd in 0..me.stages.len() - 1 {
1732                let ev = me.stages[bnd]
1733                    .gpu
1734                    .ctx
1735                    .new_event(None)
1736                    .map_err(e("boundary event"))?;
1737                me.boundary_ev.push(ev);
1738            }
1739            // PEER BYTE-INTEGRITY PROBE (lane/hermes-perf-fixes, 2026-08-23): enable +
1740            // pool grants alone prove ADDRESSABILITY, not integrity — see the probe
1741            // helpers' header for the Pod B receipt. Ladder up to the prefill
1742            // hidden-state payload class; both directions per cross-device boundary;
1743            // FAIL-CLOSED at load (the device PP path has no host-bounce twin).
1744            {
1745                let hidden = me.model.mc.n_embd as usize;
1746                let hc = me.model.cfg().hc_mult as usize;
1747                // Include the exact persistent decode payload (hc*hidden f32) and the
1748                // maximum served verify-width payload, not only nearby powers of two. Peer
1749                // corruption on the affected drivers is size-class-sensitive (Hermes
1750                // `58843bb6b924125b`).
1751                let ladder = dsv4_peer_probe_ladder(hidden, hc);
1752                let probe_t0 = std::time::Instant::now();
1753                let mut copies = 0usize;
1754                for bnd in 0..me.stages.len() - 1 {
1755                    if me.stages[bnd].dev == me.stages[bnd + 1].dev {
1756                        continue;
1757                    }
1758                    for (s, d) in [(bnd, bnd + 1), (bnd + 1, bnd)] {
1759                        for &bytes in &ladder {
1760                            dsv4_peer_probe_copy(&me.stages[s], &me.stages[d], bnd, bytes)
1761                                .map_err(|err| {
1762                                    format!(
1763                                        "dsv4 PP peer byte-integrity probe FAILED: \
1764                                         boundary={bnd} dev{}->dev{} bytes={bytes}: {err}; \
1765                                         refusing the device PP path (silent hidden-state \
1766                                         corruption class — fix the P2P fabric or serve a \
1767                                         non-device MEMRA_DSV4_DECODE_PATH)",
1768                                        me.stages[s].dev, me.stages[d].dev,
1769                                    )
1770                                })?;
1771                            copies += 1;
1772                        }
1773                    }
1774                }
1775                eprintln!(
1776                    "[load] lane-8 peer byte-integrity probe PASS: {} boundaries, \
1777                     {copies} copies, ladder {ladder:?} bytes, {:.1}ms",
1778                    me.boundary_ev.len(),
1779                    probe_t0.elapsed().as_secs_f64() * 1e3,
1780                );
1781            }
1782            eprintln!(
1783                "[load] lane-8 peer transport enabled ({} boundaries)",
1784                me.boundary_ev.len()
1785            );
1786        }
1787
1788        // stage 0: embed; last stage: head + trunk hc_head/norm
1789        me.stages[0].embed = Some({
1790            let (_, raw) = me.model.st.raw("embed.weight").expect("embed.weight");
1791            let stream = me.stages[0].gpu.stream();
1792            me.stages[0].loaded_bytes += raw.len() as u64;
1793            upload_u8(&stream, raw)?
1794        });
1795        let last = me.stages.len() - 1;
1796        me.stages[last].head = Some({
1797            let (_, raw) = me.model.st.raw("head.weight").expect("head.weight");
1798            let stream = me.stages[last].gpu.stream();
1799            me.stages[last].loaded_bytes += raw.len() as u64;
1800            upload_u8(&stream, raw)?
1801        });
1802        me.stages[last].trunk_norm = Some(me.tensor_f32_dev(last, "norm.weight")?);
1803        me.stages[last].hc_head_fn = Some(me.tensor_f32_dev(last, "hc_head_fn")?);
1804        me.hc_head_base = me.model.tensor_f32("hc_head_base").1;
1805        me.hc_head_scale = me.model.tensor_f32("hc_head_scale").1;
1806        {
1807            let stream = me.stages[last].gpu.stream();
1808            let base_dev = upload_f32(&stream, &me.hc_head_base)?;
1809            let scale_dev = upload_f32(&stream, &me.hc_head_scale)?;
1810            me.stages[last].hc_head_base_dev = Some(base_dev);
1811            me.stages[last].hc_head_scale_dev = Some(scale_dev);
1812        }
1813
1814        let t0 = std::time::Instant::now();
1815        for il in 0..n_trunk {
1816            let stage = me.layer_stage[il as usize];
1817            let l = me.load_layer(stage, il, &format!("layers.{il}"))?;
1818            me.stages[stage].layers.push(l);
1819            if il % 4 == 3 || il + 1 == n_trunk {
1820                eprintln!(
1821                    "[load] layer {il} -> dev{} done t={:.0}s",
1822                    me.stages[stage].dev,
1823                    t0.elapsed().as_secs_f64()
1824                );
1825            }
1826        }
1827        // MTP (NextN) block on the last stage — optional path taken because the trunk
1828        // landed with box time to spare (lane brief); layer id = n_trunk from config.
1829        // 0731 lineage: the `mtp.*` namespace is the DSPARK drafter (3 window-only
1830        // blocks; census per the mint receipts: mtp.0 main_proj/main_norm, mtp.2
1831        // markov_w1/w2 + confidence_head — no e_proj/enorm), NOT a NextN head. Its GPU
1832        // path is a separate lane; the trunk forward never consumes it. Discriminate on
1833        // the artifact's own stored structure (lane-1 law: stored tensor names are the
1834        // recipe truth): a NextN block carries `mtp.0.e_proj.weight` (RAW safetensors
1835        // name — measured on both artifacts: preview has e_proj.weight+.scale, 0731 has
1836        // no e_proj keys; the stem alone misses because `has` is raw-exact).
1837        let nextn = me.model.mc.nextn_predict_layers;
1838        if nextn > 0 && me.model.has("mtp.0.e_proj.weight") {
1839            assert_eq!(
1840                nextn, 1,
1841                "multi-NextN chains not wired (single MTP layer expected)"
1842            );
1843            let p = "mtp.0";
1844            let layer = me.load_layer(last, n_trunk, p)?;
1845            assert_eq!(
1846                layer.expert_kind,
1847                ExpertKind::Mxfp4,
1848                "MTP experts must be MXFP4"
1849            );
1850            let mtp = MtpDev {
1851                layer,
1852                enorm: me.tensor_f32_dev(last, &format!("{p}.enorm.weight"))?,
1853                hnorm: me.tensor_f32_dev(last, &format!("{p}.hnorm.weight"))?,
1854                norm: me.tensor_f32_dev(last, &format!("{p}.norm.weight"))?,
1855                e_proj: me.tensor_bf16(last, &format!("{p}.e_proj"))?,
1856                h_proj: me.tensor_bf16(last, &format!("{p}.h_proj"))?,
1857                hc_head_fn: me.tensor_f32_dev(last, &format!("{p}.hc_head_fn"))?,
1858                hc_head_base: me.model.tensor_f32(&format!("{p}.hc_head_base")).1,
1859                hc_head_scale: me.model.tensor_f32(&format!("{p}.hc_head_scale")).1,
1860            };
1861            me.mtp = Some(mtp);
1862        } else if nextn > 0 {
1863            if std::env::var("MEMRA_DSV4_DRAFTER").as_deref() == Ok("dspark") {
1864                // iteration 3: the DSpark drafter, whole module on the LAST stage
1865                // (tap layers 40/41/42 + shared head locality — VRAM plan in the
1866                // iteration-3 receipts). Census pins + NextN refusal ride the CPU
1867                // oracle's own config loader (one refusal program, two realizations).
1868                let cfg = memra_gguf::dsv4_dspark::DsparkConfig::load(dir, &me.model);
1869                let hidden = me.model.mc.n_embd as usize;
1870                let mut blocks = Vec::with_capacity(cfg.n_blocks);
1871                for k in 0..cfg.n_blocks {
1872                    let layer = me.load_layer(last, n_trunk + k as u32, &format!("mtp.{k}"))?;
1873                    assert_eq!(layer.ratio, 0, "dspark block mtp.{k} must be ratio 0");
1874                    assert_eq!(
1875                        layer.expert_kind,
1876                        ExpertKind::Mxfp4,
1877                        "dspark experts must be MXFP4"
1878                    );
1879                    blocks.push(layer);
1880                }
1881                let last_p = format!("mtp.{}", cfg.n_blocks - 1);
1882                let (mp_shape, _) = me.model.tensor_f32("mtp.0.main_proj");
1883                assert_eq!(
1884                    mp_shape,
1885                    vec![hidden, cfg.target_layer_ids.len() * hidden],
1886                    "main_proj shape"
1887                );
1888                let (w1_shape, w1) = me
1889                    .model
1890                    .tensor_f32(&format!("{last_p}.markov_head.markov_w1.weight"));
1891                let (w2_shape, w2) = me
1892                    .model
1893                    .tensor_f32(&format!("{last_p}.markov_head.markov_w2.weight"));
1894                let vocab = w1_shape[0];
1895                assert_eq!(w1_shape[1], cfg.markov_rank, "markov_w1 rank");
1896                assert_eq!(w2_shape, vec![vocab, cfg.markov_rank], "markov_w2 shape");
1897                let (cf_shape, _) = me
1898                    .model
1899                    .tensor_f32(&format!("{last_p}.confidence_head.proj.weight"));
1900                assert_eq!(
1901                    cf_shape,
1902                    vec![1, hidden + cfg.markov_rank],
1903                    "confidence proj shape"
1904                );
1905                let st_stream = me.stages[last].gpu.stream();
1906                let markov_w1 = upload_f32(&st_stream, &w1)?;
1907                let markov_w2 = upload_f32(&st_stream, &w2)?;
1908                let dspark = DsparkDev {
1909                    blocks,
1910                    main_proj: me.tensor_bf16(last, "mtp.0.main_proj")?,
1911                    main_norm: me.tensor_f32_dev(last, "mtp.0.main_norm.weight")?,
1912                    norm: me.tensor_f32_dev(last, &format!("{last_p}.norm.weight"))?,
1913                    markov_w1,
1914                    markov_w2,
1915                    markov_w1_host: w1,
1916                    conf_w: me
1917                        .tensor_f32_dev(last, &format!("{last_p}.confidence_head.proj.weight"))?,
1918                    hc_head_fn: me.tensor_f32_dev(last, &format!("{last_p}.hc_head_fn"))?,
1919                    hc_head_base: me.model.tensor_f32(&format!("{last_p}.hc_head_base")).1,
1920                    hc_head_scale: me.model.tensor_f32(&format!("{last_p}.hc_head_scale")).1,
1921                    block_size: cfg.block_size,
1922                    noise_token: cfg.noise_token_id,
1923                    targets: cfg.target_layer_ids.clone(),
1924                    rank: cfg.markov_rank,
1925                    vocab,
1926                };
1927                eprintln!(
1928                    "[load] drafter: DSpark ({} blocks, block_size {}, targets {:?}) \
1929                     resident on stage {last}",
1930                    cfg.n_blocks, cfg.block_size, cfg.target_layer_ids
1931                );
1932                me.dspark = Some(dspark);
1933            } else {
1934                eprintln!(
1935                    "[load] drafter: {nextn} DSpark block(s) (mtp.0.e_proj absent) — GPU \
1936                     drafter path off (set MEMRA_DSV4_DRAFTER=dspark); trunk-only"
1937                );
1938            }
1939        }
1940        for st in &me.stages {
1941            st.gpu.stream().synchronize().map_err(e("load sync"))?;
1942        }
1943        Ok(me)
1944    }
1945
1946    /// (free, total, resident-by-loader) bytes per device — the placement table source.
1947    pub fn vram_report(&self) -> Res<Vec<(usize, u64, u64, u64)>> {
1948        let mut out = Vec::new();
1949        for st in &self.stages {
1950            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
1951            let (free, total) = st.gpu.ctx.mem_get_info().map_err(e("mem_get_info"))?;
1952            out.push((st.dev, free as u64, total as u64, st.loaded_bytes));
1953        }
1954        Ok(out)
1955    }
1956
1957    // ---------------------------------------------------------------- forward pieces
1958
1959    /// bf16 GEMM y[mxn] f32 = x[mxk] (f32, cast here) @ w[nxk]ᵀ (bf16 resident).
1960    /// `w_off_elems` slices the weight (grouped wo_a).
1961    #[allow(clippy::too_many_arguments)]
1962    fn gemm(
1963        st: &Stage,
1964        x_f32: &CudaSlice<f32>,
1965        w_bf16: &CudaSlice<u8>,
1966        w_off_elems: usize,
1967        m: usize,
1968        n: usize,
1969        kdim: usize,
1970        y: &mut CudaSlice<f32>,
1971    ) -> Res<()> {
1972        let stream = st.gpu.stream();
1973        let mut xb = stream
1974            .alloc_zeros::<u8>(m * kdim * 2)
1975            .map_err(e("alloc xb"))?;
1976        unsafe {
1977            ck(
1978                "cvt_bf16",
1979                k::memra_dsv4_cvt_bf16(
1980                    dpf!(x_f32, &stream),
1981                    xb.device_ptr_mut(&stream).0 as *mut c_void,
1982                    (m * kdim) as i64,
1983                    sp(&stream),
1984                ),
1985            )?;
1986            ck(
1987                "gemm_bf16",
1988                k::memra_dsv4_gemm_bf16(
1989                    (w_bf16.device_ptr(&stream).0 as usize + w_off_elems * 2) as *const c_void,
1990                    dp!(xb, &stream),
1991                    dpm!(y, &stream),
1992                    m as i32,
1993                    n as i32,
1994                    kdim as i32,
1995                    st.dev as i32,
1996                    st.ws.device_ptr(&stream).0 as *mut c_void,
1997                    st.ws.len(),
1998                    sp(&stream),
1999                ),
2000            )?;
2001        }
2002        Ok(())
2003    }
2004
2005    /// bf16 GEMM from an ALREADY-bf16 activation buffer.
2006    #[allow(clippy::too_many_arguments)]
2007    fn gemm_pre(
2008        st: &Stage,
2009        xb: &CudaSlice<u8>,
2010        w_bf16_ptr: *const c_void,
2011        m: usize,
2012        n: usize,
2013        kdim: usize,
2014        y: &mut CudaSlice<f32>,
2015    ) -> Res<()> {
2016        let stream = st.gpu.stream();
2017        unsafe {
2018            ck(
2019                "gemm_bf16",
2020                k::memra_dsv4_gemm_bf16(
2021                    w_bf16_ptr,
2022                    dp!(xb, &stream),
2023                    dpm!(y, &stream),
2024                    m as i32,
2025                    n as i32,
2026                    kdim as i32,
2027                    st.dev as i32,
2028                    st.ws.device_ptr(&stream).0 as *mut c_void,
2029                    st.ws.len(),
2030                    sp(&stream),
2031                ),
2032            )?;
2033        }
2034        Ok(())
2035    }
2036
2037    /// f32-island GEMM (f64-accumulated dots kernel).
2038    fn dots(
2039        st: &Stage,
2040        x: &CudaSlice<f32>,
2041        w_f32: &CudaSlice<f32>,
2042        s: usize,
2043        kdim: usize,
2044        n: usize,
2045        y: &mut CudaSlice<f32>,
2046    ) -> Res<()> {
2047        let stream = st.gpu.stream();
2048        unsafe {
2049            ck(
2050                "dots_f32",
2051                k::memra_dsv4_dots_f32(
2052                    dpf!(x, &stream),
2053                    dp!(w_f32, &stream),
2054                    0,
2055                    dpm!(y, &stream),
2056                    s as i32,
2057                    kdim as i32,
2058                    n as i32,
2059                    sp(&stream),
2060                ),
2061            )?;
2062        }
2063        Ok(())
2064    }
2065
2066    /// Island dots on the DEVICE decode path (lane 9): routes to the f64 oracle-truth
2067    /// arm (default — byte-identical to `Self::dots`) or the owner-gated
2068    /// f32-accumulation serving arm (MEMRA_DSV4_DOTS_ARM=f32; fork gated by
2069    /// decode-gate + oracle teacher-forcing, RECEIPTS.md "Lane 9").
2070    fn dots_dev(
2071        &self,
2072        st: &Stage,
2073        x: &CudaSlice<f32>,
2074        w_f32: &CudaSlice<f32>,
2075        s: usize,
2076        kdim: usize,
2077        n: usize,
2078        y: &mut CudaSlice<f32>,
2079    ) -> Res<()> {
2080        if !self.dots_f32 {
2081            return Self::dots(st, x, w_f32, s, kdim, n, y);
2082        }
2083        let stream = st.gpu.stream();
2084        unsafe {
2085            ck(
2086                "dots_f32acc",
2087                k::memra_dsv4_dots_f32acc(
2088                    dpf!(x, &stream),
2089                    dp!(w_f32, &stream),
2090                    0,
2091                    dpm!(y, &stream),
2092                    s as i32,
2093                    kdim as i32,
2094                    n as i32,
2095                    sp(&stream),
2096                ),
2097            )?;
2098        }
2099        Ok(())
2100    }
2101
2102    /// Compressor forward (f32 island end-to-end). Returns (Some((ckv [nb, d], nb)) or
2103    /// None when no complete block, kv_raw [s, latent], score_raw [s, latent]).
2104    /// The raw GEMM outputs are ALWAYS computed (the reference does too, M:330-331) —
2105    /// lane 6 seeds the decode pending state from their trailing rows.
2106    #[allow(clippy::too_many_arguments)]
2107    fn compressor(
2108        &self,
2109        st: &Stage,
2110        cmp: &CmpDev,
2111        x: &CudaSlice<f32>, // [s, hidden] post-attn-norm
2112        s: usize,
2113        hidden: usize,
2114        fc_dev: &CudaSlice<f32>,
2115        rd: usize,
2116        eps: f32,
2117    ) -> Res<(
2118        Option<(CudaSlice<f32>, usize)>,
2119        CudaSlice<f32>,
2120        CudaSlice<f32>,
2121    )> {
2122        let stream = st.gpu.stream();
2123        let mut kv = stream
2124            .alloc_zeros::<f32>(s * cmp.latent)
2125            .map_err(e("cmp kv"))?;
2126        let mut score = stream
2127            .alloc_zeros::<f32>(s * cmp.latent)
2128            .map_err(e("cmp score"))?;
2129        Self::dots(st, x, &cmp.wkv, s, hidden, cmp.latent, &mut kv)?;
2130        Self::dots(st, x, &cmp.wgate, s, hidden, cmp.latent, &mut score)?;
2131        if s < cmp.ratio {
2132            return Ok((None, kv, score));
2133        }
2134        let cutoff = s - s % cmp.ratio;
2135        let nb = cutoff / cmp.ratio;
2136        let mut pooled = stream
2137            .alloc_zeros::<f32>(nb * cmp.d)
2138            .map_err(e("cmp out"))?;
2139        unsafe {
2140            ck(
2141                "compressor_pool",
2142                k::memra_dsv4_compressor_pool(
2143                    dpf!(kv, &stream),
2144                    dpf!(score, &stream),
2145                    dpf!(cmp.ape, &stream),
2146                    dpm!(pooled, &stream),
2147                    nb as i32,
2148                    cmp.ratio as i32,
2149                    cmp.d as i32,
2150                    cmp.latent as i32,
2151                    cmp.overlap as i32,
2152                    sp(&stream),
2153                ),
2154            )?;
2155            ck(
2156                "rmsnorm cmp",
2157                k::memra_dsv4_rmsnorm(
2158                    dpf!(pooled, &stream),
2159                    dpf!(cmp.norm, &stream),
2160                    dpm!(pooled, &stream),
2161                    nb as i32,
2162                    cmp.d as i32,
2163                    eps,
2164                    sp(&stream),
2165                ),
2166            )?;
2167            let positions: Vec<i32> = (0..nb).map(|j| (j * cmp.ratio) as i32).collect();
2168            let pos_dev = upload_i32(&stream, &positions)?;
2169            ck(
2170                "rope cmp",
2171                k::memra_dsv4_rope(
2172                    dpm!(pooled, &stream),
2173                    nb as i32,
2174                    1,
2175                    cmp.d as i32,
2176                    rd as i32,
2177                    dpf!(fc_dev, &stream),
2178                    pos_dev.device_ptr(&stream).0 as *const i32,
2179                    0,
2180                    sp(&stream),
2181                ),
2182            )?;
2183            if cmp.rotate {
2184                // oracle hadamard scale: (d as f32).powf(-0.5)
2185                let scale = (cmp.d as f32).powf(-0.5);
2186                ck(
2187                    "hadamard cmp",
2188                    k::memra_dsv4_hadamard(
2189                        dpm!(pooled, &stream),
2190                        nb as i32,
2191                        cmp.d as i32,
2192                        scale,
2193                        sp(&stream),
2194                    ),
2195                )?;
2196                ck(
2197                    "fp4 cmp",
2198                    k::memra_dsv4_fp4_act_quant(
2199                        dpm!(pooled, &stream),
2200                        nb as i32,
2201                        cmp.d as i64,
2202                        cmp.d as i32,
2203                        sp(&stream),
2204                    ),
2205                )?;
2206            } else {
2207                ck(
2208                    "act_quant cmp",
2209                    k::memra_dsv4_act_quant(
2210                        dpm!(pooled, &stream),
2211                        nb as i32,
2212                        cmp.d as i64,
2213                        (cmp.d - rd) as i32,
2214                        64,
2215                        (self.variant == ActQuantVariant::ClampOnly) as i32,
2216                        sp(&stream),
2217                    ),
2218                )?;
2219            }
2220        }
2221        Ok((Some((pooled, nb)), kv, score))
2222    }
2223
2224    /// Prefill→decode handoff for one compressor: copy the pooled blocks into the
2225    /// store rows [row0, row0+nb) and seed the pending state from the raw kv/score
2226    /// trailing rows (fine: last COMPLETE block → prev slots + remainder → cur slots,
2227    /// M:346-352; coarse: remainder → slots [0, rem)).
2228    #[allow(clippy::too_many_arguments)]
2229    fn populate_cmp_cache(
2230        stream: &std::sync::Arc<CudaStream>,
2231        s: usize,
2232        cmp_ratio: usize,
2233        latent: usize,
2234        d: usize,
2235        pooled: &Option<(CudaSlice<f32>, usize)>,
2236        kv_raw: &CudaSlice<f32>,
2237        score_raw: &CudaSlice<f32>,
2238        store: &mut CudaSlice<f32>,
2239        row0: usize,
2240        blocks: &mut usize,
2241        pend_kv: &mut CudaSlice<f32>,
2242        pend_score: &mut CudaSlice<f32>,
2243        overlap: bool,
2244    ) -> Res<()> {
2245        *blocks = 0;
2246        if let Some((buf, nb)) = pooled {
2247            let src = buf.slice(0..nb * d);
2248            let mut dst = store.slice_mut(row0 * d..(row0 + nb) * d);
2249            stream.memcpy_dtod(&src, &mut dst).map_err(e("cmp store"))?;
2250            *blocks = *nb;
2251        }
2252        let cutoff = s - s % cmp_ratio;
2253        let rem = s - cutoff;
2254        if overlap {
2255            if cutoff >= cmp_ratio {
2256                let a = (cutoff - cmp_ratio) * latent;
2257                let b = cutoff * latent;
2258                let src = kv_raw.slice(a..b);
2259                let mut dst = pend_kv.slice_mut(0..cmp_ratio * latent);
2260                stream
2261                    .memcpy_dtod(&src, &mut dst)
2262                    .map_err(e("pend kv prev"))?;
2263                let src = score_raw.slice(a..b);
2264                let mut dst = pend_score.slice_mut(0..cmp_ratio * latent);
2265                stream
2266                    .memcpy_dtod(&src, &mut dst)
2267                    .map_err(e("pend sc prev"))?;
2268            }
2269            if rem > 0 {
2270                let a = cutoff * latent;
2271                let src = kv_raw.slice(a..s * latent);
2272                let mut dst = pend_kv.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2273                stream
2274                    .memcpy_dtod(&src, &mut dst)
2275                    .map_err(e("pend kv cur"))?;
2276                let src = score_raw.slice(a..s * latent);
2277                let mut dst = pend_score.slice_mut(cmp_ratio * latent..(cmp_ratio + rem) * latent);
2278                stream
2279                    .memcpy_dtod(&src, &mut dst)
2280                    .map_err(e("pend sc cur"))?;
2281            }
2282        } else if rem > 0 {
2283            let a = cutoff * latent;
2284            let src = kv_raw.slice(a..s * latent);
2285            let mut dst = pend_kv.slice_mut(0..rem * latent);
2286            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
2287            let src = score_raw.slice(a..s * latent);
2288            let mut dst = pend_score.slice_mut(0..rem * latent);
2289            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
2290        }
2291        Ok(())
2292    }
2293
2294    /// hc_pre: mixes GEMM (f32 island) + rowsq scale on GPU, Sinkhorn on HOST via the
2295    /// oracle's own hc_split_sinkhorn. Returns (y [s,hidden] dev, post dev, comb dev).
2296    #[allow(clippy::too_many_arguments)]
2297    fn hc_pre(
2298        st: &Stage,
2299        h: &CudaSlice<f32>, // [s, hc, hidden]
2300        fn_w: &CudaSlice<f32>,
2301        base: &[f32],
2302        scale: &[f32],
2303        s: usize,
2304        hc: usize,
2305        hidden: usize,
2306        iters: u32,
2307        hc_eps: f32,
2308    ) -> Res<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)> {
2309        let stream = st.gpu.stream();
2310        let w = hc * hidden;
2311        let rows = (2 + hc) * hc;
2312        let mut mixes = stream.alloc_zeros::<f32>(s * rows).map_err(e("mixes"))?;
2313        Self::dots(st, h, fn_w, s, w, rows, &mut mixes)?;
2314        unsafe {
2315            ck(
2316                "rowsq_scale",
2317                k::memra_dsv4_rowsq_scale(
2318                    dpf!(h, &stream),
2319                    dpm!(mixes, &stream),
2320                    s as i32,
2321                    w as i32,
2322                    rows as i32,
2323                    hc_eps,
2324                    sp(&stream),
2325                ),
2326            )?;
2327        }
2328        let mixes_h = dtoh_f32(&stream, &mixes)?;
2329        let (pre, post, comb) = hc_split_sinkhorn(&mixes_h, s, hc, scale, base, iters, hc_eps);
2330        let pre_d = upload_f32(&stream, &pre)?;
2331        let post_d = upload_f32(&stream, &post)?;
2332        let comb_d = upload_f32(&stream, &comb)?;
2333        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("hc y"))?;
2334        unsafe {
2335            ck(
2336                "hc_collapse",
2337                k::memra_dsv4_hc_collapse(
2338                    dpf!(h, &stream),
2339                    dpf!(pre_d, &stream),
2340                    dpm!(y, &stream),
2341                    s as i32,
2342                    hc as i32,
2343                    hidden as i32,
2344                    sp(&stream),
2345                ),
2346            )?;
2347        }
2348        Ok((y, post_d, comb_d))
2349    }
2350
2351    /// Host routing — the oracle MoeW::forward selection/weight math verbatim.
2352    #[allow(clippy::too_many_arguments)]
2353    fn route_host(
2354        layer: &LayerDev,
2355        raw_scores: &[f32], // [s, ne] gate GEMM output (pre-softplus)
2356        ids: &[u32],
2357        s: usize,
2358        ne: usize,
2359        topk: usize,
2360        route_scale: f32,
2361    ) -> (Vec<usize>, Vec<f32>) {
2362        let mut scores = raw_scores.to_vec();
2363        for v in &mut scores {
2364            *v = softplus_f32(*v).sqrt();
2365        }
2366        let mut indices = vec![0usize; s * topk];
2367        if let Some(tid2eid) = &layer.tid2eid {
2368            for t in 0..s {
2369                let row = &tid2eid[ids[t] as usize * topk..(ids[t] as usize + 1) * topk];
2370                let mut seen = std::collections::BTreeSet::new();
2371                for (kk, &ex) in row.iter().enumerate() {
2372                    assert!(
2373                        (0..ne as i64).contains(&ex),
2374                        "layer {}: tid2eid out of range",
2375                        layer.il
2376                    );
2377                    assert!(
2378                        seen.insert(ex),
2379                        "layer {}: duplicate expert id in tid2eid row {}",
2380                        layer.il,
2381                        ids[t]
2382                    );
2383                    indices[t * topk + kk] = ex as usize;
2384                }
2385            }
2386        } else {
2387            let bias = layer.gate_bias.as_ref().expect("score layer needs bias");
2388            for t in 0..s {
2389                let biased: Vec<f32> = (0..ne).map(|ex| scores[t * ne + ex] + bias[ex]).collect();
2390                let mut order: Vec<usize> = (0..ne).collect();
2391                order.sort_by(|&a, &b| {
2392                    biased[b]
2393                        .partial_cmp(&biased[a])
2394                        .unwrap_or(std::cmp::Ordering::Equal)
2395                        .then(a.cmp(&b))
2396                });
2397                for kk in 0..topk {
2398                    indices[t * topk + kk] = order[kk];
2399                }
2400            }
2401        }
2402        let mut weights = vec![0f32; s * topk];
2403        for t in 0..s {
2404            let mut sum = 0f32;
2405            for kk in 0..topk {
2406                let w = scores[t * ne + indices[t * topk + kk]];
2407                weights[t * topk + kk] = w;
2408                sum += w;
2409            }
2410            for kk in 0..topk {
2411                weights[t * topk + kk] = weights[t * topk + kk] / sum * route_scale;
2412            }
2413        }
2414        (indices, weights)
2415    }
2416
2417    /// One trunk block on its stage. h is [s, hc, hidden] f32 on the stage device.
2418    /// `cache` (lane 6): populate this layer's decode cache while prefilling.
2419    #[allow(clippy::too_many_arguments)]
2420    fn block_forward(
2421        &self,
2422        st: &Stage,
2423        layer: &LayerDev,
2424        h: &CudaSlice<f32>,
2425        s: usize,
2426        ids: &[u32],
2427        mut capture: Option<&mut GpuCapture>,
2428        mut cache: Option<&mut LayerCache>,
2429    ) -> Res<CudaSlice<f32>> {
2430        let d = self.model.cfg();
2431        let mc = &self.model.mc;
2432        let hc = d.hc_mult as usize;
2433        let hidden = mc.n_embd as usize;
2434        let heads = mc.n_head as usize;
2435        let hd = d.head_dim as usize;
2436        let rd = d.qk_rope_head_dim as usize;
2437        let q_lora = d.q_lora_rank as usize;
2438        let win = d.sliding_window as usize;
2439        let o_groups = d.o_groups as usize;
2440        let o_lora = d.o_lora_rank as usize;
2441        let eps = mc.rms_eps;
2442        let iters = d.hc_sinkhorn_iters;
2443        let hc_eps = d.hc_eps;
2444        // Runtime-API kernel launches in the FFI TU need this stage's context current on
2445        // the calling thread (cudarc binds it inside its own ops, but the previous op may
2446        // have been another stage's).
2447        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
2448        let stream = st.gpu.stream();
2449        let fc_dev = if layer.ratio != 0 {
2450            &st.fc_yarn
2451        } else {
2452            &st.fc_plain
2453        };
2454        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
2455
2456        // ---- attention sub-block
2457        let (y, post, comb) = Self::hc_pre(
2458            st,
2459            h,
2460            &layer.hc_attn_fn,
2461            &layer.hc_attn_base,
2462            &layer.hc_attn_scale,
2463            s,
2464            hc,
2465            hidden,
2466            iters,
2467            hc_eps,
2468        )?;
2469        let mut x = stream.alloc_zeros::<f32>(s * hidden).map_err(e("x"))?;
2470        unsafe {
2471            ck(
2472                "rmsnorm attn",
2473                k::memra_dsv4_rmsnorm(
2474                    dpf!(y, &stream),
2475                    dpf!(layer.attn_norm, &stream),
2476                    dpm!(x, &stream),
2477                    s as i32,
2478                    hidden as i32,
2479                    eps,
2480                    sp(&stream),
2481                ),
2482            )?;
2483        }
2484
2485        // q path (item 3: under the fp8 dense arm the bf16 slabs are host-staged —
2486        // each `staged` view uploads a transient device copy freed, stream-ordered,
2487        // when the view drops at the end of this pass; on the bf16 arm it borrows
2488        // the resident slab and stages nothing)
2489        let wq_a_v = layer.wq_a.staged(&stream)?;
2490        let mut qr = stream.alloc_zeros::<f32>(s * q_lora).map_err(e("qr"))?;
2491        Self::gemm(st, &x, wq_a_v.slab(), 0, s, q_lora, hidden, &mut qr)?;
2492        unsafe {
2493            ck(
2494                "rmsnorm q",
2495                k::memra_dsv4_rmsnorm(
2496                    dpf!(qr, &stream),
2497                    dpf!(layer.q_norm, &stream),
2498                    dpm!(qr, &stream),
2499                    s as i32,
2500                    q_lora as i32,
2501                    eps,
2502                    sp(&stream),
2503                ),
2504            )?;
2505        }
2506        // qr as bf16 once (feeds wq_b and the indexer wq_b, oracle reuses qr the same way)
2507        let mut qr_b = stream
2508            .alloc_zeros::<u8>(s * q_lora * 2)
2509            .map_err(e("qr_b"))?;
2510        unsafe {
2511            ck(
2512                "cvt qr",
2513                k::memra_dsv4_cvt_bf16(
2514                    dpf!(qr, &stream),
2515                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
2516                    (s * q_lora) as i64,
2517                    sp(&stream),
2518                ),
2519            )?;
2520        }
2521        let wq_b_v = layer.wq_b.staged(&stream)?;
2522        let mut q = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("q"))?;
2523        Self::gemm_pre(
2524            st,
2525            &qr_b,
2526            wq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2527            s,
2528            heads * hd,
2529            q_lora,
2530            &mut q,
2531        )?;
2532        let positions: Vec<i32> = (0..s as i32).collect();
2533        let pos_dev = upload_i32(&stream, &positions)?;
2534        unsafe {
2535            ck(
2536                "headrms",
2537                k::memra_dsv4_headrms(
2538                    dpm!(q, &stream),
2539                    (s * heads) as i32,
2540                    hd as i32,
2541                    eps,
2542                    sp(&stream),
2543                ),
2544            )?;
2545            ck(
2546                "rope q",
2547                k::memra_dsv4_rope(
2548                    dpm!(q, &stream),
2549                    s as i32,
2550                    heads as i32,
2551                    hd as i32,
2552                    rd as i32,
2553                    dpf!(fc_dev, &stream),
2554                    pos_dev.device_ptr(&stream).0 as *const i32,
2555                    0,
2556                    sp(&stream),
2557                ),
2558            )?;
2559        }
2560
2561        // shared K==V latent + window QAT
2562        let wkv_v = layer.wkv.staged(&stream)?;
2563        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("kv"))?;
2564        Self::gemm(st, &x, wkv_v.slab(), 0, s, hd, hidden, &mut kv)?;
2565        unsafe {
2566            ck(
2567                "rmsnorm kv",
2568                k::memra_dsv4_rmsnorm(
2569                    dpf!(kv, &stream),
2570                    dpf!(layer.kv_norm, &stream),
2571                    dpm!(kv, &stream),
2572                    s as i32,
2573                    hd as i32,
2574                    eps,
2575                    sp(&stream),
2576                ),
2577            )?;
2578            ck(
2579                "rope kv",
2580                k::memra_dsv4_rope(
2581                    dpm!(kv, &stream),
2582                    s as i32,
2583                    1,
2584                    hd as i32,
2585                    rd as i32,
2586                    dpf!(fc_dev, &stream),
2587                    pos_dev.device_ptr(&stream).0 as *const i32,
2588                    0,
2589                    sp(&stream),
2590                ),
2591            )?;
2592            ck(
2593                "act_quant kv",
2594                k::memra_dsv4_act_quant(
2595                    dpm!(kv, &stream),
2596                    s as i32,
2597                    hd as i64,
2598                    (hd - rd) as i32,
2599                    64,
2600                    clamp_only,
2601                    sp(&stream),
2602                ),
2603            )?;
2604        }
2605        // lane 6: window ring handoff — last min(s, win) post-QAT rows at slot p % win
2606        // (M:524-527: prefill leaves the cache exactly as if the ring had been written
2607        // position by position).
2608        if let Some(c) = cache.as_deref_mut() {
2609            for p in s.saturating_sub(win)..s {
2610                let slot = p % win;
2611                let src = kv.slice(p * hd..(p + 1) * hd);
2612                let mut dst = c.kvc.slice_mut(slot * hd..(slot + 1) * hd);
2613                stream.memcpy_dtod(&src, &mut dst).map_err(e("ring copy"))?;
2614            }
2615        }
2616
2617        // index assembly (host, oracle builders) + compressed kv
2618        let (widx, wslots) = window_topk_idxs(win, s);
2619        let mut idxs: Vec<i64> = widx;
2620        let mut slots = wslots;
2621        let mut n_kv = s;
2622        let mut kv_full = kv;
2623        let mut cap_cmp: Option<(Vec<f32>, usize)> = None;
2624        let mut cap_ikv: Option<(Vec<f32>, usize)> = None;
2625        let mut cap_isc: Option<(Vec<f32>, usize)> = None;
2626        let want_cap = capture
2627            .as_ref()
2628            .map(|c| c.want.contains(&layer.il))
2629            .unwrap_or(false);
2630        if layer.ratio != 0 {
2631            let offset = s;
2632            let (cidx, cslots) = if let Some(ix) = &layer.idx {
2633                // indexer q
2634                let mut qi = stream
2635                    .alloc_zeros::<f32>(s * ix.heads * ix.hd)
2636                    .map_err(e("qi"))?;
2637                let iwq_b_v = ix.wq_b.staged(&stream)?;
2638                Self::gemm_pre(
2639                    st,
2640                    &qr_b,
2641                    iwq_b_v.slab().device_ptr(&stream).0 as *const c_void,
2642                    s,
2643                    ix.heads * ix.hd,
2644                    q_lora,
2645                    &mut qi,
2646                )?;
2647                unsafe {
2648                    ck(
2649                        "rope qi",
2650                        k::memra_dsv4_rope(
2651                            dpm!(qi, &stream),
2652                            s as i32,
2653                            ix.heads as i32,
2654                            ix.hd as i32,
2655                            rd as i32,
2656                            dpf!(fc_dev, &stream),
2657                            pos_dev.device_ptr(&stream).0 as *const i32,
2658                            0,
2659                            sp(&stream),
2660                        ),
2661                    )?;
2662                    let scale = (ix.hd as f32).powf(-0.5);
2663                    ck(
2664                        "hadamard qi",
2665                        k::memra_dsv4_hadamard(
2666                            dpm!(qi, &stream),
2667                            (s * ix.heads) as i32,
2668                            ix.hd as i32,
2669                            scale,
2670                            sp(&stream),
2671                        ),
2672                    )?;
2673                    ck(
2674                        "fp4 qi",
2675                        k::memra_dsv4_fp4_act_quant(
2676                            dpm!(qi, &stream),
2677                            (s * ix.heads) as i32,
2678                            ix.hd as i64,
2679                            ix.hd as i32,
2680                            sp(&stream),
2681                        ),
2682                    )?;
2683                }
2684                // indexer compressed kv
2685                let (ckv_i, ikv_raw, isc_raw) =
2686                    self.compressor(st, &ix.cmp, &x, s, hidden, fc_dev, rd, eps)?;
2687                if want_cap {
2688                    if let Some((buf, nb)) = &ckv_i {
2689                        cap_ikv = Some((dtoh_f32(&stream, buf)?, *nb));
2690                    }
2691                }
2692                if let Some(c) = cache.as_deref_mut() {
2693                    let mut i_blocks = c.i_blocks;
2694                    Self::populate_cmp_cache(
2695                        &stream,
2696                        s,
2697                        ix.cmp.ratio,
2698                        ix.cmp.latent,
2699                        ix.cmp.d,
2700                        &ckv_i,
2701                        &ikv_raw,
2702                        &isc_raw,
2703                        c.ikvc.as_mut().expect("fine layer has indexer store"),
2704                        0,
2705                        &mut i_blocks,
2706                        c.ipend_kv.as_mut().expect("ipend"),
2707                        c.ipend_score.as_mut().expect("ipend"),
2708                        ix.cmp.overlap,
2709                    )?;
2710                    c.i_blocks = i_blocks;
2711                }
2712                // head weights (weights_proj is BF16 — lawful bf16 GEMM)
2713                let iwp_v = ix.weights_proj.staged(&stream)?;
2714                let mut wproj = stream.alloc_zeros::<f32>(s * ix.heads).map_err(e("wp"))?;
2715                Self::gemm(st, &x, iwp_v.slab(), 0, s, ix.heads, hidden, &mut wproj)?;
2716                if let Some((ckv, nb)) = &ckv_i {
2717                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
2718                    let mut score = stream.alloc_zeros::<f32>(s * nb).map_err(e("iscore"))?;
2719                    unsafe {
2720                        ck(
2721                            "indexer_score",
2722                            k::memra_dsv4_indexer_score(
2723                                dpf!(qi, &stream),
2724                                dpf!(ckv, &stream),
2725                                dpf!(wproj, &stream),
2726                                wscale,
2727                                dpm!(score, &stream),
2728                                s as i32,
2729                                ix.heads as i32,
2730                                ix.hd as i32,
2731                                *nb as i32,
2732                                layer.ratio as i32,
2733                                -1, // prefill law: lim = (t+1)/ratio with local t
2734                                sp(&stream),
2735                            ),
2736                        )?;
2737                    }
2738                    let score_h = dtoh_f32(&stream, &score)?;
2739                    if want_cap {
2740                        cap_isc = Some((score_h.clone(), *nb));
2741                    }
2742                    // host topk with the oracle's exact ordering + re-mask (model.py:508-510)
2743                    let kk = ix.topk.min(*nb);
2744                    let mut cidx = vec![-1i64; s * kk];
2745                    for t in 0..s {
2746                        let lim = (t + 1) / layer.ratio;
2747                        let mut order: Vec<usize> = (0..*nb).collect();
2748                        order.sort_by(|&a, &b| {
2749                            score_h[t * nb + b]
2750                                .partial_cmp(&score_h[t * nb + a])
2751                                .unwrap_or(std::cmp::Ordering::Equal)
2752                                .then(a.cmp(&b))
2753                        });
2754                        for (slot, &j) in order.iter().take(kk).enumerate() {
2755                            cidx[t * kk + slot] = if j >= lim { -1 } else { (j + offset) as i64 };
2756                        }
2757                    }
2758                    (cidx, kk)
2759                } else {
2760                    (Vec::new(), 0)
2761                }
2762            } else {
2763                compress_topk_idxs(layer.ratio, s, offset)
2764            };
2765            if cslots > 0 {
2766                let mut merged = vec![-1i64; s * (slots + cslots)];
2767                for t in 0..s {
2768                    merged[t * (slots + cslots)..t * (slots + cslots) + slots]
2769                        .copy_from_slice(&idxs[t * slots..(t + 1) * slots]);
2770                    merged[t * (slots + cslots) + slots..(t + 1) * (slots + cslots)]
2771                        .copy_from_slice(&cidx[t * cslots..(t + 1) * cslots]);
2772                }
2773                idxs = merged;
2774                slots += cslots;
2775            }
2776            // attention-side compressed kv appended to the kv stream
2777            let acmp = layer.cmp.as_ref().expect("ratio!=0 has compressor");
2778            let (ckv, akv_raw, asc_raw) =
2779                self.compressor(st, acmp, &x, s, hidden, fc_dev, rd, eps)?;
2780            if want_cap {
2781                if let Some((buf, nb)) = &ckv {
2782                    cap_cmp = Some((dtoh_f32(&stream, buf)?, *nb));
2783                }
2784            }
2785            if let Some(c) = cache.as_deref_mut() {
2786                let mut n_blocks = c.n_blocks;
2787                Self::populate_cmp_cache(
2788                    &stream,
2789                    s,
2790                    acmp.ratio,
2791                    acmp.latent,
2792                    acmp.d,
2793                    &ckv,
2794                    &akv_raw,
2795                    &asc_raw,
2796                    &mut c.kvc,
2797                    win,
2798                    &mut n_blocks,
2799                    c.pend_kv.as_mut().expect("pend"),
2800                    c.pend_score.as_mut().expect("pend"),
2801                    acmp.overlap,
2802                )?;
2803                c.n_blocks = n_blocks;
2804            }
2805            if let Some((ckv_buf, nb)) = ckv {
2806                let mut merged_kv = stream
2807                    .alloc_zeros::<f32>((s + nb) * hd)
2808                    .map_err(e("kv_full"))?;
2809                {
2810                    let mut head_view = merged_kv.slice_mut(0..s * hd);
2811                    stream
2812                        .memcpy_dtod(&kv_full.slice(0..s * hd), &mut head_view)
2813                        .map_err(e("kv copy"))?;
2814                }
2815                {
2816                    let mut tail = merged_kv.slice_mut(s * hd..(s + nb) * hd);
2817                    stream
2818                        .memcpy_dtod(&ckv_buf.slice(0..nb * hd), &mut tail)
2819                        .map_err(e("ckv copy"))?;
2820                }
2821                kv_full = merged_kv;
2822                n_kv += nb;
2823            }
2824        }
2825        let _ = n_kv;
2826        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
2827        let idx_dev = upload_i32(&stream, &idxs_i32)?;
2828
2829        // sparse sink attention + query-position de-rotation
2830        let mut o = stream.alloc_zeros::<f32>(s * heads * hd).map_err(e("o"))?;
2831        let scale = (hd as f64).powf(-0.5) as f32;
2832        unsafe {
2833            ck(
2834                "sink_attn",
2835                k::memra_dsv4_sink_attn(
2836                    dpf!(q, &stream),
2837                    dpf!(kv_full, &stream),
2838                    idx_dev.device_ptr(&stream).0 as *const i32,
2839                    dpf!(layer.sink, &stream),
2840                    dpm!(o, &stream),
2841                    s as i32,
2842                    heads as i32,
2843                    hd as i32,
2844                    slots as i32,
2845                    scale,
2846                    sp(&stream),
2847                ),
2848            )?;
2849            ck(
2850                "rope o inv",
2851                k::memra_dsv4_rope(
2852                    dpm!(o, &stream),
2853                    s as i32,
2854                    heads as i32,
2855                    hd as i32,
2856                    rd as i32,
2857                    dpf!(fc_dev, &stream),
2858                    pos_dev.device_ptr(&stream).0 as *const i32,
2859                    1,
2860                    sp(&stream),
2861                ),
2862            )?;
2863        }
2864
2865        // grouped wo: per group g, og[:, g*o_lora..] = o_g @ wo_a[g]ᵀ; then wo_b.
2866        let gw = heads / o_groups * hd;
2867        let mut og = stream
2868            .alloc_zeros::<f32>(s * o_groups * o_lora)
2869            .map_err(e("og"))?;
2870        let mut o_grp = stream.alloc_zeros::<f32>(s * gw).map_err(e("o_grp"))?;
2871        let mut y_grp = stream.alloc_zeros::<f32>(s * o_lora).map_err(e("y_grp"))?;
2872        let wo_a_v = layer.wo_a.staged(&stream)?; // once, outside the group loop
2873        for g in 0..o_groups {
2874            unsafe {
2875                ck(
2876                    "take_cols",
2877                    k::memra_dsv4_take_cols(
2878                        dpf!(o, &stream),
2879                        dpm!(o_grp, &stream),
2880                        s as i32,
2881                        gw as i32,
2882                        (heads * hd) as i64,
2883                        (g * gw) as i64,
2884                        sp(&stream),
2885                    ),
2886                )?;
2887            }
2888            Self::gemm(
2889                st,
2890                &o_grp,
2891                wo_a_v.slab(),
2892                g * o_lora * gw,
2893                s,
2894                o_lora,
2895                gw,
2896                &mut y_grp,
2897            )?;
2898            unsafe {
2899                ck(
2900                    "place_cols",
2901                    k::memra_dsv4_place_cols(
2902                        dpf!(y_grp, &stream),
2903                        dpm!(og, &stream),
2904                        s as i32,
2905                        o_lora as i32,
2906                        (o_groups * o_lora) as i64,
2907                        (g * o_lora) as i64,
2908                        sp(&stream),
2909                    ),
2910                )?;
2911            }
2912        }
2913        let wo_b_v = layer.wo_b.staged(&stream)?;
2914        let mut attn_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("ao"))?;
2915        Self::gemm(
2916            st,
2917            &og,
2918            wo_b_v.slab(),
2919            0,
2920            s,
2921            hidden,
2922            o_groups * o_lora,
2923            &mut attn_out,
2924        )?;
2925
2926        let mut cap_attn: Option<Vec<f32>> = None;
2927        if want_cap {
2928            cap_attn = Some(dtoh_f32(&stream, &attn_out)?);
2929        }
2930
2931        // hc_post (attention)
2932        let mut h2 = stream
2933            .alloc_zeros::<f32>(s * hc * hidden)
2934            .map_err(e("h2"))?;
2935        unsafe {
2936            ck(
2937                "hc_post attn",
2938                k::memra_dsv4_hc_post(
2939                    dpf!(attn_out, &stream),
2940                    dpf!(h, &stream),
2941                    dpf!(post, &stream),
2942                    dpf!(comb, &stream),
2943                    dpm!(h2, &stream),
2944                    s as i32,
2945                    hc as i32,
2946                    hidden as i32,
2947                    sp(&stream),
2948                ),
2949            )?;
2950        }
2951
2952        // ---- ffn sub-block
2953        let (y2, post2, comb2) = Self::hc_pre(
2954            st,
2955            &h2,
2956            &layer.hc_ffn_fn,
2957            &layer.hc_ffn_base,
2958            &layer.hc_ffn_scale,
2959            s,
2960            hc,
2961            hidden,
2962            iters,
2963            hc_eps,
2964        )?;
2965        let mut xf = stream.alloc_zeros::<f32>(s * hidden).map_err(e("xf"))?;
2966        unsafe {
2967            ck(
2968                "rmsnorm ffn",
2969                k::memra_dsv4_rmsnorm(
2970                    dpf!(y2, &stream),
2971                    dpf!(layer.ffn_norm, &stream),
2972                    dpm!(xf, &stream),
2973                    s as i32,
2974                    hidden as i32,
2975                    eps,
2976                    sp(&stream),
2977                ),
2978            )?;
2979        }
2980        if let Some(c) = capture.as_deref_mut() {
2981            if c.want.contains(&layer.il) {
2982                c.moe_x.insert(layer.il, dtoh_f32(&stream, &xf)?);
2983            }
2984        }
2985        let moe_out = self.moe_forward(st, layer, &xf, s, ids)?;
2986        let mut h3 = stream
2987            .alloc_zeros::<f32>(s * hc * hidden)
2988            .map_err(e("h3"))?;
2989        unsafe {
2990            ck(
2991                "hc_post ffn",
2992                k::memra_dsv4_hc_post(
2993                    dpf!(moe_out, &stream),
2994                    dpf!(h2, &stream),
2995                    dpf!(post2, &stream),
2996                    dpf!(comb2, &stream),
2997                    dpm!(h3, &stream),
2998                    s as i32,
2999                    hc as i32,
3000                    hidden as i32,
3001                    sp(&stream),
3002                ),
3003            )?;
3004        }
3005
3006        if let Some(c) = capture {
3007            if c.want.contains(&layer.il) {
3008                c.layer_out.insert(layer.il, dtoh_f32(&stream, &h3)?);
3009                c.x_dbg.insert(layer.il, dtoh_f32(&stream, &x)?);
3010                c.q_dbg.insert(layer.il, dtoh_f32(&stream, &q)?);
3011                {
3012                    let mut kvh = vec![0f32; s * hd];
3013                    stream
3014                        .memcpy_dtoh(&kv_full.slice(0..s * hd), &mut kvh[..])
3015                        .map_err(e("dtoh kv_dbg"))?;
3016                    stream.synchronize().map_err(e("sync kv_dbg"))?;
3017                    c.kv_dbg.insert(layer.il, kvh);
3018                }
3019                c.o_dbg.insert(layer.il, dtoh_f32(&stream, &o)?);
3020                if let Some(a) = cap_attn {
3021                    c.attn_out.insert(layer.il, a);
3022                }
3023                if let Some(v) = cap_cmp {
3024                    c.compressor_kv.insert(layer.il, v);
3025                }
3026                if let Some(v) = cap_ikv {
3027                    c.indexer_kv.insert(layer.il, v);
3028                }
3029                if let Some(v) = cap_isc {
3030                    c.index_score.insert(layer.il, v);
3031                }
3032            }
3033        }
3034        Ok(h3)
3035    }
3036
3037    /// MoE on GPU: gate GEMM f32 island -> host routing (oracle math) -> per-expert
3038    /// on-the-fly NVFP4 dequant + bf16 GEMMs (ascending expert order, oracle
3039    /// accumulation order) -> shared expert last.
3040    fn moe_forward(
3041        &self,
3042        st: &Stage,
3043        layer: &LayerDev,
3044        x: &CudaSlice<f32>, // [s, hidden] post-ffn-norm
3045        s: usize,
3046        ids: &[u32],
3047    ) -> Res<CudaSlice<f32>> {
3048        let mc = &self.model.mc;
3049        let d = self.model.cfg();
3050        let moe = mc.moe.as_ref().expect("moe");
3051        let hidden = mc.n_embd as usize;
3052        let ne = moe.expert_count as usize;
3053        let topk = moe.expert_used_count as usize;
3054        let inter = moe.expert_ff_length as usize;
3055        let limit = d.swiglu_limit;
3056        let stream = st.gpu.stream();
3057
3058        let mut raw = stream.alloc_zeros::<f32>(s * ne).map_err(e("gate raw"))?;
3059        Self::dots(st, x, &layer.gate_w, s, hidden, ne, &mut raw)?;
3060        let raw_h = dtoh_f32(&stream, &raw)?;
3061        let (indices, weights) =
3062            Self::route_host(layer, &raw_h, ids, s, ne, topk, d.routed_scaling_factor);
3063
3064        // x as bf16 once for all expert GEMMs
3065        let mut xb = stream
3066            .alloc_zeros::<u8>(s * hidden * 2)
3067            .map_err(e("xb moe"))?;
3068        unsafe {
3069            ck(
3070                "cvt moe x",
3071                k::memra_dsv4_cvt_bf16(
3072                    dpf!(x, &stream),
3073                    xb.device_ptr_mut(&stream).0 as *mut c_void,
3074                    (s * hidden) as i64,
3075                    sp(&stream),
3076                ),
3077            )?;
3078        }
3079        let mut y = stream.alloc_zeros::<f32>(s * hidden).map_err(e("moe y"))?;
3080
3081        let wbytes = inter * hidden / 2;
3082        let sbytes = match layer.expert_kind {
3083            ExpertKind::Nvfp4 => inter * hidden / 16,
3084            ExpertKind::Mxfp4 => inter * hidden / 32,
3085        };
3086        let mut uniq: Vec<usize> = indices.clone();
3087        uniq.sort_unstable();
3088        uniq.dedup();
3089        if self.expert_arm == ExpertArm::Native {
3090            // lane 7: reference-law quantized expert GEMMs (RECEIPTS.md "Lane 7").
3091            // x quantized ONCE per-row-per-128 (model.py:113-115); code/scale rows
3092            // gathered per expert (row-local quant commutes with gathering exactly);
3093            // h re-quantized AFTER the routing-weight multiply (M:604-606) before w2.
3094            let kind = match layer.expert_kind {
3095                ExpertKind::Nvfp4 => 0i32,
3096                ExpertKind::Mxfp4 => 1i32,
3097            };
3098            let kq_x = hidden / 128;
3099            let kq_h = inter / 128;
3100            let mut xq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xq"))?;
3101            let mut xs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xs"))?;
3102            unsafe {
3103                ck(
3104                    "act_quant_fp8 x",
3105                    k::memra_dsv4_act_quant_fp8(
3106                        dpf!(x, &stream),
3107                        xq.device_ptr_mut(&stream).0 as *mut c_void,
3108                        dpm!(xs, &stream),
3109                        s as i32,
3110                        hidden as i32,
3111                        sp(&stream),
3112                    ),
3113                )?;
3114            }
3115            let mut xgq = stream.alloc_zeros::<u8>(s * hidden).map_err(e("xgq"))?;
3116            let mut xgs = stream.alloc_zeros::<f32>(s * kq_x).map_err(e("xgs"))?;
3117            let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3118            let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3119            let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3120            let mut hq = stream.alloc_zeros::<u8>(s * inter).map_err(e("hq"))?;
3121            let mut hs = stream.alloc_zeros::<f32>(s * kq_h).map_err(e("hs"))?;
3122            let mut contrib = stream
3123                .alloc_zeros::<f32>(s * hidden)
3124                .map_err(e("contrib"))?;
3125            for &ex in &uniq {
3126                let toks: Vec<(usize, usize)> = (0..s * topk)
3127                    .filter(|i| indices[*i] == ex)
3128                    .map(|i| (i / topk, i % topk))
3129                    .collect();
3130                let g = toks.len();
3131                let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3132                let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3133                let rows_dev = upload_i32(&stream, &tok_rows)?;
3134                let wrow_dev = upload_f32(&stream, &wrow)?;
3135                unsafe {
3136                    ck(
3137                        "gather xq",
3138                        k::memra_dsv4_gather_rows_u8(
3139                            dp!(xq, &stream),
3140                            rows_dev.device_ptr(&stream).0 as *const i32,
3141                            xgq.device_ptr_mut(&stream).0 as *mut c_void,
3142                            g as i32,
3143                            hidden as i64,
3144                            sp(&stream),
3145                        ),
3146                    )?;
3147                    ck(
3148                        "gather xs",
3149                        k::memra_dsv4_gather_rows_u8(
3150                            xs.device_ptr(&stream).0 as *const c_void,
3151                            rows_dev.device_ptr(&stream).0 as *const i32,
3152                            xgs.device_ptr_mut(&stream).0 as *mut c_void,
3153                            g as i32,
3154                            (kq_x * 4) as i64,
3155                            sp(&stream),
3156                        ),
3157                    )?;
3158                    // w1 (out inter), w3 (out inter) from x codes; w2 (out hidden) from h codes
3159                    for (pi, dst) in [(0usize, &mut g1), (2usize, &mut g3)] {
3160                        let woff = (ex * 3 + pi) * wbytes;
3161                        let soff = (ex * 3 + pi) * sbytes;
3162                        ck(
3163                            "fp4_gemm w1/w3",
3164                            k::memra_dsv4_fp4_gemm(
3165                                dp!(xgq, &stream),
3166                                dpf!(xgs, &stream),
3167                                (layer.experts_w.device_ptr(&stream).0 as usize + woff)
3168                                    as *const c_void,
3169                                (layer.experts_sc.device_ptr(&stream).0 as usize + soff)
3170                                    as *const c_void,
3171                                layer.experts_s2[ex * 3 + pi],
3172                                kind,
3173                                dpm!(*dst, &stream),
3174                                g as i32,
3175                                inter as i32,
3176                                hidden as i32,
3177                                sp(&stream),
3178                            ),
3179                        )?;
3180                    }
3181                    ck(
3182                        "swiglu",
3183                        k::memra_dsv4_swiglu(
3184                            dpf!(g1, &stream),
3185                            dpf!(g3, &stream),
3186                            dpm!(hbuf, &stream),
3187                            g as i32,
3188                            inter as i32,
3189                            limit,
3190                            wrow_dev.device_ptr(&stream).0 as *const f32,
3191                            sp(&stream),
3192                        ),
3193                    )?;
3194                    ck(
3195                        "act_quant_fp8 h",
3196                        k::memra_dsv4_act_quant_fp8(
3197                            dpf!(hbuf, &stream),
3198                            hq.device_ptr_mut(&stream).0 as *mut c_void,
3199                            dpm!(hs, &stream),
3200                            g as i32,
3201                            inter as i32,
3202                            sp(&stream),
3203                        ),
3204                    )?;
3205                    let woff2 = (ex * 3 + 1) * wbytes;
3206                    let soff2 = (ex * 3 + 1) * sbytes;
3207                    ck(
3208                        "fp4_gemm w2",
3209                        k::memra_dsv4_fp4_gemm(
3210                            dp!(hq, &stream),
3211                            dpf!(hs, &stream),
3212                            (layer.experts_w.device_ptr(&stream).0 as usize + woff2)
3213                                as *const c_void,
3214                            (layer.experts_sc.device_ptr(&stream).0 as usize + soff2)
3215                                as *const c_void,
3216                            layer.experts_s2[ex * 3 + 1],
3217                            kind,
3218                            dpm!(contrib, &stream),
3219                            g as i32,
3220                            hidden as i32,
3221                            inter as i32,
3222                            sp(&stream),
3223                        ),
3224                    )?;
3225                    ck(
3226                        "scatter",
3227                        k::memra_dsv4_scatter_add(
3228                            dpm!(y, &stream),
3229                            dpf!(contrib, &stream),
3230                            rows_dev.device_ptr(&stream).0 as *const i32,
3231                            g as i32,
3232                            hidden as i32,
3233                            sp(&stream),
3234                        ),
3235                    )?;
3236                }
3237            }
3238            return self.moe_shared_and_finish(st, layer, &xb, s, y);
3239        }
3240        // reusable per-expert buffers sized for the worst case (all tokens on one expert)
3241        let mut xg = stream.alloc_zeros::<u8>(s * hidden * 2).map_err(e("xg"))?;
3242        let mut g1 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g1"))?;
3243        let mut g3 = stream.alloc_zeros::<f32>(s * inter).map_err(e("g3"))?;
3244        let mut hbuf = stream.alloc_zeros::<f32>(s * inter).map_err(e("hbuf"))?;
3245        let mut hb = stream.alloc_zeros::<u8>(s * inter * 2).map_err(e("hb"))?;
3246        let mut contrib = stream
3247            .alloc_zeros::<f32>(s * hidden)
3248            .map_err(e("contrib"))?;
3249        for &ex in &uniq {
3250            let toks: Vec<(usize, usize)> = (0..s * topk)
3251                .filter(|i| indices[*i] == ex)
3252                .map(|i| (i / topk, i % topk))
3253                .collect();
3254            let g = toks.len();
3255            let tok_rows: Vec<i32> = toks.iter().map(|&(t, _)| t as i32).collect();
3256            let wrow: Vec<f32> = toks.iter().map(|&(t, kk)| weights[t * topk + kk]).collect();
3257            let rows_dev = upload_i32(&stream, &tok_rows)?;
3258            let wrow_dev = upload_f32(&stream, &wrow)?;
3259            unsafe {
3260                ck(
3261                    "gather",
3262                    k::memra_dsv4_gather_bf16(
3263                        dp!(xb, &stream),
3264                        rows_dev.device_ptr(&stream).0 as *const i32,
3265                        xg.device_ptr_mut(&stream).0 as *mut c_void,
3266                        g as i32,
3267                        hidden as i32,
3268                        sp(&stream),
3269                    ),
3270                )?;
3271                // dequant w1 (rows=inter, cols=hidden), w2 (rows=hidden, cols=inter), w3
3272                for (pi, (rows, cols)) in [(inter, hidden), (hidden, inter), (inter, hidden)]
3273                    .iter()
3274                    .enumerate()
3275                {
3276                    let woff = (ex * 3 + pi) * wbytes;
3277                    let soff = (ex * 3 + pi) * sbytes;
3278                    let wp =
3279                        (layer.experts_w.device_ptr(&stream).0 as usize + woff) as *const c_void;
3280                    let scp =
3281                        (layer.experts_sc.device_ptr(&stream).0 as usize + soff) as *const c_void;
3282                    let dst = st.deq[pi].device_ptr(&stream).0 as *mut c_void;
3283                    match layer.expert_kind {
3284                        ExpertKind::Nvfp4 => ck(
3285                            "nvfp4 deq",
3286                            k::memra_dsv4_nvfp4_deq_bf16(
3287                                wp,
3288                                scp,
3289                                layer.experts_s2[ex * 3 + pi],
3290                                *rows as i32,
3291                                *cols as i32,
3292                                dst,
3293                                sp(&stream),
3294                            ),
3295                        )?,
3296                        ExpertKind::Mxfp4 => ck(
3297                            "mxfp4 deq",
3298                            k::memra_dsv4_mxfp4_deq_bf16(
3299                                wp,
3300                                scp,
3301                                *rows as i32,
3302                                *cols as i32,
3303                                dst,
3304                                sp(&stream),
3305                            ),
3306                        )?,
3307                    }
3308                }
3309                ck(
3310                    "gemm w1",
3311                    k::memra_dsv4_gemm_bf16(
3312                        st.deq[0].device_ptr(&stream).0 as *const c_void,
3313                        dp!(xg, &stream),
3314                        dpm!(g1, &stream),
3315                        g as i32,
3316                        inter as i32,
3317                        hidden as i32,
3318                        st.dev as i32,
3319                        st.ws.device_ptr(&stream).0 as *mut c_void,
3320                        st.ws.len(),
3321                        sp(&stream),
3322                    ),
3323                )?;
3324                ck(
3325                    "gemm w3",
3326                    k::memra_dsv4_gemm_bf16(
3327                        st.deq[2].device_ptr(&stream).0 as *const c_void,
3328                        dp!(xg, &stream),
3329                        dpm!(g3, &stream),
3330                        g as i32,
3331                        inter as i32,
3332                        hidden as i32,
3333                        st.dev as i32,
3334                        st.ws.device_ptr(&stream).0 as *mut c_void,
3335                        st.ws.len(),
3336                        sp(&stream),
3337                    ),
3338                )?;
3339                ck(
3340                    "swiglu",
3341                    k::memra_dsv4_swiglu(
3342                        dpf!(g1, &stream),
3343                        dpf!(g3, &stream),
3344                        dpm!(hbuf, &stream),
3345                        g as i32,
3346                        inter as i32,
3347                        limit,
3348                        wrow_dev.device_ptr(&stream).0 as *const f32,
3349                        sp(&stream),
3350                    ),
3351                )?;
3352                ck(
3353                    "cvt h",
3354                    k::memra_dsv4_cvt_bf16(
3355                        dpf!(hbuf, &stream),
3356                        hb.device_ptr_mut(&stream).0 as *mut c_void,
3357                        (g * inter) as i64,
3358                        sp(&stream),
3359                    ),
3360                )?;
3361                ck(
3362                    "gemm w2",
3363                    k::memra_dsv4_gemm_bf16(
3364                        st.deq[1].device_ptr(&stream).0 as *const c_void,
3365                        dp!(hb, &stream),
3366                        dpm!(contrib, &stream),
3367                        g as i32,
3368                        hidden as i32,
3369                        inter as i32,
3370                        st.dev as i32,
3371                        st.ws.device_ptr(&stream).0 as *mut c_void,
3372                        st.ws.len(),
3373                        sp(&stream),
3374                    ),
3375                )?;
3376                ck(
3377                    "scatter",
3378                    k::memra_dsv4_scatter_add(
3379                        dpm!(y, &stream),
3380                        dpf!(contrib, &stream),
3381                        rows_dev.device_ptr(&stream).0 as *const i32,
3382                        g as i32,
3383                        hidden as i32,
3384                        sp(&stream),
3385                    ),
3386                )?;
3387            }
3388        }
3389        self.moe_shared_and_finish(st, layer, &xb, s, y)
3390    }
3391
3392    /// Shared expert (unweighted, added last — oracle order) + return. Stays on the
3393    /// lane-4 bf16 rung under BOTH expert arms (lane-7 banked deviation: shared experts
3394    /// are FP8-blk weights — the FP8-linear stay-bf16 decision).
3395    fn moe_shared_and_finish(
3396        &self,
3397        st: &Stage,
3398        layer: &LayerDev,
3399        xb: &CudaSlice<u8>,
3400        s: usize,
3401        mut y: CudaSlice<f32>,
3402    ) -> Res<CudaSlice<f32>> {
3403        let d = self.model.cfg();
3404        let hidden = self.model.mc.n_embd as usize;
3405        let limit = d.swiglu_limit;
3406        let stream = st.gpu.stream();
3407        let sh_inter = {
3408            // width derived from the tensor itself (n_shared_experts * inter)
3409            let (shape, _) = self
3410                .model
3411                .st
3412                .raw("layers.0.ffn.shared_experts.w1.weight")
3413                .map(|(i, _)| (i.shape.clone(), ()))
3414                .expect("shared w1");
3415            shape[0] as usize
3416        };
3417        let mut sg1 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg1"))?;
3418        let mut sg3 = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("sg3"))?;
3419        let mut shbuf = stream.alloc_zeros::<f32>(s * sh_inter).map_err(e("shb"))?;
3420        let mut shb16 = stream
3421            .alloc_zeros::<u8>(s * sh_inter * 2)
3422            .map_err(e("shb16"))?;
3423        let mut sh_out = stream.alloc_zeros::<f32>(s * hidden).map_err(e("sh_out"))?;
3424        // item 3: staged views (transient upload under the fp8 arm, borrow otherwise)
3425        let sw = [
3426            layer.shared_w[0].staged(&stream)?,
3427            layer.shared_w[1].staged(&stream)?,
3428            layer.shared_w[2].staged(&stream)?,
3429        ];
3430        Self::gemm_pre(
3431            st,
3432            xb,
3433            sw[0].slab().device_ptr(&stream).0 as *const c_void,
3434            s,
3435            sh_inter,
3436            hidden,
3437            &mut sg1,
3438        )?;
3439        Self::gemm_pre(
3440            st,
3441            xb,
3442            sw[2].slab().device_ptr(&stream).0 as *const c_void,
3443            s,
3444            sh_inter,
3445            hidden,
3446            &mut sg3,
3447        )?;
3448        unsafe {
3449            ck(
3450                "swiglu sh",
3451                k::memra_dsv4_swiglu(
3452                    dpf!(sg1, &stream),
3453                    dpf!(sg3, &stream),
3454                    dpm!(shbuf, &stream),
3455                    s as i32,
3456                    sh_inter as i32,
3457                    limit,
3458                    std::ptr::null(),
3459                    sp(&stream),
3460                ),
3461            )?;
3462            ck(
3463                "cvt sh",
3464                k::memra_dsv4_cvt_bf16(
3465                    dpf!(shbuf, &stream),
3466                    shb16.device_ptr_mut(&stream).0 as *mut c_void,
3467                    (s * sh_inter) as i64,
3468                    sp(&stream),
3469                ),
3470            )?;
3471        }
3472        Self::gemm_pre(
3473            st,
3474            &shb16,
3475            sw[1].slab().device_ptr(&stream).0 as *const c_void,
3476            s,
3477            hidden,
3478            sh_inter,
3479            &mut sh_out,
3480        )?;
3481        unsafe {
3482            ck(
3483                "add shared",
3484                k::memra_dsv4_add_inplace(
3485                    dpm!(y, &stream),
3486                    dpf!(sh_out, &stream),
3487                    (s * hidden) as i64,
3488                    sp(&stream),
3489                ),
3490            )?;
3491        }
3492        Ok(y)
3493    }
3494
3495    /// Full trunk prefill. Returns last-position logits, or None on early exit.
3496    /// `early_exit_after` stops after that layer (fixture Input B replays layers 0..=3).
3497    pub fn forward(
3498        &self,
3499        ids: &[u32],
3500        capture: Option<&mut GpuCapture>,
3501        early_exit_after: Option<u32>,
3502    ) -> Res<Option<ForwardOut>> {
3503        self.forward_impl(ids, capture, early_exit_after, None)
3504    }
3505
3506    /// Lane 6: prefill the prompt with the lane-4 path while POPULATING the decode
3507    /// caches, so decode_step can continue incrementally from ids.len().
3508    pub fn prefill_with_cache(&self, ids: &[u32], state: &mut DecodeState) -> Res<ForwardOut> {
3509        assert_eq!(state.pos, 0, "prefill_with_cache needs a fresh DecodeState");
3510        assert!(!ids.is_empty(), "empty prompt");
3511        let out = self
3512            .forward_impl(ids, None, None, Some(state))?
3513            .expect("prefill logits");
3514        state.pos = ids.len();
3515        Ok(out)
3516    }
3517
3518    fn forward_impl(
3519        &self,
3520        ids: &[u32],
3521        mut capture: Option<&mut GpuCapture>,
3522        early_exit_after: Option<u32>,
3523        mut state: Option<&mut DecodeState>,
3524    ) -> Res<Option<ForwardOut>> {
3525        let mc = &self.model.mc;
3526        let d = self.model.cfg();
3527        let s = ids.len();
3528        assert!(s <= self.max_seq, "seq {s} > max_seq {}", self.max_seq);
3529        let hidden = mc.n_embd as usize;
3530        let hc = d.hc_mult as usize;
3531        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3532
3533        // stage 0: embed -> hc state
3534        let st0 = &self.stages[0];
3535        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
3536        let stream0 = st0.gpu.stream();
3537        let ids_i32: Vec<i32> = ids.iter().map(|&x| x as i32).collect();
3538        let ids_dev = upload_i32(&stream0, &ids_i32)?;
3539        let mut emb = stream0.alloc_zeros::<f32>(s * hidden).map_err(e("emb"))?;
3540        unsafe {
3541            ck(
3542                "embed_rows",
3543                k::memra_dsv4_embed_rows(
3544                    st0.embed
3545                        .as_ref()
3546                        .expect("embed on stage 0")
3547                        .device_ptr(&stream0)
3548                        .0 as *const c_void,
3549                    ids_dev.device_ptr(&stream0).0 as *const i32,
3550                    dpm!(emb, &stream0),
3551                    s as i32,
3552                    hidden as i32,
3553                    sp(&stream0),
3554                ),
3555            )?;
3556        }
3557        if let Some(c) = capture.as_deref_mut() {
3558            if c.embed_out.is_none() {
3559                c.embed_out = Some(dtoh_f32(&stream0, &emb)?);
3560            }
3561        }
3562        let mut h = stream0
3563            .alloc_zeros::<f32>(s * hc * hidden)
3564            .map_err(e("h0"))?;
3565        unsafe {
3566            ck(
3567                "repeat_hc",
3568                k::memra_dsv4_repeat_hc(
3569                    dpf!(emb, &stream0),
3570                    dpm!(h, &stream0),
3571                    s as i32,
3572                    hc as i32,
3573                    hidden as i32,
3574                    sp(&stream0),
3575                ),
3576            )?;
3577        }
3578
3579        // layers, stage by stage; ONE host-bounce boundary copy at the split
3580        let mut cur_stage = 0usize;
3581        for il in 0..n_trunk {
3582            let stage = self.layer_stage[il as usize];
3583            if stage != cur_stage {
3584                let src_stream = self.stages[cur_stage].gpu.stream();
3585                let host = dtoh_f32(&src_stream, &h)?;
3586                let dst_stream = self.stages[stage].gpu.stream();
3587                self.stages[stage]
3588                    .gpu
3589                    .ctx
3590                    .bind_to_thread()
3591                    .map_err(e("bind"))?;
3592                h = upload_f32(&dst_stream, &host)?;
3593                cur_stage = stage;
3594            }
3595            let st = &self.stages[stage];
3596            let lidx = st
3597                .layers
3598                .iter()
3599                .position(|l| l.il == il)
3600                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
3601            let layer_cache = state.as_deref_mut().map(|ds| &mut ds.caches[il as usize]);
3602            h = self.block_forward(
3603                st,
3604                &st.layers[lidx],
3605                &h,
3606                s,
3607                ids,
3608                capture.as_deref_mut(),
3609                layer_cache,
3610            )?;
3611            if early_exit_after == Some(il) {
3612                self.stages[cur_stage]
3613                    .gpu
3614                    .stream()
3615                    .synchronize()
3616                    .map_err(e("sync"))?;
3617                return Ok(None);
3618            }
3619        }
3620
3621        // head (last stage): hc_head collapse (host sigmoid gates) -> norm -> logits
3622        let last = self.stages.len() - 1;
3623        if cur_stage != last {
3624            let src_stream = self.stages[cur_stage].gpu.stream();
3625            let host = dtoh_f32(&src_stream, &h)?;
3626            let dst_stream = self.stages[last].gpu.stream();
3627            h = upload_f32(&dst_stream, &host)?;
3628        }
3629        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3630        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3631        let logits = self.head_logits_from(
3632            &h,
3633            s,
3634            hc_head_fn,
3635            &self.hc_head_base,
3636            &self.hc_head_scale,
3637            trunk_norm,
3638        )?;
3639        Ok(Some(ForwardOut { logits, h_last: h }))
3640    }
3641
3642    /// ParallelHead (model.py:713-735): hc_head collapse (mix GEMM f32 island + host
3643    /// sigmoid gates, the oracle's own arithmetic) -> final RMSNorm -> last-position
3644    /// logits over the SHARED bf16 head. Used by the trunk head and the MTP head.
3645    fn head_logits_from(
3646        &self,
3647        h: &CudaSlice<f32>,
3648        s: usize,
3649        fn_w: &CudaSlice<f32>,
3650        base: &[f32],
3651        scale: &[f32],
3652        norm: &CudaSlice<f32>,
3653    ) -> Res<Vec<f32>> {
3654        self.head_logits_row(h, s, s - 1, fn_w, base, scale, norm)
3655    }
3656
3657    /// Same head, logits at an arbitrary position row (lane-6 m-sensitivity probe:
3658    /// the reference's own realization noise is measured by comparing the SAME row
3659    /// under two prefill lengths).
3660    #[allow(clippy::too_many_arguments)]
3661    fn head_logits_row(
3662        &self,
3663        h: &CudaSlice<f32>,
3664        s: usize,
3665        row: usize,
3666        fn_w: &CudaSlice<f32>,
3667        base: &[f32],
3668        scale: &[f32],
3669        norm: &CudaSlice<f32>,
3670    ) -> Res<Vec<f32>> {
3671        let d = self.model.cfg();
3672        let mc = &self.model.mc;
3673        let hc = d.hc_mult as usize;
3674        let hidden = mc.n_embd as usize;
3675        let eps = mc.rms_eps;
3676        let last = self.stages.len() - 1;
3677        let st = &self.stages[last];
3678        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx head"))?;
3679        let stream = st.gpu.stream();
3680        let w = hc * hidden;
3681        let mut mixes = stream.alloc_zeros::<f32>(s * hc).map_err(e("hm"))?;
3682        Self::dots(st, h, fn_w, s, w, hc, &mut mixes)?;
3683        unsafe {
3684            ck(
3685                "rowsq head",
3686                k::memra_dsv4_rowsq_scale(
3687                    dpf!(h, &stream),
3688                    dpm!(mixes, &stream),
3689                    s as i32,
3690                    w as i32,
3691                    hc as i32,
3692                    eps,
3693                    sp(&stream),
3694                ),
3695            )?;
3696        }
3697        // oracle hc_head: pre = sigmoid(mix*scale + base) + hc_eps (note: RMS eps is the
3698        // model rms_eps inside the mean, hc_eps only in the gate — mirrored exactly)
3699        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
3700        for t in 0..s {
3701            for c in 0..hc {
3702                let m = mixes_h[t * hc + c];
3703                mixes_h[t * hc + c] = sigmoid_f32(m * scale[0] + base[c]) + d.hc_eps;
3704            }
3705        }
3706        let pre_d = upload_f32(&stream, &mixes_h)?;
3707        let mut collapsed = stream.alloc_zeros::<f32>(s * hidden).map_err(e("col"))?;
3708        unsafe {
3709            ck(
3710                "hc_collapse head",
3711                k::memra_dsv4_hc_collapse(
3712                    dpf!(h, &stream),
3713                    dpf!(pre_d, &stream),
3714                    dpm!(collapsed, &stream),
3715                    s as i32,
3716                    hc as i32,
3717                    hidden as i32,
3718                    sp(&stream),
3719                ),
3720            )?;
3721            ck(
3722                "rmsnorm head",
3723                k::memra_dsv4_rmsnorm(
3724                    dpf!(collapsed, &stream),
3725                    dpf!(norm, &stream),
3726                    dpm!(collapsed, &stream),
3727                    s as i32,
3728                    hidden as i32,
3729                    eps,
3730                    sp(&stream),
3731                ),
3732            )?;
3733        }
3734        // logits for the selected position (f32 island GEMM over bf16 head rows)
3735        assert!(row < s, "logits row {row} out of range (s = {s})");
3736        let vocab = {
3737            let (info, _) = self.model.st.raw("head.weight").expect("head");
3738            info.shape[0] as usize
3739        };
3740        let last_row = collapsed.slice(row * hidden..(row + 1) * hidden);
3741        let mut logits = stream.alloc_zeros::<f32>(vocab).map_err(e("logits"))?;
3742        unsafe {
3743            ck(
3744                "head dots",
3745                k::memra_dsv4_dots_f32(
3746                    last_row.device_ptr(&stream).0 as *const f32,
3747                    st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
3748                    1,
3749                    dpm!(logits, &stream),
3750                    1,
3751                    hidden as i32,
3752                    vocab as i32,
3753                    sp(&stream),
3754                ),
3755            )?;
3756        }
3757        dtoh_f32(&stream, &logits)
3758    }
3759
3760    /// MTP logits at the fixture call shape (model.py:826 — same ids to trunk and MTP;
3761    /// the V3 NextN drafter shift is the spec-decode lane's wiring, not claimed here).
3762    /// `h_trunk` = the trunk's final hc state on the LAST stage (ForwardOut::h_last).
3763    pub fn mtp_logits_last(&self, h_trunk: &CudaSlice<f32>, ids: &[u32]) -> Res<Vec<f32>> {
3764        self.mtp_logits_last_cap(h_trunk, ids, None)
3765    }
3766
3767    /// [`Self::mtp_logits_last`] with a capture pass-through (lane 7: the native-GEMM
3768    /// kernel gate captures the MTP block's moe_x under want = {n_trunk}).
3769    pub fn mtp_logits_last_cap(
3770        &self,
3771        h_trunk: &CudaSlice<f32>,
3772        ids: &[u32],
3773        capture: Option<&mut GpuCapture>,
3774    ) -> Res<Vec<f32>> {
3775        let mtp = self.mtp.as_ref().expect("MTP not loaded");
3776        let d = self.model.cfg();
3777        let mc = &self.model.mc;
3778        let hc = d.hc_mult as usize;
3779        let hidden = mc.n_embd as usize;
3780        let eps = mc.rms_eps;
3781        let s = ids.len();
3782        let last = self.stages.len() - 1;
3783        let st = &self.stages[last];
3784        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx mtp"))?;
3785        let stream = st.gpu.stream();
3786
3787        // e = rmsnorm(embed(ids), enorm): embed rows gathered HOST-side (bit-exact bf16
3788        // decode, same as the oracle's embed_rows) — the embed table lives on stage 0.
3789        let e_host = self.model.embed_rows(ids);
3790        let mut e_dev = upload_f32(&stream, &e_host)?;
3791        unsafe {
3792            ck(
3793                "rmsnorm enorm",
3794                k::memra_dsv4_rmsnorm(
3795                    dpf!(e_dev, &stream),
3796                    dpf!(mtp.enorm, &stream),
3797                    dpm!(e_dev, &stream),
3798                    s as i32,
3799                    hidden as i32,
3800                    eps,
3801                    sp(&stream),
3802                ),
3803            )?;
3804        }
3805        // x = hnorm(h_trunk) per hc copy
3806        let mut xh = stream
3807            .alloc_zeros::<f32>(s * hc * hidden)
3808            .map_err(e("mtp xh"))?;
3809        unsafe {
3810            ck(
3811                "rmsnorm hnorm",
3812                k::memra_dsv4_rmsnorm(
3813                    dpf!(h_trunk, &stream),
3814                    dpf!(mtp.hnorm, &stream),
3815                    dpm!(xh, &stream),
3816                    (s * hc) as i32,
3817                    hidden as i32,
3818                    eps,
3819                    sp(&stream),
3820                ),
3821            )?;
3822        }
3823        // ep = e_proj(e) [s, hidden]; hp = h_proj(xh) per copy [s*hc, hidden]
3824        let mut ep = stream.alloc_zeros::<f32>(s * hidden).map_err(e("mtp ep"))?;
3825        Self::gemm(st, &e_dev, &mtp.e_proj, 0, s, hidden, hidden, &mut ep)?;
3826        let mut hp = stream
3827            .alloc_zeros::<f32>(s * hc * hidden)
3828            .map_err(e("mtp hp"))?;
3829        Self::gemm(st, &xh, &mtp.h_proj, 0, s * hc, hidden, hidden, &mut hp)?;
3830        // xm[t, c, :] = ep[t, :] + hp[t, c, :]  (e broadcast over the hc copies)
3831        let mut xm = stream
3832            .alloc_zeros::<f32>(s * hc * hidden)
3833            .map_err(e("mtp xm"))?;
3834        unsafe {
3835            ck(
3836                "repeat ep",
3837                k::memra_dsv4_repeat_hc(
3838                    dpf!(ep, &stream),
3839                    dpm!(xm, &stream),
3840                    s as i32,
3841                    hc as i32,
3842                    hidden as i32,
3843                    sp(&stream),
3844                ),
3845            )?;
3846            ck(
3847                "add hp",
3848                k::memra_dsv4_add_inplace(
3849                    dpm!(xm, &stream),
3850                    dpf!(hp, &stream),
3851                    (s * hc * hidden) as i64,
3852                    sp(&stream),
3853                ),
3854            )?;
3855        }
3856        let xm = self.block_forward(st, &mtp.layer, &xm, s, ids, capture, None)?;
3857        self.head_logits_from(
3858            &xm,
3859            s,
3860            &mtp.hc_head_fn,
3861            &mtp.hc_head_base,
3862            &mtp.hc_head_scale,
3863            &mtp.norm,
3864        )
3865    }
3866
3867    /// Trunk-head logits at position `row` of a ForwardOut hc state (m-sensitivity probe).
3868    pub fn trunk_logits_row(&self, h: &CudaSlice<f32>, s: usize, row: usize) -> Res<Vec<f32>> {
3869        let last = self.stages.len() - 1;
3870        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
3871        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
3872        self.head_logits_row(
3873            h,
3874            s,
3875            row,
3876            hc_head_fn,
3877            &self.hc_head_base,
3878            &self.hc_head_scale,
3879            trunk_norm,
3880        )
3881    }
3882
3883    // ---------------------------------------------------------------- lane 6: decode
3884
3885    /// Allocate the per-layer decode caches (capacity = max_seq, the reference
3886    /// register_buffer shape) on each layer's owning stage. Returns a fresh state
3887    /// (pos = 0) ready for [`Self::prefill_with_cache`].
3888    pub fn alloc_decode_state(&self) -> Res<DecodeState> {
3889        let d = self.model.cfg();
3890        let mc = &self.model.mc;
3891        let win = d.sliding_window as usize;
3892        let hd = d.head_dim as usize;
3893        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
3894        let mut caches = Vec::with_capacity(n_trunk as usize);
3895        let mut cache_bytes = vec![0u64; self.stages.len()];
3896        // iteration 3, rung 4: reserve T_max TRANSIENT window-kv rows per layer at
3897        // kvc rows [win + cap_blocks, win + cap_blocks + T_max) — where a batched verify
3898        // round's kv lands so the persistent ring stays read-only until commit (§3.1).
3899        // Zero rows when the drafter is not loaded: today's exact allocation, byte for byte.
3900        let trans_rows = self.verify_tmax();
3901        for il in 0..n_trunk {
3902            let stage_i = self.layer_stage[il as usize];
3903            let st = &self.stages[stage_i];
3904            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx cache"))?;
3905            let stream = st.gpu.stream();
3906            let lidx = st
3907                .layers
3908                .iter()
3909                .position(|l| l.il == il)
3910                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
3911            let layer = &st.layers[lidx];
3912            let ratio = layer.ratio;
3913            let cap_blocks = if ratio != 0 { self.max_seq / ratio } else { 0 };
3914            let kvc_rows = win + cap_blocks + trans_rows;
3915            let mut bytes = (kvc_rows * hd * 4) as u64;
3916            let kvc = stream
3917                .alloc_zeros::<f32>(kvc_rows * hd)
3918                .map_err(e("kvc alloc"))?;
3919            // pending pair: kv zeros, score -inf (block-0-at-decode masking, receipts)
3920            let mk_pend = |latent: usize, slots: usize| -> Res<(CudaSlice<f32>, CudaSlice<f32>)> {
3921                let kv = stream
3922                    .alloc_zeros::<f32>(slots * latent)
3923                    .map_err(e("pend kv alloc"))?;
3924                let sc = upload_f32(&stream, &vec![f32::NEG_INFINITY; slots * latent])?;
3925                Ok((kv, sc))
3926            };
3927            let (pend_kv, pend_score) = if let Some(cmp) = &layer.cmp {
3928                let slots = if cmp.overlap {
3929                    2 * cmp.ratio
3930                } else {
3931                    cmp.ratio
3932                };
3933                bytes += (2 * slots * cmp.latent * 4) as u64;
3934                let (a, b) = mk_pend(cmp.latent, slots)?;
3935                (Some(a), Some(b))
3936            } else {
3937                (None, None)
3938            };
3939            let (ikvc, ipend_kv, ipend_score) = if let Some(ix) = &layer.idx {
3940                bytes += (cap_blocks * ix.cmp.d * 4) as u64;
3941                let store = stream
3942                    .alloc_zeros::<f32>(cap_blocks * ix.cmp.d)
3943                    .map_err(e("ikvc alloc"))?;
3944                let slots = if ix.cmp.overlap {
3945                    2 * ix.cmp.ratio
3946                } else {
3947                    ix.cmp.ratio
3948                };
3949                bytes += (2 * slots * ix.cmp.latent * 4) as u64;
3950                let (a, b) = mk_pend(ix.cmp.latent, slots)?;
3951                (Some(store), Some(a), Some(b))
3952            } else {
3953                (None, None, None)
3954            };
3955            cache_bytes[stage_i] += bytes;
3956            caches.push(LayerCache {
3957                kvc,
3958                n_blocks: 0,
3959                pend_kv,
3960                pend_score,
3961                ikvc,
3962                i_blocks: 0,
3963                ipend_kv,
3964                ipend_score,
3965            });
3966        }
3967        let ws = if matches!(self.decode_path, DecodePath::Device { .. }) {
3968            Some(self.alloc_step_ws()?)
3969        } else {
3970            None
3971        };
3972        for st in &self.stages {
3973            st.gpu.stream().synchronize().map_err(e("cache sync"))?;
3974        }
3975        Ok(DecodeState {
3976            caches,
3977            pos: 0,
3978            cache_bytes,
3979            ws,
3980        })
3981    }
3982
3983    /// Lane 8: allocate the per-stage step workspace (device decode path only).
3984    fn alloc_step_ws(&self) -> Res<Vec<StepWs>> {
3985        let d = self.model.cfg();
3986        let mc = &self.model.mc;
3987        let moe = mc.moe.as_ref().expect("moe");
3988        let hc = d.hc_mult as usize;
3989        let hidden = mc.n_embd as usize;
3990        let heads = mc.n_head as usize;
3991        let hd = d.head_dim as usize;
3992        let q_lora = d.q_lora_rank as usize;
3993        let win = d.sliding_window as usize;
3994        let o_groups = d.o_groups as usize;
3995        let o_lora = d.o_lora_rank as usize;
3996        let iheads = d.index_n_heads as usize;
3997        let ihd = d.index_head_dim as usize;
3998        let topk = moe.expert_used_count as usize;
3999        let ne = moe.expert_count as usize;
4000        let inter = moe.expert_ff_length as usize;
4001        let itopk = d.index_topk as usize;
4002        let vocab = {
4003            let (info, _) = self.model.st.raw("head.weight").expect("head");
4004            info.shape[0] as usize
4005        };
4006        let sh_inter = {
4007            let (info, _) = self
4008                .model
4009                .st
4010                .raw("layers.0.ffn.shared_experts.w1.weight")
4011                .expect("shared w1");
4012            info.shape[0] as usize
4013        };
4014        // fine ratio (indexer-carrying) and per-class compressor maxima, config-derived
4015        let mut max_latent = 0usize;
4016        let mut max_d = 0usize;
4017        let mut max_shift = 0usize;
4018        let mut min_ratio = usize::MAX;
4019        for st in &self.stages {
4020            for l in &st.layers {
4021                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
4022                    max_latent = max_latent.max(cmp.latent);
4023                    max_d = max_d.max(cmp.d);
4024                    if cmp.overlap {
4025                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
4026                    }
4027                    min_ratio = min_ratio.min(cmp.ratio);
4028                }
4029            }
4030        }
4031        assert!(min_ratio != usize::MAX, "no compressor layers?");
4032        let score_cap = self.max_seq / min_ratio + 1;
4033        let idx_tail = itopk.max(self.max_seq / 128 + 1);
4034        // the largest bf16 cvt any device-path gemm() performs (activation side, m=1):
4035        // wo_b consumes o_groups*o_lora, the o cvt covers heads*hd separately.
4036        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
4037        let mut out = Vec::with_capacity(self.stages.len());
4038        for st in &self.stages {
4039            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ws"))?;
4040            let s = st.gpu.stream();
4041            let f = |n: usize| s.alloc_zeros::<f32>(n).map_err(e("ws f32"));
4042            let b = |n: usize| s.alloc_zeros::<u8>(n).map_err(e("ws u8"));
4043            let i = |n: usize| s.alloc_zeros::<i32>(n).map_err(e("ws i32"));
4044            out.push(StepWs {
4045                h_a: f(hc * hidden)?,
4046                h_b: f(hc * hidden)?,
4047                h_rx: f(hc * hidden)?,
4048                emb: f(hidden)?,
4049                mixes: f((2 + hc) * hc)?,
4050                pre: f(hc)?,
4051                post: f(hc)?,
4052                comb: f(hc * hc)?,
4053                y_hc: f(hidden)?,
4054                x: f(hidden)?,
4055                xf: f(hidden)?,
4056                qr: f(q_lora)?,
4057                qr_b: b(q_lora * 2)?,
4058                q: f(heads * hd)?,
4059                kv: f(hd)?,
4060                qi: f(iheads * ihd)?,
4061                wproj: f(iheads)?,
4062                score: f(score_cap)?,
4063                idx: i(win + idx_tail)?,
4064                o: f(heads * hd)?,
4065                o_b: b(heads * hd * 2)?,
4066                og: f(o_groups * o_lora)?,
4067                attn_out: f(hidden)?,
4068                gemm_xb: b(max_gemm_k * 2)?,
4069                raw: f(ne)?,
4070                sel: i(topk)?,
4071                selw: f(topk)?,
4072                order: i(topk)?,
4073                xq: b(hidden)?,
4074                xs: f(hidden / 128)?,
4075                g1: f(topk * inter)?,
4076                g3: f(topk * inter)?,
4077                hbuf: f(topk * inter)?,
4078                hq: b(topk * inter)?,
4079                hs: f(topk * inter / 128)?,
4080                contrib: f(topk * hidden)?,
4081                y: f(hidden)?,
4082                xb: b(hidden * 2)?,
4083                sg1: f(sh_inter)?,
4084                sg3: f(sh_inter)?,
4085                shbuf: f(sh_inter)?,
4086                shb16: b(sh_inter * 2)?,
4087                sh_out: f(hidden)?,
4088                cmp_kv_row: f(max_latent)?,
4089                cmp_sc_row: f(max_latent)?,
4090                cmp_emit: f(2 * max_d)?,
4091                cmp_shift: f(max_shift.max(1))?,
4092                sink_scores: f(heads * (win + idx_tail))?,
4093                sink_evals: f(heads * (win + idx_tail))?,
4094                sink_den: s.alloc_zeros::<f64>(heads).map_err(e("ws f64"))?,
4095                head_mixes: f(hc)?,
4096                head_pre: f(hc)?,
4097                collapsed: f(hidden)?,
4098                logits: f(vocab)?,
4099                argmax: i(1)?,
4100                tok: i(1)?,
4101            });
4102        }
4103        Ok(out)
4104    }
4105
4106    /// Incremental compressor step (reference decode state machine, M:344-377): append
4107    /// this position's RAW wkv/wgate rows to the pending state; when the block
4108    /// completes ((pos+1) % ratio == 0), emit block pos/ratio into `store` row
4109    /// row0 + j via the SAME pooling kernel prefill uses (overlap rides a 2-block
4110    /// launch whose block 1 reads prev rows [0,ratio) through dims [0,d) and cur rows
4111    /// [ratio,2ratio) through dims [d,2d) — the emission pooling verbatim), then
4112    /// norm→rope(j·ratio)→QAT, and shift cur→prev.
4113    #[allow(clippy::too_many_arguments)]
4114    fn cmp_decode(
4115        &self,
4116        st: &Stage,
4117        cmp: &CmpDev,
4118        x: &CudaSlice<f32>, // [1, hidden] post-attn-norm
4119        pos: usize,
4120        hidden: usize,
4121        fc_dev: &CudaSlice<f32>,
4122        rd: usize,
4123        eps: f32,
4124        pend_kv: &mut CudaSlice<f32>,
4125        pend_score: &mut CudaSlice<f32>,
4126        store: &mut CudaSlice<f32>,
4127        row0: usize,
4128        blocks: &mut usize,
4129    ) -> Res<()> {
4130        let stream = st.gpu.stream();
4131        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
4132        let mut kv_row = stream.alloc_zeros::<f32>(latent).map_err(e("dkv"))?;
4133        let mut sc_row = stream.alloc_zeros::<f32>(latent).map_err(e("dsc"))?;
4134        Self::dots(st, x, &cmp.wkv, 1, hidden, latent, &mut kv_row)?;
4135        Self::dots(st, x, &cmp.wgate, 1, hidden, latent, &mut sc_row)?;
4136        let slot = if cmp.overlap {
4137            ratio + pos % ratio
4138        } else {
4139            pos % ratio
4140        };
4141        {
4142            let src = kv_row.slice(0..latent);
4143            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
4144            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
4145            let src = sc_row.slice(0..latent);
4146            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
4147            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
4148        }
4149        if (pos + 1) % ratio != 0 {
4150            return Ok(());
4151        }
4152        let j = pos / ratio;
4153        let nb_launch = if cmp.overlap { 2usize } else { 1 };
4154        let row_off = if cmp.overlap { d } else { 0 };
4155        let mut out = stream
4156            .alloc_zeros::<f32>(nb_launch * d)
4157            .map_err(e("emit"))?;
4158        unsafe {
4159            ck(
4160                "compressor_pool dec",
4161                k::memra_dsv4_compressor_pool(
4162                    dpf!(pend_kv, &stream),
4163                    dpf!(pend_score, &stream),
4164                    dpf!(cmp.ape, &stream),
4165                    dpm!(out, &stream),
4166                    nb_launch as i32,
4167                    ratio as i32,
4168                    d as i32,
4169                    latent as i32,
4170                    cmp.overlap as i32,
4171                    sp(&stream),
4172                ),
4173            )?;
4174            // in-place row ops at the emitted row (base + row_off), lane-4 ptr idiom
4175            let row_c = (out.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
4176            let row_m = (out.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
4177            ck(
4178                "rmsnorm dec cmp",
4179                k::memra_dsv4_rmsnorm(
4180                    row_c,
4181                    dpf!(cmp.norm, &stream),
4182                    row_m,
4183                    1,
4184                    d as i32,
4185                    eps,
4186                    sp(&stream),
4187                ),
4188            )?;
4189            let pos_dev = upload_i32(&stream, &[(j * ratio) as i32])?;
4190            ck(
4191                "rope dec cmp",
4192                k::memra_dsv4_rope(
4193                    row_m,
4194                    1,
4195                    1,
4196                    d as i32,
4197                    rd as i32,
4198                    dpf!(fc_dev, &stream),
4199                    pos_dev.device_ptr(&stream).0 as *const i32,
4200                    0,
4201                    sp(&stream),
4202                ),
4203            )?;
4204            if cmp.rotate {
4205                let scale = (d as f32).powf(-0.5);
4206                ck(
4207                    "hadamard dec cmp",
4208                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
4209                )?;
4210                ck(
4211                    "fp4 dec cmp",
4212                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
4213                )?;
4214            } else {
4215                ck(
4216                    "act_quant dec cmp",
4217                    k::memra_dsv4_act_quant(
4218                        row_m,
4219                        1,
4220                        d as i64,
4221                        (d - rd) as i32,
4222                        64,
4223                        (self.variant == ActQuantVariant::ClampOnly) as i32,
4224                        sp(&stream),
4225                    ),
4226                )?;
4227            }
4228        }
4229        {
4230            let src = out.slice(row_off..row_off + d);
4231            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
4232            stream
4233                .memcpy_dtod(&src, &mut dst)
4234                .map_err(e("emit store"))?;
4235        }
4236        if cmp.overlap {
4237            // shift cur -> prev through a bounce (same-buffer D2D ranges must not alias)
4238            let mut tmp = stream
4239                .alloc_zeros::<f32>(ratio * latent)
4240                .map_err(e("shift tmp"))?;
4241            {
4242                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
4243                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift1"))?;
4244            }
4245            {
4246                let mut dst = pend_kv.slice_mut(0..ratio * latent);
4247                stream
4248                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4249                    .map_err(e("shift2"))?;
4250            }
4251            {
4252                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
4253                stream.memcpy_dtod(&src, &mut tmp).map_err(e("shift3"))?;
4254            }
4255            {
4256                let mut dst = pend_score.slice_mut(0..ratio * latent);
4257                stream
4258                    .memcpy_dtod(&tmp.slice(0..ratio * latent), &mut dst)
4259                    .map_err(e("shift4"))?;
4260            }
4261        }
4262        *blocks = j + 1;
4263        Ok(())
4264    }
4265
4266    /// One trunk block, single-token decode. h is [1, hc, hidden] f32 on the stage.
4267    /// Mirrors the reference decode branches: ring write (M:530), indexer with its
4268    /// compressor BEFORE scoring (M:415), attention compressor before sparse_attn
4269    /// (M:531), window/compressed index law (M:255-276). `dump` (diagnostic only)
4270    /// collects named intermediates for the bisect probe.
4271    #[allow(clippy::too_many_arguments)]
4272    fn block_decode(
4273        &self,
4274        st: &Stage,
4275        layer: &LayerDev,
4276        cache: &mut LayerCache,
4277        h: &CudaSlice<f32>,
4278        pos: usize,
4279        tok: u32,
4280        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
4281    ) -> Res<CudaSlice<f32>> {
4282        let d = self.model.cfg();
4283        let mc = &self.model.mc;
4284        let hc = d.hc_mult as usize;
4285        let hidden = mc.n_embd as usize;
4286        let heads = mc.n_head as usize;
4287        let hd = d.head_dim as usize;
4288        let rd = d.qk_rope_head_dim as usize;
4289        let q_lora = d.q_lora_rank as usize;
4290        let win = d.sliding_window as usize;
4291        let o_groups = d.o_groups as usize;
4292        let o_lora = d.o_lora_rank as usize;
4293        let eps = mc.rms_eps;
4294        let iters = d.hc_sinkhorn_iters;
4295        let hc_eps = d.hc_eps;
4296        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx"))?;
4297        let stream = st.gpu.stream();
4298        let fc_dev = if layer.ratio != 0 {
4299            &st.fc_yarn
4300        } else {
4301            &st.fc_plain
4302        };
4303        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
4304        let LayerCache {
4305            kvc,
4306            n_blocks,
4307            pend_kv,
4308            pend_score,
4309            ikvc,
4310            i_blocks,
4311            ipend_kv,
4312            ipend_score,
4313        } = cache;
4314
4315        // ---- attention sub-block
4316        let (y, post, comb) = Self::hc_pre(
4317            st,
4318            h,
4319            &layer.hc_attn_fn,
4320            &layer.hc_attn_base,
4321            &layer.hc_attn_scale,
4322            1,
4323            hc,
4324            hidden,
4325            iters,
4326            hc_eps,
4327        )?;
4328        let mut x = stream.alloc_zeros::<f32>(hidden).map_err(e("x"))?;
4329        unsafe {
4330            ck(
4331                "rmsnorm attn",
4332                k::memra_dsv4_rmsnorm(
4333                    dpf!(y, &stream),
4334                    dpf!(layer.attn_norm, &stream),
4335                    dpm!(x, &stream),
4336                    1,
4337                    hidden as i32,
4338                    eps,
4339                    sp(&stream),
4340                ),
4341            )?;
4342        }
4343        if let Some(dm) = dump.as_deref_mut() {
4344            dm.push((format!("layer{}.x", layer.il), dtoh_f32(&stream, &x)?));
4345        }
4346
4347        // q path (item 3: `.dev()` is lawful here — the legacy path with the fp8
4348        // dense arm is a BOOT refusal, so these slabs are always device-resident)
4349        let mut qr = stream.alloc_zeros::<f32>(q_lora).map_err(e("qr"))?;
4350        Self::gemm(st, &x, layer.wq_a.dev(), 0, 1, q_lora, hidden, &mut qr)?;
4351        unsafe {
4352            ck(
4353                "rmsnorm q",
4354                k::memra_dsv4_rmsnorm(
4355                    dpf!(qr, &stream),
4356                    dpf!(layer.q_norm, &stream),
4357                    dpm!(qr, &stream),
4358                    1,
4359                    q_lora as i32,
4360                    eps,
4361                    sp(&stream),
4362                ),
4363            )?;
4364        }
4365        let mut qr_b = stream.alloc_zeros::<u8>(q_lora * 2).map_err(e("qr_b"))?;
4366        unsafe {
4367            ck(
4368                "cvt qr",
4369                k::memra_dsv4_cvt_bf16(
4370                    dpf!(qr, &stream),
4371                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
4372                    q_lora as i64,
4373                    sp(&stream),
4374                ),
4375            )?;
4376        }
4377        let mut q = stream.alloc_zeros::<f32>(heads * hd).map_err(e("q"))?;
4378        Self::gemm_pre(
4379            st,
4380            &qr_b,
4381            layer.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4382            1,
4383            heads * hd,
4384            q_lora,
4385            &mut q,
4386        )?;
4387        let pos_dev = upload_i32(&stream, &[pos as i32])?;
4388        unsafe {
4389            ck(
4390                "headrms",
4391                k::memra_dsv4_headrms(dpm!(q, &stream), heads as i32, hd as i32, eps, sp(&stream)),
4392            )?;
4393            ck(
4394                "rope q",
4395                k::memra_dsv4_rope(
4396                    dpm!(q, &stream),
4397                    1,
4398                    heads as i32,
4399                    hd as i32,
4400                    rd as i32,
4401                    dpf!(fc_dev, &stream),
4402                    pos_dev.device_ptr(&stream).0 as *const i32,
4403                    0,
4404                    sp(&stream),
4405                ),
4406            )?;
4407        }
4408
4409        if let Some(dm) = dump.as_deref_mut() {
4410            dm.push((format!("layer{}.q", layer.il), dtoh_f32(&stream, &q)?));
4411        }
4412        // shared K==V latent row + window QAT, written into the ring at pos % win
4413        let mut kv = stream.alloc_zeros::<f32>(hd).map_err(e("kv"))?;
4414        Self::gemm(st, &x, layer.wkv.dev(), 0, 1, hd, hidden, &mut kv)?;
4415        unsafe {
4416            ck(
4417                "rmsnorm kv",
4418                k::memra_dsv4_rmsnorm(
4419                    dpf!(kv, &stream),
4420                    dpf!(layer.kv_norm, &stream),
4421                    dpm!(kv, &stream),
4422                    1,
4423                    hd as i32,
4424                    eps,
4425                    sp(&stream),
4426                ),
4427            )?;
4428            ck(
4429                "rope kv",
4430                k::memra_dsv4_rope(
4431                    dpm!(kv, &stream),
4432                    1,
4433                    1,
4434                    hd as i32,
4435                    rd as i32,
4436                    dpf!(fc_dev, &stream),
4437                    pos_dev.device_ptr(&stream).0 as *const i32,
4438                    0,
4439                    sp(&stream),
4440                ),
4441            )?;
4442            ck(
4443                "act_quant kv",
4444                k::memra_dsv4_act_quant(
4445                    dpm!(kv, &stream),
4446                    1,
4447                    hd as i64,
4448                    (hd - rd) as i32,
4449                    64,
4450                    clamp_only,
4451                    sp(&stream),
4452                ),
4453            )?;
4454        }
4455        {
4456            let slot = pos % win;
4457            let src = kv.slice(0..hd);
4458            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
4459            stream
4460                .memcpy_dtod(&src, &mut dst)
4461                .map_err(e("ring write"))?;
4462        }
4463        if let Some(dm) = dump.as_deref_mut() {
4464            dm.push((format!("layer{}.kv", layer.il), dtoh_f32(&stream, &kv)?));
4465        }
4466
4467        // index assembly: window part (M:255-262 decode branches), fixed width win
4468        let mut idxs: Vec<i64> = vec![-1; win];
4469        if pos >= win - 1 {
4470            let sp_ = pos % win;
4471            let mut k_ = 0usize;
4472            for s_ in (sp_ + 1)..win {
4473                idxs[k_] = s_ as i64;
4474                k_ += 1;
4475            }
4476            for s_ in 0..=sp_ {
4477                idxs[k_] = s_ as i64;
4478                k_ += 1;
4479            }
4480        } else {
4481            for (p, v) in idxs.iter_mut().enumerate().take(pos + 1) {
4482                *v = p as i64;
4483            }
4484        }
4485
4486        if layer.ratio != 0 {
4487            let cidx: Vec<i64> = if let Some(ix) = &layer.idx {
4488                // indexer q
4489                let mut qi = stream
4490                    .alloc_zeros::<f32>(ix.heads * ix.hd)
4491                    .map_err(e("qi"))?;
4492                Self::gemm_pre(
4493                    st,
4494                    &qr_b,
4495                    ix.wq_b.dev().device_ptr(&stream).0 as *const c_void,
4496                    1,
4497                    ix.heads * ix.hd,
4498                    q_lora,
4499                    &mut qi,
4500                )?;
4501                unsafe {
4502                    ck(
4503                        "rope qi",
4504                        k::memra_dsv4_rope(
4505                            dpm!(qi, &stream),
4506                            1,
4507                            ix.heads as i32,
4508                            ix.hd as i32,
4509                            rd as i32,
4510                            dpf!(fc_dev, &stream),
4511                            pos_dev.device_ptr(&stream).0 as *const i32,
4512                            0,
4513                            sp(&stream),
4514                        ),
4515                    )?;
4516                    let scale = (ix.hd as f32).powf(-0.5);
4517                    ck(
4518                        "hadamard qi",
4519                        k::memra_dsv4_hadamard(
4520                            dpm!(qi, &stream),
4521                            ix.heads as i32,
4522                            ix.hd as i32,
4523                            scale,
4524                            sp(&stream),
4525                        ),
4526                    )?;
4527                    ck(
4528                        "fp4 qi",
4529                        k::memra_dsv4_fp4_act_quant(
4530                            dpm!(qi, &stream),
4531                            ix.heads as i32,
4532                            ix.hd as i64,
4533                            ix.hd as i32,
4534                            sp(&stream),
4535                        ),
4536                    )?;
4537                }
4538                // indexer compressor BEFORE scoring (M:415): this step's block is scored
4539                self.cmp_decode(
4540                    st,
4541                    &ix.cmp,
4542                    &x,
4543                    pos,
4544                    hidden,
4545                    fc_dev,
4546                    rd,
4547                    eps,
4548                    ipend_kv.as_mut().expect("ipend"),
4549                    ipend_score.as_mut().expect("ipend"),
4550                    ikvc.as_mut().expect("ikvc"),
4551                    0,
4552                    i_blocks,
4553                )?;
4554                let nb = *i_blocks;
4555                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
4556                if nb > 0 {
4557                    let mut wproj = stream.alloc_zeros::<f32>(ix.heads).map_err(e("wp"))?;
4558                    Self::gemm(
4559                        st,
4560                        &x,
4561                        ix.weights_proj.dev(),
4562                        0,
4563                        1,
4564                        ix.heads,
4565                        hidden,
4566                        &mut wproj,
4567                    )?;
4568                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
4569                    let mut score = stream.alloc_zeros::<f32>(nb).map_err(e("iscore"))?;
4570                    unsafe {
4571                        ck(
4572                            "indexer_score dec",
4573                            k::memra_dsv4_indexer_score(
4574                                dpf!(qi, &stream),
4575                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
4576                                dpf!(wproj, &stream),
4577                                wscale,
4578                                dpm!(score, &stream),
4579                                1,
4580                                ix.heads as i32,
4581                                ix.hd as i32,
4582                                nb as i32,
4583                                layer.ratio as i32,
4584                                nb as i32, // decode law: store is causal, lim = nb
4585                                sp(&stream),
4586                            ),
4587                        )?;
4588                    }
4589                    let score_h = dtoh_f32(&stream, &score)?;
4590                    // host topk, oracle ordering (value desc, index asc), offset = win
4591                    let kk = ix.topk.min(nb);
4592                    let mut order: Vec<usize> = (0..nb).collect();
4593                    order.sort_by(|&a, &b| {
4594                        score_h[b]
4595                            .partial_cmp(&score_h[a])
4596                            .unwrap_or(std::cmp::Ordering::Equal)
4597                            .then(a.cmp(&b))
4598                    });
4599                    order
4600                        .into_iter()
4601                        .take(kk)
4602                        .map(|j| (j + win) as i64)
4603                        .collect()
4604                } else {
4605                    Vec::new()
4606                }
4607            } else {
4608                // coarse: all blocks incl. the one emitted this step (M:268-271 decode)
4609                let nb = (pos + 1) / layer.ratio;
4610                (0..nb).map(|j| (j + win) as i64).collect()
4611            };
4612            // attention compressor before sparse_attn (M:531)
4613            self.cmp_decode(
4614                st,
4615                layer.cmp.as_ref().expect("ratio!=0 has compressor"),
4616                &x,
4617                pos,
4618                hidden,
4619                fc_dev,
4620                rd,
4621                eps,
4622                pend_kv.as_mut().expect("pend"),
4623                pend_score.as_mut().expect("pend"),
4624                kvc,
4625                win,
4626                n_blocks,
4627            )?;
4628            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
4629            idxs.extend_from_slice(&cidx);
4630        }
4631        let slots = idxs.len();
4632        let idxs_i32: Vec<i32> = idxs.iter().map(|&v| v as i32).collect();
4633        let idx_dev = upload_i32(&stream, &idxs_i32)?;
4634
4635        // sparse sink attention over the layer cache + query-position de-rotation
4636        let mut o = stream.alloc_zeros::<f32>(heads * hd).map_err(e("o"))?;
4637        let scale = (hd as f64).powf(-0.5) as f32;
4638        unsafe {
4639            ck(
4640                "sink_attn dec",
4641                k::memra_dsv4_sink_attn(
4642                    dpf!(q, &stream),
4643                    dpf!(kvc, &stream),
4644                    idx_dev.device_ptr(&stream).0 as *const i32,
4645                    dpf!(layer.sink, &stream),
4646                    dpm!(o, &stream),
4647                    1,
4648                    heads as i32,
4649                    hd as i32,
4650                    slots as i32,
4651                    scale,
4652                    sp(&stream),
4653                ),
4654            )?;
4655            ck(
4656                "rope o inv",
4657                k::memra_dsv4_rope(
4658                    dpm!(o, &stream),
4659                    1,
4660                    heads as i32,
4661                    hd as i32,
4662                    rd as i32,
4663                    dpf!(fc_dev, &stream),
4664                    pos_dev.device_ptr(&stream).0 as *const i32,
4665                    1,
4666                    sp(&stream),
4667                ),
4668            )?;
4669        }
4670
4671        if let Some(dm) = dump.as_deref_mut() {
4672            dm.push((format!("layer{}.o", layer.il), dtoh_f32(&stream, &o)?));
4673        }
4674        // grouped wo (identical to prefill at s=1)
4675        let gw = heads / o_groups * hd;
4676        let mut og = stream
4677            .alloc_zeros::<f32>(o_groups * o_lora)
4678            .map_err(e("og"))?;
4679        let mut o_grp = stream.alloc_zeros::<f32>(gw).map_err(e("o_grp"))?;
4680        let mut y_grp = stream.alloc_zeros::<f32>(o_lora).map_err(e("y_grp"))?;
4681        for g in 0..o_groups {
4682            unsafe {
4683                ck(
4684                    "take_cols",
4685                    k::memra_dsv4_take_cols(
4686                        dpf!(o, &stream),
4687                        dpm!(o_grp, &stream),
4688                        1,
4689                        gw as i32,
4690                        (heads * hd) as i64,
4691                        (g * gw) as i64,
4692                        sp(&stream),
4693                    ),
4694                )?;
4695            }
4696            Self::gemm(
4697                st,
4698                &o_grp,
4699                layer.wo_a.dev(),
4700                g * o_lora * gw,
4701                1,
4702                o_lora,
4703                gw,
4704                &mut y_grp,
4705            )?;
4706            unsafe {
4707                ck(
4708                    "place_cols",
4709                    k::memra_dsv4_place_cols(
4710                        dpf!(y_grp, &stream),
4711                        dpm!(og, &stream),
4712                        1,
4713                        o_lora as i32,
4714                        (o_groups * o_lora) as i64,
4715                        (g * o_lora) as i64,
4716                        sp(&stream),
4717                    ),
4718                )?;
4719            }
4720        }
4721        let mut attn_out = stream.alloc_zeros::<f32>(hidden).map_err(e("ao"))?;
4722        Self::gemm(
4723            st,
4724            &og,
4725            layer.wo_b.dev(),
4726            0,
4727            1,
4728            hidden,
4729            o_groups * o_lora,
4730            &mut attn_out,
4731        )?;
4732
4733        if let Some(dm) = dump.as_deref_mut() {
4734            dm.push((
4735                format!("layer{}.attn_out", layer.il),
4736                dtoh_f32(&stream, &attn_out)?,
4737            ));
4738        }
4739        // hc_post (attention)
4740        let mut h2 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h2"))?;
4741        unsafe {
4742            ck(
4743                "hc_post attn",
4744                k::memra_dsv4_hc_post(
4745                    dpf!(attn_out, &stream),
4746                    dpf!(h, &stream),
4747                    dpf!(post, &stream),
4748                    dpf!(comb, &stream),
4749                    dpm!(h2, &stream),
4750                    1,
4751                    hc as i32,
4752                    hidden as i32,
4753                    sp(&stream),
4754                ),
4755            )?;
4756        }
4757
4758        // ---- ffn sub-block
4759        let (y2, post2, comb2) = Self::hc_pre(
4760            st,
4761            &h2,
4762            &layer.hc_ffn_fn,
4763            &layer.hc_ffn_base,
4764            &layer.hc_ffn_scale,
4765            1,
4766            hc,
4767            hidden,
4768            iters,
4769            hc_eps,
4770        )?;
4771        let mut xf = stream.alloc_zeros::<f32>(hidden).map_err(e("xf"))?;
4772        unsafe {
4773            ck(
4774                "rmsnorm ffn",
4775                k::memra_dsv4_rmsnorm(
4776                    dpf!(y2, &stream),
4777                    dpf!(layer.ffn_norm, &stream),
4778                    dpm!(xf, &stream),
4779                    1,
4780                    hidden as i32,
4781                    eps,
4782                    sp(&stream),
4783                ),
4784            )?;
4785        }
4786        let moe_out = self.moe_forward(st, layer, &xf, 1, &[tok])?;
4787        if let Some(dm) = dump.as_deref_mut() {
4788            dm.push((
4789                format!("layer{}.moe_out", layer.il),
4790                dtoh_f32(&stream, &moe_out)?,
4791            ));
4792        }
4793        let mut h3 = stream.alloc_zeros::<f32>(hc * hidden).map_err(e("h3"))?;
4794        unsafe {
4795            ck(
4796                "hc_post ffn",
4797                k::memra_dsv4_hc_post(
4798                    dpf!(moe_out, &stream),
4799                    dpf!(h2, &stream),
4800                    dpf!(post2, &stream),
4801                    dpf!(comb2, &stream),
4802                    dpm!(h3, &stream),
4803                    1,
4804                    hc as i32,
4805                    hidden as i32,
4806                    sp(&stream),
4807                ),
4808            )?;
4809        }
4810        if let Some(dm) = dump.as_deref_mut() {
4811            dm.push((format!("layer{}.h3", layer.il), dtoh_f32(&stream, &h3)?));
4812        }
4813        Ok(h3)
4814    }
4815
4816    /// One incremental decode step: consume `tok` at position state.pos through all
4817    /// trunk layers + head using the caches (hc state carried across the PP boundary
4818    /// by host bounce, one copy per step). Returns the full logits row predicting
4819    /// position state.pos + 1.
4820    pub fn decode_step(&self, tok: u32, state: &mut DecodeState) -> Res<Vec<f32>> {
4821        self.decode_step_impl(tok, state, None)
4822    }
4823
4824    /// Diagnostic twin: returns (logits, named per-layer intermediates).
4825    pub fn decode_step_probe(
4826        &self,
4827        tok: u32,
4828        state: &mut DecodeState,
4829    ) -> Res<(Vec<f32>, Vec<(String, Vec<f32>)>)> {
4830        let mut dump = Vec::new();
4831        let logits = self.decode_step_impl(tok, state, Some(&mut dump))?;
4832        Ok((logits, dump))
4833    }
4834
4835    // ------------------------------------------------------------ lane 8: device path
4836
4837    /// bf16 GEMV with the arena cvt scratch and raw pointers (device decode path,
4838    /// m = 1): cvt_bf16 then the deterministic fixed-tree memra_dsv4_gemv_bf16 —
4839    /// the lane-8 class-II realization of the cuBLASLt m=1 GEMMs (gated).
4840    #[allow(clippy::too_many_arguments)]
4841    fn gemm_dev(
4842        st: &Stage,
4843        x_f32: *const f32,
4844        xb: &mut CudaSlice<u8>,
4845        w: DW,
4846        m: usize,
4847        n: usize,
4848        kdim: usize,
4849        y_ptr: *mut f32,
4850    ) -> Res<()> {
4851        assert_eq!(m, 1, "gemm_dev is the m=1 decode path");
4852        let stream = st.gpu.stream();
4853        unsafe {
4854            ck(
4855                "cvt_bf16 dev",
4856                k::memra_dsv4_cvt_bf16(
4857                    x_f32,
4858                    xb.device_ptr_mut(&stream).0 as *mut c_void,
4859                    kdim as i64,
4860                    sp(&stream),
4861                ),
4862            )?;
4863        }
4864        let xb_ptr = xb.device_ptr(&stream).0 as *const c_void;
4865        Self::gemv_pre_dev(st, xb_ptr, w, n, kdim, y_ptr)
4866    }
4867
4868    /// GEMV from an already-bf16 activation buffer (device decode path, m = 1).
4869    /// Dispatches on the dense-weight realization: bf16 slab, or the iteration-5 FP8
4870    /// pair through the bit-identical twin.
4871    fn gemv_pre_dev(
4872        st: &Stage,
4873        xb_ptr: *const c_void,
4874        w: DW,
4875        n: usize,
4876        kdim: usize,
4877        y_ptr: *mut f32,
4878    ) -> Res<()> {
4879        let stream = st.gpu.stream();
4880        unsafe {
4881            match w {
4882                DW::Bf16(w_ptr) => ck(
4883                    "gemv_bf16 pre dev",
4884                    k::memra_dsv4_gemv_bf16(
4885                        w_ptr,
4886                        xb_ptr,
4887                        y_ptr,
4888                        n as i32,
4889                        kdim as i32,
4890                        sp(&stream),
4891                    ),
4892                )?,
4893                DW::Fp8 {
4894                    codes,
4895                    scales,
4896                    sc_cols,
4897                } => ck(
4898                    "gemv_fp8 pre dev",
4899                    k::memra_dsv4_gemv_fp8(
4900                        codes,
4901                        scales,
4902                        sc_cols,
4903                        xb_ptr,
4904                        y_ptr,
4905                        n as i32,
4906                        kdim as i32,
4907                        sp(&stream),
4908                    ),
4909                )?,
4910            }
4911        }
4912        Ok(())
4913    }
4914
4915    /// hc_pre on the device path: dots + rowsq (unchanged kernels) then Sinkhorn either
4916    /// on the HOST (byte-identity arm — hc_split_sinkhorn verbatim, results uploaded
4917    /// into the arena) or as the single-thread device kernel (realization fork, class
4918    /// gated). Writes ws {mixes, pre, post, comb, y_hc}.
4919    #[allow(clippy::too_many_arguments)]
4920    // ── 0731 re-gate extension rung dispatch (MEMRA_DSV4_DOTS_ARM=f32x): each helper
4921    // picks the f64 kernel (default — the pinned oracle-truth bytes, also the lane-9
4922    // `f32` arm's bytes) or its f32acc twin. DEVICE decode path only; prefill and the
4923    // legacy path never route through these.
4924    #[allow(clippy::too_many_arguments)]
4925    unsafe fn rmsnorm_arm(
4926        &self,
4927        x: *const f32,
4928        w: *const f32,
4929        dst: *mut f32,
4930        rows: i32,
4931        ncols: i32,
4932        eps: f32,
4933        sv: *mut c_void,
4934    ) -> i32 {
4935        unsafe {
4936            if self.chains_f32 {
4937                k::memra_dsv4_rmsnorm_f32acc(x, w, dst, rows, ncols, eps, sv)
4938            } else {
4939                k::memra_dsv4_rmsnorm(x, w, dst, rows, ncols, eps, sv)
4940            }
4941        }
4942    }
4943
4944    unsafe fn headrms_arm(&self, x: *mut f32, rows: i32, d: i32, eps: f32, sv: *mut c_void) -> i32 {
4945        unsafe {
4946            if self.chains_f32 {
4947                k::memra_dsv4_headrms_f32acc(x, rows, d, eps, sv)
4948            } else {
4949                k::memra_dsv4_headrms(x, rows, d, eps, sv)
4950            }
4951        }
4952    }
4953
4954    #[allow(clippy::too_many_arguments)]
4955    unsafe fn rowsq_scale_arm(
4956        &self,
4957        x: *const f32,
4958        mixes: *mut f32,
4959        s: i32,
4960        w: i32,
4961        rows: i32,
4962        eps: f32,
4963        sv: *mut c_void,
4964    ) -> i32 {
4965        unsafe {
4966            if self.chains_f32 {
4967                k::memra_dsv4_rowsq_scale_f32acc(x, mixes, s, w, rows, eps, sv)
4968            } else {
4969                k::memra_dsv4_rowsq_scale(x, mixes, s, w, rows, eps, sv)
4970            }
4971        }
4972    }
4973
4974    #[allow(clippy::too_many_arguments)]
4975    unsafe fn indexer_score_arm(
4976        &self,
4977        q: *const f32,
4978        ckv: *const f32,
4979        w: *const f32,
4980        wscale: f32,
4981        score: *mut f32,
4982        s: i32,
4983        heads: i32,
4984        hd: i32,
4985        nb: i32,
4986        ratio: i32,
4987        lim0: i32,
4988        sv: *mut c_void,
4989    ) -> i32 {
4990        unsafe {
4991            if self.chains_f32 {
4992                k::memra_dsv4_indexer_score_f32acc(
4993                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
4994                )
4995            } else {
4996                k::memra_dsv4_indexer_score(
4997                    q, ckv, w, wscale, score, s, heads, hd, nb, ratio, lim0, sv,
4998                )
4999            }
5000        }
5001    }
5002
5003    /// `den` is the f64 workspace either way; the f32acc twin rides a FLOAT view of the
5004    /// same allocation (K2 writes it, K3 reads it, within the one FFI entry).
5005    #[allow(clippy::too_many_arguments)]
5006    unsafe fn sink_attn_dec_arm(
5007        &self,
5008        q: *const f32,
5009        kv: *const f32,
5010        idxs: *const i32,
5011        sink: *const f32,
5012        scores: *mut f32,
5013        evals: *mut f32,
5014        den: *mut f64,
5015        o: *mut f32,
5016        heads: i32,
5017        hd: i32,
5018        slots: i32,
5019        scale: f32,
5020        sv: *mut c_void,
5021    ) -> i32 {
5022        unsafe {
5023            if self.chains_f32 {
5024                k::memra_dsv4_sink_attn_dec_f32acc(
5025                    q,
5026                    kv,
5027                    idxs,
5028                    sink,
5029                    scores,
5030                    evals,
5031                    den as *mut f32,
5032                    o,
5033                    heads,
5034                    hd,
5035                    slots,
5036                    scale,
5037                    sv,
5038                )
5039            } else {
5040                k::memra_dsv4_sink_attn_dec(
5041                    q, kv, idxs, sink, scores, evals, den, o, heads, hd, slots, scale, sv,
5042                )
5043            }
5044        }
5045    }
5046
5047    fn hc_pre_dev(
5048        &self,
5049        st: &Stage,
5050        h: &CudaSlice<f32>,
5051        fn_w: &CudaSlice<f32>,
5052        base_host: &[f32],
5053        scale_host: &[f32],
5054        base_dev: &CudaSlice<f32>,
5055        scale_dev: &CudaSlice<f32>,
5056        mixes: &mut CudaSlice<f32>,
5057        pre: &mut CudaSlice<f32>,
5058        post: &mut CudaSlice<f32>,
5059        comb: &mut CudaSlice<f32>,
5060        y_hc: &mut CudaSlice<f32>,
5061        hc: usize,
5062        hidden: usize,
5063        iters: u32,
5064        hc_eps: f32,
5065        host_math: bool,
5066    ) -> Res<()> {
5067        let stream = st.gpu.stream();
5068        let w = hc * hidden;
5069        let rows = (2 + hc) * hc;
5070        self.dots_dev(st, h, fn_w, 1, w, rows, mixes)?;
5071        unsafe {
5072            ck(
5073                "rowsq_scale dev",
5074                self.rowsq_scale_arm(
5075                    dpf!(h, &stream),
5076                    dpm!(*mixes, &stream),
5077                    1,
5078                    w as i32,
5079                    rows as i32,
5080                    hc_eps,
5081                    sp(&stream),
5082                ),
5083            )?;
5084        }
5085        if host_math {
5086            let mixes_h = dtoh_f32(&stream, mixes)?;
5087            let (pre_h, post_h, comb_h) =
5088                hc_split_sinkhorn(&mixes_h, 1, hc, scale_host, base_host, iters, hc_eps);
5089            stream.memcpy_htod(&pre_h, pre).map_err(e("htod pre"))?;
5090            stream.memcpy_htod(&post_h, post).map_err(e("htod post"))?;
5091            stream.memcpy_htod(&comb_h, comb).map_err(e("htod comb"))?;
5092        } else {
5093            unsafe {
5094                ck(
5095                    "hc_sinkhorn",
5096                    k::memra_dsv4_hc_sinkhorn(
5097                        dpf!(*mixes, &stream),
5098                        dpf!(scale_dev, &stream),
5099                        dpf!(base_dev, &stream),
5100                        dpm!(*pre, &stream),
5101                        dpm!(*post, &stream),
5102                        dpm!(*comb, &stream),
5103                        hc as i32,
5104                        iters as i32,
5105                        hc_eps,
5106                        sp(&stream),
5107                    ),
5108                )?;
5109            }
5110        }
5111        unsafe {
5112            ck(
5113                "hc_collapse dev",
5114                k::memra_dsv4_hc_collapse(
5115                    dpf!(h, &stream),
5116                    dpf!(*pre, &stream),
5117                    dpm!(*y_hc, &stream),
5118                    1,
5119                    hc as i32,
5120                    hidden as i32,
5121                    sp(&stream),
5122                ),
5123            )?;
5124        }
5125        Ok(())
5126    }
5127
5128    /// Incremental compressor step on the arena (cmp_decode's arithmetic verbatim:
5129    /// same kernels, same D2D moves; rope via the scalar-position launcher — identical
5130    /// kernel body). No allocations.
5131    #[allow(clippy::too_many_arguments)]
5132    fn cmp_decode_dev(
5133        &self,
5134        st: &Stage,
5135        cmp: &CmpDev,
5136        x: &CudaSlice<f32>,
5137        pos: usize,
5138        hidden: usize,
5139        fc_dev: &CudaSlice<f32>,
5140        rd: usize,
5141        eps: f32,
5142        kv_row: &mut CudaSlice<f32>,
5143        sc_row: &mut CudaSlice<f32>,
5144        emit: &mut CudaSlice<f32>,
5145        shift: &mut CudaSlice<f32>,
5146        pend_kv: &mut CudaSlice<f32>,
5147        pend_score: &mut CudaSlice<f32>,
5148        store: &mut CudaSlice<f32>,
5149        row0: usize,
5150        blocks: &mut usize,
5151    ) -> Res<()> {
5152        let stream = st.gpu.stream();
5153        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
5154        self.dots_dev(st, x, &cmp.wkv, 1, hidden, latent, kv_row)?;
5155        self.dots_dev(st, x, &cmp.wgate, 1, hidden, latent, sc_row)?;
5156        let slot = if cmp.overlap {
5157            ratio + pos % ratio
5158        } else {
5159            pos % ratio
5160        };
5161        {
5162            let src = kv_row.slice(0..latent);
5163            let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
5164            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv"))?;
5165            let src = sc_row.slice(0..latent);
5166            let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
5167            stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc"))?;
5168        }
5169        if (pos + 1) % ratio != 0 {
5170            return Ok(());
5171        }
5172        let j = pos / ratio;
5173        let nb_launch = if cmp.overlap { 2usize } else { 1 };
5174        let row_off = if cmp.overlap { d } else { 0 };
5175        unsafe {
5176            ck(
5177                "compressor_pool dec",
5178                k::memra_dsv4_compressor_pool(
5179                    dpf!(*pend_kv, &stream),
5180                    dpf!(*pend_score, &stream),
5181                    dpf!(cmp.ape, &stream),
5182                    dpm!(*emit, &stream),
5183                    nb_launch as i32,
5184                    ratio as i32,
5185                    d as i32,
5186                    latent as i32,
5187                    cmp.overlap as i32,
5188                    sp(&stream),
5189                ),
5190            )?;
5191            let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
5192            let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
5193            ck(
5194                "rmsnorm dec cmp",
5195                self.rmsnorm_arm(
5196                    row_c,
5197                    dpf!(cmp.norm, &stream),
5198                    row_m,
5199                    1,
5200                    d as i32,
5201                    eps,
5202                    sp(&stream),
5203                ),
5204            )?;
5205            ck(
5206                "rope_at dec cmp",
5207                k::memra_dsv4_rope_at(
5208                    row_m,
5209                    1,
5210                    d as i32,
5211                    rd as i32,
5212                    dpf!(fc_dev, &stream),
5213                    (j * ratio) as i32,
5214                    0,
5215                    sp(&stream),
5216                ),
5217            )?;
5218            if cmp.rotate {
5219                let scale = (d as f32).powf(-0.5);
5220                ck(
5221                    "hadamard dec cmp",
5222                    k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
5223                )?;
5224                ck(
5225                    "fp4 dec cmp",
5226                    k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
5227                )?;
5228            } else {
5229                ck(
5230                    "act_quant dec cmp",
5231                    k::memra_dsv4_act_quant(
5232                        row_m,
5233                        1,
5234                        d as i64,
5235                        (d - rd) as i32,
5236                        64,
5237                        (self.variant == ActQuantVariant::ClampOnly) as i32,
5238                        sp(&stream),
5239                    ),
5240                )?;
5241            }
5242        }
5243        {
5244            let src = emit.slice(row_off..row_off + d);
5245            let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
5246            stream
5247                .memcpy_dtod(&src, &mut dst)
5248                .map_err(e("emit store"))?;
5249        }
5250        if cmp.overlap {
5251            {
5252                let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
5253                let mut dst = shift.slice_mut(0..ratio * latent);
5254                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift1"))?;
5255            }
5256            {
5257                let src = shift.slice(0..ratio * latent);
5258                let mut dst = pend_kv.slice_mut(0..ratio * latent);
5259                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift2"))?;
5260            }
5261            {
5262                let src = pend_score.slice(ratio * latent..2 * ratio * latent);
5263                let mut dst = shift.slice_mut(0..ratio * latent);
5264                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift3"))?;
5265            }
5266            {
5267                let src = shift.slice(0..ratio * latent);
5268                let mut dst = pend_score.slice_mut(0..ratio * latent);
5269                stream.memcpy_dtod(&src, &mut dst).map_err(e("shift4"))?;
5270            }
5271        }
5272        *blocks = j + 1;
5273        Ok(())
5274    }
5275
5276    /// One trunk block, single-token decode, device path (block_decode's flow on the
5277    /// arena; per-value arithmetic identical under host_math — deviations under device
5278    /// math are the banked Sinkhorn/router realization forks). Input h is ws.h_a
5279    /// (or ws.h_rx right after the boundary); output lands in ws.h_a.
5280    #[allow(clippy::too_many_arguments)]
5281    fn block_decode_dev(
5282        &self,
5283        st: &Stage,
5284        layer: &LayerDev,
5285        cache: &mut LayerCache,
5286        ws: &mut StepWs,
5287        input_rx: bool,
5288        pos: usize,
5289        tok: u32,
5290        host_math: bool,
5291    ) -> Res<()> {
5292        let d = self.model.cfg();
5293        let mc = &self.model.mc;
5294        let hc = d.hc_mult as usize;
5295        let hidden = mc.n_embd as usize;
5296        let heads = mc.n_head as usize;
5297        let hd = d.head_dim as usize;
5298        let rd = d.qk_rope_head_dim as usize;
5299        let q_lora = d.q_lora_rank as usize;
5300        let win = d.sliding_window as usize;
5301        let o_groups = d.o_groups as usize;
5302        let o_lora = d.o_lora_rank as usize;
5303        let eps = mc.rms_eps;
5304        let iters = d.hc_sinkhorn_iters;
5305        let hc_eps = d.hc_eps;
5306        let stream = st.gpu.stream();
5307        let fc_dev: *const f32 = if layer.ratio != 0 {
5308            st.fc_yarn.device_ptr(&stream).0 as *const f32
5309        } else {
5310            st.fc_plain.device_ptr(&stream).0 as *const f32
5311        };
5312        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
5313        let LayerCache {
5314            kvc,
5315            n_blocks,
5316            pend_kv,
5317            pend_score,
5318            ikvc,
5319            i_blocks,
5320            ipend_kv,
5321            ipend_score,
5322        } = cache;
5323
5324        // ---- attention sub-block
5325        {
5326            // split-borrow the arena fields we need for hc_pre
5327            let StepWs {
5328                h_a,
5329                h_rx,
5330                mixes,
5331                pre,
5332                post,
5333                comb,
5334                y_hc,
5335                ..
5336            } = ws;
5337            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5338            self.hc_pre_dev(
5339                st,
5340                h_in,
5341                &layer.hc_attn_fn,
5342                &layer.hc_attn_base,
5343                &layer.hc_attn_scale,
5344                &layer.hc_attn_base_dev,
5345                &layer.hc_attn_scale_dev,
5346                mixes,
5347                pre,
5348                post,
5349                comb,
5350                y_hc,
5351                hc,
5352                hidden,
5353                iters,
5354                hc_eps,
5355                host_math,
5356            )?;
5357        }
5358        unsafe {
5359            ck(
5360                "rmsnorm attn dev",
5361                self.rmsnorm_arm(
5362                    dpf!(ws.y_hc, &stream),
5363                    dpf!(layer.attn_norm, &stream),
5364                    dpm!(ws.x, &stream),
5365                    1,
5366                    hidden as i32,
5367                    eps,
5368                    sp(&stream),
5369                ),
5370            )?;
5371        }
5372
5373        // q path
5374        Self::gemm_dev(
5375            st,
5376            ws.x.device_ptr(&stream).0 as *const f32,
5377            &mut ws.gemm_xb,
5378            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
5379            1,
5380            q_lora,
5381            hidden,
5382            ws.qr.device_ptr_mut(&stream).0 as *mut f32,
5383        )?;
5384        unsafe {
5385            ck(
5386                "rmsnorm q dev",
5387                self.rmsnorm_arm(
5388                    dpf!(ws.qr, &stream),
5389                    dpf!(layer.q_norm, &stream),
5390                    dpm!(ws.qr, &stream),
5391                    1,
5392                    q_lora as i32,
5393                    eps,
5394                    sp(&stream),
5395                ),
5396            )?;
5397            ck(
5398                "cvt qr dev",
5399                k::memra_dsv4_cvt_bf16(
5400                    dpf!(ws.qr, &stream),
5401                    ws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
5402                    q_lora as i64,
5403                    sp(&stream),
5404                ),
5405            )?;
5406        }
5407        Self::gemv_pre_dev(
5408            st,
5409            ws.qr_b.device_ptr(&stream).0 as *const c_void,
5410            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
5411            heads * hd,
5412            q_lora,
5413            ws.q.device_ptr_mut(&stream).0 as *mut f32,
5414        )?;
5415        unsafe {
5416            ck(
5417                "headrms dev",
5418                self.headrms_arm(
5419                    dpm!(ws.q, &stream),
5420                    heads as i32,
5421                    hd as i32,
5422                    eps,
5423                    sp(&stream),
5424                ),
5425            )?;
5426            ck(
5427                "rope_at q dev",
5428                k::memra_dsv4_rope_at(
5429                    dpm!(ws.q, &stream),
5430                    heads as i32,
5431                    hd as i32,
5432                    rd as i32,
5433                    fc_dev,
5434                    pos as i32,
5435                    0,
5436                    sp(&stream),
5437                ),
5438            )?;
5439        }
5440
5441        // shared K==V latent row + window QAT + ring write
5442        Self::gemm_dev(
5443            st,
5444            ws.x.device_ptr(&stream).0 as *const f32,
5445            &mut ws.gemm_xb,
5446            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
5447            1,
5448            hd,
5449            hidden,
5450            ws.kv.device_ptr_mut(&stream).0 as *mut f32,
5451        )?;
5452        unsafe {
5453            ck(
5454                "rmsnorm kv dev",
5455                self.rmsnorm_arm(
5456                    dpf!(ws.kv, &stream),
5457                    dpf!(layer.kv_norm, &stream),
5458                    dpm!(ws.kv, &stream),
5459                    1,
5460                    hd as i32,
5461                    eps,
5462                    sp(&stream),
5463                ),
5464            )?;
5465            ck(
5466                "rope_at kv dev",
5467                k::memra_dsv4_rope_at(
5468                    dpm!(ws.kv, &stream),
5469                    1,
5470                    hd as i32,
5471                    rd as i32,
5472                    fc_dev,
5473                    pos as i32,
5474                    0,
5475                    sp(&stream),
5476                ),
5477            )?;
5478            ck(
5479                "act_quant kv dev",
5480                k::memra_dsv4_act_quant(
5481                    dpm!(ws.kv, &stream),
5482                    1,
5483                    hd as i64,
5484                    (hd - rd) as i32,
5485                    64,
5486                    clamp_only,
5487                    sp(&stream),
5488                ),
5489            )?;
5490        }
5491        {
5492            let slot = pos % win;
5493            let src = ws.kv.slice(0..hd);
5494            let mut dst = kvc.slice_mut(slot * hd..(slot + 1) * hd);
5495            stream
5496                .memcpy_dtod(&src, &mut dst)
5497                .map_err(e("ring write"))?;
5498        }
5499
5500        // index list: window part on device (block_decode's builder verbatim)
5501        let mut slots = win;
5502        if layer.ratio != 0 {
5503            if let Some(ix) = &layer.idx {
5504                // indexer q
5505                Self::gemv_pre_dev(
5506                    st,
5507                    ws.qr_b.device_ptr(&stream).0 as *const c_void,
5508                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
5509                    ix.heads * ix.hd,
5510                    q_lora,
5511                    ws.qi.device_ptr_mut(&stream).0 as *mut f32,
5512                )?;
5513                unsafe {
5514                    ck(
5515                        "rope_at qi dev",
5516                        k::memra_dsv4_rope_at(
5517                            dpm!(ws.qi, &stream),
5518                            ix.heads as i32,
5519                            ix.hd as i32,
5520                            rd as i32,
5521                            fc_dev,
5522                            pos as i32,
5523                            0,
5524                            sp(&stream),
5525                        ),
5526                    )?;
5527                    let scale = (ix.hd as f32).powf(-0.5);
5528                    ck(
5529                        "hadamard qi dev",
5530                        k::memra_dsv4_hadamard(
5531                            dpm!(ws.qi, &stream),
5532                            ix.heads as i32,
5533                            ix.hd as i32,
5534                            scale,
5535                            sp(&stream),
5536                        ),
5537                    )?;
5538                    ck(
5539                        "fp4 qi dev",
5540                        k::memra_dsv4_fp4_act_quant(
5541                            dpm!(ws.qi, &stream),
5542                            ix.heads as i32,
5543                            ix.hd as i64,
5544                            ix.hd as i32,
5545                            sp(&stream),
5546                        ),
5547                    )?;
5548                }
5549                // indexer compressor BEFORE scoring (M:415)
5550                {
5551                    let StepWs {
5552                        x,
5553                        cmp_kv_row,
5554                        cmp_sc_row,
5555                        cmp_emit,
5556                        cmp_shift,
5557                        ..
5558                    } = ws;
5559                    self.cmp_decode_dev(
5560                        st,
5561                        &ix.cmp,
5562                        x,
5563                        pos,
5564                        hidden,
5565                        if layer.ratio != 0 {
5566                            &st.fc_yarn
5567                        } else {
5568                            &st.fc_plain
5569                        },
5570                        rd,
5571                        eps,
5572                        cmp_kv_row,
5573                        cmp_sc_row,
5574                        cmp_emit,
5575                        cmp_shift,
5576                        ipend_kv.as_mut().expect("ipend"),
5577                        ipend_score.as_mut().expect("ipend"),
5578                        ikvc.as_mut().expect("ikvc"),
5579                        0,
5580                        i_blocks,
5581                    )?;
5582                }
5583                let nb = *i_blocks;
5584                debug_assert_eq!(nb, (pos + 1) / layer.ratio, "indexer block count");
5585                // window part (fills [0, win)); fine tail written by the top-k below
5586                unsafe {
5587                    ck(
5588                        "build_idx win",
5589                        k::memra_dsv4_build_idx(
5590                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5591                            pos as i32,
5592                            win as i32,
5593                            -1,
5594                            win as i32,
5595                            sp(&stream),
5596                        ),
5597                    )?;
5598                }
5599                if nb > 0 {
5600                    Self::gemm_dev(
5601                        st,
5602                        ws.x.device_ptr(&stream).0 as *const f32,
5603                        &mut ws.gemm_xb,
5604                        dwsel(
5605                            self.dense_fp8,
5606                            &stream,
5607                            &ix.weights_proj,
5608                            &ix.weights_proj_fp8,
5609                        ),
5610                        1,
5611                        ix.heads,
5612                        hidden,
5613                        ws.wproj.device_ptr_mut(&stream).0 as *mut f32,
5614                    )?;
5615                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
5616                    unsafe {
5617                        ck(
5618                            "indexer_score dev",
5619                            self.indexer_score_arm(
5620                                dpf!(ws.qi, &stream),
5621                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
5622                                dpf!(ws.wproj, &stream),
5623                                wscale,
5624                                dpm!(ws.score, &stream),
5625                                1,
5626                                ix.heads as i32,
5627                                ix.hd as i32,
5628                                nb as i32,
5629                                layer.ratio as i32,
5630                                nb as i32,
5631                                sp(&stream),
5632                            ),
5633                        )?;
5634                    }
5635                    let kk = ix.topk.min(nb);
5636                    if host_math {
5637                        // byte-identity arm: the legacy host sort verbatim, uploaded
5638                        // into the arena index tail
5639                        let score_h = {
5640                            let view = ws.score.slice(0..nb);
5641                            let mut v = vec![0f32; nb];
5642                            stream
5643                                .memcpy_dtoh(&view, &mut v[..])
5644                                .map_err(e("dtoh sc"))?;
5645                            stream.synchronize().map_err(e("sync sc"))?;
5646                            v
5647                        };
5648                        let mut order: Vec<usize> = (0..nb).collect();
5649                        order.sort_by(|&a, &b| {
5650                            score_h[b]
5651                                .partial_cmp(&score_h[a])
5652                                .unwrap_or(std::cmp::Ordering::Equal)
5653                                .then(a.cmp(&b))
5654                        });
5655                        let cidx: Vec<i32> = order
5656                            .into_iter()
5657                            .take(kk)
5658                            .map(|j| (j + win) as i32)
5659                            .collect();
5660                        let mut dst = ws.idx.slice_mut(win..win + kk);
5661                        stream.memcpy_htod(&cidx, &mut dst).map_err(e("htod idx"))?;
5662                    } else {
5663                        unsafe {
5664                            let idx_tail =
5665                                (ws.idx.device_ptr_mut(&stream).0 as usize + win * 4) as *mut i32;
5666                            ck(
5667                                "topk_idx dev",
5668                                k::memra_dsv4_topk_idx(
5669                                    dpf!(ws.score, &stream),
5670                                    nb as i32,
5671                                    kk as i32,
5672                                    win as i32,
5673                                    idx_tail,
5674                                    sp(&stream),
5675                                ),
5676                            )?;
5677                        }
5678                    }
5679                    slots = win + kk;
5680                }
5681            } else {
5682                // coarse: all blocks incl. the one emitted this step — but the ATTENTION
5683                // compressor below is what emits it, so the count is (pos+1)/ratio
5684                let nb = (pos + 1) / layer.ratio;
5685                unsafe {
5686                    ck(
5687                        "build_idx coarse",
5688                        k::memra_dsv4_build_idx(
5689                            ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5690                            pos as i32,
5691                            win as i32,
5692                            nb as i32,
5693                            (win + nb) as i32,
5694                            sp(&stream),
5695                        ),
5696                    )?;
5697                }
5698                slots = win + nb;
5699            }
5700            // attention compressor before sparse_attn (M:531)
5701            {
5702                let StepWs {
5703                    x,
5704                    cmp_kv_row,
5705                    cmp_sc_row,
5706                    cmp_emit,
5707                    cmp_shift,
5708                    ..
5709                } = ws;
5710                self.cmp_decode_dev(
5711                    st,
5712                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
5713                    x,
5714                    pos,
5715                    hidden,
5716                    &st.fc_yarn,
5717                    rd,
5718                    eps,
5719                    cmp_kv_row,
5720                    cmp_sc_row,
5721                    cmp_emit,
5722                    cmp_shift,
5723                    pend_kv.as_mut().expect("pend"),
5724                    pend_score.as_mut().expect("pend"),
5725                    kvc,
5726                    win,
5727                    n_blocks,
5728                )?;
5729            }
5730            debug_assert_eq!(*n_blocks, (pos + 1) / layer.ratio, "attn block count");
5731        } else {
5732            // window-only layer: fixed-width window part with -1 pads (legacy widths)
5733            unsafe {
5734                ck(
5735                    "build_idx window-only",
5736                    k::memra_dsv4_build_idx(
5737                        ws.idx.device_ptr_mut(&stream).0 as *mut i32,
5738                        pos as i32,
5739                        win as i32,
5740                        -1,
5741                        win as i32,
5742                        sp(&stream),
5743                    ),
5744                )?;
5745            }
5746        }
5747
5748        // sparse sink attention (lane-8 three-kernel split, bit-exact — see the .cu
5749        // notes) + query-position de-rotation
5750        let scale = (hd as f64).powf(-0.5) as f32;
5751        unsafe {
5752            ck(
5753                "sink_attn_dec dev",
5754                self.sink_attn_dec_arm(
5755                    dpf!(ws.q, &stream),
5756                    dpf!(kvc, &stream),
5757                    ws.idx.device_ptr(&stream).0 as *const i32,
5758                    dpf!(layer.sink, &stream),
5759                    dpm!(ws.sink_scores, &stream),
5760                    dpm!(ws.sink_evals, &stream),
5761                    ws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
5762                    dpm!(ws.o, &stream),
5763                    heads as i32,
5764                    hd as i32,
5765                    slots as i32,
5766                    scale,
5767                    sp(&stream),
5768                ),
5769            )?;
5770            ck(
5771                "rope_at o inv dev",
5772                k::memra_dsv4_rope_at(
5773                    dpm!(ws.o, &stream),
5774                    heads as i32,
5775                    hd as i32,
5776                    rd as i32,
5777                    fc_dev,
5778                    pos as i32,
5779                    1,
5780                    sp(&stream),
5781                ),
5782            )?;
5783        }
5784
5785        // grouped wo: cvt o ONCE (elementwise — bit-equal to the legacy per-group cvt),
5786        // then per-group offset GEMMs straight into og slices (take/place_cols are pure
5787        // offsets at s=1), then wo_b.
5788        let gw = heads / o_groups * hd;
5789        unsafe {
5790            ck(
5791                "cvt o dev",
5792                k::memra_dsv4_cvt_bf16(
5793                    dpf!(ws.o, &stream),
5794                    ws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
5795                    (heads * hd) as i64,
5796                    sp(&stream),
5797                ),
5798            )?;
5799        }
5800        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
5801        for g in 0..o_groups {
5802            Self::gemv_pre_dev(
5803                st,
5804                (ws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
5805                wo_a_dw.offset_rows(g * o_lora, gw),
5806                o_lora,
5807                gw,
5808                (ws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
5809            )?;
5810        }
5811        Self::gemm_dev(
5812            st,
5813            ws.og.device_ptr(&stream).0 as *const f32,
5814            &mut ws.gemm_xb,
5815            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
5816            1,
5817            hidden,
5818            o_groups * o_lora,
5819            ws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
5820        )?;
5821
5822        // hc_post (attention): h2 = ws.h_b from residual h_in
5823        {
5824            let StepWs {
5825                h_a,
5826                h_b,
5827                h_rx,
5828                attn_out,
5829                post,
5830                comb,
5831                ..
5832            } = ws;
5833            let h_in: &CudaSlice<f32> = if input_rx { h_rx } else { h_a };
5834            unsafe {
5835                ck(
5836                    "hc_post attn dev",
5837                    k::memra_dsv4_hc_post(
5838                        dpf!(attn_out, &stream),
5839                        dpf!(h_in, &stream),
5840                        dpf!(post, &stream),
5841                        dpf!(comb, &stream),
5842                        dpm!(*h_b, &stream),
5843                        1,
5844                        hc as i32,
5845                        hidden as i32,
5846                        sp(&stream),
5847                    ),
5848                )?;
5849            }
5850        }
5851
5852        // ---- ffn sub-block (input h2 = ws.h_b, output h3 = ws.h_a)
5853        {
5854            let StepWs {
5855                h_b,
5856                mixes,
5857                pre,
5858                post,
5859                comb,
5860                y_hc,
5861                ..
5862            } = ws;
5863            self.hc_pre_dev(
5864                st,
5865                h_b,
5866                &layer.hc_ffn_fn,
5867                &layer.hc_ffn_base,
5868                &layer.hc_ffn_scale,
5869                &layer.hc_ffn_base_dev,
5870                &layer.hc_ffn_scale_dev,
5871                mixes,
5872                pre,
5873                post,
5874                comb,
5875                y_hc,
5876                hc,
5877                hidden,
5878                iters,
5879                hc_eps,
5880                host_math,
5881            )?;
5882        }
5883        unsafe {
5884            ck(
5885                "rmsnorm ffn dev",
5886                self.rmsnorm_arm(
5887                    dpf!(ws.y_hc, &stream),
5888                    dpf!(layer.ffn_norm, &stream),
5889                    dpm!(ws.xf, &stream),
5890                    1,
5891                    hidden as i32,
5892                    eps,
5893                    sp(&stream),
5894                ),
5895            )?;
5896        }
5897        self.moe_forward_dev(st, layer, ws, tok, host_math)?;
5898        {
5899            let StepWs {
5900                h_a,
5901                h_b,
5902                y,
5903                post,
5904                comb,
5905                ..
5906            } = ws;
5907            unsafe {
5908                ck(
5909                    "hc_post ffn dev",
5910                    k::memra_dsv4_hc_post(
5911                        dpf!(y, &stream),
5912                        dpf!(h_b, &stream),
5913                        dpf!(post, &stream),
5914                        dpf!(comb, &stream),
5915                        dpm!(*h_a, &stream),
5916                        1,
5917                        hc as i32,
5918                        hidden as i32,
5919                        sp(&stream),
5920                    ),
5921                )?;
5922            }
5923        }
5924        Ok(())
5925    }
5926
5927    /// MoE on the device path (native fp4 arm only, asserted at load): routing via the
5928    /// device kernel (or route_host under host_math), then ONE launch per projection
5929    /// over all active-expert slots (indirect fused dispatch — attack #3 at s=1),
5930    /// combine in ascending-expert-id order (the legacy scatter sequence), shared
5931    /// expert on the lane-4 bf16 rung. Writes ws.y.
5932    fn moe_forward_dev(
5933        &self,
5934        st: &Stage,
5935        layer: &LayerDev,
5936        ws: &mut StepWs,
5937        tok: u32,
5938        host_math: bool,
5939    ) -> Res<()> {
5940        let mc = &self.model.mc;
5941        let d = self.model.cfg();
5942        let moe = mc.moe.as_ref().expect("moe");
5943        let hidden = mc.n_embd as usize;
5944        let ne = moe.expert_count as usize;
5945        let topk = moe.expert_used_count as usize;
5946        let inter = moe.expert_ff_length as usize;
5947        let limit = d.swiglu_limit;
5948        let stream = st.gpu.stream();
5949        let kind = match layer.expert_kind {
5950            ExpertKind::Nvfp4 => 0i32,
5951            ExpertKind::Mxfp4 => 1i32,
5952        };
5953        let wstride = (inter * hidden / 2) as i64;
5954        let sstride = match layer.expert_kind {
5955            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
5956            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
5957        };
5958
5959        self.dots_dev(st, &ws.xf, &layer.gate_w, 1, hidden, ne, &mut ws.raw)?;
5960        if host_math {
5961            let raw_h = dtoh_f32(&stream, &ws.raw)?;
5962            let (indices, weights) =
5963                Self::route_host(layer, &raw_h, &[tok], 1, ne, topk, d.routed_scaling_factor);
5964            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
5965            let mut order: Vec<i32> = (0..topk as i32).collect();
5966            order.sort_by_key(|&s| indices[s as usize]);
5967            stream
5968                .memcpy_htod(&sel, &mut ws.sel)
5969                .map_err(e("htod sel"))?;
5970            stream
5971                .memcpy_htod(&weights, &mut ws.selw)
5972                .map_err(e("htod selw"))?;
5973            stream
5974                .memcpy_htod(&order, &mut ws.order)
5975                .map_err(e("htod order"))?;
5976        } else {
5977            unsafe {
5978                ck(
5979                    "route dev",
5980                    k::memra_dsv4_route(
5981                        dpf!(ws.raw, &stream),
5982                        layer
5983                            .gate_bias_dev
5984                            .as_ref()
5985                            .map(|b| b.device_ptr(&stream).0 as *const f32)
5986                            .unwrap_or(std::ptr::null()),
5987                        layer
5988                            .tid2eid_dev
5989                            .as_ref()
5990                            .map(|t| t.device_ptr(&stream).0 as *const i32)
5991                            .unwrap_or(std::ptr::null()),
5992                        ws.tok.device_ptr(&stream).0 as *const i32,
5993                        ne as i32,
5994                        topk as i32,
5995                        d.routed_scaling_factor,
5996                        ws.sel.device_ptr_mut(&stream).0 as *mut i32,
5997                        ws.selw.device_ptr_mut(&stream).0 as *mut f32,
5998                        ws.order.device_ptr_mut(&stream).0 as *mut i32,
5999                        sp(&stream),
6000                    ),
6001                )?;
6002            }
6003        }
6004
6005        unsafe {
6006            ck(
6007                "act_quant_fp8 x dev",
6008                k::memra_dsv4_act_quant_fp8(
6009                    dpf!(ws.xf, &stream),
6010                    ws.xq.device_ptr_mut(&stream).0 as *mut c_void,
6011                    dpm!(ws.xs, &stream),
6012                    1,
6013                    hidden as i32,
6014                    sp(&stream),
6015                ),
6016            )?;
6017            for (proj, dst) in [(0i32, &mut ws.g1), (2i32, &mut ws.g3)] {
6018                ck(
6019                    "fp4_gemm_sel w1/w3",
6020                    k::memra_dsv4_fp4_gemm_sel(
6021                        dp!(ws.xq, &stream),
6022                        dpf!(ws.xs, &stream),
6023                        dp!(layer.experts_w, &stream),
6024                        dp!(layer.experts_sc, &stream),
6025                        dpf!(layer.experts_s2_dev, &stream),
6026                        ws.sel.device_ptr(&stream).0 as *const i32,
6027                        proj,
6028                        0,
6029                        kind,
6030                        dpm!(*dst, &stream),
6031                        topk as i32,
6032                        inter as i32,
6033                        hidden as i32,
6034                        wstride,
6035                        sstride,
6036                        sp(&stream),
6037                    ),
6038                )?;
6039            }
6040            ck(
6041                "swiglu dev",
6042                k::memra_dsv4_swiglu(
6043                    dpf!(ws.g1, &stream),
6044                    dpf!(ws.g3, &stream),
6045                    dpm!(ws.hbuf, &stream),
6046                    topk as i32,
6047                    inter as i32,
6048                    limit,
6049                    ws.selw.device_ptr(&stream).0 as *const f32,
6050                    sp(&stream),
6051                ),
6052            )?;
6053            ck(
6054                "act_quant_fp8 h dev",
6055                k::memra_dsv4_act_quant_fp8(
6056                    dpf!(ws.hbuf, &stream),
6057                    ws.hq.device_ptr_mut(&stream).0 as *mut c_void,
6058                    dpm!(ws.hs, &stream),
6059                    topk as i32,
6060                    inter as i32,
6061                    sp(&stream),
6062                ),
6063            )?;
6064            ck(
6065                "fp4_gemm_sel w2",
6066                k::memra_dsv4_fp4_gemm_sel(
6067                    dp!(ws.hq, &stream),
6068                    dpf!(ws.hs, &stream),
6069                    dp!(layer.experts_w, &stream),
6070                    dp!(layer.experts_sc, &stream),
6071                    dpf!(layer.experts_s2_dev, &stream),
6072                    ws.sel.device_ptr(&stream).0 as *const i32,
6073                    1,
6074                    1,
6075                    kind,
6076                    dpm!(ws.contrib, &stream),
6077                    topk as i32,
6078                    hidden as i32,
6079                    inter as i32,
6080                    wstride,
6081                    sstride,
6082                    sp(&stream),
6083                ),
6084            )?;
6085            ck(
6086                "combine dev",
6087                k::memra_dsv4_combine_rows(
6088                    dpf!(ws.contrib, &stream),
6089                    ws.order.device_ptr(&stream).0 as *const i32,
6090                    topk as i32,
6091                    dpm!(ws.y, &stream),
6092                    hidden as i64,
6093                    sp(&stream),
6094                ),
6095            )?;
6096            // shared expert (lane-4 bf16 rung — the lane-7 FP8-linear decision)
6097            ck(
6098                "cvt xb dev",
6099                k::memra_dsv4_cvt_bf16(
6100                    dpf!(ws.xf, &stream),
6101                    ws.xb.device_ptr_mut(&stream).0 as *mut c_void,
6102                    hidden as i64,
6103                    sp(&stream),
6104                ),
6105            )?;
6106        }
6107        let sh_inter = ws.sg1.len();
6108        Self::gemv_pre_dev(
6109            st,
6110            ws.xb.device_ptr(&stream).0 as *const c_void,
6111            dwsel(
6112                self.dense_fp8,
6113                &stream,
6114                &layer.shared_w[0],
6115                &layer.shared_fp8[0],
6116            ),
6117            sh_inter,
6118            hidden,
6119            ws.sg1.device_ptr_mut(&stream).0 as *mut f32,
6120        )?;
6121        Self::gemv_pre_dev(
6122            st,
6123            ws.xb.device_ptr(&stream).0 as *const c_void,
6124            dwsel(
6125                self.dense_fp8,
6126                &stream,
6127                &layer.shared_w[2],
6128                &layer.shared_fp8[2],
6129            ),
6130            sh_inter,
6131            hidden,
6132            ws.sg3.device_ptr_mut(&stream).0 as *mut f32,
6133        )?;
6134        unsafe {
6135            ck(
6136                "swiglu sh dev",
6137                k::memra_dsv4_swiglu(
6138                    dpf!(ws.sg1, &stream),
6139                    dpf!(ws.sg3, &stream),
6140                    dpm!(ws.shbuf, &stream),
6141                    1,
6142                    sh_inter as i32,
6143                    limit,
6144                    std::ptr::null(),
6145                    sp(&stream),
6146                ),
6147            )?;
6148            ck(
6149                "cvt sh dev",
6150                k::memra_dsv4_cvt_bf16(
6151                    dpf!(ws.shbuf, &stream),
6152                    ws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
6153                    sh_inter as i64,
6154                    sp(&stream),
6155                ),
6156            )?;
6157        }
6158        Self::gemv_pre_dev(
6159            st,
6160            ws.shb16.device_ptr(&stream).0 as *const c_void,
6161            dwsel(
6162                self.dense_fp8,
6163                &stream,
6164                &layer.shared_w[1],
6165                &layer.shared_fp8[1],
6166            ),
6167            hidden,
6168            sh_inter,
6169            ws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
6170        )?;
6171        unsafe {
6172            ck(
6173                "add shared dev",
6174                k::memra_dsv4_add_inplace(
6175                    dpm!(ws.y, &stream),
6176                    dpf!(ws.sh_out, &stream),
6177                    hidden as i64,
6178                    sp(&stream),
6179                ),
6180            )?;
6181        }
6182        Ok(())
6183    }
6184
6185    /// Head on the device path: hc_head gate + collapse + trunk norm + vocab dots into
6186    /// ws.logits (dtoh'd by the caller when wanted). head_logits_row's arithmetic with
6187    /// the host sigmoid either kept (host_math) or run as the tiny gate kernel.
6188    fn head_logits_dev(&self, ws: &mut StepWs, host_math: bool) -> Res<()> {
6189        let d = self.model.cfg();
6190        let mc = &self.model.mc;
6191        let hc = d.hc_mult as usize;
6192        let hidden = mc.n_embd as usize;
6193        let eps = mc.rms_eps;
6194        let last = self.stages.len() - 1;
6195        let st = &self.stages[last];
6196        let stream = st.gpu.stream();
6197        let w = hc * hidden;
6198        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
6199        let norm = st.trunk_norm.as_ref().expect("trunk norm");
6200        self.dots_dev(st, &ws.h_a, fn_w, 1, w, hc, &mut ws.head_mixes)?;
6201        unsafe {
6202            ck(
6203                "rowsq head dev",
6204                self.rowsq_scale_arm(
6205                    dpf!(ws.h_a, &stream),
6206                    dpm!(ws.head_mixes, &stream),
6207                    1,
6208                    w as i32,
6209                    hc as i32,
6210                    eps,
6211                    sp(&stream),
6212                ),
6213            )?;
6214        }
6215        if host_math {
6216            let mut mixes_h = dtoh_f32(&stream, &ws.head_mixes)?;
6217            for c in 0..hc {
6218                let m = mixes_h[c];
6219                mixes_h[c] =
6220                    sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
6221            }
6222            stream
6223                .memcpy_htod(&mixes_h, &mut ws.head_pre)
6224                .map_err(e("htod head pre"))?;
6225        } else {
6226            unsafe {
6227                ck(
6228                    "hc_head_pre dev",
6229                    k::memra_dsv4_hc_head_pre(
6230                        dpf!(ws.head_mixes, &stream),
6231                        st.hc_head_scale_dev
6232                            .as_ref()
6233                            .expect("head scale dev")
6234                            .device_ptr(&stream)
6235                            .0 as *const f32,
6236                        st.hc_head_base_dev
6237                            .as_ref()
6238                            .expect("head base dev")
6239                            .device_ptr(&stream)
6240                            .0 as *const f32,
6241                        dpm!(ws.head_pre, &stream),
6242                        hc as i32,
6243                        d.hc_eps,
6244                        sp(&stream),
6245                    ),
6246                )?;
6247            }
6248        }
6249        unsafe {
6250            ck(
6251                "hc_collapse head dev",
6252                k::memra_dsv4_hc_collapse(
6253                    dpf!(ws.h_a, &stream),
6254                    dpf!(ws.head_pre, &stream),
6255                    dpm!(ws.collapsed, &stream),
6256                    1,
6257                    hc as i32,
6258                    hidden as i32,
6259                    sp(&stream),
6260                ),
6261            )?;
6262            ck(
6263                "rmsnorm head dev",
6264                self.rmsnorm_arm(
6265                    dpf!(ws.collapsed, &stream),
6266                    dpf!(norm, &stream),
6267                    dpm!(ws.collapsed, &stream),
6268                    1,
6269                    hidden as i32,
6270                    eps,
6271                    sp(&stream),
6272                ),
6273            )?;
6274            let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
6275            if self.dots_f32 {
6276                ck(
6277                    "head dots f32acc dev",
6278                    k::memra_dsv4_dots_f32acc(
6279                        dpf!(ws.collapsed, &stream),
6280                        head_ptr,
6281                        1,
6282                        dpm!(ws.logits, &stream),
6283                        1,
6284                        hidden as i32,
6285                        ws.logits.len() as i32,
6286                        sp(&stream),
6287                    ),
6288                )?;
6289            } else {
6290                ck(
6291                    "head dots dev",
6292                    k::memra_dsv4_dots_f32(
6293                        dpf!(ws.collapsed, &stream),
6294                        head_ptr,
6295                        1,
6296                        dpm!(ws.logits, &stream),
6297                        1,
6298                        hidden as i32,
6299                        ws.logits.len() as i32,
6300                        sp(&stream),
6301                    ),
6302                )?;
6303            }
6304        }
6305        Ok(())
6306    }
6307
6308    /// One device-path decode step. `want_logits` = dtoh the full row (the gates'
6309    /// contract); otherwise the greedy token comes back through the device argmax
6310    /// (4-byte D2H). Exactly one boundary peer copy per crossed stage boundary.
6311    fn decode_step_fast(
6312        &self,
6313        tok: u32,
6314        state: &mut DecodeState,
6315        want_logits: bool,
6316        host_math: bool,
6317    ) -> Res<(Option<Vec<f32>>, u32)> {
6318        self.decode_step_fast_tap(tok, state, want_logits, host_math, None)
6319    }
6320
6321    /// [`Self::decode_step_fast`] with the iteration-3 DSpark trunk tap: when `taps`
6322    /// is Some((buffer, base)), the hc-mean of the post-block hc state at each drafter
6323    /// target layer (40/41/42) is written at buffer[base + k*hidden ..] (concat in
6324    /// target order, M:917-925) — a pure capture; no kernel computes anything
6325    /// differently.
6326    fn decode_step_fast_tap(
6327        &self,
6328        tok: u32,
6329        state: &mut DecodeState,
6330        want_logits: bool,
6331        host_math: bool,
6332        mut taps: Option<(&mut CudaSlice<f32>, usize)>,
6333    ) -> Res<(Option<Vec<f32>>, u32)> {
6334        let mc = &self.model.mc;
6335        let d = self.model.cfg();
6336        let pos = state.pos;
6337        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6338        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6339        let hidden = mc.n_embd as usize;
6340        let hc = d.hc_mult as usize;
6341        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6342        let ws_all = state.ws.as_mut().expect("device path needs StepWs");
6343
6344        // stage 0: token -> embed -> hc state
6345        let st0 = &self.stages[0];
6346        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6347        let stream0 = st0.gpu.stream();
6348        {
6349            let ws0 = &mut ws_all[0];
6350            stream0
6351                .memcpy_htod(&[tok as i32], &mut ws0.tok)
6352                .map_err(e("htod tok"))?;
6353            unsafe {
6354                ck(
6355                    "embed_rows dev",
6356                    k::memra_dsv4_embed_rows(
6357                        st0.embed
6358                            .as_ref()
6359                            .expect("embed on stage 0")
6360                            .device_ptr(&stream0)
6361                            .0 as *const c_void,
6362                        ws0.tok.device_ptr(&stream0).0 as *const i32,
6363                        dpm!(ws0.emb, &stream0),
6364                        1,
6365                        hidden as i32,
6366                        sp(&stream0),
6367                    ),
6368                )?;
6369                ck(
6370                    "repeat_hc dev",
6371                    k::memra_dsv4_repeat_hc(
6372                        dpf!(ws0.emb, &stream0),
6373                        dpm!(ws0.h_a, &stream0),
6374                        1,
6375                        hc as i32,
6376                        hidden as i32,
6377                        sp(&stream0),
6378                    ),
6379                )?;
6380            }
6381        }
6382
6383        let mut cur_stage = 0usize;
6384        let mut input_rx = false;
6385        for il in 0..n_trunk {
6386            let stage = self.layer_stage[il as usize];
6387            if stage != cur_stage {
6388                // boundary: peer-copy h (TX stream) + event; tok for the hash layers
6389                // never crosses (they live on stage 0)
6390                let bytes = hc * hidden * std::mem::size_of::<f32>();
6391                let src_stream = self.stages[cur_stage].gpu.stream();
6392                let dst_stream = self.stages[stage].gpu.stream();
6393                let (ws_src, ws_dst) = ws_all.split_at_mut(stage);
6394                let src_ws = &ws_src[cur_stage];
6395                let dst_ws = &mut ws_dst[0];
6396                self.stages[cur_stage]
6397                    .gpu
6398                    .ctx
6399                    .bind_to_thread()
6400                    .map_err(e("bind tx"))?;
6401                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
6402                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
6403                unsafe {
6404                    cudarc::driver::result::memcpy_peer_async(
6405                        self.stages[stage].gpu.ctx.cu_ctx(),
6406                        dp_,
6407                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
6408                        sp_,
6409                        bytes,
6410                        src_stream.cu_stream(),
6411                    )
6412                    .map_err(e("peer copy h"))?;
6413                }
6414                let bnd = stage - 1;
6415                self.boundary_ev[bnd]
6416                    .record(&src_stream)
6417                    .map_err(e("ev record"))?;
6418                dst_stream
6419                    .wait(&self.boundary_ev[bnd])
6420                    .map_err(e("ev wait"))?;
6421                self.stages[stage]
6422                    .gpu
6423                    .ctx
6424                    .bind_to_thread()
6425                    .map_err(e("bind rx"))?;
6426                cur_stage = stage;
6427                input_rx = true;
6428            }
6429            let st = &self.stages[stage];
6430            let lidx = st
6431                .layers
6432                .iter()
6433                .position(|l| l.il == il)
6434                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6435            self.block_decode_dev(
6436                st,
6437                &st.layers[lidx],
6438                &mut state.caches[il as usize],
6439                &mut ws_all[stage],
6440                input_rx,
6441                pos,
6442                tok,
6443                host_math,
6444            )?;
6445            input_rx = false;
6446            // iteration-3 DSpark tap (capture-only): hc-mean of this layer's output
6447            // hc state into the tap row at the target's concat offset.
6448            if let Some((t, base)) = taps.as_mut() {
6449                if let Some(ds) = &self.dspark {
6450                    if let Some(k) = ds.targets.iter().position(|&tl| tl == il as usize) {
6451                        let stream = self.stages[stage].gpu.stream();
6452                        let hidden_i = hidden as i32;
6453                        unsafe {
6454                            ck(
6455                                "hc_mean tap dev",
6456                                k::memra_dsv4_hc_mean(
6457                                    dpf!(ws_all[stage].h_a, &stream),
6458                                    (t.device_ptr_mut(&stream).0 as usize
6459                                        + (*base + k * hidden) * 4)
6460                                        as *mut f32,
6461                                    1,
6462                                    hc as i32,
6463                                    hidden_i,
6464                                    sp(&stream),
6465                                ),
6466                            )?;
6467                        }
6468                    }
6469                }
6470            }
6471        }
6472
6473        let last = self.stages.len() - 1;
6474        assert_eq!(cur_stage, last, "device path expects the head stage last");
6475        self.head_logits_dev(&mut ws_all[last], host_math)?;
6476        let stream_last = self.stages[last].gpu.stream();
6477        state.pos += 1;
6478        if want_logits {
6479            let logits = dtoh_f32(&stream_last, &ws_all[last].logits)?;
6480            let mut best = 0usize;
6481            for i in 1..logits.len() {
6482                if logits[i] > logits[best] {
6483                    best = i;
6484                }
6485            }
6486            Ok((Some(logits), best as u32))
6487        } else {
6488            unsafe {
6489                ck(
6490                    "argmax dev",
6491                    k::memra_dsv4_argmax(
6492                        dpf!(ws_all[last].logits, &stream_last),
6493                        ws_all[last].logits.len() as i64,
6494                        ws_all[last].argmax.device_ptr_mut(&stream_last).0 as *mut i32,
6495                        sp(&stream_last),
6496                    ),
6497                )?;
6498            }
6499            let mut out = [0i32; 1];
6500            stream_last
6501                .memcpy_dtoh(&ws_all[last].argmax, &mut out[..])
6502                .map_err(e("dtoh argmax"))?;
6503            stream_last.synchronize().map_err(e("sync argmax"))?;
6504            Ok((None, out[0] as u32))
6505        }
6506    }
6507
6508    /// Greedy decode step (bench serving shape): returns ONLY the next token; on the
6509    /// device path the argmax runs on-device and 4 bytes cross back. Legacy path
6510    /// falls back to the full-logits step + host argmax (same value by the argmax
6511    /// tie-rule equivalence).
6512    pub fn decode_step_greedy(&self, tok: u32, state: &mut DecodeState) -> Res<u32> {
6513        match self.decode_path {
6514            DecodePath::Legacy => {
6515                let logits = self.decode_step_impl(tok, state, None)?;
6516                let mut best = 0usize;
6517                for i in 1..logits.len() {
6518                    if logits[i] > logits[best] {
6519                        best = i;
6520                    }
6521                }
6522                Ok(best as u32)
6523            }
6524            DecodePath::Device { host_math } => {
6525                Ok(self.decode_step_fast(tok, state, false, host_math)?.1)
6526            }
6527        }
6528    }
6529
6530    fn decode_step_impl(
6531        &self,
6532        tok: u32,
6533        state: &mut DecodeState,
6534        mut dump: Option<&mut Vec<(String, Vec<f32>)>>,
6535    ) -> Res<Vec<f32>> {
6536        if let DecodePath::Device { host_math } = self.decode_path {
6537            assert!(
6538                dump.is_none(),
6539                "decode_step_probe is a legacy-path diagnostic (set MEMRA_DSV4_DECODE_PATH=legacy)"
6540            );
6541            let (logits, _) = self.decode_step_fast(tok, state, true, host_math)?;
6542            return Ok(logits.expect("want_logits"));
6543        }
6544        let mc = &self.model.mc;
6545        let d = self.model.cfg();
6546        let pos = state.pos;
6547        assert!(pos > 0, "decode_step needs prefill_with_cache first");
6548        assert!(pos < self.max_seq, "pos {pos} >= max_seq {}", self.max_seq);
6549        let hidden = mc.n_embd as usize;
6550        let hc = d.hc_mult as usize;
6551        let n_trunk = mc.n_layer - mc.nextn_predict_layers;
6552
6553        // stage 0: embed row -> hc state
6554        let st0 = &self.stages[0];
6555        st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0"))?;
6556        let stream0 = st0.gpu.stream();
6557        let ids_dev = upload_i32(&stream0, &[tok as i32])?;
6558        let mut emb = stream0.alloc_zeros::<f32>(hidden).map_err(e("emb"))?;
6559        unsafe {
6560            ck(
6561                "embed_rows",
6562                k::memra_dsv4_embed_rows(
6563                    st0.embed
6564                        .as_ref()
6565                        .expect("embed on stage 0")
6566                        .device_ptr(&stream0)
6567                        .0 as *const c_void,
6568                    ids_dev.device_ptr(&stream0).0 as *const i32,
6569                    dpm!(emb, &stream0),
6570                    1,
6571                    hidden as i32,
6572                    sp(&stream0),
6573                ),
6574            )?;
6575        }
6576        let mut h = stream0.alloc_zeros::<f32>(hc * hidden).map_err(e("h0"))?;
6577        unsafe {
6578            ck(
6579                "repeat_hc",
6580                k::memra_dsv4_repeat_hc(
6581                    dpf!(emb, &stream0),
6582                    dpm!(h, &stream0),
6583                    1,
6584                    hc as i32,
6585                    hidden as i32,
6586                    sp(&stream0),
6587                ),
6588            )?;
6589        }
6590
6591        let mut cur_stage = 0usize;
6592        for il in 0..n_trunk {
6593            let stage = self.layer_stage[il as usize];
6594            if stage != cur_stage {
6595                let src_stream = self.stages[cur_stage].gpu.stream();
6596                let host = dtoh_f32(&src_stream, &h)?;
6597                let dst_stream = self.stages[stage].gpu.stream();
6598                self.stages[stage]
6599                    .gpu
6600                    .ctx
6601                    .bind_to_thread()
6602                    .map_err(e("bind"))?;
6603                h = upload_f32(&dst_stream, &host)?;
6604                cur_stage = stage;
6605            }
6606            let st = &self.stages[stage];
6607            let lidx = st
6608                .layers
6609                .iter()
6610                .position(|l| l.il == il)
6611                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
6612            h = self.block_decode(
6613                st,
6614                &st.layers[lidx],
6615                &mut state.caches[il as usize],
6616                &h,
6617                pos,
6618                tok,
6619                dump.as_deref_mut(),
6620            )?;
6621        }
6622
6623        let last = self.stages.len() - 1;
6624        if cur_stage != last {
6625            let src_stream = self.stages[cur_stage].gpu.stream();
6626            let host = dtoh_f32(&src_stream, &h)?;
6627            let dst_stream = self.stages[last].gpu.stream();
6628            h = upload_f32(&dst_stream, &host)?;
6629        }
6630        let hc_head_fn = self.stages[last].hc_head_fn.as_ref().expect("hc_head_fn");
6631        let trunk_norm = self.stages[last].trunk_norm.as_ref().expect("trunk norm");
6632        let logits = self.head_logits_from(
6633            &h,
6634            1,
6635            hc_head_fn,
6636            &self.hc_head_base,
6637            &self.hc_head_scale,
6638            trunk_norm,
6639        )?;
6640        state.pos += 1;
6641        Ok(logits)
6642    }
6643}
6644
6645// ================================================================ iteration 3: DSpark drafter (device)
6646//
6647// Semantic law: DSPARK-SEMANTICS.md (M-cites); numeric truth: the lane-10 CPU oracle
6648// (memra_gguf::dsv4_dspark) — every gate compares against its fixtures/trajectory.
6649// Realization: the PREFILL-class helpers (Self::hc_pre host-Sinkhorn, cuBLASLt bf16
6650// gemm, moe_forward bf16-dequant experts, prefill sink_attn) at s = block_size —
6651// the lane-4-gated numeric class; the drafter's arena/native-expert perf rungs are
6652// banked follow-ups, never correctness requirements.
6653impl Dsv4Gpu {
6654    fn dspark(&self) -> &DsparkDev {
6655        self.dspark
6656            .as_ref()
6657            .expect("MEMRA_DSV4_DRAFTER=dspark not loaded")
6658    }
6659
6660    /// The drafter's exit-head island dots, HOISTED across the block's rows (weight row
6661    /// read once instead of once per row) with the rung-4c arm selection. The f64 branch
6662    /// is BIT-EXACT vs the pinned `Self::dots` — identical per-(t, j) element order and
6663    /// reduction tree — so the default arm's bytes are unchanged by the hoist; the f32x
6664    /// branch is the measured fork (`MEMRA_DSV4_DSPARK_HEAD_ARM=f32x`) offered for owner
6665    /// ratification. Either way this touches only WHICH tokens are drafted: verification
6666    /// always emits the trunk's own argmax, so the emitted stream cannot depend on it.
6667    #[allow(clippy::too_many_arguments)]
6668    fn dspark_head_dots(
6669        &self,
6670        st: &Stage,
6671        x: *const f32,
6672        w: *const c_void,
6673        w_is_bf16: i32,
6674        s: usize,
6675        kdim: usize,
6676        n: usize,
6677        y: *mut f32,
6678    ) -> Res<()> {
6679        let stream = st.gpu.stream();
6680        unsafe {
6681            if self.dspark_head_f32 {
6682                ck(
6683                    "dspark head dots f32acc_mrow",
6684                    k::memra_dsv4_dots_f32acc_mrow(
6685                        x,
6686                        w,
6687                        w_is_bf16,
6688                        y,
6689                        s as i32,
6690                        kdim as i32,
6691                        n as i32,
6692                        sp(&stream),
6693                    ),
6694                )
6695            } else {
6696                ck(
6697                    "dspark head dots f32_mrow",
6698                    k::memra_dsv4_dots_f32_mrow(
6699                        x,
6700                        w,
6701                        w_is_bf16,
6702                        y,
6703                        s as i32,
6704                        kdim as i32,
6705                        n as i32,
6706                        sp(&stream),
6707                    ),
6708                )
6709            }
6710        }
6711    }
6712
6713    /// Allocate the drafter decode state on the last stage: 3 rings [win + block, hd]
6714    /// (ring + transient draft rows, struct doc) + the tap rows [block+1, n_t*hidden].
6715    pub fn dspark_alloc_state(&self) -> Res<DsparkState> {
6716        let ds = self.dspark();
6717        let d = self.model.cfg();
6718        let hd = d.head_dim as usize;
6719        let win = d.sliding_window as usize;
6720        let hidden = self.model.mc.n_embd as usize;
6721        let last = self.stages.len() - 1;
6722        let stream = self.stages[last].gpu.stream();
6723        let mut rings = Vec::with_capacity(ds.blocks.len());
6724        for _ in 0..ds.blocks.len() {
6725            rings.push(
6726                stream
6727                    .alloc_zeros::<f32>((win + ds.block_size) * hd)
6728                    .map_err(e("dspark ring"))?,
6729            );
6730        }
6731        let taps = stream
6732            .alloc_zeros::<f32>((ds.block_size + 1) * ds.targets.len() * hidden)
6733            .map_err(e("dspark taps"))?;
6734        Ok(DsparkState { rings, taps })
6735    }
6736
6737    /// main_x = main_norm(main_proj(main_hidden)) (M:853), s rows on the last stage.
6738    fn dspark_main_x(&self, main_hidden: &CudaSlice<f32>, s: usize) -> Res<CudaSlice<f32>> {
6739        let ds = self.dspark();
6740        let hidden = self.model.mc.n_embd as usize;
6741        let k = ds.targets.len() * hidden;
6742        let last = self.stages.len() - 1;
6743        let st = &self.stages[last];
6744        let stream = st.gpu.stream();
6745        let mut mx = stream.alloc_zeros::<f32>(s * hidden).map_err(e("main_x"))?;
6746        Self::gemm(st, main_hidden, &ds.main_proj, 0, s, hidden, k, &mut mx)?;
6747        unsafe {
6748            ck(
6749                "rmsnorm main_x",
6750                k::memra_dsv4_rmsnorm(
6751                    dpf!(mx, &stream),
6752                    dpf!(ds.main_norm, &stream),
6753                    dpm!(mx, &stream),
6754                    s as i32,
6755                    hidden as i32,
6756                    self.model.mc.rms_eps,
6757                    sp(&stream),
6758                ),
6759            )?;
6760        }
6761        Ok(mx)
6762    }
6763
6764    /// Per-block main_kv rows (M:758-761): kv_norm(wkv(main_x)) + rope(REAL positions)
6765    /// + group-64 FP8 QAT on the nope dims. Returns [s, hd] on the last stage.
6766    fn dspark_main_kv(
6767        &self,
6768        blk: &LayerDev,
6769        main_x: &CudaSlice<f32>,
6770        s: usize,
6771        positions: &[i32],
6772    ) -> Res<CudaSlice<f32>> {
6773        let d = self.model.cfg();
6774        let hd = d.head_dim as usize;
6775        let rd = d.qk_rope_head_dim as usize;
6776        let hidden = self.model.mc.n_embd as usize;
6777        let eps = self.model.mc.rms_eps;
6778        let last = self.stages.len() - 1;
6779        let st = &self.stages[last];
6780        let stream = st.gpu.stream();
6781        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
6782        // item 3: `.dev()` — the drafter blocks carry no fp8 twins this rung, so
6783        // their bf16 slabs are always device-resident; if a future rung stages them,
6784        // this must fail loudly rather than pay a per-round upload silently.
6785        let mut kv = stream.alloc_zeros::<f32>(s * hd).map_err(e("dspark kv"))?;
6786        Self::gemm(st, main_x, blk.wkv.dev(), 0, s, hd, hidden, &mut kv)?;
6787        let pos_dev = upload_i32(&stream, positions)?;
6788        unsafe {
6789            ck(
6790                "rmsnorm dspark kv",
6791                k::memra_dsv4_rmsnorm(
6792                    dpf!(kv, &stream),
6793                    dpf!(blk.kv_norm, &stream),
6794                    dpm!(kv, &stream),
6795                    s as i32,
6796                    hd as i32,
6797                    eps,
6798                    sp(&stream),
6799                ),
6800            )?;
6801            ck(
6802                "rope dspark kv",
6803                k::memra_dsv4_rope(
6804                    dpm!(kv, &stream),
6805                    s as i32,
6806                    1,
6807                    hd as i32,
6808                    rd as i32,
6809                    dpf!(st.fc_plain, &stream),
6810                    pos_dev.device_ptr(&stream).0 as *const i32,
6811                    0,
6812                    sp(&stream),
6813                ),
6814            )?;
6815            ck(
6816                "act_quant dspark kv",
6817                k::memra_dsv4_act_quant(
6818                    dpm!(kv, &stream),
6819                    s as i32,
6820                    hd as i64,
6821                    (hd - rd) as i32,
6822                    64,
6823                    clamp_only,
6824                    sp(&stream),
6825                ),
6826            )?;
6827        }
6828        Ok(kv)
6829    }
6830
6831    /// Prefill ring priming (M:763-769): last min(s, win) positions land at slot
6832    /// p % win. `main_hidden` = [s, n_t*hidden] tap rows from the prefill.
6833    pub fn dspark_prime_prefill(
6834        &self,
6835        state: &mut DsparkState,
6836        main_hidden: &CudaSlice<f32>,
6837        s: usize,
6838    ) -> Res<()> {
6839        let d = self.model.cfg();
6840        let hd = d.head_dim as usize;
6841        let win = d.sliding_window as usize;
6842        let last = self.stages.len() - 1;
6843        let stream = self.stages[last].gpu.stream();
6844        let mx = self.dspark_main_x(main_hidden, s)?;
6845        let positions: Vec<i32> = (0..s as i32).collect();
6846        let n_blocks = self.dspark().blocks.len();
6847        for bi in 0..n_blocks {
6848            let blk = &self.dspark().blocks[bi];
6849            let kv = self.dspark_main_kv(blk, &mx, s, &positions)?;
6850            for p in s.saturating_sub(win)..s {
6851                let slot = p % win;
6852                let src = kv.slice(p * hd..(p + 1) * hd);
6853                let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
6854                stream
6855                    .memcpy_dtod(&src, &mut dst)
6856                    .map_err(e("prime ring"))?;
6857            }
6858        }
6859        Ok(())
6860    }
6861
6862    /// Trunk prefill + DSpark ring prime in ONE pass — the device twin of the CPU
6863    /// oracle's `trunk.forward(&seq[..p0], 0)` + `dspark.prime_prefill(&pre.main_hidden,
6864    /// p0)` pair (dsv4_dspark_gate components mode).
6865    ///
6866    /// The prefill taps come from the existing `GpuCapture::layer_out` hook (the target
6867    /// layers' full hc state `[s, hc, hidden]`), then run through the SAME
6868    /// `memra_dsv4_hc_mean` kernel the decode tap uses — prefill and decode taps must
6869    /// not be two numeric realizations of one tap — and are placed at the target's
6870    /// concat stride with `place_cols`, reproducing the oracle's
6871    /// `main_hidden[(p*n_t + k)*hidden ..]` layout exactly.
6872    pub fn dspark_prefill_prime(
6873        &self,
6874        ids: &[u32],
6875        state: &mut DecodeState,
6876        dstate: &mut DsparkState,
6877    ) -> Res<ForwardOut> {
6878        assert_eq!(
6879            state.pos, 0,
6880            "dspark_prefill_prime needs a fresh DecodeState"
6881        );
6882        assert!(!ids.is_empty(), "empty prompt");
6883        let hidden = self.model.mc.n_embd as usize;
6884        let hc = self.model.cfg().hc_mult as usize;
6885        let s = ids.len();
6886        let targets = self.dspark().targets.clone();
6887        let n_t = targets.len();
6888        let mut cap = GpuCapture {
6889            want: targets.iter().map(|&t| t as u32).collect(),
6890            ..Default::default()
6891        };
6892        let out = self
6893            .forward_impl(ids, Some(&mut cap), None, Some(state))?
6894            .expect("prefill logits");
6895        state.pos = s;
6896
6897        let last = self.stages.len() - 1;
6898        let stream = self.stages[last].gpu.stream();
6899        self.stages[last]
6900            .gpu
6901            .ctx
6902            .bind_to_thread()
6903            .map_err(e("bind ctx prime"))?;
6904        let mut main_hidden = stream
6905            .alloc_zeros::<f32>(s * n_t * hidden)
6906            .map_err(e("prefill main_hidden"))?;
6907        let mut tmp = stream
6908            .alloc_zeros::<f32>(s * hidden)
6909            .map_err(e("tap tmp"))?;
6910        for (k, &il) in targets.iter().enumerate() {
6911            let h = cap
6912                .layer_out
6913                .get(&(il as u32))
6914                .unwrap_or_else(|| panic!("prefill capture missing target layer {il}"));
6915            assert_eq!(
6916                h.len(),
6917                s * hc * hidden,
6918                "target layer {il} capture is not [s, hc, hidden]"
6919            );
6920            let h_dev = upload_f32(&stream, h)?;
6921            unsafe {
6922                ck(
6923                    "hc_mean prefill tap",
6924                    k::memra_dsv4_hc_mean(
6925                        dpf!(h_dev, &stream),
6926                        dpm!(tmp, &stream),
6927                        s as i32,
6928                        hc as i32,
6929                        hidden as i32,
6930                        sp(&stream),
6931                    ),
6932                )?;
6933                ck(
6934                    "place_cols prefill tap",
6935                    k::memra_dsv4_place_cols(
6936                        dpf!(tmp, &stream),
6937                        dpm!(main_hidden, &stream),
6938                        s as i32,
6939                        hidden as i32,
6940                        (n_t * hidden) as i64,
6941                        (k * hidden) as i64,
6942                        sp(&stream),
6943                    ),
6944                )?;
6945            }
6946        }
6947        self.dspark_prime_prefill(dstate, &main_hidden, s)?;
6948        // Seed taps row 0 with the LAST prefill position's tap: the generic spec loop's
6949        // first proposal is `propose(t, mh_last, p0-1)` with mh_last = pre_taps row
6950        // p0-1 (spec_oracle::run_spec_greedy) — without this the first round would draft
6951        // off a zeroed tap.
6952        {
6953            let src = main_hidden.slice((s - 1) * n_t * hidden..s * n_t * hidden);
6954            let mut dst = dstate.taps.slice_mut(0..n_t * hidden);
6955            stream
6956                .memcpy_dtod(&src, &mut dst)
6957                .map_err(e("seed tap row"))?;
6958        }
6959        stream.synchronize().map_err(e("prime sync"))?;
6960        Ok(out)
6961    }
6962
6963    /// Ring advance for ONE committed position (§3.1 drafter rule: accepted positions
6964    /// only). `tap_row` indexes into `state.taps` (the row that holds position `pos`'s
6965    /// hc-mean concat).
6966    pub fn dspark_write_rings(
6967        &self,
6968        state: &mut DsparkState,
6969        tap_row: usize,
6970        pos: usize,
6971    ) -> Res<()> {
6972        let d = self.model.cfg();
6973        let hd = d.head_dim as usize;
6974        let win = d.sliding_window as usize;
6975        let hidden = self.model.mc.n_embd as usize;
6976        let n_t = self.dspark().targets.len();
6977        let last = self.stages.len() - 1;
6978        let stream = self.stages[last].gpu.stream();
6979        let tap = {
6980            // one-row view as an owned slice copy (gemm wants a base slice)
6981            let mut row = stream
6982                .alloc_zeros::<f32>(n_t * hidden)
6983                .map_err(e("tap row"))?;
6984            let src = state
6985                .taps
6986                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
6987            stream.memcpy_dtod(&src, &mut row).map_err(e("tap copy"))?;
6988            row
6989        };
6990        let mx = self.dspark_main_x(&tap, 1)?;
6991        let n_blocks = self.dspark().blocks.len();
6992        for bi in 0..n_blocks {
6993            let blk = &self.dspark().blocks[bi];
6994            let kv = self.dspark_main_kv(blk, &mx, 1, &[pos as i32])?;
6995            let slot = pos % win;
6996            let src = kv.slice(0..hd);
6997            let mut dst = state.rings[bi].slice_mut(slot * hd..(slot + 1) * hd);
6998            stream
6999                .memcpy_dtod(&src, &mut dst)
7000                .map_err(e("ring write"))?;
7001        }
7002        Ok(())
7003    }
7004
7005    /// One DSpark draft-block forward (M:695-707 body with DSparkAttention M:771-792):
7006    /// h [block, hc, hidden] -> same shape. Reads the ring; writes ONLY the transient
7007    /// draft-kv rows [win, win+block) of `ring` (never persistent ring slots).
7008    #[allow(clippy::too_many_arguments)]
7009    fn dspark_block_forward(
7010        &self,
7011        blk: &LayerDev,
7012        ring: &mut CudaSlice<f32>,
7013        h: &CudaSlice<f32>,
7014        block: usize,
7015        pos: usize,
7016    ) -> Res<CudaSlice<f32>> {
7017        let d = self.model.cfg();
7018        let mc = &self.model.mc;
7019        let hc = d.hc_mult as usize;
7020        let hidden = mc.n_embd as usize;
7021        let heads = mc.n_head as usize;
7022        let hd = d.head_dim as usize;
7023        let rd = d.qk_rope_head_dim as usize;
7024        let q_lora = d.q_lora_rank as usize;
7025        let win = d.sliding_window as usize;
7026        let o_groups = d.o_groups as usize;
7027        let o_lora = d.o_lora_rank as usize;
7028        let eps = mc.rms_eps;
7029        let iters = d.hc_sinkhorn_iters;
7030        let hc_eps = d.hc_eps;
7031        let last = self.stages.len() - 1;
7032        let st = &self.stages[last];
7033        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx dspark"))?;
7034        let stream = st.gpu.stream();
7035        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
7036        // draft positions pos+1 .. pos+block (M:772)
7037        let positions: Vec<i32> = (1..=block as i32).map(|j| pos as i32 + j).collect();
7038        let pos_dev = upload_i32(&stream, &positions)?;
7039
7040        // ---- attention sub-block
7041        let (y, post, comb) = Self::hc_pre(
7042            st,
7043            h,
7044            &blk.hc_attn_fn,
7045            &blk.hc_attn_base,
7046            &blk.hc_attn_scale,
7047            block,
7048            hc,
7049            hidden,
7050            iters,
7051            hc_eps,
7052        )?;
7053        let mut x = stream.alloc_zeros::<f32>(block * hidden).map_err(e("x"))?;
7054        unsafe {
7055            ck(
7056                "rmsnorm dspark attn",
7057                k::memra_dsv4_rmsnorm(
7058                    dpf!(y, &stream),
7059                    dpf!(blk.attn_norm, &stream),
7060                    dpm!(x, &stream),
7061                    block as i32,
7062                    hidden as i32,
7063                    eps,
7064                    sp(&stream),
7065                ),
7066            )?;
7067        }
7068        // q path (trunk-identical, M:774-777)
7069        let mut qr = stream.alloc_zeros::<f32>(block * q_lora).map_err(e("qr"))?;
7070        Self::gemm(st, &x, blk.wq_a.dev(), 0, block, q_lora, hidden, &mut qr)?;
7071        unsafe {
7072            ck(
7073                "rmsnorm dspark q",
7074                k::memra_dsv4_rmsnorm(
7075                    dpf!(qr, &stream),
7076                    dpf!(blk.q_norm, &stream),
7077                    dpm!(qr, &stream),
7078                    block as i32,
7079                    q_lora as i32,
7080                    eps,
7081                    sp(&stream),
7082                ),
7083            )?;
7084        }
7085        let mut qr_b = stream
7086            .alloc_zeros::<u8>(block * q_lora * 2)
7087            .map_err(e("qr_b"))?;
7088        unsafe {
7089            ck(
7090                "cvt dspark qr",
7091                k::memra_dsv4_cvt_bf16(
7092                    dpf!(qr, &stream),
7093                    qr_b.device_ptr_mut(&stream).0 as *mut c_void,
7094                    (block * q_lora) as i64,
7095                    sp(&stream),
7096                ),
7097            )?;
7098        }
7099        let mut q = stream
7100            .alloc_zeros::<f32>(block * heads * hd)
7101            .map_err(e("q"))?;
7102        Self::gemm_pre(
7103            st,
7104            &qr_b,
7105            blk.wq_b.dev().device_ptr(&stream).0 as *const c_void,
7106            block,
7107            heads * hd,
7108            q_lora,
7109            &mut q,
7110        )?;
7111        unsafe {
7112            ck(
7113                "headrms dspark",
7114                k::memra_dsv4_headrms(
7115                    dpm!(q, &stream),
7116                    (block * heads) as i32,
7117                    hd as i32,
7118                    eps,
7119                    sp(&stream),
7120                ),
7121            )?;
7122            ck(
7123                "rope dspark q",
7124                k::memra_dsv4_rope(
7125                    dpm!(q, &stream),
7126                    block as i32,
7127                    heads as i32,
7128                    hd as i32,
7129                    rd as i32,
7130                    dpf!(st.fc_plain, &stream),
7131                    pos_dev.device_ptr(&stream).0 as *const i32,
7132                    0,
7133                    sp(&stream),
7134                ),
7135            )?;
7136        }
7137        // draft kv (M:778-780) -> transient rows [win, win+block) of the ring buffer
7138        {
7139            let mut kv = stream.alloc_zeros::<f32>(block * hd).map_err(e("dkv"))?;
7140            Self::gemm(st, &x, blk.wkv.dev(), 0, block, hd, hidden, &mut kv)?;
7141            unsafe {
7142                ck(
7143                    "rmsnorm dspark dkv",
7144                    k::memra_dsv4_rmsnorm(
7145                        dpf!(kv, &stream),
7146                        dpf!(blk.kv_norm, &stream),
7147                        dpm!(kv, &stream),
7148                        block as i32,
7149                        hd as i32,
7150                        eps,
7151                        sp(&stream),
7152                    ),
7153                )?;
7154                ck(
7155                    "rope dspark dkv",
7156                    k::memra_dsv4_rope(
7157                        dpm!(kv, &stream),
7158                        block as i32,
7159                        1,
7160                        hd as i32,
7161                        rd as i32,
7162                        dpf!(st.fc_plain, &stream),
7163                        pos_dev.device_ptr(&stream).0 as *const i32,
7164                        0,
7165                        sp(&stream),
7166                    ),
7167                )?;
7168                ck(
7169                    "act_quant dspark dkv",
7170                    k::memra_dsv4_act_quant(
7171                        dpm!(kv, &stream),
7172                        block as i32,
7173                        hd as i64,
7174                        (hd - rd) as i32,
7175                        64,
7176                        clamp_only,
7177                        sp(&stream),
7178                    ),
7179                )?;
7180            }
7181            let src = kv.slice(0..block * hd);
7182            let mut dst = ring.slice_mut(win * hd..(win + block) * hd);
7183            stream.memcpy_dtod(&src, &mut dst).map_err(e("draft kv"))?;
7184        }
7185        // attention set (M:743-747): ring slots 0..min(win, pos+1) then the block's
7186        // transient rows — ONE shared row for every draft query (bidirectional
7187        // intra-block attention), replicated per query for the prefill kernel.
7188        let n_ring = win.min(pos + 1);
7189        let mut idx_row: Vec<i32> = (0..n_ring as i32).collect();
7190        idx_row.extend((0..block as i32).map(|j| win as i32 + j));
7191        let slots = idx_row.len();
7192        let mut idxs = Vec::with_capacity(block * slots);
7193        for _ in 0..block {
7194            idxs.extend_from_slice(&idx_row);
7195        }
7196        let idx_dev = upload_i32(&stream, &idxs)?;
7197        let mut o = stream
7198            .alloc_zeros::<f32>(block * heads * hd)
7199            .map_err(e("o"))?;
7200        let scale = (hd as f64).powf(-0.5) as f32;
7201        unsafe {
7202            ck(
7203                "sink_attn dspark",
7204                k::memra_dsv4_sink_attn(
7205                    dpf!(q, &stream),
7206                    dpf!(ring, &stream),
7207                    idx_dev.device_ptr(&stream).0 as *const i32,
7208                    dpf!(blk.sink, &stream),
7209                    dpm!(o, &stream),
7210                    block as i32,
7211                    heads as i32,
7212                    hd as i32,
7213                    slots as i32,
7214                    scale,
7215                    sp(&stream),
7216                ),
7217            )?;
7218            ck(
7219                "rope dspark o inv",
7220                k::memra_dsv4_rope(
7221                    dpm!(o, &stream),
7222                    block as i32,
7223                    heads as i32,
7224                    hd as i32,
7225                    rd as i32,
7226                    dpf!(st.fc_plain, &stream),
7227                    pos_dev.device_ptr(&stream).0 as *const i32,
7228                    1,
7229                    sp(&stream),
7230                ),
7231            )?;
7232        }
7233        // grouped wo (trunk-identical)
7234        let gw = heads / o_groups * hd;
7235        let mut og = stream
7236            .alloc_zeros::<f32>(block * o_groups * o_lora)
7237            .map_err(e("og"))?;
7238        let mut o_grp = stream.alloc_zeros::<f32>(block * gw).map_err(e("o_grp"))?;
7239        let mut y_grp = stream
7240            .alloc_zeros::<f32>(block * o_lora)
7241            .map_err(e("y_grp"))?;
7242        for g in 0..o_groups {
7243            unsafe {
7244                ck(
7245                    "take_cols dspark",
7246                    k::memra_dsv4_take_cols(
7247                        dpf!(o, &stream),
7248                        dpm!(o_grp, &stream),
7249                        block as i32,
7250                        gw as i32,
7251                        (heads * hd) as i64,
7252                        (g * gw) as i64,
7253                        sp(&stream),
7254                    ),
7255                )?;
7256            }
7257            Self::gemm(
7258                st,
7259                &o_grp,
7260                blk.wo_a.dev(),
7261                g * o_lora * gw,
7262                block,
7263                o_lora,
7264                gw,
7265                &mut y_grp,
7266            )?;
7267            unsafe {
7268                ck(
7269                    "place_cols dspark",
7270                    k::memra_dsv4_place_cols(
7271                        dpf!(y_grp, &stream),
7272                        dpm!(og, &stream),
7273                        block as i32,
7274                        o_lora as i32,
7275                        (o_groups * o_lora) as i64,
7276                        (g * o_lora) as i64,
7277                        sp(&stream),
7278                    ),
7279                )?;
7280            }
7281        }
7282        let mut attn_out = stream.alloc_zeros::<f32>(block * hidden).map_err(e("ao"))?;
7283        Self::gemm(
7284            st,
7285            &og,
7286            blk.wo_b.dev(),
7287            0,
7288            block,
7289            hidden,
7290            o_groups * o_lora,
7291            &mut attn_out,
7292        )?;
7293        let mut h2 = stream
7294            .alloc_zeros::<f32>(block * hc * hidden)
7295            .map_err(e("h2"))?;
7296        unsafe {
7297            ck(
7298                "hc_post dspark attn",
7299                k::memra_dsv4_hc_post(
7300                    dpf!(attn_out, &stream),
7301                    dpf!(h, &stream),
7302                    dpf!(post, &stream),
7303                    dpf!(comb, &stream),
7304                    dpm!(h2, &stream),
7305                    block as i32,
7306                    hc as i32,
7307                    hidden as i32,
7308                    sp(&stream),
7309                ),
7310            )?;
7311        }
7312        // ---- ffn sub-block (score-routed MoE; ids unused by a non-hash gate)
7313        let (y2, post2, comb2) = Self::hc_pre(
7314            st,
7315            &h2,
7316            &blk.hc_ffn_fn,
7317            &blk.hc_ffn_base,
7318            &blk.hc_ffn_scale,
7319            block,
7320            hc,
7321            hidden,
7322            iters,
7323            hc_eps,
7324        )?;
7325        let mut xf = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xf"))?;
7326        unsafe {
7327            ck(
7328                "rmsnorm dspark ffn",
7329                k::memra_dsv4_rmsnorm(
7330                    dpf!(y2, &stream),
7331                    dpf!(blk.ffn_norm, &stream),
7332                    dpm!(xf, &stream),
7333                    block as i32,
7334                    hidden as i32,
7335                    eps,
7336                    sp(&stream),
7337                ),
7338            )?;
7339        }
7340        let ids = vec![0u32; block];
7341        let moe_out = self.moe_forward(st, blk, &xf, block, &ids)?;
7342        let mut h3 = stream
7343            .alloc_zeros::<f32>(block * hc * hidden)
7344            .map_err(e("h3"))?;
7345        unsafe {
7346            ck(
7347                "hc_post dspark ffn",
7348                k::memra_dsv4_hc_post(
7349                    dpf!(moe_out, &stream),
7350                    dpf!(h2, &stream),
7351                    dpf!(post2, &stream),
7352                    dpf!(comb2, &stream),
7353                    dpm!(h3, &stream),
7354                    block as i32,
7355                    hc as i32,
7356                    hidden as i32,
7357                    sp(&stream),
7358                ),
7359            )?;
7360        }
7361        Ok(h3)
7362    }
7363
7364    /// forward_spec (M:928-936) + forward_head (M:860-874) on the device: ONE parallel
7365    /// noise-block draft through the 3 blocks, shared trunk head over all block rows,
7366    /// sequential rank-256 markov chaining (greedy), fp32 confidence. Mutates ONLY the
7367    /// rings' transient rows (drafting is side-effect-free on trunk + persistent ring
7368    /// state — §3.1). `tap_row` = the taps row holding position `pos`'s hc-mean concat.
7369    pub fn dspark_forward_spec(
7370        &self,
7371        state: &mut DsparkState,
7372        input_token: u32,
7373        tap_row: usize,
7374        pos: usize,
7375        capture: bool,
7376    ) -> Res<DsparkProposal> {
7377        let ds = self.dspark();
7378        let mc = &self.model.mc;
7379        let d = self.model.cfg();
7380        let hc = d.hc_mult as usize;
7381        let hidden = mc.n_embd as usize;
7382        let eps = mc.rms_eps;
7383        let block = ds.block_size;
7384        let rank = ds.rank;
7385        let vocab = ds.vocab;
7386        let n_t = ds.targets.len();
7387        let last = self.stages.len() - 1;
7388        let st = &self.stages[last];
7389        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx spec"))?;
7390        let stream = st.gpu.stream();
7391
7392        let prof = if dsv4_prof_on() {
7393            Some(stream.clone())
7394        } else {
7395            None
7396        };
7397        // main_x from the tap row (computed once per call, M:930-932)
7398        let tap = {
7399            let _p = phase!("1a.tap_copy", prof.as_ref());
7400            let mut row = stream.alloc_zeros::<f32>(n_t * hidden).map_err(e("tapr"))?;
7401            let src = state
7402                .taps
7403                .slice(tap_row * n_t * hidden..(tap_row + 1) * n_t * hidden);
7404            stream.memcpy_dtod(&src, &mut row).map_err(e("tap cp"))?;
7405            row
7406        };
7407        let mx = {
7408            let _p = phase!("1b.main_x", prof.as_ref());
7409            self.dspark_main_x(&tap, 1)?
7410        };
7411        let (cap_main_hidden, cap_main_x) = if capture {
7412            (
7413                Some(dtoh_f32(&stream, &tap)?),
7414                Some(dtoh_f32(&stream, &mx)?),
7415            )
7416        } else {
7417            (None, None)
7418        };
7419        // draft block ids: [input token, noise ×(block-1)] via the SHARED trunk embed
7420        // (host-gathered — the MtpDev precedent)
7421        let _p_embed = phase!("1c.embed_h2d_repeat", prof.as_ref());
7422        let mut draft_ids = vec![ds.noise_token; block];
7423        draft_ids[0] = input_token;
7424        let e_rows = self.model.embed_rows(&draft_ids);
7425        let e_dev = upload_f32(&stream, &e_rows)?;
7426        let mut h = stream
7427            .alloc_zeros::<f32>(block * hc * hidden)
7428            .map_err(e("h0"))?;
7429        unsafe {
7430            ck(
7431                "repeat_hc dspark",
7432                k::memra_dsv4_repeat_hc(
7433                    dpf!(e_dev, &stream),
7434                    dpm!(h, &stream),
7435                    block as i32,
7436                    hc as i32,
7437                    hidden as i32,
7438                    sp(&stream),
7439                ),
7440            )?;
7441        }
7442        drop(_p_embed);
7443        let mut block_outs: Vec<Vec<f32>> = Vec::new();
7444        let _p_blocks = phase!("1d.drafter_blocks", prof.as_ref());
7445        let n_blocks = ds.blocks.len();
7446        for bi in 0..n_blocks {
7447            // rings[bi] transient rows are rewritten; persistent slots untouched
7448            let mut ring = std::mem::replace(
7449                &mut state.rings[bi],
7450                stream.alloc_zeros::<f32>(0).map_err(e("swap"))?,
7451            );
7452            let out =
7453                self.dspark_block_forward(&self.dspark().blocks[bi], &mut ring, &h, block, pos);
7454            state.rings[bi] = ring;
7455            h = out?;
7456            if capture {
7457                block_outs.push(dtoh_f32(&stream, &h)?);
7458            }
7459        }
7460        drop(_p_blocks);
7461        // exit head (mtp.2): pre-only hc collapse -> xc (pre-norm, feeds confidence),
7462        // norm, shared trunk head over ALL block rows
7463        let w = hc * hidden;
7464        let _p_mix = phase!("1e.exit_mix_dots", prof.as_ref());
7465        let mut mixes = stream.alloc_zeros::<f32>(block * hc).map_err(e("mx"))?;
7466        // hoisted (weight row read once across the block's rows). The f64 twin is
7467        // BIT-EXACT vs `Self::dots` — same per-(t, j) element order and reduction tree —
7468        // so the default arm's values are untouched by the hoist.
7469        self.dspark_head_dots(
7470            st,
7471            h.device_ptr(&stream).0 as *const f32,
7472            ds.hc_head_fn.device_ptr(&stream).0 as *const c_void,
7473            0,
7474            block,
7475            w,
7476            hc,
7477            mixes.device_ptr_mut(&stream).0 as *mut f32,
7478        )?;
7479        unsafe {
7480            ck(
7481                "rowsq dspark head",
7482                k::memra_dsv4_rowsq_scale(
7483                    dpf!(h, &stream),
7484                    dpm!(mixes, &stream),
7485                    block as i32,
7486                    w as i32,
7487                    hc as i32,
7488                    eps,
7489                    sp(&stream),
7490                ),
7491            )?;
7492        }
7493        drop(_p_mix);
7494        let _p_mixrt = phase!("1f.mix_D2H_host_H2D", prof.as_ref());
7495        let mut mixes_h = dtoh_f32(&stream, &mixes)?;
7496        for t in 0..block {
7497            for c in 0..hc {
7498                let m = mixes_h[t * hc + c];
7499                mixes_h[t * hc + c] =
7500                    sigmoid_f32(m * ds.hc_head_scale[0] + ds.hc_head_base[c]) + d.hc_eps;
7501            }
7502        }
7503        let pre_d = upload_f32(&stream, &mixes_h)?;
7504        drop(_p_mixrt);
7505        let _p_cn = phase!("1g.collapse_norm", prof.as_ref());
7506        let mut xc = stream.alloc_zeros::<f32>(block * hidden).map_err(e("xc"))?;
7507        unsafe {
7508            ck(
7509                "hc_collapse dspark",
7510                k::memra_dsv4_hc_collapse(
7511                    dpf!(h, &stream),
7512                    dpf!(pre_d, &stream),
7513                    dpm!(xc, &stream),
7514                    block as i32,
7515                    hc as i32,
7516                    hidden as i32,
7517                    sp(&stream),
7518                ),
7519            )?;
7520        }
7521        let mut normed = stream.alloc_zeros::<f32>(block * hidden).map_err(e("nr"))?;
7522        unsafe {
7523            ck(
7524                "rmsnorm dspark head",
7525                k::memra_dsv4_rmsnorm(
7526                    dpf!(xc, &stream),
7527                    dpf!(ds.norm, &stream),
7528                    dpm!(normed, &stream),
7529                    block as i32,
7530                    hidden as i32,
7531                    eps,
7532                    sp(&stream),
7533                ),
7534            )?;
7535        }
7536        drop(_p_cn);
7537        let _p_head = phase!("1h.exit_head_dots", prof.as_ref());
7538        let mut logits = stream.alloc_zeros::<f32>(block * vocab).map_err(e("lg"))?;
7539        // THE 21%-of-a-round instance (nsys, rung 4c): vocab x block over the 1.06 GiB
7540        // shared head. f64 default (gated bytes, hoisted bit-exactly);
7541        // MEMRA_DSV4_DSPARK_HEAD_ARM=f32x switches it to the ratified accumulation class.
7542        self.dspark_head_dots(
7543            st,
7544            normed.device_ptr(&stream).0 as *const f32,
7545            st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void,
7546            1,
7547            block,
7548            hidden,
7549            vocab,
7550            logits.device_ptr_mut(&stream).0 as *mut f32,
7551        )?;
7552        drop(_p_head);
7553        // pre-markov head logits (the gate's logits_pre array) — captured BEFORE the
7554        // chaining loop adds any bias row in place.
7555        let cap_logits_pre = if capture {
7556            Some(dtoh_f32(&stream, &logits)?)
7557        } else {
7558            None
7559        };
7560        // sequential markov chaining (M:866-871), greedy (temperature 0).
7561        //
7562        // ITERATION-5 (F itemisation, rung 2): the chain is inherently sequential -- draft i+1's
7563        // markov row is indexed by draft i -- but the DEPENDENCY never needed a HOST round trip.
7564        // The shipped loop reads each argmax back (4 B D2H + `stream.synchronize()`) and each
7565        // confidence back the same way, so a block_size-5 chain DRAINS the only stream TEN times
7566        // per round. Those drains are pure F: T-independent, all latency, no work.
7567        // `MEMRA_DSV4_DSPARK_CHAIN=device` keeps the chain resident -- the argmax lands in
7568        // `am_dev[i + 1]`, the next markov row is gathered BY DEVICE INDEX, confidences
7569        // accumulate into `conf_out[i]`, and ONE D2H at the end of the loop returns every id and
7570        // confidence together. Same kernels, same operands, same reduction order: the arm is
7571        // bit-identical BY CONSTRUCTION rather than by tolerance, because only transport moved.
7572        let chain_device = dsv4_dspark_chain_device();
7573        let markov_rowblk = dsv4_dspark_markov_rowblk();
7574        let mut w1_row = stream.alloc_zeros::<f32>(rank).map_err(e("w1r"))?;
7575        let mut bias = stream.alloc_zeros::<f32>(vocab).map_err(e("bias"))?;
7576        // Slot 0 carries the round's input token so even the FIRST gather is device-indexed and
7577        // the two arms share one code path.
7578        let mut am_dev = stream.alloc_zeros::<i32>(block + 1).map_err(e("am"))?;
7579        {
7580            let mut dst = am_dev.slice_mut(0..1);
7581            stream
7582                .memcpy_htod(&[input_token as i32][..], &mut dst)
7583                .map_err(e("htod am0"))?;
7584        }
7585        let mut out_ids = vec![input_token];
7586        let mut margins = Vec::with_capacity(block);
7587        let mut top1_logits = Vec::with_capacity(block);
7588        let mut conf_in = stream.alloc_zeros::<f32>(hidden + rank).map_err(e("cin"))?;
7589        let mut conf_out = stream.alloc_zeros::<f32>(block).map_err(e("cout"))?;
7590        let mut confidence = Vec::with_capacity(block);
7591        let _p_mk = phase!("1i.markov_chain", prof.as_ref());
7592        for i in 0..block {
7593            {
7594                let _p = phase!("1i1.markov_w1_gather", prof.as_ref());
7595                if chain_device {
7596                    unsafe {
7597                        ck(
7598                            "markov w1 gather dev",
7599                            k::memra_dsv4_gather_row_by_idx(
7600                                dpf!(ds.markov_w1, &stream),
7601                                am_dev.device_ptr(&stream).0 as *const i32,
7602                                i as i32,
7603                                dpm!(w1_row, &stream),
7604                                rank as i32,
7605                                sp(&stream),
7606                            ),
7607                        )?;
7608                    }
7609                } else {
7610                    let prev = out_ids[i] as usize;
7611                    let src = ds.markov_w1.slice(prev * rank..(prev + 1) * rank);
7612                    stream.memcpy_dtod(&src, &mut w1_row).map_err(e("w1 cp"))?;
7613                }
7614            }
7615            {
7616                let _p = phase!("1i2.markov_bias_gemv", prof.as_ref());
7617                if markov_rowblk {
7618                    unsafe {
7619                        ck(
7620                            "dots_f32 markov rowblk",
7621                            k::memra_dsv4_dots_f32_rowblk(
7622                                dpf!(w1_row, &stream),
7623                                dp!(ds.markov_w2, &stream),
7624                                0,
7625                                dpm!(bias, &stream),
7626                                1,
7627                                rank as i32,
7628                                vocab as i32,
7629                                sp(&stream),
7630                            ),
7631                        )?;
7632                    }
7633                } else {
7634                    Self::dots(st, &w1_row, &ds.markov_w2, 1, rank, vocab, &mut bias)?;
7635                }
7636            }
7637            let _p_aa = phase!("1i3.markov_add_argmax", prof.as_ref());
7638            unsafe {
7639                ck(
7640                    "markov add dspark",
7641                    k::memra_dsv4_add_inplace(
7642                        (logits.device_ptr_mut(&stream).0 as usize + i * vocab * 4) as *mut f32,
7643                        dpf!(bias, &stream),
7644                        vocab as i64,
7645                        sp(&stream),
7646                    ),
7647                )?;
7648                ck(
7649                    "argmax dspark",
7650                    k::memra_dsv4_argmax(
7651                        (logits.device_ptr(&stream).0 as usize + i * vocab * 4) as *const f32,
7652                        vocab as i64,
7653                        (am_dev.device_ptr_mut(&stream).0 as usize + (i + 1) * 4) as *mut i32,
7654                        sp(&stream),
7655                    ),
7656                )?;
7657            }
7658            drop(_p_aa);
7659            if !chain_device {
7660                let _p_d2h = phase!("1i4.markov_argmax_D2H_SYNC", None);
7661                let mut am = [0i32; 1];
7662                let view = am_dev.slice(i + 1..i + 2);
7663                stream
7664                    .memcpy_dtoh(&view, &mut am[..])
7665                    .map_err(e("dtoh am"))?;
7666                stream.synchronize().map_err(e("sync am"))?;
7667                out_ids.push(am[0] as u32);
7668            }
7669            // confidence (M:807-815): fp32 proj of concat(PRE-norm xc row, markov_embed)
7670            {
7671                let _p = phase!("1i5.conf_in_copies", prof.as_ref());
7672                let src = xc.slice(i * hidden..(i + 1) * hidden);
7673                let mut dst = conf_in.slice_mut(0..hidden);
7674                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin x"))?;
7675                let src = w1_row.slice(0..rank);
7676                let mut dst = conf_in.slice_mut(hidden..hidden + rank);
7677                stream.memcpy_dtod(&src, &mut dst).map_err(e("cin m"))?;
7678            }
7679            {
7680                // `Self::dots` writes y[0]; the confidence now lands in slot i of a
7681                // block-wide buffer, so the launcher is called with the offset directly
7682                // (the same pointer-arithmetic pattern the add/argmax above use). Kernel,
7683                // f64 accumulation and operand order are untouched.
7684                let _p = phase!("1i6.conf_dots", prof.as_ref());
7685                unsafe {
7686                    ck(
7687                        "dots_f32 conf dspark",
7688                        k::memra_dsv4_dots_f32(
7689                            dpf!(conf_in, &stream),
7690                            dp!(ds.conf_w, &stream),
7691                            0,
7692                            (conf_out.device_ptr_mut(&stream).0 as usize + i * 4) as *mut f32,
7693                            1,
7694                            (hidden + rank) as i32,
7695                            1,
7696                            sp(&stream),
7697                        ),
7698                    )?;
7699                }
7700            }
7701            if !chain_device {
7702                let _p = phase!("1i7.conf_D2H_SYNC", None);
7703                let mut c = [0f32; 1];
7704                let view = conf_out.slice(i..i + 1);
7705                stream
7706                    .memcpy_dtoh(&view, &mut c[..])
7707                    .map_err(e("dtoh cf"))?;
7708                stream.synchronize().map_err(e("sync cf"))?;
7709                confidence.push(c[0]);
7710            }
7711        }
7712        if chain_device {
7713            // ONE drain for the whole chain: block ids + block confidences.
7714            let _p = phase!("1i8.chain_D2H_SYNC_once", None);
7715            let mut ids = vec![0i32; block];
7716            let view = am_dev.slice(1..block + 1);
7717            stream
7718                .memcpy_dtoh(&view, &mut ids[..])
7719                .map_err(e("dtoh chain ids"))?;
7720            let mut cf = vec![0f32; block];
7721            stream
7722                .memcpy_dtoh(&conf_out, &mut cf[..])
7723                .map_err(e("dtoh chain conf"))?;
7724            stream.synchronize().map_err(e("sync chain"))?;
7725            out_ids.extend(ids.iter().map(|&x| x as u32));
7726            confidence.extend_from_slice(&cf);
7727        }
7728        drop(_p_mk);
7729        // `markov_embed`, `margins` and `top1_logits` are CAPTURE-ONLY observables, and wanting
7730        // them mid-chain was the other reason the shipped loop had to know each id on the host.
7731        // `add_inplace` touches logits row i only at step i, so every row is final once the loop
7732        // ends and one post-loop read is bit-identical to the per-step reads it replaces.
7733        let membeds: Vec<f32> = if capture {
7734            let mut m = Vec::with_capacity(block * rank);
7735            for i in 0..block {
7736                let prev = out_ids[i] as usize;
7737                m.extend_from_slice(&ds.markov_w1_host[prev * rank..(prev + 1) * rank]);
7738            }
7739            m
7740        } else {
7741            Vec::new()
7742        };
7743        let cap = if capture {
7744            let logits_post = dtoh_f32(&stream, &logits)?;
7745            for i in 0..block {
7746                let row = &logits_post[i * vocab..(i + 1) * vocab];
7747                let top = out_ids[i + 1];
7748                let mut second = f32::NEG_INFINITY;
7749                for (vv, &val) in row.iter().enumerate() {
7750                    if vv as u32 != top && val > second {
7751                        second = val;
7752                    }
7753                }
7754                margins.push(row[top as usize] - second);
7755                top1_logits.push(row[top as usize]);
7756            }
7757            Some(DsparkCaptureOut {
7758                main_hidden: cap_main_hidden.unwrap(),
7759                main_x: cap_main_x.unwrap(),
7760                block_outs,
7761                x_collapsed: dtoh_f32(&stream, &xc)?,
7762                logits_pre: cap_logits_pre.unwrap(),
7763                logits_post,
7764                markov_embed: membeds,
7765            })
7766        } else {
7767            None
7768        };
7769        Ok(DsparkProposal {
7770            out_ids,
7771            confidence,
7772            margins,
7773            top1_logits,
7774            capture: cap,
7775        })
7776    }
7777
7778    /// Device decode step + the DSpark tap into `dspark_state.taps` row `tap_row`
7779    /// (full logits — the gates' contract).
7780    pub fn decode_step_tap(
7781        &self,
7782        tok: u32,
7783        state: &mut DecodeState,
7784        dspark_state: &mut DsparkState,
7785        tap_row: usize,
7786    ) -> Res<Vec<f32>> {
7787        let DecodePath::Device { host_math } = self.decode_path else {
7788            return Err("decode_step_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7789        };
7790        let n_t = self.dspark().targets.len();
7791        let hidden = self.model.mc.n_embd as usize;
7792        let (logits, _) = self.decode_step_fast_tap(
7793            tok,
7794            state,
7795            true,
7796            host_math,
7797            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7798        )?;
7799        Ok(logits.expect("want_logits"))
7800    }
7801
7802    /// Greedy twin of [`Self::decode_step_tap`] (device argmax, 4-byte D2H).
7803    pub fn decode_step_greedy_tap(
7804        &self,
7805        tok: u32,
7806        state: &mut DecodeState,
7807        dspark_state: &mut DsparkState,
7808        tap_row: usize,
7809    ) -> Res<u32> {
7810        let DecodePath::Device { host_math } = self.decode_path else {
7811            return Err("decode_step_greedy_tap requires MEMRA_DSV4_DECODE_PATH=device".into());
7812        };
7813        let ds = self.dspark();
7814        let hidden = self.model.mc.n_embd as usize;
7815        let n_t = ds.targets.len();
7816        let (_, tok_next) = self.decode_step_fast_tap(
7817            tok,
7818            state,
7819            false,
7820            host_math,
7821            Some((&mut dspark_state.taps, tap_row * n_t * hidden)),
7822        )?;
7823        Ok(tok_next)
7824    }
7825}
7826
7827// ================================================================ iteration 3, rung 4: batched T=k+1 device verify
7828//
7829// The rung that makes drafted decode pay. Design law (banked in the iteration-3 receipts
7830// before this code was written, and restated in cu/dsv4_gpu.cu's batched section):
7831//
7832//   1. BIT-EXACT against T sequential single-position steps. The greedy spec==plain
7833//      identity law is this lane's verdict instrument; if the verify pass computed
7834//      different logits than the plain pass, identity would break silently at every
7835//      near-tie and no gate could tell a port bug from a rounding fork. Achievable
7836//      because the device decode path's dense projections are OUR kernels: the batched
7837//      twins hoist the WEIGHT load across T activation rows without touching any
7838//      accumulation order. cuBLASLt is deliberately absent from this path (its m-order
7839//      changes split-K plans and shifts logits 0.18-3.08 — banked).
7840//   2. §3.1 ring hazard, exactly as GATED on the CPU oracle: window-ring writes go to
7841//      TRANSIENT rows (kvc rows [win+cap_blocks, win+cap_blocks+T)) and reads of
7842//      in-round positions are redirected there (`dsv4_build_idx_redirect`); the
7843//      compressor/indexer pending state advances in place with a snapshot + replay
7844//      payload; the append-only stores roll back by high-water mark; the drafter rings
7845//      advance for ACCEPTED positions only.
7846//   3. Where a kernel cannot batch (per-position compressor state machine, per-position
7847//      indexer top-k), the loop runs t = 0..T-1 in POSITION ORDER — the sequential
7848//      program's order, so in-round block emissions are visible to later queries exactly
7849//      as they would be sequentially.
7850//
7851// The one place uniformity is imposed: the batched sink attention takes ONE `slots`
7852// width for all T queries (the max over the round) and shorter queries' index tails are
7853// -1 pads. That is bit-inert by the pinned kernels' own pad contract (score -inf ->
7854// eval +0.0 -> skipped in both the denominator and the output chain), which is why it is
7855// legal rather than merely convenient.
7856
7857/// Per-stage batched-verify workspace: the lane-8 arena widened to `tmax` rows. Held
7858/// separately from [`StepWs`] so the gated single-position path's allocations, launches
7859/// and bytes are literally untouched by this rung.
7860pub struct VerifyWs {
7861    pub tmax: usize,
7862    h_a: CudaSlice<f32>,
7863    h_b: CudaSlice<f32>,
7864    h_rx: CudaSlice<f32>,
7865    emb: CudaSlice<f32>,
7866    mixes: CudaSlice<f32>,
7867    pre: CudaSlice<f32>,
7868    post: CudaSlice<f32>,
7869    comb: CudaSlice<f32>,
7870    y_hc: CudaSlice<f32>,
7871    x: CudaSlice<f32>,
7872    xf: CudaSlice<f32>,
7873    qr: CudaSlice<f32>,
7874    qr_b: CudaSlice<u8>,
7875    q: CudaSlice<f32>,
7876    kv: CudaSlice<f32>,
7877    qi: CudaSlice<f32>,
7878    wproj: CudaSlice<f32>,
7879    score: CudaSlice<f32>,
7880    idx: CudaSlice<i32>,
7881    idx_stride: usize,
7882    o: CudaSlice<f32>,
7883    o_b: CudaSlice<u8>,
7884    og: CudaSlice<f32>,
7885    attn_out: CudaSlice<f32>,
7886    gemm_xb: CudaSlice<u8>,
7887    raw: CudaSlice<f32>,
7888    sel: CudaSlice<i32>,
7889    selw: CudaSlice<f32>,
7890    order: CudaSlice<i32>,
7891    xq: CudaSlice<u8>,
7892    xs: CudaSlice<f32>,
7893    g1: CudaSlice<f32>,
7894    g3: CudaSlice<f32>,
7895    hbuf: CudaSlice<f32>,
7896    hq: CudaSlice<u8>,
7897    hs: CudaSlice<f32>,
7898    contrib: CudaSlice<f32>,
7899    y: CudaSlice<f32>,
7900    xb: CudaSlice<u8>,
7901    sg1: CudaSlice<f32>,
7902    sg3: CudaSlice<f32>,
7903    shbuf: CudaSlice<f32>,
7904    shb16: CudaSlice<u8>,
7905    sh_out: CudaSlice<f32>,
7906    cmp_emit: CudaSlice<f32>,
7907    cmp_shift: CudaSlice<f32>,
7908    sink_scores: CudaSlice<f32>,
7909    sink_evals: CudaSlice<f32>,
7910    sink_den: CudaSlice<f64>,
7911    head_mixes: CudaSlice<f32>,
7912    head_pre: CudaSlice<f32>,
7913    collapsed: CudaSlice<f32>,
7914    logits: CudaSlice<f32>,
7915    tok: CudaSlice<i32>,
7916    pos_dev: CudaSlice<i32>,
7917    argmax: CudaSlice<i32>,
7918    /// ring-commit staging: transient rows copied out, then scattered to ring slots
7919    /// (source and destination live in the same `kvc` allocation, so the bounce is a
7920    /// borrow requirement, not a numeric one).
7921    bounce: CudaSlice<f32>,
7922    slot_rows: CudaSlice<i32>,
7923    /// hc-mean staging for the DSpark tap (one target at a time, then `place_cols`)
7924    tap_tmp: CudaSlice<f32>,
7925}
7926
7927/// One compressor's verify-round checkpoint on device — the CPU oracle's `CompCkpt`,
7928/// device-realized: full pending snapshot + the per-position RAW (kv, score) rows that
7929/// were written, plus the store high-water mark. `dst` and `emitted` are pure functions
7930/// of the position, so nothing has to come back to the host to replay.
7931struct CmpCkptDev {
7932    kv_snap: CudaSlice<f32>,
7933    sc_snap: CudaSlice<f32>,
7934    rows_kv: CudaSlice<f32>,
7935    rows_sc: CudaSlice<f32>,
7936    latent: usize,
7937    ratio: usize,
7938    overlap: bool,
7939    n_blocks0: usize,
7940}
7941
7942/// One trunk layer's verify-round checkpoint: the two compressor payloads. The window
7943/// ring needs no payload at all — the round never wrote it (transient rows instead).
7944struct LayerCkptDev {
7945    cmp: Option<CmpCkptDev>,
7946    idx: Option<CmpCkptDev>,
7947    /// first transient row id in this layer's `kvc` (== win + cap_blocks)
7948    trans_base: usize,
7949}
7950
7951/// Whole-round verify state: the per-stage arenas + the per-layer §3.1 checkpoints.
7952pub struct VerifyState {
7953    ws: Vec<VerifyWs>,
7954    layers: Vec<LayerCkptDev>,
7955    pub tmax: usize,
7956    /// (pos0, t) of the open round; `None` between rounds. `commit_verify_dev` closes it.
7957    open: Option<(usize, usize)>,
7958    /// allocated bytes per device index (reported next to the drafter VRAM plan)
7959    pub bytes: Vec<u64>,
7960}
7961
7962impl Dsv4Gpu {
7963    /// Verify-round depth ceiling: block_size + 1 with the drafter loaded, else 0 (and
7964    /// then no transient rows are reserved anywhere — today's exact allocation).
7965    pub fn verify_tmax(&self) -> usize {
7966        self.dspark.as_ref().map(|d| d.block_size + 1).unwrap_or(0)
7967    }
7968
7969    /// Allocate the batched-verify state (arenas + §3.1 checkpoints). Requires the
7970    /// drafter (the only producer of rounds) and the device decode path.
7971    pub fn alloc_verify_state(&self) -> Res<VerifyState> {
7972        let tmax = self.verify_tmax();
7973        if tmax == 0 {
7974            return Err("alloc_verify_state needs MEMRA_DSV4_DRAFTER=dspark".into());
7975        }
7976        if !matches!(self.decode_path, DecodePath::Device { .. }) {
7977            return Err(
7978                "batched verify is a device-path rung (MEMRA_DSV4_DECODE_PATH=device)".into(),
7979            );
7980        }
7981        let d = self.model.cfg();
7982        let mc = &self.model.mc;
7983        let moe = mc.moe.as_ref().expect("moe");
7984        let hc = d.hc_mult as usize;
7985        let hidden = mc.n_embd as usize;
7986        let heads = mc.n_head as usize;
7987        let hd = d.head_dim as usize;
7988        let q_lora = d.q_lora_rank as usize;
7989        let win = d.sliding_window as usize;
7990        let o_groups = d.o_groups as usize;
7991        let o_lora = d.o_lora_rank as usize;
7992        let iheads = d.index_n_heads as usize;
7993        let ihd = d.index_head_dim as usize;
7994        let topk = moe.expert_used_count as usize;
7995        let ne = moe.expert_count as usize;
7996        let inter = moe.expert_ff_length as usize;
7997        let itopk = d.index_topk as usize;
7998        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
7999        let vocab = {
8000            let (info, _) = self.model.st.raw("head.weight").expect("head");
8001            info.shape[0] as usize
8002        };
8003        let sh_inter = {
8004            let (info, _) = self
8005                .model
8006                .st
8007                .raw("layers.0.ffn.shared_experts.w1.weight")
8008                .expect("shared w1");
8009            info.shape[0] as usize
8010        };
8011        let mut max_d = 0usize;
8012        let mut max_shift = 0usize;
8013        let mut min_ratio = usize::MAX;
8014        for st in &self.stages {
8015            for l in &st.layers {
8016                for cmp in l.cmp.iter().chain(l.idx.as_ref().map(|ix| &ix.cmp)) {
8017                    max_d = max_d.max(cmp.d);
8018                    if cmp.overlap {
8019                        max_shift = max_shift.max(cmp.ratio * cmp.latent);
8020                    }
8021                    min_ratio = min_ratio.min(cmp.ratio);
8022                }
8023            }
8024        }
8025        assert!(min_ratio != usize::MAX, "no compressor layers?");
8026        let score_cap = self.max_seq / min_ratio + 1;
8027        let idx_tail = itopk.max(self.max_seq / 128 + 1);
8028        let idx_stride = win + idx_tail;
8029        let max_gemm_k = (o_groups * o_lora).max(hidden).max(q_lora).max(sh_inter);
8030        let mut bytes = vec![0u64; self.stages.len()];
8031        let mut ws = Vec::with_capacity(self.stages.len());
8032        for st in &self.stages {
8033            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx vws"))?;
8034            let s = st.gpu.stream();
8035            let acc = std::cell::Cell::new(0u64);
8036            let f = |n: usize| {
8037                acc.set(acc.get() + (n * 4) as u64);
8038                s.alloc_zeros::<f32>(n).map_err(e("vws f32"))
8039            };
8040            let b = |n: usize| {
8041                acc.set(acc.get() + n as u64);
8042                s.alloc_zeros::<u8>(n).map_err(e("vws u8"))
8043            };
8044            let i = |n: usize| {
8045                acc.set(acc.get() + (n * 4) as u64);
8046                s.alloc_zeros::<i32>(n).map_err(e("vws i32"))
8047            };
8048            let w = VerifyWs {
8049                tmax,
8050                h_a: f(tmax * hc * hidden)?,
8051                h_b: f(tmax * hc * hidden)?,
8052                h_rx: f(tmax * hc * hidden)?,
8053                emb: f(tmax * hidden)?,
8054                mixes: f(tmax * (2 + hc) * hc)?,
8055                pre: f(tmax * hc)?,
8056                post: f(tmax * hc)?,
8057                comb: f(tmax * hc * hc)?,
8058                y_hc: f(tmax * hidden)?,
8059                x: f(tmax * hidden)?,
8060                xf: f(tmax * hidden)?,
8061                qr: f(tmax * q_lora)?,
8062                qr_b: b(tmax * q_lora * 2)?,
8063                q: f(tmax * heads * hd)?,
8064                kv: f(tmax * hd)?,
8065                qi: f(tmax * iheads * ihd)?,
8066                wproj: f(tmax * iheads)?,
8067                score: f(score_cap)?,
8068                idx: i(tmax * idx_stride)?,
8069                idx_stride,
8070                o: f(tmax * heads * hd)?,
8071                o_b: b(tmax * heads * hd * 2)?,
8072                og: f(tmax * o_groups * o_lora)?,
8073                attn_out: f(tmax * hidden)?,
8074                gemm_xb: b(tmax * max_gemm_k * 2)?,
8075                raw: f(tmax * ne)?,
8076                sel: i(tmax * topk)?,
8077                selw: f(tmax * topk)?,
8078                order: i(tmax * topk)?,
8079                xq: b(tmax * hidden)?,
8080                xs: f(tmax * hidden / 128)?,
8081                g1: f(tmax * topk * inter)?,
8082                g3: f(tmax * topk * inter)?,
8083                hbuf: f(tmax * topk * inter)?,
8084                hq: b(tmax * topk * inter)?,
8085                hs: f(tmax * topk * inter / 128)?,
8086                contrib: f(tmax * topk * hidden)?,
8087                y: f(tmax * hidden)?,
8088                xb: b(tmax * hidden * 2)?,
8089                sg1: f(tmax * sh_inter)?,
8090                sg3: f(tmax * sh_inter)?,
8091                shbuf: f(tmax * sh_inter)?,
8092                shb16: b(tmax * sh_inter * 2)?,
8093                sh_out: f(tmax * hidden)?,
8094                cmp_emit: f(2 * max_d)?,
8095                cmp_shift: f(max_shift.max(1))?,
8096                sink_scores: f(tmax * heads * idx_stride)?,
8097                sink_evals: f(tmax * heads * idx_stride)?,
8098                sink_den: {
8099                    acc.set(acc.get() + (tmax * heads * 8) as u64);
8100                    s.alloc_zeros::<f64>(tmax * heads).map_err(e("vws f64"))?
8101                },
8102                head_mixes: f(tmax * hc)?,
8103                head_pre: f(tmax * hc)?,
8104                collapsed: f(tmax * hidden)?,
8105                logits: f(tmax * vocab)?,
8106                tok: i(tmax)?,
8107                pos_dev: i(tmax)?,
8108                argmax: i(tmax)?,
8109                bounce: f(tmax * hd)?,
8110                slot_rows: i(tmax)?,
8111                tap_tmp: f(tmax * hidden)?,
8112            };
8113            bytes[st.dev] += acc.get();
8114            ws.push(w);
8115        }
8116        // per-layer §3.1 checkpoints, each on the layer's own device
8117        let mut layers = Vec::with_capacity(n_trunk);
8118        for il in 0..n_trunk {
8119            let stage_i = self.layer_stage[il];
8120            let st = &self.stages[stage_i];
8121            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx ckpt"))?;
8122            let stream = st.gpu.stream();
8123            let lidx = st
8124                .layers
8125                .iter()
8126                .position(|l| l.il == il as u32)
8127                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
8128            let layer = &st.layers[lidx];
8129            let cap_blocks = self.max_seq.checked_div(layer.ratio).unwrap_or(0);
8130            let mk = |cmp: &CmpDev| -> Res<CmpCkptDev> {
8131                let slots = if cmp.overlap {
8132                    2 * cmp.ratio
8133                } else {
8134                    cmp.ratio
8135                };
8136                Ok(CmpCkptDev {
8137                    kv_snap: stream
8138                        .alloc_zeros::<f32>(slots * cmp.latent)
8139                        .map_err(e("ckpt kv snap"))?,
8140                    sc_snap: stream
8141                        .alloc_zeros::<f32>(slots * cmp.latent)
8142                        .map_err(e("ckpt sc snap"))?,
8143                    rows_kv: stream
8144                        .alloc_zeros::<f32>(tmax * cmp.latent)
8145                        .map_err(e("ckpt rows kv"))?,
8146                    rows_sc: stream
8147                        .alloc_zeros::<f32>(tmax * cmp.latent)
8148                        .map_err(e("ckpt rows sc"))?,
8149                    latent: cmp.latent,
8150                    ratio: cmp.ratio,
8151                    overlap: cmp.overlap,
8152                    n_blocks0: 0,
8153                })
8154            };
8155            let cmp = match &layer.cmp {
8156                Some(c) => Some(mk(c)?),
8157                None => None,
8158            };
8159            let idxc = match &layer.idx {
8160                Some(ix) => Some(mk(&ix.cmp)?),
8161                None => None,
8162            };
8163            for c in cmp.iter().chain(idxc.iter()) {
8164                let slots = if c.overlap { 2 * c.ratio } else { c.ratio };
8165                bytes[st.dev] += ((2 * slots * c.latent + 2 * tmax * c.latent) * 4) as u64;
8166            }
8167            layers.push(LayerCkptDev {
8168                cmp,
8169                idx: idxc,
8170                trans_base: d.sliding_window as usize + cap_blocks,
8171            });
8172        }
8173        for st in &self.stages {
8174            st.gpu.stream().synchronize().map_err(e("vws sync"))?;
8175        }
8176        Ok(VerifyState {
8177            ws,
8178            layers,
8179            tmax,
8180            open: None,
8181            bytes,
8182        })
8183    }
8184
8185    /// Batched bf16 GEMV: y[m, n] = x[m, k] @ W[n, k]^T with the weight row read once.
8186    /// `xstride`/`ystride` in elements (0 == packed) — the grouped output projection is
8187    /// the only caller that needs them.
8188    #[allow(clippy::too_many_arguments)]
8189    fn gemv_m_dev(
8190        st: &Stage,
8191        w: DW,
8192        x_ptr: *const c_void,
8193        y_ptr: *mut f32,
8194        m: usize,
8195        n: usize,
8196        kdim: usize,
8197        xstride: usize,
8198        ystride: usize,
8199    ) -> Res<()> {
8200        let stream = st.gpu.stream();
8201        unsafe {
8202            match w {
8203                DW::Bf16(w_ptr) => ck(
8204                    "gemv_bf16_m dev",
8205                    k::memra_dsv4_gemv_bf16_m(
8206                        w_ptr,
8207                        x_ptr,
8208                        y_ptr,
8209                        m as i32,
8210                        n as i32,
8211                        kdim as i32,
8212                        xstride as i32,
8213                        ystride as i32,
8214                        sp(&stream),
8215                    ),
8216                ),
8217                DW::Fp8 {
8218                    codes,
8219                    scales,
8220                    sc_cols,
8221                } => ck(
8222                    "gemv_fp8_m dev",
8223                    k::memra_dsv4_gemv_fp8_m(
8224                        codes,
8225                        scales,
8226                        sc_cols,
8227                        x_ptr,
8228                        y_ptr,
8229                        m as i32,
8230                        n as i32,
8231                        kdim as i32,
8232                        xstride as i32,
8233                        ystride as i32,
8234                        sp(&stream),
8235                    ),
8236                ),
8237            }
8238        }
8239    }
8240
8241    /// f32 cvt + batched GEMV (the m=T twin of `gemm_dev`).
8242    #[allow(clippy::too_many_arguments)]
8243    fn gemm_m_dev(
8244        st: &Stage,
8245        x_f32: *const f32,
8246        xb: &mut CudaSlice<u8>,
8247        w: DW,
8248        m: usize,
8249        n: usize,
8250        kdim: usize,
8251        y_ptr: *mut f32,
8252    ) -> Res<()> {
8253        let stream = st.gpu.stream();
8254        unsafe {
8255            ck(
8256                "cvt_bf16 m dev",
8257                k::memra_dsv4_cvt_bf16(
8258                    x_f32,
8259                    xb.device_ptr_mut(&stream).0 as *mut c_void,
8260                    (m * kdim) as i64,
8261                    sp(&stream),
8262                ),
8263            )?;
8264        }
8265        Self::gemv_m_dev(
8266            st,
8267            w,
8268            xb.device_ptr(&stream).0 as *const c_void,
8269            y_ptr,
8270            m,
8271            n,
8272            kdim,
8273            0,
8274            0,
8275        )
8276    }
8277
8278    /// Island dots, batched rows, weight row hoisted. Same arm selection as `dots_dev`.
8279    #[allow(clippy::too_many_arguments)]
8280    fn dots_m_dev(
8281        &self,
8282        st: &Stage,
8283        x: *const f32,
8284        w_f32: *const c_void,
8285        w_is_bf16: i32,
8286        s: usize,
8287        kdim: usize,
8288        n: usize,
8289        y: *mut f32,
8290    ) -> Res<()> {
8291        let stream = st.gpu.stream();
8292        unsafe {
8293            if self.dots_f32 {
8294                ck(
8295                    "dots_f32acc_mrow",
8296                    k::memra_dsv4_dots_f32acc_mrow(
8297                        x,
8298                        w_f32,
8299                        w_is_bf16,
8300                        y,
8301                        s as i32,
8302                        kdim as i32,
8303                        n as i32,
8304                        sp(&stream),
8305                    ),
8306                )
8307            } else {
8308                ck(
8309                    "dots_f32_mrow",
8310                    k::memra_dsv4_dots_f32_mrow(
8311                        x,
8312                        w_f32,
8313                        w_is_bf16,
8314                        y,
8315                        s as i32,
8316                        kdim as i32,
8317                        n as i32,
8318                        sp(&stream),
8319                    ),
8320                )
8321            }
8322        }
8323    }
8324}
8325
8326impl Dsv4Gpu {
8327    /// hc_pre for T rows: the `hc_pre_dev` program with every kernel taking the row
8328    /// count (Sinkhorn either the host closure per row — byte-identity arm — or the
8329    /// one-block-per-position device twin).
8330    #[allow(clippy::too_many_arguments)]
8331    fn hc_pre_batch_dev(
8332        &self,
8333        st: &Stage,
8334        h_ptr: *const f32,
8335        fn_w: &CudaSlice<f32>,
8336        base_host: &[f32],
8337        scale_host: &[f32],
8338        base_dev: &CudaSlice<f32>,
8339        scale_dev: &CudaSlice<f32>,
8340        vws: &mut VerifyWs,
8341        t: usize,
8342        hc: usize,
8343        hidden: usize,
8344        iters: u32,
8345        hc_eps: f32,
8346        host_math: bool,
8347    ) -> Res<()> {
8348        let stream = st.gpu.stream();
8349        let w = hc * hidden;
8350        let rows = (2 + hc) * hc;
8351        self.dots_m_dev(
8352            st,
8353            h_ptr,
8354            fn_w.device_ptr(&stream).0 as *const c_void,
8355            0,
8356            t,
8357            w,
8358            rows,
8359            vws.mixes.device_ptr_mut(&stream).0 as *mut f32,
8360        )?;
8361        unsafe {
8362            ck(
8363                "rowsq_scale batch",
8364                self.rowsq_scale_arm(
8365                    h_ptr,
8366                    dpm!(vws.mixes, &stream),
8367                    t as i32,
8368                    w as i32,
8369                    rows as i32,
8370                    hc_eps,
8371                    sp(&stream),
8372                ),
8373            )?;
8374        }
8375        if host_math {
8376            let mut mixes_h = vec![0f32; t * rows];
8377            let view = vws.mixes.slice(0..t * rows);
8378            stream
8379                .memcpy_dtoh(&view, &mut mixes_h[..])
8380                .map_err(e("dtoh mixes batch"))?;
8381            stream.synchronize().map_err(e("sync mixes batch"))?;
8382            let (pre_h, post_h, comb_h) =
8383                hc_split_sinkhorn(&mixes_h, t, hc, scale_host, base_host, iters, hc_eps);
8384            let mut dp = vws.pre.slice_mut(0..t * hc);
8385            stream
8386                .memcpy_htod(&pre_h, &mut dp)
8387                .map_err(e("htod pre b"))?;
8388            let mut dp = vws.post.slice_mut(0..t * hc);
8389            stream
8390                .memcpy_htod(&post_h, &mut dp)
8391                .map_err(e("htod post b"))?;
8392            let mut dp = vws.comb.slice_mut(0..t * hc * hc);
8393            stream
8394                .memcpy_htod(&comb_h, &mut dp)
8395                .map_err(e("htod comb b"))?;
8396        } else {
8397            unsafe {
8398                ck(
8399                    "hc_sinkhorn_m",
8400                    k::memra_dsv4_hc_sinkhorn_m(
8401                        dpf!(vws.mixes, &stream),
8402                        dpf!(scale_dev, &stream),
8403                        dpf!(base_dev, &stream),
8404                        dpm!(vws.pre, &stream),
8405                        dpm!(vws.post, &stream),
8406                        dpm!(vws.comb, &stream),
8407                        t as i32,
8408                        hc as i32,
8409                        iters as i32,
8410                        hc_eps,
8411                        sp(&stream),
8412                    ),
8413                )?;
8414            }
8415        }
8416        unsafe {
8417            ck(
8418                "hc_collapse batch",
8419                k::memra_dsv4_hc_collapse(
8420                    h_ptr,
8421                    dpf!(vws.pre, &stream),
8422                    dpm!(vws.y_hc, &stream),
8423                    t as i32,
8424                    hc as i32,
8425                    hidden as i32,
8426                    sp(&stream),
8427                ),
8428            )?;
8429        }
8430        Ok(())
8431    }
8432
8433    /// Compressor advance for a whole verify round (§3.1): the two projection GEMMs run
8434    /// batched STRAIGHT INTO the checkpoint's row payload (which is both the record and
8435    /// the source of the pending writes — one copy, not two), then the pending state
8436    /// machine + emissions run t = 0..T-1 in POSITION ORDER, exactly the sequential
8437    /// program. The snapshot is taken before the first write.
8438    #[allow(clippy::too_many_arguments)]
8439    fn cmp_decode_batch_dev(
8440        &self,
8441        st: &Stage,
8442        cmp: &CmpDev,
8443        x_ptr: *const f32,
8444        t: usize,
8445        pos0: usize,
8446        hidden: usize,
8447        fc_dev: &CudaSlice<f32>,
8448        rd: usize,
8449        eps: f32,
8450        ck_dev: &mut CmpCkptDev,
8451        emit: &mut CudaSlice<f32>,
8452        shift: &mut CudaSlice<f32>,
8453        pend_kv: &mut CudaSlice<f32>,
8454        pend_score: &mut CudaSlice<f32>,
8455        store: &mut CudaSlice<f32>,
8456        row0: usize,
8457        blocks: &mut usize,
8458    ) -> Res<()> {
8459        let stream = st.gpu.stream();
8460        let (ratio, d, latent) = (cmp.ratio, cmp.d, cmp.latent);
8461        // snapshot + high-water mark BEFORE anything is written
8462        stream
8463            .memcpy_dtod(pend_kv, &mut ck_dev.kv_snap)
8464            .map_err(e("ckpt snap kv"))?;
8465        stream
8466            .memcpy_dtod(pend_score, &mut ck_dev.sc_snap)
8467            .map_err(e("ckpt snap sc"))?;
8468        ck_dev.n_blocks0 = *blocks;
8469        self.dots_m_dev(
8470            st,
8471            x_ptr,
8472            cmp.wkv.device_ptr(&stream).0 as *const c_void,
8473            0,
8474            t,
8475            hidden,
8476            latent,
8477            ck_dev.rows_kv.device_ptr_mut(&stream).0 as *mut f32,
8478        )?;
8479        self.dots_m_dev(
8480            st,
8481            x_ptr,
8482            cmp.wgate.device_ptr(&stream).0 as *const c_void,
8483            0,
8484            t,
8485            hidden,
8486            latent,
8487            ck_dev.rows_sc.device_ptr_mut(&stream).0 as *mut f32,
8488        )?;
8489        for i in 0..t {
8490            let pos = pos0 + i;
8491            let slot = if cmp.overlap {
8492                ratio + pos % ratio
8493            } else {
8494                pos % ratio
8495            };
8496            {
8497                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8498                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8499                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend kv b"))?;
8500                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8501                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8502                stream.memcpy_dtod(&src, &mut dst).map_err(e("pend sc b"))?;
8503            }
8504            if (pos + 1) % ratio != 0 {
8505                continue;
8506            }
8507            let j = pos / ratio;
8508            let nb_launch = if cmp.overlap { 2usize } else { 1 };
8509            let row_off = if cmp.overlap { d } else { 0 };
8510            unsafe {
8511                ck(
8512                    "compressor_pool batch",
8513                    k::memra_dsv4_compressor_pool(
8514                        dpf!(*pend_kv, &stream),
8515                        dpf!(*pend_score, &stream),
8516                        dpf!(cmp.ape, &stream),
8517                        dpm!(*emit, &stream),
8518                        nb_launch as i32,
8519                        ratio as i32,
8520                        d as i32,
8521                        latent as i32,
8522                        cmp.overlap as i32,
8523                        sp(&stream),
8524                    ),
8525                )?;
8526                let row_c = (emit.device_ptr(&stream).0 as usize + row_off * 4) as *const f32;
8527                let row_m = (emit.device_ptr_mut(&stream).0 as usize + row_off * 4) as *mut f32;
8528                ck(
8529                    "rmsnorm batch cmp",
8530                    self.rmsnorm_arm(
8531                        row_c,
8532                        dpf!(cmp.norm, &stream),
8533                        row_m,
8534                        1,
8535                        d as i32,
8536                        eps,
8537                        sp(&stream),
8538                    ),
8539                )?;
8540                ck(
8541                    "rope_at batch cmp",
8542                    k::memra_dsv4_rope_at(
8543                        row_m,
8544                        1,
8545                        d as i32,
8546                        rd as i32,
8547                        dpf!(fc_dev, &stream),
8548                        (j * ratio) as i32,
8549                        0,
8550                        sp(&stream),
8551                    ),
8552                )?;
8553                if cmp.rotate {
8554                    let scale = (d as f32).powf(-0.5);
8555                    ck(
8556                        "hadamard batch cmp",
8557                        k::memra_dsv4_hadamard(row_m, 1, d as i32, scale, sp(&stream)),
8558                    )?;
8559                    ck(
8560                        "fp4 batch cmp",
8561                        k::memra_dsv4_fp4_act_quant(row_m, 1, d as i64, d as i32, sp(&stream)),
8562                    )?;
8563                } else {
8564                    ck(
8565                        "act_quant batch cmp",
8566                        k::memra_dsv4_act_quant(
8567                            row_m,
8568                            1,
8569                            d as i64,
8570                            (d - rd) as i32,
8571                            64,
8572                            (self.variant == ActQuantVariant::ClampOnly) as i32,
8573                            sp(&stream),
8574                        ),
8575                    )?;
8576                }
8577            }
8578            {
8579                let src = emit.slice(row_off..row_off + d);
8580                let mut dst = store.slice_mut((row0 + j) * d..(row0 + j + 1) * d);
8581                stream
8582                    .memcpy_dtod(&src, &mut dst)
8583                    .map_err(e("emit store b"))?;
8584            }
8585            if cmp.overlap {
8586                {
8587                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8588                    let mut dst = shift.slice_mut(0..ratio * latent);
8589                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift1"))?;
8590                }
8591                {
8592                    let src = shift.slice(0..ratio * latent);
8593                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8594                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift2"))?;
8595                }
8596                {
8597                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8598                    let mut dst = shift.slice_mut(0..ratio * latent);
8599                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift3"))?;
8600                }
8601                {
8602                    let src = shift.slice(0..ratio * latent);
8603                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8604                    stream.memcpy_dtod(&src, &mut dst).map_err(e("bshift4"))?;
8605                }
8606            }
8607            *blocks = j + 1;
8608        }
8609        Ok(())
8610    }
8611
8612    /// §3.1 compressor rollback: restore the snapshot, then REPLAY the committed
8613    /// positions' row writes + cur->prev shifts + block accounting. Emitted store rows
8614    /// of the committed prefix are kept as the round wrote them (bit-identical to the
8615    /// sequential twin — the batch advanced the pending in position order, so every
8616    /// emission pooled the same inputs). The CPU oracle's `rollback_replay`, verbatim.
8617    #[allow(clippy::too_many_arguments)]
8618    fn cmp_rollback_replay_dev(
8619        &self,
8620        st: &Stage,
8621        ck_dev: &CmpCkptDev,
8622        n_commit: usize,
8623        t: usize,
8624        pos0: usize,
8625        shift: &mut CudaSlice<f32>,
8626        pend_kv: &mut CudaSlice<f32>,
8627        pend_score: &mut CudaSlice<f32>,
8628        blocks: &mut usize,
8629    ) -> Res<()> {
8630        if n_commit == t {
8631            return Ok(()); // fully committed: the in-place batch state is already exact
8632        }
8633        let stream = st.gpu.stream();
8634        let (ratio, latent, overlap) = (ck_dev.ratio, ck_dev.latent, ck_dev.overlap);
8635        stream
8636            .memcpy_dtod(&ck_dev.kv_snap, pend_kv)
8637            .map_err(e("rb kv snap"))?;
8638        stream
8639            .memcpy_dtod(&ck_dev.sc_snap, pend_score)
8640            .map_err(e("rb sc snap"))?;
8641        *blocks = ck_dev.n_blocks0;
8642        for i in 0..n_commit {
8643            let pos = pos0 + i;
8644            let slot = if overlap {
8645                ratio + pos % ratio
8646            } else {
8647                pos % ratio
8648            };
8649            {
8650                let src = ck_dev.rows_kv.slice(i * latent..(i + 1) * latent);
8651                let mut dst = pend_kv.slice_mut(slot * latent..(slot + 1) * latent);
8652                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row kv"))?;
8653                let src = ck_dev.rows_sc.slice(i * latent..(i + 1) * latent);
8654                let mut dst = pend_score.slice_mut(slot * latent..(slot + 1) * latent);
8655                stream.memcpy_dtod(&src, &mut dst).map_err(e("rb row sc"))?;
8656            }
8657            if (pos + 1) % ratio != 0 {
8658                continue;
8659            }
8660            if overlap {
8661                {
8662                    let src = pend_kv.slice(ratio * latent..2 * ratio * latent);
8663                    let mut dst = shift.slice_mut(0..ratio * latent);
8664                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift1"))?;
8665                }
8666                {
8667                    let src = shift.slice(0..ratio * latent);
8668                    let mut dst = pend_kv.slice_mut(0..ratio * latent);
8669                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift2"))?;
8670                }
8671                {
8672                    let src = pend_score.slice(ratio * latent..2 * ratio * latent);
8673                    let mut dst = shift.slice_mut(0..ratio * latent);
8674                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift3"))?;
8675                }
8676                {
8677                    let src = shift.slice(0..ratio * latent);
8678                    let mut dst = pend_score.slice_mut(0..ratio * latent);
8679                    stream.memcpy_dtod(&src, &mut dst).map_err(e("rbshift4"))?;
8680                }
8681            }
8682            *blocks += 1;
8683        }
8684        Ok(())
8685    }
8686}
8687
8688impl Dsv4Gpu {
8689    /// One trunk block, BATCHED T-position verify (§3.1). Positions pos0..pos0+t-1,
8690    /// tokens `toks`. Input h is vws.h_a (or vws.h_rx right after a stage boundary);
8691    /// output lands in vws.h_a. Window-ring writes go to the layer's TRANSIENT kvc rows
8692    /// and every query's index list is built with the redirect, so the persistent ring
8693    /// is read-only for the whole round.
8694    #[allow(clippy::too_many_arguments)]
8695    fn block_verify_dev(
8696        &self,
8697        st: &Stage,
8698        layer: &LayerDev,
8699        cache: &mut LayerCache,
8700        lck: &mut LayerCkptDev,
8701        vws: &mut VerifyWs,
8702        input_rx: bool,
8703        pos0: usize,
8704        t: usize,
8705        toks: &[u32],
8706        host_math: bool,
8707    ) -> Res<()> {
8708        let d = self.model.cfg();
8709        let mc = &self.model.mc;
8710        let hc = d.hc_mult as usize;
8711        let hidden = mc.n_embd as usize;
8712        let heads = mc.n_head as usize;
8713        let hd = d.head_dim as usize;
8714        let rd = d.qk_rope_head_dim as usize;
8715        let q_lora = d.q_lora_rank as usize;
8716        let win = d.sliding_window as usize;
8717        let o_groups = d.o_groups as usize;
8718        let o_lora = d.o_lora_rank as usize;
8719        let eps = mc.rms_eps;
8720        let iters = d.hc_sinkhorn_iters;
8721        let hc_eps = d.hc_eps;
8722        let stream = st.gpu.stream();
8723        let fc_dev: *const f32 = if layer.ratio != 0 {
8724            st.fc_yarn.device_ptr(&stream).0 as *const f32
8725        } else {
8726            st.fc_plain.device_ptr(&stream).0 as *const f32
8727        };
8728        let clamp_only = (self.variant == ActQuantVariant::ClampOnly) as i32;
8729        let trans_base = lck.trans_base;
8730        let LayerCache {
8731            kvc,
8732            n_blocks,
8733            pend_kv,
8734            pend_score,
8735            ikvc,
8736            i_blocks,
8737            ipend_kv,
8738            ipend_score,
8739        } = cache;
8740
8741        // ---- attention sub-block
8742        let h_in_ptr: *const f32 = if input_rx {
8743            vws.h_rx.device_ptr(&stream).0 as *const f32
8744        } else {
8745            vws.h_a.device_ptr(&stream).0 as *const f32
8746        };
8747        self.hc_pre_batch_dev(
8748            st,
8749            h_in_ptr,
8750            &layer.hc_attn_fn,
8751            &layer.hc_attn_base,
8752            &layer.hc_attn_scale,
8753            &layer.hc_attn_base_dev,
8754            &layer.hc_attn_scale_dev,
8755            vws,
8756            t,
8757            hc,
8758            hidden,
8759            iters,
8760            hc_eps,
8761            host_math,
8762        )?;
8763        unsafe {
8764            ck(
8765                "rmsnorm attn batch",
8766                self.rmsnorm_arm(
8767                    dpf!(vws.y_hc, &stream),
8768                    dpf!(layer.attn_norm, &stream),
8769                    dpm!(vws.x, &stream),
8770                    t as i32,
8771                    hidden as i32,
8772                    eps,
8773                    sp(&stream),
8774                ),
8775            )?;
8776        }
8777
8778        // q path (weights read once for all t rows)
8779        Self::gemm_m_dev(
8780            st,
8781            vws.x.device_ptr(&stream).0 as *const f32,
8782            &mut vws.gemm_xb,
8783            dwsel(self.dense_fp8, &stream, &layer.wq_a, &layer.wq_a_fp8),
8784            t,
8785            q_lora,
8786            hidden,
8787            vws.qr.device_ptr_mut(&stream).0 as *mut f32,
8788        )?;
8789        unsafe {
8790            ck(
8791                "rmsnorm q batch",
8792                self.rmsnorm_arm(
8793                    dpf!(vws.qr, &stream),
8794                    dpf!(layer.q_norm, &stream),
8795                    dpm!(vws.qr, &stream),
8796                    t as i32,
8797                    q_lora as i32,
8798                    eps,
8799                    sp(&stream),
8800                ),
8801            )?;
8802            ck(
8803                "cvt qr batch",
8804                k::memra_dsv4_cvt_bf16(
8805                    dpf!(vws.qr, &stream),
8806                    vws.qr_b.device_ptr_mut(&stream).0 as *mut c_void,
8807                    (t * q_lora) as i64,
8808                    sp(&stream),
8809                ),
8810            )?;
8811        }
8812        Self::gemv_m_dev(
8813            st,
8814            dwsel(self.dense_fp8, &stream, &layer.wq_b, &layer.wq_b_fp8),
8815            vws.qr_b.device_ptr(&stream).0 as *const c_void,
8816            vws.q.device_ptr_mut(&stream).0 as *mut f32,
8817            t,
8818            heads * hd,
8819            q_lora,
8820            0,
8821            0,
8822        )?;
8823        unsafe {
8824            ck(
8825                "headrms batch",
8826                self.headrms_arm(
8827                    dpm!(vws.q, &stream),
8828                    (t * heads) as i32,
8829                    hd as i32,
8830                    eps,
8831                    sp(&stream),
8832                ),
8833            )?;
8834            ck(
8835                "rope q batch",
8836                k::memra_dsv4_rope(
8837                    dpm!(vws.q, &stream),
8838                    t as i32,
8839                    heads as i32,
8840                    hd as i32,
8841                    rd as i32,
8842                    fc_dev,
8843                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8844                    0,
8845                    sp(&stream),
8846                ),
8847            )?;
8848        }
8849
8850        // shared K==V latent rows + window QAT, then the TRANSIENT ring write
8851        Self::gemm_m_dev(
8852            st,
8853            vws.x.device_ptr(&stream).0 as *const f32,
8854            &mut vws.gemm_xb,
8855            dwsel(self.dense_fp8, &stream, &layer.wkv, &layer.wkv_fp8),
8856            t,
8857            hd,
8858            hidden,
8859            vws.kv.device_ptr_mut(&stream).0 as *mut f32,
8860        )?;
8861        unsafe {
8862            ck(
8863                "rmsnorm kv batch",
8864                self.rmsnorm_arm(
8865                    dpf!(vws.kv, &stream),
8866                    dpf!(layer.kv_norm, &stream),
8867                    dpm!(vws.kv, &stream),
8868                    t as i32,
8869                    hd as i32,
8870                    eps,
8871                    sp(&stream),
8872                ),
8873            )?;
8874            ck(
8875                "rope kv batch",
8876                k::memra_dsv4_rope(
8877                    dpm!(vws.kv, &stream),
8878                    t as i32,
8879                    1,
8880                    hd as i32,
8881                    rd as i32,
8882                    fc_dev,
8883                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
8884                    0,
8885                    sp(&stream),
8886                ),
8887            )?;
8888            ck(
8889                "act_quant kv batch",
8890                k::memra_dsv4_act_quant(
8891                    dpm!(vws.kv, &stream),
8892                    t as i32,
8893                    hd as i64,
8894                    (hd - rd) as i32,
8895                    64,
8896                    clamp_only,
8897                    sp(&stream),
8898                ),
8899            )?;
8900        }
8901        {
8902            let src = vws.kv.slice(0..t * hd);
8903            let mut dst = kvc.slice_mut(trans_base * hd..(trans_base + t) * hd);
8904            stream
8905                .memcpy_dtod(&src, &mut dst)
8906                .map_err(e("transient ring write"))?;
8907        }
8908
8909        // ---- per-position index lists (redirected) + compressor advances
8910        let mut slots = win;
8911        if layer.ratio != 0 {
8912            let ratio = layer.ratio;
8913            // the round's per-position block counts (host arithmetic, exactly the
8914            // sequential program's `(pos+1)/ratio`)
8915            let nbs: Vec<usize> = (0..t).map(|i| (pos0 + i + 1) / ratio).collect();
8916            if let Some(ix) = &layer.idx {
8917                // indexer q, batched
8918                Self::gemv_m_dev(
8919                    st,
8920                    dwsel(self.dense_fp8, &stream, &ix.wq_b, &ix.wq_b_fp8),
8921                    vws.qr_b.device_ptr(&stream).0 as *const c_void,
8922                    vws.qi.device_ptr_mut(&stream).0 as *mut f32,
8923                    t,
8924                    ix.heads * ix.hd,
8925                    q_lora,
8926                    0,
8927                    0,
8928                )?;
8929                unsafe {
8930                    ck(
8931                        "rope qi batch",
8932                        k::memra_dsv4_rope(
8933                            dpm!(vws.qi, &stream),
8934                            t as i32,
8935                            ix.heads as i32,
8936                            ix.hd as i32,
8937                            rd as i32,
8938                            fc_dev,
8939                            vws.pos_dev.device_ptr(&stream).0 as *const i32,
8940                            0,
8941                            sp(&stream),
8942                        ),
8943                    )?;
8944                    let scale = (ix.hd as f32).powf(-0.5);
8945                    ck(
8946                        "hadamard qi batch",
8947                        k::memra_dsv4_hadamard(
8948                            dpm!(vws.qi, &stream),
8949                            (t * ix.heads) as i32,
8950                            ix.hd as i32,
8951                            scale,
8952                            sp(&stream),
8953                        ),
8954                    )?;
8955                    ck(
8956                        "fp4 qi batch",
8957                        k::memra_dsv4_fp4_act_quant(
8958                            dpm!(vws.qi, &stream),
8959                            (t * ix.heads) as i32,
8960                            ix.hd as i64,
8961                            ix.hd as i32,
8962                            sp(&stream),
8963                        ),
8964                    )?;
8965                }
8966                // indexer weights projection, batched
8967                Self::gemm_m_dev(
8968                    st,
8969                    vws.x.device_ptr(&stream).0 as *const f32,
8970                    &mut vws.gemm_xb,
8971                    dwsel(
8972                        self.dense_fp8,
8973                        &stream,
8974                        &ix.weights_proj,
8975                        &ix.weights_proj_fp8,
8976                    ),
8977                    t,
8978                    ix.heads,
8979                    hidden,
8980                    vws.wproj.device_ptr_mut(&stream).0 as *mut f32,
8981                )?;
8982                // indexer compressor: batched projections + position-ordered state machine
8983                {
8984                    let VerifyWs {
8985                        x,
8986                        cmp_emit,
8987                        cmp_shift,
8988                        ..
8989                    } = vws;
8990                    self.cmp_decode_batch_dev(
8991                        st,
8992                        &ix.cmp,
8993                        x.device_ptr(&stream).0 as *const f32,
8994                        t,
8995                        pos0,
8996                        hidden,
8997                        &st.fc_yarn,
8998                        rd,
8999                        eps,
9000                        lck.idx.as_mut().expect("idx ckpt"),
9001                        cmp_emit,
9002                        cmp_shift,
9003                        ipend_kv.as_mut().expect("ipend"),
9004                        ipend_score.as_mut().expect("ipend"),
9005                        ikvc.as_mut().expect("ikvc"),
9006                        0,
9007                        i_blocks,
9008                    )?;
9009                }
9010                debug_assert_eq!(*i_blocks, nbs[t - 1], "indexer block count (batch)");
9011                let kks: Vec<usize> = nbs.iter().map(|&nb| ix.topk.min(nb)).collect();
9012                let tail_max = kks.iter().cloned().max().unwrap_or(0);
9013                slots = win + tail_max;
9014                for i in 0..t {
9015                    let pos = pos0 + i;
9016                    let idx_off = i * vws.idx_stride;
9017                    unsafe {
9018                        ck(
9019                            "build_idx_redirect fine",
9020                            k::memra_dsv4_build_idx_redirect(
9021                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9022                                    as *mut i32,
9023                                pos as i32,
9024                                win as i32,
9025                                0, // fine layers: -1 pads over the whole tail; top-k overwrites
9026                                slots as i32,
9027                                pos0 as i32,
9028                                trans_base as i32,
9029                                sp(&stream),
9030                            ),
9031                        )?;
9032                    }
9033                    let nb = nbs[i];
9034                    if nb == 0 {
9035                        continue;
9036                    }
9037                    let wscale = ((ix.hd as f64).powf(-0.5) * (ix.heads as f64).powf(-0.5)) as f32;
9038                    unsafe {
9039                        ck(
9040                            "indexer_score batch",
9041                            self.indexer_score_arm(
9042                                (vws.qi.device_ptr(&stream).0 as usize + i * ix.heads * ix.hd * 4)
9043                                    as *const f32,
9044                                dpf!(ikvc.as_ref().expect("ikvc"), &stream),
9045                                (vws.wproj.device_ptr(&stream).0 as usize + i * ix.heads * 4)
9046                                    as *const f32,
9047                                wscale,
9048                                dpm!(vws.score, &stream),
9049                                1,
9050                                ix.heads as i32,
9051                                ix.hd as i32,
9052                                nb as i32,
9053                                ratio as i32,
9054                                nb as i32,
9055                                sp(&stream),
9056                            ),
9057                        )?;
9058                    }
9059                    let kk = kks[i];
9060                    if host_math {
9061                        let score_h = {
9062                            let view = vws.score.slice(0..nb);
9063                            let mut v = vec![0f32; nb];
9064                            stream
9065                                .memcpy_dtoh(&view, &mut v[..])
9066                                .map_err(e("dtoh sc b"))?;
9067                            stream.synchronize().map_err(e("sync sc b"))?;
9068                            v
9069                        };
9070                        let mut order: Vec<usize> = (0..nb).collect();
9071                        order.sort_by(|&a, &b| {
9072                            score_h[b]
9073                                .partial_cmp(&score_h[a])
9074                                .unwrap_or(std::cmp::Ordering::Equal)
9075                                .then(a.cmp(&b))
9076                        });
9077                        let cidx: Vec<i32> = order
9078                            .into_iter()
9079                            .take(kk)
9080                            .map(|j| (j + win) as i32)
9081                            .collect();
9082                        let mut dst = vws.idx.slice_mut(idx_off + win..idx_off + win + kk);
9083                        stream
9084                            .memcpy_htod(&cidx, &mut dst)
9085                            .map_err(e("htod idx b"))?;
9086                    } else {
9087                        unsafe {
9088                            let idx_tail_ptr = (vws.idx.device_ptr_mut(&stream).0 as usize
9089                                + (idx_off + win) * 4)
9090                                as *mut i32;
9091                            ck(
9092                                "topk_idx batch",
9093                                k::memra_dsv4_topk_idx(
9094                                    dpf!(vws.score, &stream),
9095                                    nb as i32,
9096                                    kk as i32,
9097                                    win as i32,
9098                                    idx_tail_ptr,
9099                                    sp(&stream),
9100                                ),
9101                            )?;
9102                        }
9103                    }
9104                }
9105            } else {
9106                let tail_max = nbs.iter().cloned().max().unwrap_or(0);
9107                slots = win + tail_max;
9108                for (i, &nb_i) in nbs.iter().enumerate() {
9109                    let pos = pos0 + i;
9110                    let idx_off = i * vws.idx_stride;
9111                    unsafe {
9112                        ck(
9113                            "build_idx_redirect coarse",
9114                            k::memra_dsv4_build_idx_redirect(
9115                                (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4)
9116                                    as *mut i32,
9117                                pos as i32,
9118                                win as i32,
9119                                nb_i as i32,
9120                                slots as i32,
9121                                pos0 as i32,
9122                                trans_base as i32,
9123                                sp(&stream),
9124                            ),
9125                        )?;
9126                    }
9127                }
9128            }
9129            // attention compressor: batched projections + position-ordered state machine
9130            {
9131                let VerifyWs {
9132                    x,
9133                    cmp_emit,
9134                    cmp_shift,
9135                    ..
9136                } = vws;
9137                self.cmp_decode_batch_dev(
9138                    st,
9139                    layer.cmp.as_ref().expect("ratio!=0 has compressor"),
9140                    x.device_ptr(&stream).0 as *const f32,
9141                    t,
9142                    pos0,
9143                    hidden,
9144                    &st.fc_yarn,
9145                    rd,
9146                    eps,
9147                    lck.cmp.as_mut().expect("cmp ckpt"),
9148                    cmp_emit,
9149                    cmp_shift,
9150                    pend_kv.as_mut().expect("pend"),
9151                    pend_score.as_mut().expect("pend"),
9152                    kvc,
9153                    win,
9154                    n_blocks,
9155                )?;
9156            }
9157            debug_assert_eq!(*n_blocks, nbs[t - 1], "attn block count (batch)");
9158        } else {
9159            for i in 0..t {
9160                let pos = pos0 + i;
9161                let idx_off = i * vws.idx_stride;
9162                unsafe {
9163                    ck(
9164                        "build_idx_redirect window-only",
9165                        k::memra_dsv4_build_idx_redirect(
9166                            (vws.idx.device_ptr_mut(&stream).0 as usize + idx_off * 4) as *mut i32,
9167                            pos as i32,
9168                            win as i32,
9169                            -1,
9170                            win as i32,
9171                            pos0 as i32,
9172                            trans_base as i32,
9173                            sp(&stream),
9174                        ),
9175                    )?;
9176                }
9177            }
9178        }
9179
9180        // sparse sink attention, T queries in one launch (uniform `slots`, -1 pads —
9181        // bit-inert by the pinned pad contract) + per-position de-rotation
9182        let scale = (hd as f64).powf(-0.5) as f32;
9183        unsafe {
9184            if self.chains_f32 {
9185                ck(
9186                    "sink_attn_dec_mq_f32acc",
9187                    k::memra_dsv4_sink_attn_dec_mq_f32acc(
9188                        dpf!(vws.q, &stream),
9189                        dpf!(kvc, &stream),
9190                        vws.idx.device_ptr(&stream).0 as *const i32,
9191                        dpf!(layer.sink, &stream),
9192                        dpm!(vws.sink_scores, &stream),
9193                        dpm!(vws.sink_evals, &stream),
9194                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f32,
9195                        dpm!(vws.o, &stream),
9196                        t as i32,
9197                        heads as i32,
9198                        hd as i32,
9199                        slots as i32,
9200                        vws.idx_stride as i32,
9201                        scale,
9202                        sp(&stream),
9203                    ),
9204                )?;
9205            } else {
9206                ck(
9207                    "sink_attn_dec_mq",
9208                    k::memra_dsv4_sink_attn_dec_mq(
9209                        dpf!(vws.q, &stream),
9210                        dpf!(kvc, &stream),
9211                        vws.idx.device_ptr(&stream).0 as *const i32,
9212                        dpf!(layer.sink, &stream),
9213                        dpm!(vws.sink_scores, &stream),
9214                        dpm!(vws.sink_evals, &stream),
9215                        vws.sink_den.device_ptr_mut(&stream).0 as *mut f64,
9216                        dpm!(vws.o, &stream),
9217                        t as i32,
9218                        heads as i32,
9219                        hd as i32,
9220                        slots as i32,
9221                        vws.idx_stride as i32,
9222                        scale,
9223                        sp(&stream),
9224                    ),
9225                )?;
9226            }
9227            ck(
9228                "rope o inv batch",
9229                k::memra_dsv4_rope(
9230                    dpm!(vws.o, &stream),
9231                    t as i32,
9232                    heads as i32,
9233                    hd as i32,
9234                    rd as i32,
9235                    fc_dev,
9236                    vws.pos_dev.device_ptr(&stream).0 as *const i32,
9237                    1,
9238                    sp(&stream),
9239                ),
9240            )?;
9241        }
9242
9243        // grouped output projection: cvt o once, then per-group strided batched GEMVs
9244        let gw = heads / o_groups * hd;
9245        unsafe {
9246            ck(
9247                "cvt o batch",
9248                k::memra_dsv4_cvt_bf16(
9249                    dpf!(vws.o, &stream),
9250                    vws.o_b.device_ptr_mut(&stream).0 as *mut c_void,
9251                    (t * heads * hd) as i64,
9252                    sp(&stream),
9253                ),
9254            )?;
9255        }
9256        let wo_a_dw = dwsel(self.dense_fp8, &stream, &layer.wo_a, &layer.wo_a_fp8);
9257        for g in 0..o_groups {
9258            Self::gemv_m_dev(
9259                st,
9260                wo_a_dw.offset_rows(g * o_lora, gw),
9261                (vws.o_b.device_ptr(&stream).0 as usize + g * gw * 2) as *const c_void,
9262                (vws.og.device_ptr_mut(&stream).0 as usize + g * o_lora * 4) as *mut f32,
9263                t,
9264                o_lora,
9265                gw,
9266                heads * hd,
9267                o_groups * o_lora,
9268            )?;
9269        }
9270        Self::gemm_m_dev(
9271            st,
9272            vws.og.device_ptr(&stream).0 as *const f32,
9273            &mut vws.gemm_xb,
9274            dwsel(self.dense_fp8, &stream, &layer.wo_b, &layer.wo_b_fp8),
9275            t,
9276            hidden,
9277            o_groups * o_lora,
9278            vws.attn_out.device_ptr_mut(&stream).0 as *mut f32,
9279        )?;
9280
9281        // hc_post (attention) -> vws.h_b
9282        unsafe {
9283            ck(
9284                "hc_post attn batch",
9285                k::memra_dsv4_hc_post(
9286                    dpf!(vws.attn_out, &stream),
9287                    h_in_ptr,
9288                    dpf!(vws.post, &stream),
9289                    dpf!(vws.comb, &stream),
9290                    dpm!(vws.h_b, &stream),
9291                    t as i32,
9292                    hc as i32,
9293                    hidden as i32,
9294                    sp(&stream),
9295                ),
9296            )?;
9297        }
9298
9299        // ---- ffn sub-block (input vws.h_b, output vws.h_a)
9300        let h_b_ptr = vws.h_b.device_ptr(&stream).0 as *const f32;
9301        self.hc_pre_batch_dev(
9302            st,
9303            h_b_ptr,
9304            &layer.hc_ffn_fn,
9305            &layer.hc_ffn_base,
9306            &layer.hc_ffn_scale,
9307            &layer.hc_ffn_base_dev,
9308            &layer.hc_ffn_scale_dev,
9309            vws,
9310            t,
9311            hc,
9312            hidden,
9313            iters,
9314            hc_eps,
9315            host_math,
9316        )?;
9317        unsafe {
9318            ck(
9319                "rmsnorm ffn batch",
9320                self.rmsnorm_arm(
9321                    dpf!(vws.y_hc, &stream),
9322                    dpf!(layer.ffn_norm, &stream),
9323                    dpm!(vws.xf, &stream),
9324                    t as i32,
9325                    hidden as i32,
9326                    eps,
9327                    sp(&stream),
9328                ),
9329            )?;
9330        }
9331        self.moe_verify_dev(st, layer, vws, t, toks, host_math)?;
9332        unsafe {
9333            ck(
9334                "hc_post ffn batch",
9335                k::memra_dsv4_hc_post(
9336                    dpf!(vws.y, &stream),
9337                    dpf!(vws.h_b, &stream),
9338                    dpf!(vws.post, &stream),
9339                    dpf!(vws.comb, &stream),
9340                    dpm!(vws.h_a, &stream),
9341                    t as i32,
9342                    hc as i32,
9343                    hidden as i32,
9344                    sp(&stream),
9345                ),
9346            )?;
9347        }
9348        Ok(())
9349    }
9350
9351    /// MoE for T rows: per-position routing (the hash layers need the per-position TOKEN,
9352    /// which is why a round carries a token array), then ONE launch per projection over
9353    /// the whole T x topk slot set — routed-expert weight traffic scales with T (each
9354    /// position's experts are its own) while the shared expert and the gate amortize.
9355    fn moe_verify_dev(
9356        &self,
9357        st: &Stage,
9358        layer: &LayerDev,
9359        vws: &mut VerifyWs,
9360        t: usize,
9361        toks: &[u32],
9362        host_math: bool,
9363    ) -> Res<()> {
9364        let mc = &self.model.mc;
9365        let d = self.model.cfg();
9366        let moe = mc.moe.as_ref().expect("moe");
9367        let hidden = mc.n_embd as usize;
9368        let ne = moe.expert_count as usize;
9369        let topk = moe.expert_used_count as usize;
9370        let inter = moe.expert_ff_length as usize;
9371        let limit = d.swiglu_limit;
9372        let stream = st.gpu.stream();
9373        let kind = match layer.expert_kind {
9374            ExpertKind::Nvfp4 => 0i32,
9375            ExpertKind::Mxfp4 => 1i32,
9376        };
9377        let wstride = (inter * hidden / 2) as i64;
9378        let sstride = match layer.expert_kind {
9379            ExpertKind::Nvfp4 => (inter * hidden / 16) as i64,
9380            ExpertKind::Mxfp4 => (inter * hidden / 32) as i64,
9381        };
9382        let slots = t * topk;
9383
9384        self.dots_m_dev(
9385            st,
9386            vws.xf.device_ptr(&stream).0 as *const f32,
9387            layer.gate_w.device_ptr(&stream).0 as *const c_void,
9388            0,
9389            t,
9390            hidden,
9391            ne,
9392            vws.raw.device_ptr_mut(&stream).0 as *mut f32,
9393        )?;
9394        if host_math {
9395            let raw_h = {
9396                let view = vws.raw.slice(0..t * ne);
9397                let mut v = vec![0f32; t * ne];
9398                stream
9399                    .memcpy_dtoh(&view, &mut v[..])
9400                    .map_err(e("dtoh raw b"))?;
9401                stream.synchronize().map_err(e("sync raw b"))?;
9402                v
9403            };
9404            let (indices, weights) =
9405                Self::route_host(layer, &raw_h, toks, t, ne, topk, d.routed_scaling_factor);
9406            let sel: Vec<i32> = indices.iter().map(|&x| x as i32).collect();
9407            let mut order = vec![0i32; t * topk];
9408            for p in 0..t {
9409                let mut o: Vec<i32> = (0..topk as i32).collect();
9410                o.sort_by_key(|&s| indices[p * topk + s as usize]);
9411                order[p * topk..(p + 1) * topk].copy_from_slice(&o);
9412            }
9413            let mut dst = vws.sel.slice_mut(0..t * topk);
9414            stream
9415                .memcpy_htod(&sel, &mut dst)
9416                .map_err(e("htod sel b"))?;
9417            let mut dst = vws.selw.slice_mut(0..t * topk);
9418            stream
9419                .memcpy_htod(&weights, &mut dst)
9420                .map_err(e("htod selw b"))?;
9421            let mut dst = vws.order.slice_mut(0..t * topk);
9422            stream
9423                .memcpy_htod(&order, &mut dst)
9424                .map_err(e("htod order b"))?;
9425        } else {
9426            unsafe {
9427                ck(
9428                    "route_m",
9429                    k::memra_dsv4_route_m(
9430                        dpf!(vws.raw, &stream),
9431                        layer
9432                            .gate_bias_dev
9433                            .as_ref()
9434                            .map(|b| b.device_ptr(&stream).0 as *const f32)
9435                            .unwrap_or(std::ptr::null()),
9436                        layer
9437                            .tid2eid_dev
9438                            .as_ref()
9439                            .map(|x| x.device_ptr(&stream).0 as *const i32)
9440                            .unwrap_or(std::ptr::null()),
9441                        vws.tok.device_ptr(&stream).0 as *const i32,
9442                        t as i32,
9443                        ne as i32,
9444                        topk as i32,
9445                        d.routed_scaling_factor,
9446                        vws.sel.device_ptr_mut(&stream).0 as *mut i32,
9447                        vws.selw.device_ptr_mut(&stream).0 as *mut f32,
9448                        vws.order.device_ptr_mut(&stream).0 as *mut i32,
9449                        sp(&stream),
9450                    ),
9451                )?;
9452            }
9453        }
9454
9455        unsafe {
9456            ck(
9457                "act_quant_fp8 x batch",
9458                k::memra_dsv4_act_quant_fp8(
9459                    dpf!(vws.xf, &stream),
9460                    vws.xq.device_ptr_mut(&stream).0 as *mut c_void,
9461                    dpm!(vws.xs, &stream),
9462                    t as i32,
9463                    hidden as i32,
9464                    sp(&stream),
9465                ),
9466            )?;
9467            for (proj, dst) in [(0i32, &mut vws.g1), (2i32, &mut vws.g3)] {
9468                ck(
9469                    "fp4_gemm_sel_g w1/w3",
9470                    k::memra_dsv4_fp4_gemm_sel_g(
9471                        dp!(vws.xq, &stream),
9472                        dpf!(vws.xs, &stream),
9473                        dp!(layer.experts_w, &stream),
9474                        dp!(layer.experts_sc, &stream),
9475                        dpf!(layer.experts_s2_dev, &stream),
9476                        vws.sel.device_ptr(&stream).0 as *const i32,
9477                        proj,
9478                        0,
9479                        kind,
9480                        dpm!(*dst, &stream),
9481                        slots as i32,
9482                        inter as i32,
9483                        hidden as i32,
9484                        wstride,
9485                        sstride,
9486                        topk as i32,
9487                        sp(&stream),
9488                    ),
9489                )?;
9490            }
9491            ck(
9492                "swiglu batch",
9493                k::memra_dsv4_swiglu(
9494                    dpf!(vws.g1, &stream),
9495                    dpf!(vws.g3, &stream),
9496                    dpm!(vws.hbuf, &stream),
9497                    slots as i32,
9498                    inter as i32,
9499                    limit,
9500                    vws.selw.device_ptr(&stream).0 as *const f32,
9501                    sp(&stream),
9502                ),
9503            )?;
9504            ck(
9505                "act_quant_fp8 h batch",
9506                k::memra_dsv4_act_quant_fp8(
9507                    dpf!(vws.hbuf, &stream),
9508                    vws.hq.device_ptr_mut(&stream).0 as *mut c_void,
9509                    dpm!(vws.hs, &stream),
9510                    slots as i32,
9511                    inter as i32,
9512                    sp(&stream),
9513                ),
9514            )?;
9515            ck(
9516                "fp4_gemm_sel_g w2",
9517                k::memra_dsv4_fp4_gemm_sel_g(
9518                    dp!(vws.hq, &stream),
9519                    dpf!(vws.hs, &stream),
9520                    dp!(layer.experts_w, &stream),
9521                    dp!(layer.experts_sc, &stream),
9522                    dpf!(layer.experts_s2_dev, &stream),
9523                    vws.sel.device_ptr(&stream).0 as *const i32,
9524                    1,
9525                    1,
9526                    kind,
9527                    dpm!(vws.contrib, &stream),
9528                    slots as i32,
9529                    hidden as i32,
9530                    inter as i32,
9531                    wstride,
9532                    sstride,
9533                    0,
9534                    sp(&stream),
9535                ),
9536            )?;
9537            ck(
9538                "combine_rows_m",
9539                k::memra_dsv4_combine_rows_m(
9540                    dpf!(vws.contrib, &stream),
9541                    vws.order.device_ptr(&stream).0 as *const i32,
9542                    topk as i32,
9543                    dpm!(vws.y, &stream),
9544                    hidden as i64,
9545                    t as i32,
9546                    sp(&stream),
9547                ),
9548            )?;
9549            ck(
9550                "cvt xb batch",
9551                k::memra_dsv4_cvt_bf16(
9552                    dpf!(vws.xf, &stream),
9553                    vws.xb.device_ptr_mut(&stream).0 as *mut c_void,
9554                    (t * hidden) as i64,
9555                    sp(&stream),
9556                ),
9557            )?;
9558        }
9559        let sh_inter = vws.sg1.len() / vws.tmax;
9560        Self::gemv_m_dev(
9561            st,
9562            dwsel(
9563                self.dense_fp8,
9564                &stream,
9565                &layer.shared_w[0],
9566                &layer.shared_fp8[0],
9567            ),
9568            vws.xb.device_ptr(&stream).0 as *const c_void,
9569            vws.sg1.device_ptr_mut(&stream).0 as *mut f32,
9570            t,
9571            sh_inter,
9572            hidden,
9573            0,
9574            0,
9575        )?;
9576        Self::gemv_m_dev(
9577            st,
9578            dwsel(
9579                self.dense_fp8,
9580                &stream,
9581                &layer.shared_w[2],
9582                &layer.shared_fp8[2],
9583            ),
9584            vws.xb.device_ptr(&stream).0 as *const c_void,
9585            vws.sg3.device_ptr_mut(&stream).0 as *mut f32,
9586            t,
9587            sh_inter,
9588            hidden,
9589            0,
9590            0,
9591        )?;
9592        unsafe {
9593            ck(
9594                "swiglu sh batch",
9595                k::memra_dsv4_swiglu(
9596                    dpf!(vws.sg1, &stream),
9597                    dpf!(vws.sg3, &stream),
9598                    dpm!(vws.shbuf, &stream),
9599                    t as i32,
9600                    sh_inter as i32,
9601                    limit,
9602                    std::ptr::null(),
9603                    sp(&stream),
9604                ),
9605            )?;
9606            ck(
9607                "cvt sh batch",
9608                k::memra_dsv4_cvt_bf16(
9609                    dpf!(vws.shbuf, &stream),
9610                    vws.shb16.device_ptr_mut(&stream).0 as *mut c_void,
9611                    (t * sh_inter) as i64,
9612                    sp(&stream),
9613                ),
9614            )?;
9615        }
9616        Self::gemv_m_dev(
9617            st,
9618            dwsel(
9619                self.dense_fp8,
9620                &stream,
9621                &layer.shared_w[1],
9622                &layer.shared_fp8[1],
9623            ),
9624            vws.shb16.device_ptr(&stream).0 as *const c_void,
9625            vws.sh_out.device_ptr_mut(&stream).0 as *mut f32,
9626            t,
9627            hidden,
9628            sh_inter,
9629            0,
9630            0,
9631        )?;
9632        unsafe {
9633            ck(
9634                "add shared batch",
9635                k::memra_dsv4_add_inplace(
9636                    dpm!(vws.y, &stream),
9637                    dpf!(vws.sh_out, &stream),
9638                    (t * hidden) as i64,
9639                    sp(&stream),
9640                ),
9641            )?;
9642        }
9643        Ok(())
9644    }
9645
9646    /// Head for T rows: the `head_logits_dev` program with the row count, and the vocab
9647    /// dots on the batched island kernel so the 1.06 GiB head slab is read ONCE per round
9648    /// instead of once per verified position.
9649    fn head_logits_batch_dev(&self, vws: &mut VerifyWs, t: usize, host_math: bool) -> Res<()> {
9650        let d = self.model.cfg();
9651        let mc = &self.model.mc;
9652        let hc = d.hc_mult as usize;
9653        let hidden = mc.n_embd as usize;
9654        let eps = mc.rms_eps;
9655        let last = self.stages.len() - 1;
9656        let st = &self.stages[last];
9657        let stream = st.gpu.stream();
9658        let w = hc * hidden;
9659        let fn_w = st.hc_head_fn.as_ref().expect("hc_head_fn");
9660        let norm = st.trunk_norm.as_ref().expect("trunk norm");
9661        let vocab = vws.logits.len() / vws.tmax;
9662        self.dots_m_dev(
9663            st,
9664            vws.h_a.device_ptr(&stream).0 as *const f32,
9665            fn_w.device_ptr(&stream).0 as *const c_void,
9666            0,
9667            t,
9668            w,
9669            hc,
9670            vws.head_mixes.device_ptr_mut(&stream).0 as *mut f32,
9671        )?;
9672        unsafe {
9673            ck(
9674                "rowsq head batch",
9675                self.rowsq_scale_arm(
9676                    dpf!(vws.h_a, &stream),
9677                    dpm!(vws.head_mixes, &stream),
9678                    t as i32,
9679                    w as i32,
9680                    hc as i32,
9681                    eps,
9682                    sp(&stream),
9683                ),
9684            )?;
9685        }
9686        if host_math {
9687            let mut mixes_h = vec![0f32; t * hc];
9688            let view = vws.head_mixes.slice(0..t * hc);
9689            stream
9690                .memcpy_dtoh(&view, &mut mixes_h[..])
9691                .map_err(e("dtoh head mixes b"))?;
9692            stream.synchronize().map_err(e("sync head mixes b"))?;
9693            for p in 0..t {
9694                for c in 0..hc {
9695                    let m = mixes_h[p * hc + c];
9696                    mixes_h[p * hc + c] =
9697                        sigmoid_f32(m * self.hc_head_scale[0] + self.hc_head_base[c]) + d.hc_eps;
9698                }
9699            }
9700            let mut dst = vws.head_pre.slice_mut(0..t * hc);
9701            stream
9702                .memcpy_htod(&mixes_h, &mut dst)
9703                .map_err(e("htod head pre b"))?;
9704        } else {
9705            unsafe {
9706                ck(
9707                    "hc_head_pre_m",
9708                    k::memra_dsv4_hc_head_pre_m(
9709                        dpf!(vws.head_mixes, &stream),
9710                        st.hc_head_scale_dev
9711                            .as_ref()
9712                            .expect("head scale dev")
9713                            .device_ptr(&stream)
9714                            .0 as *const f32,
9715                        st.hc_head_base_dev
9716                            .as_ref()
9717                            .expect("head base dev")
9718                            .device_ptr(&stream)
9719                            .0 as *const f32,
9720                        dpm!(vws.head_pre, &stream),
9721                        t as i32,
9722                        hc as i32,
9723                        d.hc_eps,
9724                        sp(&stream),
9725                    ),
9726                )?;
9727            }
9728        }
9729        unsafe {
9730            ck(
9731                "hc_collapse head batch",
9732                k::memra_dsv4_hc_collapse(
9733                    dpf!(vws.h_a, &stream),
9734                    dpf!(vws.head_pre, &stream),
9735                    dpm!(vws.collapsed, &stream),
9736                    t as i32,
9737                    hc as i32,
9738                    hidden as i32,
9739                    sp(&stream),
9740                ),
9741            )?;
9742            ck(
9743                "rmsnorm head batch",
9744                self.rmsnorm_arm(
9745                    dpf!(vws.collapsed, &stream),
9746                    dpf!(norm, &stream),
9747                    dpm!(vws.collapsed, &stream),
9748                    t as i32,
9749                    hidden as i32,
9750                    eps,
9751                    sp(&stream),
9752                ),
9753            )?;
9754        }
9755        let head_ptr = st.head.as_ref().expect("head").device_ptr(&stream).0 as *const c_void;
9756        self.dots_m_dev(
9757            st,
9758            vws.collapsed.device_ptr(&stream).0 as *const f32,
9759            head_ptr,
9760            1,
9761            t,
9762            hidden,
9763            vocab,
9764            vws.logits.device_ptr_mut(&stream).0 as *mut f32,
9765        )?;
9766        Ok(())
9767    }
9768}
9769
9770/// One verify round's bookkeeping (the device twin of `spec_oracle::SpecRound`).
9771pub struct SpecRoundGpu {
9772    pub start_pos: usize,
9773    pub drafts: Vec<u32>,
9774    pub accepts: usize,
9775    pub verified: usize,
9776    /// batch depth actually forwarded this round (T = 1 + verifiable drafts)
9777    pub t_batch: usize,
9778    /// STRUCTURAL depth ceiling for this round: min(k_drafts + 1, MEMRA_DSV4_SPEC_DEPTH,
9779    /// vstate.tmax) -- i.e. `t_batch` before the n_new budget is applied. `t_batch < t_cap`
9780    /// is exactly "the budget truncated this round", which is what `carry_pending` keys on.
9781    pub t_cap: usize,
9782    /// The drafter's fp32 per-slot confidence for this round's proposal (pre-sigmoid
9783    /// logits; the head is supervised on c* = 1 - TV, i.e. conditional acceptance
9784    /// probability). Banked per round so the DSpark Algorithm-1 scheduler can be scored
9785    /// offline against measured round costs -- never consumed by the round itself.
9786    pub confidence: Vec<f32>,
9787    /// tokens this round contributed to the output stream (head + accepted drafts)
9788    pub emitted: usize,
9789    /// wall time of the whole round — proposal, batched verify, commit/rollback, drafter
9790    /// ring advance — with the drafter stream synchronized at the round boundary so no
9791    /// work leaks into the next round's measurement. The A/B instrument.
9792    pub round_us: u64,
9793}
9794
9795pub struct SpecRunGpu {
9796    pub tokens: Vec<u32>,
9797    pub rounds: Vec<SpecRoundGpu>,
9798}
9799
9800impl Dsv4Gpu {
9801    /// Batched T=k+1 verify forward (§3.1): ONE trunk pass over `toks` at positions
9802    /// state.pos .. state.pos+T-1, logits for EVERY position (the accept walk needs them
9803    /// all), state advanced PROVISIONALLY for all T. Exactly one
9804    /// [`Self::commit_verify_dev`] must follow, which makes the accepted prefix permanent
9805    /// and rolls the rest back. The DSpark trunk tap is written for all T rows when
9806    /// `taps` is Some (rows 0..T-1 of the drafter's taps buffer).
9807    ///
9808    /// Returns (logits `[T, vocab]` when `want_logits`, per-position argmax `[T]`).
9809    pub fn verify_batch_dev(
9810        &self,
9811        toks: &[u32],
9812        state: &mut DecodeState,
9813        vstate: &mut VerifyState,
9814        taps: Option<&mut CudaSlice<f32>>,
9815        want_logits: bool,
9816    ) -> Res<(Option<Vec<f32>>, Vec<u32>)> {
9817        let DecodePath::Device { host_math } = self.decode_path else {
9818            return Err("verify_batch_dev requires MEMRA_DSV4_DECODE_PATH=device".into());
9819        };
9820        let mc = &self.model.mc;
9821        let d = self.model.cfg();
9822        let t = toks.len();
9823        assert!(
9824            t >= 1 && t <= vstate.tmax,
9825            "round depth {t} > tmax {}",
9826            vstate.tmax
9827        );
9828        assert!(vstate.open.is_none(), "verify_batch_dev with an open round");
9829        let pos0 = state.pos;
9830        assert!(pos0 > 0, "batched verify needs prefill_with_cache first");
9831        assert!(
9832            pos0 + t <= self.max_seq,
9833            "round [{pos0}, {}) exceeds max_seq {}",
9834            pos0 + t,
9835            self.max_seq
9836        );
9837        let hidden = mc.n_embd as usize;
9838        let hc = d.hc_mult as usize;
9839        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
9840        let tok_i32: Vec<i32> = toks.iter().map(|&x| x as i32).collect();
9841        let pos_i32: Vec<i32> = (0..t).map(|i| (pos0 + i) as i32).collect();
9842
9843        // per-stage round constants (the hash layers read the token array; every layer's
9844        // ropes read the position array — both live on whichever stage the layer does)
9845        for (si, st) in self.stages.iter().enumerate() {
9846            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx round"))?;
9847            let stream = st.gpu.stream();
9848            let vws = &mut vstate.ws[si];
9849            let mut dst = vws.tok.slice_mut(0..t);
9850            stream
9851                .memcpy_htod(&tok_i32, &mut dst)
9852                .map_err(e("htod tok round"))?;
9853            let mut dst = vws.pos_dev.slice_mut(0..t);
9854            stream
9855                .memcpy_htod(&pos_i32, &mut dst)
9856                .map_err(e("htod pos round"))?;
9857        }
9858
9859        // stage 0: tokens -> embed rows -> hc state
9860        {
9861            let st0 = &self.stages[0];
9862            st0.gpu.ctx.bind_to_thread().map_err(e("bind ctx0 round"))?;
9863            let stream0 = st0.gpu.stream();
9864            let vws0 = &mut vstate.ws[0];
9865            unsafe {
9866                ck(
9867                    "embed_rows batch",
9868                    k::memra_dsv4_embed_rows(
9869                        st0.embed
9870                            .as_ref()
9871                            .expect("embed on stage 0")
9872                            .device_ptr(&stream0)
9873                            .0 as *const c_void,
9874                        vws0.tok.device_ptr(&stream0).0 as *const i32,
9875                        dpm!(vws0.emb, &stream0),
9876                        t as i32,
9877                        hidden as i32,
9878                        sp(&stream0),
9879                    ),
9880                )?;
9881                ck(
9882                    "repeat_hc batch",
9883                    k::memra_dsv4_repeat_hc(
9884                        dpf!(vws0.emb, &stream0),
9885                        dpm!(vws0.h_a, &stream0),
9886                        t as i32,
9887                        hc as i32,
9888                        hidden as i32,
9889                        sp(&stream0),
9890                    ),
9891                )?;
9892            }
9893        }
9894
9895        let targets = self.dspark.as_ref().map(|ds| ds.targets.clone());
9896        let n_t = targets.as_ref().map(|x| x.len()).unwrap_or(0);
9897        let mut taps = taps;
9898        let mut cur_stage = 0usize;
9899        let mut input_rx = false;
9900        for il in 0..n_trunk {
9901            let stage = self.layer_stage[il];
9902            if stage != cur_stage {
9903                let bytes = t * hc * hidden * std::mem::size_of::<f32>();
9904                let src_stream = self.stages[cur_stage].gpu.stream();
9905                let dst_stream = self.stages[stage].gpu.stream();
9906                let (ws_src, ws_dst) = vstate.ws.split_at_mut(stage);
9907                let src_ws = &ws_src[cur_stage];
9908                let dst_ws = &mut ws_dst[0];
9909                self.stages[cur_stage]
9910                    .gpu
9911                    .ctx
9912                    .bind_to_thread()
9913                    .map_err(e("bind tx round"))?;
9914                let (sp_, _g0) = src_ws.h_a.device_ptr(&src_stream);
9915                let (dp_, _g1) = dst_ws.h_rx.device_ptr_mut(&src_stream);
9916                unsafe {
9917                    cudarc::driver::result::memcpy_peer_async(
9918                        self.stages[stage].gpu.ctx.cu_ctx(),
9919                        dp_,
9920                        self.stages[cur_stage].gpu.ctx.cu_ctx(),
9921                        sp_,
9922                        bytes,
9923                        src_stream.cu_stream(),
9924                    )
9925                    .map_err(e("peer copy h round"))?;
9926                }
9927                let bnd = stage - 1;
9928                self.boundary_ev[bnd]
9929                    .record(&src_stream)
9930                    .map_err(e("ev record round"))?;
9931                dst_stream
9932                    .wait(&self.boundary_ev[bnd])
9933                    .map_err(e("ev wait round"))?;
9934                self.stages[stage]
9935                    .gpu
9936                    .ctx
9937                    .bind_to_thread()
9938                    .map_err(e("bind rx round"))?;
9939                cur_stage = stage;
9940                input_rx = true;
9941            }
9942            let st = &self.stages[stage];
9943            let lidx = st
9944                .layers
9945                .iter()
9946                .position(|l| l.il == il as u32)
9947                .unwrap_or_else(|| panic!("layer {il} not on stage {stage}"));
9948            self.block_verify_dev(
9949                st,
9950                &st.layers[lidx],
9951                &mut state.caches[il],
9952                &mut vstate.layers[il],
9953                &mut vstate.ws[stage],
9954                input_rx,
9955                pos0,
9956                t,
9957                toks,
9958                host_math,
9959            )?;
9960            input_rx = false;
9961            // DSpark trunk tap for all T rows (capture only)
9962            if let (Some(tp), Some(tg)) = (taps.as_mut(), targets.as_ref()) {
9963                if let Some(kk) = tg.iter().position(|&tl| tl == il) {
9964                    let stream = self.stages[stage].gpu.stream();
9965                    let vws = &mut vstate.ws[stage];
9966                    unsafe {
9967                        ck(
9968                            "hc_mean tap batch",
9969                            k::memra_dsv4_hc_mean(
9970                                dpf!(vws.h_a, &stream),
9971                                dpm!(vws.tap_tmp, &stream),
9972                                t as i32,
9973                                hc as i32,
9974                                hidden as i32,
9975                                sp(&stream),
9976                            ),
9977                        )?;
9978                        ck(
9979                            "place_cols tap batch",
9980                            k::memra_dsv4_place_cols(
9981                                dpf!(vws.tap_tmp, &stream),
9982                                dpm!(**tp, &stream),
9983                                t as i32,
9984                                hidden as i32,
9985                                (n_t * hidden) as i64,
9986                                (kk * hidden) as i64,
9987                                sp(&stream),
9988                            ),
9989                        )?;
9990                    }
9991                }
9992            }
9993        }
9994
9995        let last = self.stages.len() - 1;
9996        assert_eq!(cur_stage, last, "device path expects the head stage last");
9997        self.head_logits_batch_dev(&mut vstate.ws[last], t, host_math)?;
9998        let stream_last = self.stages[last].gpu.stream();
9999        let vws = &mut vstate.ws[last];
10000        let vocab = vws.logits.len() / vws.tmax;
10001        let logits = if want_logits {
10002            let mut v = vec![0f32; t * vocab];
10003            let view = vws.logits.slice(0..t * vocab);
10004            stream_last
10005                .memcpy_dtoh(&view, &mut v[..])
10006                .map_err(e("dtoh logits batch"))?;
10007            stream_last.synchronize().map_err(e("sync logits batch"))?;
10008            Some(v)
10009        } else {
10010            None
10011        };
10012        let mut am = vec![0i32; t];
10013        if let Some(lg) = &logits {
10014            for (i, slot) in am.iter_mut().enumerate() {
10015                let row = &lg[i * vocab..(i + 1) * vocab];
10016                let mut best = 0usize;
10017                for j in 1..vocab {
10018                    if row[j] > row[best] {
10019                        best = j;
10020                    }
10021                }
10022                *slot = best as i32;
10023            }
10024        } else {
10025            unsafe {
10026                for i in 0..t {
10027                    ck(
10028                        "argmax batch",
10029                        k::memra_dsv4_argmax(
10030                            (vws.logits.device_ptr(&stream_last).0 as usize + i * vocab * 4)
10031                                as *const f32,
10032                            vocab as i64,
10033                            (vws.argmax.device_ptr_mut(&stream_last).0 as usize + i * 4)
10034                                as *mut i32,
10035                            sp(&stream_last),
10036                        ),
10037                    )?;
10038                }
10039            }
10040            let view = vws.argmax.slice(0..t);
10041            stream_last
10042                .memcpy_dtoh(&view, &mut am[..])
10043                .map_err(e("dtoh argmax batch"))?;
10044            stream_last.synchronize().map_err(e("sync argmax batch"))?;
10045        }
10046        vstate.open = Some((pos0, t));
10047        Ok((logits, am.into_iter().map(|x| x as u32).collect()))
10048    }
10049
10050    /// Commit the first `n_commit` positions of the open round and roll the rest back
10051    /// (§3.1 invariant: every trunk cache class ends bit-identical to plain sequential
10052    /// decode of exactly the committed positions). Ring slots take their transient rows;
10053    /// the compressors replay; the append-only stores fall back to their high-water mark.
10054    pub fn commit_verify_dev(
10055        &self,
10056        state: &mut DecodeState,
10057        vstate: &mut VerifyState,
10058        n_commit: usize,
10059    ) -> Res<()> {
10060        let (pos0, t) = vstate
10061            .open
10062            .take()
10063            .ok_or_else(|| "commit_verify_dev without an open round".to_string())?;
10064        assert!(
10065            n_commit >= 1 && n_commit <= t,
10066            "commit {n_commit} outside round width {t}"
10067        );
10068        let d = self.model.cfg();
10069        let mc = &self.model.mc;
10070        let hd = d.head_dim as usize;
10071        let win = d.sliding_window as usize;
10072        let n_trunk = (mc.n_layer - mc.nextn_predict_layers) as usize;
10073        let slot_rows: Vec<i32> = (0..n_commit).map(|j| ((pos0 + j) % win) as i32).collect();
10074        for il in 0..n_trunk {
10075            let stage = self.layer_stage[il];
10076            let st = &self.stages[stage];
10077            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx commit"))?;
10078            let stream = st.gpu.stream();
10079            let lck = &mut vstate.layers[il];
10080            let vws = &mut vstate.ws[stage];
10081            let cache = &mut state.caches[il];
10082            let trans_base = lck.trans_base;
10083            // ring commit: bounce out the transient rows (same allocation as the ring),
10084            // then scatter to slot (pos0+j) % win in one launch
10085            {
10086                let src = cache
10087                    .kvc
10088                    .slice(trans_base * hd..(trans_base + n_commit) * hd);
10089                let mut dst = vws.bounce.slice_mut(0..n_commit * hd);
10090                stream
10091                    .memcpy_dtod(&src, &mut dst)
10092                    .map_err(e("commit bounce"))?;
10093            }
10094            {
10095                let mut dst = vws.slot_rows.slice_mut(0..n_commit);
10096                stream
10097                    .memcpy_htod(&slot_rows, &mut dst)
10098                    .map_err(e("htod slot rows"))?;
10099            }
10100            unsafe {
10101                ck(
10102                    "scatter_rows commit",
10103                    k::memra_dsv4_scatter_rows(
10104                        dpf!(vws.bounce, &stream),
10105                        dpm!(cache.kvc, &stream),
10106                        vws.slot_rows.device_ptr(&stream).0 as *const i32,
10107                        n_commit as i32,
10108                        hd as i32,
10109                        sp(&stream),
10110                    ),
10111                )?;
10112            }
10113            if let Some(ckd) = &lck.cmp {
10114                self.cmp_rollback_replay_dev(
10115                    st,
10116                    ckd,
10117                    n_commit,
10118                    t,
10119                    pos0,
10120                    &mut vws.cmp_shift,
10121                    cache.pend_kv.as_mut().expect("pend kv"),
10122                    cache.pend_score.as_mut().expect("pend sc"),
10123                    &mut cache.n_blocks,
10124                )?;
10125            }
10126            if let Some(ckd) = &lck.idx {
10127                self.cmp_rollback_replay_dev(
10128                    st,
10129                    ckd,
10130                    n_commit,
10131                    t,
10132                    pos0,
10133                    &mut vws.cmp_shift,
10134                    cache.ipend_kv.as_mut().expect("ipend kv"),
10135                    cache.ipend_score.as_mut().expect("ipend sc"),
10136                    &mut cache.i_blocks,
10137                )?;
10138            }
10139        }
10140        for st in &self.stages {
10141            st.gpu
10142                .ctx
10143                .bind_to_thread()
10144                .map_err(e("bind ctx commit sync"))?;
10145            st.gpu.stream().synchronize().map_err(e("commit sync"))?;
10146        }
10147        state.pos = pos0 + n_commit;
10148        Ok(())
10149    }
10150
10151    /// The device propose-then-verify greedy loop with BATCHED verification — the
10152    /// engine-side twin of `spec_oracle::run_spec_greedy_batched`, including its
10153    /// round/budget accounting (the budget-truncated final round and its pending-carry
10154    /// no-propose tail), so proposal streams and token streams are comparable
10155    /// item-for-item with the CPU oracle's.
10156    ///
10157    /// Greedy law: the trunk's own argmax is ALWAYS the emitted token, so the output
10158    /// stream is plain greedy by construction — and because every batched kernel on this
10159    /// path is bit-exact against its single-position twin, that identity is byte-exact on
10160    /// device too, not merely mathematical.
10161    /// Reads the `MEMRA_DSV4_SPEC_DEPTH` knob and delegates to
10162    /// [`Self::spec_greedy_batched_depth`]. Every existing gate and bench calls this form,
10163    /// so their behaviour is decided by the environment exactly as before.
10164    pub fn spec_greedy_batched_with(
10165        &self,
10166        prompt: &[u32],
10167        n_new: usize,
10168        state: &mut DecodeState,
10169        dstate: &mut DsparkState,
10170        vstate: &mut VerifyState,
10171    ) -> Res<SpecRunGpu> {
10172        // MEMRA_DSV4_SPEC_DEPTH=T: structural cap on the batched verify depth (T rows =
10173        // 1 head + T-1 verified drafts). Unset or 0 => no cap, which reproduces the
10174        // pre-knob driver exactly. Clamped to >= 1 so a typo cannot ask for a zero-row
10175        // verify.
10176        let depth_cap = std::env::var("MEMRA_DSV4_SPEC_DEPTH")
10177            .ok()
10178            .and_then(|v| v.trim().parse::<usize>().ok())
10179            .filter(|t| *t > 0)
10180            .unwrap_or(usize::MAX)
10181            .max(1);
10182        if depth_cap != usize::MAX {
10183            println!("[spec] verify depth capped at T={depth_cap} (MEMRA_DSV4_SPEC_DEPTH)");
10184        }
10185        self.spec_greedy_batched_depth(prompt, n_new, state, dstate, vstate, depth_cap)
10186    }
10187
10188    /// [`Self::spec_greedy_batched_with`] with the verify-depth ceiling passed explicitly.
10189    /// `usize::MAX` means "no cap" (the drafter's own `block_size + 1`).
10190    ///
10191    /// Greedy identity is preserved at every cap by construction: truncating the proposal
10192    /// only shortens the accepted prefix, and the head token of every round is the trunk's
10193    /// own argmax. That is what makes a depth sweep measurable without re-earning the
10194    /// identity law at each rung -- though the sweep still asserts it per arm.
10195    pub fn spec_greedy_batched_depth(
10196        &self,
10197        prompt: &[u32],
10198        n_new: usize,
10199        state: &mut DecodeState,
10200        dstate: &mut DsparkState,
10201        vstate: &mut VerifyState,
10202        depth_cap: usize,
10203    ) -> Res<SpecRunGpu> {
10204        // ds4f rung 1: confidence-window policy, read once per run (see resolve_vt).
10205        // Off reproduces the pre-policy t_cap expression exactly (vt_drafts == k_drafts).
10206        let vt = resolve_vt(
10207            std::env::var("MEMRA_DSV4_VT").ok().as_deref(),
10208            std::env::var("MEMRA_DSV4_VT_TAU").ok().as_deref(),
10209            std::env::var("MEMRA_DSV4_VT_FLOOR").ok().as_deref(),
10210        )?;
10211        self.spec_greedy_batched_policy(prompt, n_new, state, dstate, vstate, depth_cap, vt)
10212    }
10213
10214    /// [`Self::spec_greedy_batched_depth`] with the vt policy passed EXPLICITLY — the
10215    /// in-process multi-arm sweep entry (one load, one thermal window; the env seam
10216    /// stays the serving/gate path). `Dsv4Vt::Off` + the same depth_cap is
10217    /// byte-identical to the env path with `MEMRA_DSV4_VT` unset.
10218    #[allow(clippy::too_many_arguments)]
10219    #[allow(clippy::too_many_arguments)]
10220    pub fn spec_greedy_batched_policy(
10221        &self,
10222        prompt: &[u32],
10223        n_new: usize,
10224        state: &mut DecodeState,
10225        dstate: &mut DsparkState,
10226        vstate: &mut VerifyState,
10227        depth_cap: usize,
10228        vt: Dsv4Vt,
10229    ) -> Res<SpecRunGpu> {
10230        self.spec_greedy_batched_stream(prompt, n_new, state, dstate, vstate, depth_cap, vt, None)
10231    }
10232
10233    /// ds4f rung 3 — [`Self::spec_greedy_batched_policy`] with a per-round COMMIT
10234    /// callback: `round_cb` receives every newly committed token slice after the
10235    /// round's ring writes + close sync (i.e. the tokens are final), and returning
10236    /// `false` stops generation at that round boundary — the serve door's streaming,
10237    /// EOS/stop-string, and client-disconnect cancel all ride this one seam. `None`
10238    /// is byte-identical to the gated driver (the closure is never constructed).
10239    #[allow(clippy::too_many_arguments)]
10240    pub fn spec_greedy_batched_stream(
10241        &self,
10242        prompt: &[u32],
10243        n_new: usize,
10244        state: &mut DecodeState,
10245        dstate: &mut DsparkState,
10246        vstate: &mut VerifyState,
10247        depth_cap: usize,
10248        vt: Dsv4Vt,
10249        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10250    ) -> Res<SpecRunGpu> {
10251        let p0 = prompt.len();
10252        assert!(n_new >= 1, "n_new must be positive");
10253        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10254        let mut t_tok = {
10255            let lg = &pre.logits;
10256            let mut best = 0usize;
10257            for i in 1..lg.len() {
10258                if lg[i] > lg[best] {
10259                    best = i;
10260                }
10261            }
10262            best as u32
10263        };
10264        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10265        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10266        let mut mh_row = 0usize; // taps row holding the tap of the position behind `t_tok`
10267        let mut carry_pending = false;
10268        // MEMRA_DSV4_BENCH_PROFILE=1: bracket steady-state ROUNDS [4, 12) with
10269        // cudaProfilerStart/Stop so `nsys profile -c cudaProfilerApi` captures only
10270        // rounds — no load, no prefill/prime, no warmup. Read ONCE (never per round).
10271        // Profiling runs are rung-0 instruments, never A/B observations.
10272        let profile_bracket = std::env::var("MEMRA_DSV4_BENCH_PROFILE").as_deref() == Ok("1");
10273        let depth_cap = depth_cap.max(1);
10274        if let Dsv4Vt::Slot { tau_logit, floor } = vt {
10275            println!(
10276                "[spec] vt policy: slot (tau_logit {tau_logit:.6}, floor {floor}) — \
10277                 per-round verify window from the confidence head"
10278            );
10279        }
10280        while tokens.len() < n_new {
10281            if profile_bracket && rounds.len() == 4 {
10282                cudarc::driver::safe::profiler_start().map_err(e("profiler_start"))?;
10283            }
10284            if profile_bracket && rounds.len() == 12 {
10285                cudarc::driver::safe::profiler_stop().map_err(e("profiler_stop"))?;
10286            }
10287            let cb_from = tokens.len();
10288            if carry_pending {
10289                tokens.push(t_tok);
10290                if let Some(cb) = round_cb.as_deref_mut() {
10291                    cb(&tokens[cb_from..]);
10292                }
10293                break;
10294            }
10295            let round_t0 = std::time::Instant::now();
10296            let prof_stream = if dsv4_prof_on() {
10297                Some(self.stages[self.stages.len() - 1].gpu.stream())
10298            } else {
10299                None
10300            };
10301            let _p_round = phase!("round", prof_stream.as_ref());
10302            let m0 = p0 + tokens.len();
10303            let prop = {
10304                let _p = phase!("1.drafter_forward", prof_stream.as_ref());
10305                self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?
10306            };
10307            let k_drafts = prop.out_ids.len() - 1;
10308            tokens.push(t_tok);
10309            if tokens.len() == n_new {
10310                rounds.push(SpecRoundGpu {
10311                    start_pos: m0 - 1,
10312                    drafts: prop.out_ids[1..].to_vec(),
10313                    accepts: 0,
10314                    verified: 0,
10315                    t_batch: 0,
10316                    t_cap: 0,
10317                    confidence: prop.confidence.clone(),
10318                    emitted: 1,
10319                    round_us: round_t0.elapsed().as_micros() as u64,
10320                });
10321                if let Some(cb) = round_cb.as_deref_mut() {
10322                    cb(&tokens[cb_from..]);
10323                }
10324                break;
10325            }
10326            let forwards_left = n_new - tokens.len();
10327            // STRUCTURAL ceiling (drafts available / depth knob / vt window /
10328            // verify-state capacity), then the n_new BUDGET on top. Keeping them
10329            // separate is what lets the depth knob (and the vt window, which is a
10330            // per-round depth) shorten a round without it looking like "we ran out of
10331            // tokens" — carry_pending below fires on the BUDGET only.
10332            let vt_drafts = match vt {
10333                Dsv4Vt::Off => k_drafts,
10334                Dsv4Vt::Slot { tau_logit, floor } => {
10335                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10336                }
10337            };
10338            let t_cap = (vt_drafts + 1)
10339                .min(k_drafts + 1)
10340                .min(depth_cap)
10341                .min(vstate.tmax);
10342            let t_batch = t_cap.min(forwards_left);
10343            let kv = t_batch - 1;
10344            let mut batch_ids = Vec::with_capacity(t_batch);
10345            batch_ids.push(t_tok);
10346            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10347            let (_, am) = {
10348                let _p = phase!("2.verify_batch", prof_stream.as_ref());
10349                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), false)?
10350            };
10351            // accept walk: row i (position m0+i) arbitrates draft i+1
10352            let mut c_d = 0usize;
10353            let mut t_next = 0u32;
10354            for i in 0..t_batch {
10355                let a = am[i];
10356                if i < kv && a == batch_ids[i + 1] {
10357                    c_d += 1;
10358                    continue;
10359                }
10360                t_next = a;
10361                break;
10362            }
10363            let n_commit = c_d + 1;
10364            {
10365                let _p = phase!("3.commit_rollback", prof_stream.as_ref());
10366                self.commit_verify_dev(state, vstate, n_commit)?;
10367            }
10368            // drafter rings advance for EVERY accepted position and no rejected one
10369            {
10370                let _p = phase!("4.ring_writes", prof_stream.as_ref());
10371                for i in 0..n_commit {
10372                    self.dspark_write_rings(dstate, i, m0 + i)?;
10373                }
10374            }
10375            {
10376                let _p = phase!("5.round_close_sync", None);
10377                // close the round on device too, so the ring advance is inside THIS
10378                // round's measurement and not the next one's
10379                let last = self.stages.len() - 1;
10380                self.stages[last]
10381                    .gpu
10382                    .stream()
10383                    .synchronize()
10384                    .map_err(e("round close sync"))?;
10385            }
10386            mh_row = c_d;
10387            for i in 0..c_d {
10388                tokens.push(batch_ids[i + 1]);
10389            }
10390            // Carry (= stop after emitting the bonus token) only when the n_new BUDGET
10391            // truncated this round -- never when the depth knob did. Identical to the old
10392            // `kv < k_drafts` whenever the knob is unset and vstate.tmax >= k_drafts + 1.
10393            carry_pending = c_d == kv && t_batch < t_cap;
10394            rounds.push(SpecRoundGpu {
10395                start_pos: m0 - 1,
10396                drafts: prop.out_ids[1..].to_vec(),
10397                accepts: c_d,
10398                verified: (c_d + 1).min(kv),
10399                t_batch,
10400                t_cap,
10401                confidence: prop.confidence.clone(),
10402                emitted: 1 + c_d,
10403                round_us: round_t0.elapsed().as_micros() as u64,
10404            });
10405            t_tok = t_next;
10406            if let Some(cb) = round_cb.as_deref_mut() {
10407                if !cb(&tokens[cb_from..]) {
10408                    break;
10409                }
10410            }
10411        }
10412        Ok(SpecRunGpu { tokens, rounds })
10413    }
10414
10415    /// [`Self::spec_greedy_batched_with`] with freshly allocated state (gate shape).
10416    pub fn spec_greedy_batched(&self, prompt: &[u32], n_new: usize) -> Res<SpecRunGpu> {
10417        let mut state = self.alloc_decode_state()?;
10418        let mut dstate = self.dspark_alloc_state()?;
10419        let mut vstate = self.alloc_verify_state()?;
10420        self.spec_greedy_batched_with(prompt, n_new, &mut state, &mut dstate, &mut vstate)
10421    }
10422
10423    /// ds4f rung 2 (slice 1) — the SAMPLED propose-then-verify loop (it5 item 8).
10424    ///
10425    /// A deliberate near-copy of [`Self::spec_greedy_batched_policy`] with the accept
10426    /// walk arbitrated by POSITION-KEYED seeded target draws instead of argmax — the
10427    /// gated greedy driver's bytes are not touched (its accept-sha receipts stay the
10428    /// witness; a shared parameterized loop would put those bytes at refactor risk for
10429    /// zero measurement gain). Identity law: the emitted stream equals the plain
10430    /// sampled stream at the same seed BY CONSTRUCTION — row i of the batched verify
10431    /// is bit-exact against the sequential step's row at the same position (the it3
10432    /// gate (c) proof) and [`dsv4_sample_row`] is a pure function of (row, pos, seed).
10433    /// The drafter proposes greedily (deterministic one-hot proposal); a draft is
10434    /// accepted iff it EQUALS the target draw at its position — the correct
10435    /// arbitration for a one-hot proposal (full min(1, p/q) rejection sampling
10436    /// degenerates to exactly this when q is one-hot). Penalties are slice 2 and NOT
10437    /// claimed here.
10438    #[allow(clippy::too_many_arguments)]
10439    pub fn spec_sampled_batched_policy(
10440        &self,
10441        prompt: &[u32],
10442        n_new: usize,
10443        state: &mut DecodeState,
10444        dstate: &mut DsparkState,
10445        vstate: &mut VerifyState,
10446        depth_cap: usize,
10447        vt: Dsv4Vt,
10448        sample: &Dsv4SampleCfg,
10449    ) -> Res<SpecRunGpu> {
10450        self.spec_sampled_batched_stream(
10451            prompt, n_new, state, dstate, vstate, depth_cap, vt, sample, None,
10452        )
10453    }
10454
10455    /// [`Self::spec_sampled_batched_policy`] with the rung-3 per-round commit callback
10456    /// (see [`Self::spec_greedy_batched_stream`] — same seam, same None-is-byte-identical
10457    /// contract).
10458    #[allow(clippy::too_many_arguments)]
10459    pub fn spec_sampled_batched_stream(
10460        &self,
10461        prompt: &[u32],
10462        n_new: usize,
10463        state: &mut DecodeState,
10464        dstate: &mut DsparkState,
10465        vstate: &mut VerifyState,
10466        depth_cap: usize,
10467        vt: Dsv4Vt,
10468        sample: &Dsv4SampleCfg,
10469        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10470    ) -> Res<SpecRunGpu> {
10471        self.spec_sampled_batched_pen(
10472            prompt,
10473            n_new,
10474            state,
10475            dstate,
10476            vstate,
10477            depth_cap,
10478            vt,
10479            sample,
10480            None,
10481            round_cb.take(),
10482        )
10483    }
10484
10485    /// ds4f rung-2 slice 2 — the sampled driver with PENALTIES over the true
10486    /// per-state window (row-incremental: row r penalizes over prompt ++ committed
10487    /// ++ this round's accepts before r — the q38 penalized-sampled law). `None` is
10488    /// byte-identical to the unpenalized driver. Identity vs the plain penalized
10489    /// loop is structural for the same reason as the unpenalized path: the window at
10490    /// a given position is a pure function of the shared committed prefix.
10491    #[allow(clippy::too_many_arguments)]
10492    pub fn spec_sampled_batched_pen(
10493        &self,
10494        prompt: &[u32],
10495        n_new: usize,
10496        state: &mut DecodeState,
10497        dstate: &mut DsparkState,
10498        vstate: &mut VerifyState,
10499        depth_cap: usize,
10500        vt: Dsv4Vt,
10501        sample: &Dsv4SampleCfg,
10502        pen: Option<&Dsv4PenaltyCfg>,
10503        mut round_cb: Option<&mut dyn FnMut(&[u32]) -> bool>,
10504    ) -> Res<SpecRunGpu> {
10505        let p0 = prompt.len();
10506        assert!(n_new >= 1, "n_new must be positive");
10507        let pre = self.dspark_prefill_prime(prompt, state, dstate)?;
10508        // token at absolute position p0 (output index 0): the seeded draw, keyed p0
10509        let mut t_tok = if let Some(pc) = pen {
10510            let mut row = pre.logits.clone();
10511            dsv4_penalize_row(&mut row, prompt, pc);
10512            dsv4_sample_row(&row, p0, sample)?
10513        } else {
10514            dsv4_sample_row(&pre.logits, p0, sample)?
10515        };
10516        let mut tokens: Vec<u32> = Vec::with_capacity(n_new);
10517        let mut rounds: Vec<SpecRoundGpu> = Vec::new();
10518        let mut mh_row = 0usize;
10519        let mut carry_pending = false;
10520        let depth_cap = depth_cap.max(1);
10521        while tokens.len() < n_new {
10522            let cb_from = tokens.len();
10523            if carry_pending {
10524                tokens.push(t_tok);
10525                if let Some(cb) = round_cb.as_deref_mut() {
10526                    cb(&tokens[cb_from..]);
10527                }
10528                break;
10529            }
10530            let round_t0 = std::time::Instant::now();
10531            let m0 = p0 + tokens.len();
10532            let prop = self.dspark_forward_spec(dstate, t_tok, mh_row, m0 - 1, false)?;
10533            let k_drafts = prop.out_ids.len() - 1;
10534            tokens.push(t_tok);
10535            if tokens.len() == n_new {
10536                rounds.push(SpecRoundGpu {
10537                    start_pos: m0 - 1,
10538                    drafts: prop.out_ids[1..].to_vec(),
10539                    accepts: 0,
10540                    verified: 0,
10541                    t_batch: 0,
10542                    t_cap: 0,
10543                    confidence: prop.confidence.clone(),
10544                    emitted: 1,
10545                    round_us: round_t0.elapsed().as_micros() as u64,
10546                });
10547                if let Some(cb) = round_cb.as_deref_mut() {
10548                    cb(&tokens[cb_from..]);
10549                }
10550                break;
10551            }
10552            let forwards_left = n_new - tokens.len();
10553            let vt_drafts = match vt {
10554                Dsv4Vt::Off => k_drafts,
10555                Dsv4Vt::Slot { tau_logit, floor } => {
10556                    vt_slot_drafts(&prop.confidence, tau_logit, floor)
10557                }
10558            };
10559            let t_cap = (vt_drafts + 1)
10560                .min(k_drafts + 1)
10561                .min(depth_cap)
10562                .min(vstate.tmax);
10563            let t_batch = t_cap.min(forwards_left);
10564            let kv = t_batch - 1;
10565            let mut batch_ids = Vec::with_capacity(t_batch);
10566            batch_ids.push(t_tok);
10567            batch_ids.extend_from_slice(&prop.out_ids[1..1 + kv]);
10568            let (rows, _am) =
10569                self.verify_batch_dev(&batch_ids, state, vstate, Some(&mut dstate.taps), true)?;
10570            let rows = rows.expect("verify_batch_dev(want_logits=true) returned rows");
10571            let vocab = rows.len() / t_batch;
10572            // sampled accept walk: row i's input token sits at position m0 + i, so the
10573            // row PREDICTS the token at position m0 + i + 1 — that predicted position
10574            // is the draw key (the plain loop keys every token by its own absolute
10575            // position; misaligning this by one would silently break the identity law
10576            // at every accepted draft). Draft i+1 is accepted iff it equals the draw.
10577            let mut c_d = 0usize;
10578            let mut t_next = 0u32;
10579            // row-incremental penalty window: prompt ++ tokens (head included) ++ the
10580            // accepts of rows < i in THIS round (batch_ids[1..=c_d] at walk time).
10581            let mut wround: Vec<u32> = Vec::new();
10582            for i in 0..t_batch {
10583                let s = if let Some(pc) = pen {
10584                    let mut row = rows[i * vocab..(i + 1) * vocab].to_vec();
10585                    let mut window = Vec::with_capacity(prompt.len() + tokens.len() + wround.len());
10586                    window.extend_from_slice(prompt);
10587                    window.extend_from_slice(&tokens);
10588                    window.extend_from_slice(&wround);
10589                    dsv4_penalize_row(&mut row, &window, pc);
10590                    dsv4_sample_row(&row, m0 + i + 1, sample)?
10591                } else {
10592                    dsv4_sample_row(&rows[i * vocab..(i + 1) * vocab], m0 + i + 1, sample)?
10593                };
10594                if i < kv && s == batch_ids[i + 1] {
10595                    c_d += 1;
10596                    wround.push(batch_ids[i + 1]);
10597                    continue;
10598                }
10599                t_next = s;
10600                break;
10601            }
10602            let n_commit = c_d + 1;
10603            self.commit_verify_dev(state, vstate, n_commit)?;
10604            for i in 0..n_commit {
10605                self.dspark_write_rings(dstate, i, m0 + i)?;
10606            }
10607            {
10608                let last = self.stages.len() - 1;
10609                self.stages[last]
10610                    .gpu
10611                    .stream()
10612                    .synchronize()
10613                    .map_err(e("round close sync"))?;
10614            }
10615            mh_row = c_d;
10616            for i in 0..c_d {
10617                tokens.push(batch_ids[i + 1]);
10618            }
10619            carry_pending = c_d == kv && t_batch < t_cap;
10620            rounds.push(SpecRoundGpu {
10621                start_pos: m0 - 1,
10622                drafts: prop.out_ids[1..].to_vec(),
10623                accepts: c_d,
10624                verified: (c_d + 1).min(kv),
10625                t_batch,
10626                t_cap,
10627                confidence: prop.confidence.clone(),
10628                emitted: 1 + c_d,
10629                round_us: round_t0.elapsed().as_micros() as u64,
10630            });
10631            t_tok = t_next;
10632            if let Some(cb) = round_cb.as_deref_mut() {
10633                if !cb(&tokens[cb_from..]) {
10634                    break;
10635                }
10636            }
10637        }
10638        Ok(SpecRunGpu { tokens, rounds })
10639    }
10640}
10641
10642impl Dsv4Gpu {
10643    /// Every LIVE trunk cache class, per layer, as host f32 arrays — the instrument for
10644    /// the §3.1 device state gate (batched round + commit vs plain sequential decode of
10645    /// the committed tokens, bit for bit). "Live" is load-bearing: bytes past `n_blocks`
10646    /// in an append-only store, and the TRANSIENT verify rows, are dead scratch and are
10647    /// deliberately excluded (the CPU-oracle gate draws the same line).
10648    pub fn cache_classes(&self, state: &DecodeState) -> Res<Vec<(String, Vec<f32>)>> {
10649        let d = self.model.cfg();
10650        let hd = d.head_dim as usize;
10651        let win = d.sliding_window as usize;
10652        let mut out = Vec::new();
10653        for (il, cache) in state.caches.iter().enumerate() {
10654            let stage_i = self.layer_stage[il];
10655            let st = &self.stages[stage_i];
10656            st.gpu.ctx.bind_to_thread().map_err(e("bind ctx classes"))?;
10657            let stream = st.gpu.stream();
10658            let lidx = st
10659                .layers
10660                .iter()
10661                .position(|l| l.il == il as u32)
10662                .unwrap_or_else(|| panic!("layer {il} not on stage {stage_i}"));
10663            let layer = &st.layers[lidx];
10664            let read = |sl: cudarc::driver::CudaView<'_, f32>| -> Res<Vec<f32>> {
10665                let mut v = vec![0f32; sl.len()];
10666                stream
10667                    .memcpy_dtoh(&sl, &mut v[..])
10668                    .map_err(e("dtoh class"))?;
10669                stream.synchronize().map_err(e("sync class"))?;
10670                Ok(v)
10671            };
10672            out.push((format!("l{il}.ring"), read(cache.kvc.slice(0..win * hd))?));
10673            if let Some(cmp) = &layer.cmp {
10674                out.push((
10675                    format!("l{il}.cmp_store"),
10676                    read(cache.kvc.slice(win * hd..(win + cache.n_blocks) * cmp.d))?,
10677                ));
10678                out.push((
10679                    format!("l{il}.cmp_pend_kv"),
10680                    read(cache.pend_kv.as_ref().expect("pend kv").slice(..))?,
10681                ));
10682                out.push((
10683                    format!("l{il}.cmp_pend_score"),
10684                    read(cache.pend_score.as_ref().expect("pend sc").slice(..))?,
10685                ));
10686            }
10687            if let Some(ix) = &layer.idx {
10688                let ikvc = cache.ikvc.as_ref().expect("ikvc");
10689                out.push((
10690                    format!("l{il}.idx_store"),
10691                    read(ikvc.slice(0..cache.i_blocks * ix.cmp.d))?,
10692                ));
10693                out.push((
10694                    format!("l{il}.idx_pend_kv"),
10695                    read(cache.ipend_kv.as_ref().expect("ipend kv").slice(..))?,
10696                ));
10697                out.push((
10698                    format!("l{il}.idx_pend_score"),
10699                    read(cache.ipend_score.as_ref().expect("ipend sc").slice(..))?,
10700                ));
10701            }
10702        }
10703        Ok(out)
10704    }
10705
10706    /// The DSpark drafter's main_kv rings as host f32 arrays (accepted-position-only
10707    /// ring-write rule gate: the batched drafted arm's rings must end bit-identical to a
10708    /// plain greedy run that wrote a ring row at EVERY decoded position).
10709    pub fn dspark_ring_classes(&self, dstate: &DsparkState) -> Res<Vec<(String, Vec<f32>)>> {
10710        let last = self.stages.len() - 1;
10711        let st = &self.stages[last];
10712        st.gpu.ctx.bind_to_thread().map_err(e("bind ctx rings"))?;
10713        let stream = st.gpu.stream();
10714        let d = self.model.cfg();
10715        let hd = d.head_dim as usize;
10716        let win = d.sliding_window as usize;
10717        let mut out = Vec::new();
10718        for (bi, ring) in dstate.rings.iter().enumerate() {
10719            // persistent ring only — rows [win, win+block) are the drafter's transient
10720            // draft-kv scratch, rewritten by every propose and never state.
10721            let view = ring.slice(0..win * hd);
10722            let mut v = vec![0f32; view.len()];
10723            stream
10724                .memcpy_dtoh(&view, &mut v[..])
10725                .map_err(e("dtoh ring class"))?;
10726            stream.synchronize().map_err(e("sync ring class"))?;
10727            out.push((format!("dspark.ring{bi}"), v));
10728        }
10729        Ok(out)
10730    }
10731}
10732
10733/// The dense-arm resolution, pure for the flip's toothed tests (owner ratification
10734/// 2026-08-20, executed v0.98): unset = `fp8` on the DEVICE decode path, `bf16` on
10735/// legacy (device-scoped default, the 82a754fbec dots-default shape); explicit values
10736/// keep their exact prior semantics including the legacy+fp8 refusal and the
10737/// unknown-value refusal.
10738pub fn resolve_dense_arm(v: Option<&str>, on_device: bool) -> Result<bool, String> {
10739    match v {
10740        None | Some("") => Ok(on_device),
10741        Some("bf16") => Ok(false),
10742        Some("fp8") if !on_device => Err(
10743            "MEMRA_DSV4_DENSE_ARM=fp8 requires MEMRA_DSV4_DECODE_PATH=device (the \
10744             fp8 GEMV twins exist on the device decode/verify paths only; prefill \
10745             and the legacy path consume the bf16 slabs)"
10746                .to_string(),
10747        ),
10748        Some("fp8") => Ok(true),
10749        Some(other) => Err(format!(
10750            "MEMRA_DSV4_DENSE_ARM '{other}' unknown (bf16 | fp8)"
10751        )),
10752    }
10753}
10754
10755/// ds4f rung 1 — per-round verify-window policy from the drafter's OWN confidence head
10756/// (`MEMRA_DSV4_VT={off|slot}`, unset = off = the byte-identical round driver).
10757///
10758/// `slot` is the owner-directive per-slot reading. The q38 H4 verdict transfers as a
10759/// MECHANISM, never as receipts (no-generic-support): their head emits MARGINAL accept
10760/// probabilities, so cumprod-survival double-counts depth decay — and dsv4's own head
10761/// was independently measured discriminative per-slot (AUC 0.871–0.918, it5 rung 4,
10762/// where STS recalibration was the measured NEGATIVE — the policy consumes RAW
10763/// sigmoids by design). Verification still arbitrates every forwarded draft, so the
10764/// policy moves acceptance ECONOMICS only; greedy identity holds at any window (the
10765/// `MEMRA_DSV4_SPEC_DEPTH` argument, verbatim — this is a per-round depth).
10766///
10767/// Knobs: `MEMRA_DSV4_VT_TAU` in (0,1) exclusive, default 0.5; `MEMRA_DSV4_VT_FLOOR`
10768/// = minimum drafts forwarded, default 0, max `DSPARK_BLOCK-1` (0 is legal: a
10769/// fully-unconfident proposal degenerates to a 1-row verify — the it5 Algorithm-1
10770/// scans price exactly that round shape). Unknown values, out-of-range tau/floor, and
10771/// orphan knobs (tau/floor set without `slot`) REFUSE BY NAME.
10772#[derive(Clone, Copy, Debug, PartialEq)]
10773pub enum Dsv4Vt {
10774    Off,
10775    /// tau stored in LOGIT space (sigmoid(c) >= tau  <=>  c >= tau_logit, exact for
10776    /// tau = 0.5 -> 0.0); floor = minimum number of drafts forwarded per round.
10777    Slot {
10778        tau_logit: f32,
10779        floor: usize,
10780    },
10781}
10782
10783pub fn resolve_vt(
10784    policy: Option<&str>,
10785    tau: Option<&str>,
10786    floor: Option<&str>,
10787) -> Result<Dsv4Vt, String> {
10788    match policy {
10789        None | Some("") | Some("off") => {
10790            if let Some(t) = tau {
10791                return Err(format!(
10792                    "MEMRA_DSV4_VT_TAU='{t}' set without MEMRA_DSV4_VT=slot (orphan knob \
10793                     would be silently inert — refuse instead)"
10794                ));
10795            }
10796            if let Some(f) = floor {
10797                return Err(format!(
10798                    "MEMRA_DSV4_VT_FLOOR='{f}' set without MEMRA_DSV4_VT=slot (orphan \
10799                     knob would be silently inert — refuse instead)"
10800                ));
10801            }
10802            Ok(Dsv4Vt::Off)
10803        }
10804        Some("slot") => {
10805            let tau_v: f32 = match tau {
10806                None => 0.5,
10807                Some(s) => s
10808                    .trim()
10809                    .parse::<f32>()
10810                    .map_err(|_| format!("MEMRA_DSV4_VT_TAU '{s}' is not a float in (0,1)"))?,
10811            };
10812            if !(tau_v > 0.0 && tau_v < 1.0) {
10813                return Err(format!(
10814                    "MEMRA_DSV4_VT_TAU {tau_v} out of range: need 0 < tau < 1 \
10815                     (a probability threshold on the per-slot sigmoid)"
10816                ));
10817            }
10818            let floor_v: usize = match floor {
10819                None => 0,
10820                Some(s) => s.trim().parse::<usize>().map_err(|_| {
10821                    format!("MEMRA_DSV4_VT_FLOOR '{s}' is not a non-negative integer")
10822                })?,
10823            };
10824            // block_size is baked into the weights at 5 (DSPARK-SEMANTICS §1.5); a
10825            // floor >= block would pin the window fully open, i.e. silently disable
10826            // the policy while claiming to run it.
10827            if floor_v >= 5 {
10828                return Err(format!(
10829                    "MEMRA_DSV4_VT_FLOOR {floor_v} >= dspark block size 5 would pin the \
10830                     window fully open (use MEMRA_DSV4_VT=off to disable)"
10831                ));
10832            }
10833            Ok(Dsv4Vt::Slot {
10834                tau_logit: (tau_v / (1.0 - tau_v)).ln(),
10835                floor: floor_v,
10836            })
10837        }
10838        Some(other) => Err(format!("MEMRA_DSV4_VT '{other}' unknown (off | slot)")),
10839    }
10840}
10841
10842/// ds4f rung 2 (slice 1) — the dsv4 SAMPLED path's sampler: deterministic,
10843/// POSITION-KEYED seeded draws over a temperature/top-k/top-p-filtered target row.
10844///
10845/// Position keying is the identity law's load-bearing choice: the uniform draw for
10846/// absolute position `pos` is a pure function of (seed, pos), never of how many draws
10847/// happened before — so the plain sampled loop and the sampled-leader verify walk
10848/// consume IDENTICAL randomness at every position, and (because the batched verify's
10849/// logits rows are bit-exact against the sequential step's — the it3 gate (c) proof)
10850/// **sampled spec == sampled plain identity is structural, per seed**, exactly like
10851/// greedy. The drafter keeps proposing greedily (its chain is a deterministic
10852/// proposal policy); arbitration is sample-match against the target draw — the
10853/// correct accept rule for a one-hot proposal (the q38 "cold sampled leader" shape).
10854///
10855/// Filter semantics (vendor-posture defaults live at the call sites: temperature 1.0,
10856/// top_p 0.95, top_k off): logits/T -> softmax -> top-k by (value desc, index asc)
10857/// -> smallest prefix of that order with cumulative mass >= top_p (always >= 1 token)
10858/// -> renormalize -> inverse-CDF draw at u(seed, pos). temperature <= 0 REFUSES BY
10859/// NAME (greedy is the greedy driver's job; a silent argmax fallback here would be
10860/// the q38 penalized-greedy footgun).
10861#[derive(Clone, Copy, Debug)]
10862pub struct Dsv4SampleCfg {
10863    pub temperature: f32,
10864    pub top_p: f32,
10865    pub top_k: usize,
10866    pub seed: u64,
10867}
10868
10869fn splitmix64(mut x: u64) -> u64 {
10870    x = x.wrapping_add(0x9e3779b97f4a7c15);
10871    let mut z = x;
10872    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
10873    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
10874    z ^ (z >> 31)
10875}
10876
10877/// The uniform draw for absolute position `pos` under `seed` — in [0, 1).
10878pub fn dsv4_pos_uniform(seed: u64, pos: usize) -> f64 {
10879    let h = splitmix64(seed ^ (pos as u64).wrapping_mul(0xa24baed4963ee407));
10880    (h >> 11) as f64 / (1u64 << 53) as f64
10881}
10882
10883/// One sampled token from a full-vocab logits row at absolute position `pos`.
10884pub fn dsv4_sample_row(logits: &[f32], pos: usize, cfg: &Dsv4SampleCfg) -> Result<u32, String> {
10885    if !(cfg.temperature > 0.0) {
10886        return Err(format!(
10887            "dsv4 sampled path: temperature {} refused (need > 0; greedy is served by \
10888             the greedy driver, never a silent argmax fallback)",
10889            cfg.temperature
10890        ));
10891    }
10892    if !(cfg.top_p > 0.0 && cfg.top_p <= 1.0) {
10893        return Err(format!(
10894            "dsv4 sampled path: top_p {} out of (0, 1]",
10895            cfg.top_p
10896        ));
10897    }
10898    // candidate order: value desc, index asc (the house tie ordering)
10899    let k = if cfg.top_k == 0 || cfg.top_k > logits.len() {
10900        logits.len()
10901    } else {
10902        cfg.top_k
10903    };
10904    let mut idx: Vec<u32> = (0..logits.len() as u32).collect();
10905    idx.sort_by(|&a, &b| {
10906        let (va, vb) = (logits[a as usize], logits[b as usize]);
10907        vb.partial_cmp(&va)
10908            .unwrap_or(std::cmp::Ordering::Equal)
10909            .then(a.cmp(&b))
10910    });
10911    idx.truncate(k);
10912    // softmax over the kept set in kept order (f64 accumulation, max-shifted)
10913    let m = logits[idx[0] as usize] as f64;
10914    let t = cfg.temperature as f64;
10915    let mut probs: Vec<f64> = idx
10916        .iter()
10917        .map(|&i| (((logits[i as usize] as f64) - m) / t).exp())
10918        .collect();
10919    let z: f64 = probs.iter().sum();
10920    for p in &mut probs {
10921        *p /= z;
10922    }
10923    // nucleus: smallest prefix with cumulative >= top_p (>= 1 token), renormalize
10924    let mut cum = 0.0f64;
10925    let mut keep = probs.len();
10926    for (i, p) in probs.iter().enumerate() {
10927        cum += p;
10928        if cum >= cfg.top_p as f64 {
10929            keep = i + 1;
10930            break;
10931        }
10932    }
10933    probs.truncate(keep);
10934    idx.truncate(keep);
10935    let z2: f64 = probs.iter().sum();
10936    let u = dsv4_pos_uniform(cfg.seed, pos) * z2;
10937    let mut acc = 0.0f64;
10938    for (i, p) in probs.iter().enumerate() {
10939        acc += p;
10940        if u < acc {
10941            return Ok(idx[i]);
10942        }
10943    }
10944    Ok(idx[keep - 1]) // u landed on the tail boundary (float roundoff)
10945}
10946
10947/// ds4f rung 2 slice 2 — penalties for the dsv4 sampled path, over an EXPLICIT
10948/// window. The rule is `memra-sampling`'s own `Sampler::apply_penalties` (Keskar
10949/// repeat divide/multiply toward 0 + frequency*count + presence), replicated here
10950/// because the dsv4 path needs per-ROW windows (the spec verify's row-incremental
10951/// state: row r penalizes over prompt ++ committed ++ this round's accepts < r),
10952/// and CROSS-PINNED by unit test against a real `Sampler` so the two
10953/// implementations cannot drift apart silently.
10954#[derive(Clone, Copy, Debug)]
10955pub struct Dsv4PenaltyCfg {
10956    pub last_n: usize,
10957    pub repeat: f32,
10958    pub freq: f32,
10959    pub present: f32,
10960}
10961
10962impl Dsv4PenaltyCfg {
10963    pub fn armed(&self) -> bool {
10964        self.last_n > 0 && (self.repeat != 1.0 || self.freq != 0.0 || self.present != 0.0)
10965    }
10966}
10967
10968/// Apply the Keskar penalties in place over `window`'s last `cfg.last_n` entries.
10969pub fn dsv4_penalize_row(logits: &mut [f32], window: &[u32], cfg: &Dsv4PenaltyCfg) {
10970    if !cfg.armed() {
10971        return;
10972    }
10973    let start = window.len().saturating_sub(cfg.last_n);
10974    let win = &window[start..];
10975    if win.is_empty() {
10976        return;
10977    }
10978    let mut counts: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
10979    for &t in win {
10980        *counts.entry(t).or_insert(0) += 1;
10981    }
10982    for (&id, &cnt) in &counts {
10983        let Some(l) = logits.get_mut(id as usize) else {
10984            continue;
10985        };
10986        if cfg.repeat != 1.0 {
10987            if *l > 0.0 {
10988                *l /= cfg.repeat;
10989            } else {
10990                *l *= cfg.repeat;
10991            }
10992        }
10993        *l -= cfg.freq * cnt as f32;
10994        if cnt > 0 {
10995            *l -= cfg.present;
10996        }
10997    }
10998}
10999
11000/// Drafts to forward under the slot policy: the longest LEADING prefix of `conf`
11001/// (the drafter's pre-sigmoid per-slot logits) with `c >= tau_logit`, raised to
11002/// `floor`, clamped to `conf.len()`. A NaN slot compares false = unconfident
11003/// (conservative: it truncates, and verification still owns correctness).
11004pub fn vt_slot_drafts(conf: &[f32], tau_logit: f32, floor: usize) -> usize {
11005    let mut k = 0usize;
11006    for &c in conf {
11007        if c >= tau_logit {
11008            k += 1;
11009        } else {
11010            break;
11011        }
11012    }
11013    k.max(floor).min(conf.len())
11014}
11015
11016#[cfg(test)]
11017mod peer_probe_tests {
11018    use super::{dsv4_peer_probe_ladder, dsv4_peer_probe_mismatches, dsv4_peer_probe_pattern};
11019
11020    /// TOOTH for the lane-8 byte probe (host-side halves; the on-box halves are the boot
11021    /// PASS line and the MEMRA_DSV4_PEER_PROBE_POISON refusal arm): the pattern must be
11022    /// deterministic, non-trivial, and keyed per (bytes, boundary, src, dst) so a stuck or
11023    /// crossed lane cannot alias another probe's expectation; the mismatch count must see
11024    /// single-byte flips, inversion (the poison), and truncation.
11025    #[test]
11026    fn peer_probe_pattern_is_keyed_and_mismatches_are_counted() {
11027        let a = dsv4_peer_probe_pattern(4096, 0, 0, 1);
11028        assert_eq!(a.len(), 4096);
11029        assert_eq!(a, dsv4_peer_probe_pattern(4096, 0, 0, 1), "deterministic");
11030        assert_ne!(a, dsv4_peer_probe_pattern(4096, 0, 1, 0), "direction-keyed");
11031        assert_ne!(a, dsv4_peer_probe_pattern(4096, 1, 0, 1), "boundary-keyed");
11032        assert!(a.iter().any(|&b| b != a[0]), "non-constant pattern");
11033
11034        assert_eq!(dsv4_peer_probe_mismatches(&a, &a), 0);
11035        let mut flipped = a.clone();
11036        flipped[17] ^= 1;
11037        assert_eq!(dsv4_peer_probe_mismatches(&a, &flipped), 1);
11038        let poison: Vec<u8> = a.iter().map(|b| !b).collect();
11039        assert_eq!(dsv4_peer_probe_mismatches(&a, &poison), a.len());
11040        assert_eq!(dsv4_peer_probe_mismatches(&a, &a[..4000]), 96);
11041    }
11042
11043    #[test]
11044    fn peer_probe_ladder_contains_live_hc_payloads() {
11045        let ladder = dsv4_peer_probe_ladder(4096, 4);
11046        assert!(ladder.contains(&(64 << 10)), "one-token hc state");
11047        assert!(ladder.contains(&(512 << 10)), "eight-row verify hc state");
11048        assert!(ladder.contains(&(64 << 20)), "maximum prefill handoff");
11049    }
11050}
11051
11052#[cfg(test)]
11053mod dense_arm_default_tests {
11054    use super::resolve_dense_arm;
11055
11056    /// The owner-ratified flip (2026-08-20): unset env on the device decode path = fp8.
11057    /// Mutating the default back to bf16 fails this with the evidence named.
11058    #[test]
11059    fn ratified_default_dense_arm_is_fp8_on_device() {
11060        assert_eq!(
11061            resolve_dense_arm(None, true),
11062            Ok(true),
11063            "owner-ratified 2026-08-20: unset MEMRA_DSV4_DENSE_ARM defaults the DEVICE \
11064             decode path to fp8 (bit-identical on four boxes, x5 A/B 41.06->47.19, \
11065             item-3 residency green on box7)"
11066        );
11067        assert_eq!(resolve_dense_arm(Some(""), true), Ok(true));
11068        // Legacy path: unset resolves bf16 (no fp8 twins there — must keep booting).
11069        assert_eq!(resolve_dense_arm(None, false), Ok(false));
11070        // Explicit values keep their exact prior semantics.
11071        assert_eq!(resolve_dense_arm(Some("bf16"), true), Ok(false));
11072        assert_eq!(resolve_dense_arm(Some("fp8"), true), Ok(true));
11073        assert!(
11074            resolve_dense_arm(Some("fp8"), false).is_err(),
11075            "legacy+fp8 stays a refusal"
11076        );
11077        assert!(
11078            resolve_dense_arm(Some("q8"), true).is_err(),
11079            "unknown values refuse"
11080        );
11081    }
11082}
11083
11084#[cfg(test)]
11085mod vt_policy_tests {
11086    use super::{Dsv4Vt, resolve_vt, vt_slot_drafts};
11087
11088    /// Unset env = Off = the byte-identical round driver. Mutating the default fails
11089    /// this by name.
11090    #[test]
11091    fn default_vt_is_off_and_byte_inert() {
11092        assert_eq!(resolve_vt(None, None, None), Ok(Dsv4Vt::Off));
11093        assert_eq!(resolve_vt(Some(""), None, None), Ok(Dsv4Vt::Off));
11094        assert_eq!(resolve_vt(Some("off"), None, None), Ok(Dsv4Vt::Off));
11095    }
11096
11097    #[test]
11098    fn slot_defaults_tau_half_floor_zero() {
11099        // tau 0.5 must land on tau_logit 0.0 EXACTLY (ln(0.5/0.5) = ln(1) = 0), so the
11100        // default threshold admits c = 0.0 with no float fuzz.
11101        match resolve_vt(Some("slot"), None, None) {
11102            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11103                assert_eq!(tau_logit, 0.0);
11104                assert_eq!(floor, 0);
11105            }
11106            other => panic!("slot default parse broke: {other:?}"),
11107        }
11108        // explicit tau round-trips through logit space
11109        match resolve_vt(Some("slot"), Some("0.6"), Some("2")) {
11110            Ok(Dsv4Vt::Slot { tau_logit, floor }) => {
11111                assert!((tau_logit - (0.6f32 / 0.4).ln()).abs() < 1e-6);
11112                assert_eq!(floor, 2);
11113            }
11114            other => panic!("slot tau/floor parse broke: {other:?}"),
11115        }
11116    }
11117
11118    #[test]
11119    fn unknown_and_out_of_range_refuse_by_name() {
11120        for (p, t, f) in [
11121            (Some("banana"), None, None),    // unknown policy
11122            (Some("slot"), Some("0"), None), // tau not in (0,1)
11123            (Some("slot"), Some("1"), None),
11124            (Some("slot"), Some("nan"), None),
11125            (Some("slot"), Some("x"), None),
11126            (Some("slot"), None, Some("5")), // floor pins window open
11127            (Some("slot"), None, Some("-1")),
11128            (None, Some("0.5"), None),      // orphan tau
11129            (Some("off"), None, Some("2")), // orphan floor
11130        ] {
11131            let r = resolve_vt(p, t, f);
11132            assert!(r.is_err(), "({p:?},{t:?},{f:?}) must refuse, got {r:?}");
11133            let msg = r.unwrap_err();
11134            assert!(
11135                msg.contains("MEMRA_DSV4_VT"),
11136                "refusal must name the knob: {msg}"
11137            );
11138        }
11139    }
11140
11141    /// The slot rule is a LEADING-prefix rule: a confident slot after an unconfident
11142    /// one is never forwarded (chained markov ids past a rejected slot are garbage).
11143    #[test]
11144    fn slot_truncation_is_leading_prefix_with_floor() {
11145        let up = 3.0f32; // sigmoid ~0.95
11146        let dn = -3.0f32; // sigmoid ~0.05
11147        assert_eq!(vt_slot_drafts(&[up, up, up, up, up], 0.0, 0), 5);
11148        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 0), 0);
11149        assert_eq!(vt_slot_drafts(&[up, up, dn, up, up], 0.0, 0), 2);
11150        // boundary equality counts as confident (>=): tau 0.5 admits c = 0.0
11151        assert_eq!(vt_slot_drafts(&[0.0, dn, dn, dn, dn], 0.0, 0), 1);
11152        // floor raises a fully-unconfident round; clamped to the block
11153        assert_eq!(vt_slot_drafts(&[dn, dn, dn, dn, dn], 0.0, 2), 2);
11154        assert_eq!(vt_slot_drafts(&[dn, dn], 0.0, 4), 2);
11155        // NaN slot is unconfident (conservative), never a panic
11156        assert_eq!(vt_slot_drafts(&[f32::NAN, up, up, up, up], 0.0, 0), 0);
11157        assert_eq!(vt_slot_drafts(&[], 0.0, 0), 0);
11158    }
11159
11160    /// Off must reproduce the pre-policy t_cap expression exactly: with
11161    /// vt_drafts == k_drafts, (vt_drafts+1).min(k_drafts+1) == k_drafts+1.
11162    #[test]
11163    fn off_arm_t_cap_expression_is_identity() {
11164        for k_drafts in 0usize..=5 {
11165            let vt_drafts = k_drafts; // the Off branch in the driver
11166            assert_eq!((vt_drafts + 1).min(k_drafts + 1), k_drafts + 1);
11167        }
11168    }
11169}
11170
11171#[cfg(test)]
11172mod penalty_cross_pin_tests {
11173    use super::{Dsv4PenaltyCfg, dsv4_penalize_row};
11174
11175    /// The dsv4 explicit-window penalty rule must equal memra-sampling's own
11176    /// `Sampler::apply_penalties` (the house Keskar law) — pinned by running BOTH on
11177    /// the same rows/windows and comparing the penalized-greedy argmax, plus a direct
11178    /// per-element check through the Sampler's greedy path. Drift in either
11179    /// implementation fails here by name.
11180    #[test]
11181    fn penalize_matches_the_sampling_crate_reference() {
11182        let mk_row = |seed: u32| -> Vec<f32> {
11183            (0..64u32)
11184                .map(|i| {
11185                    let h = i.wrapping_mul(2654435761).wrapping_add(seed);
11186                    ((h % 2000) as f32 / 100.0) - 10.0
11187                })
11188                .collect()
11189        };
11190        for (seed, window, last_n, rep, freq, present) in [
11191            (
11192                1u32,
11193                vec![3u32, 3, 3, 7, 12, 3],
11194                8usize,
11195                1.8f32,
11196                0.4f32,
11197                0.6f32,
11198            ),
11199            (2, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4, 1.3, 0.0, 0.0),
11200            (3, vec![63, 63, 63, 63], 64, 1.0, 1.1, 0.0),
11201            (4, vec![5], 1, 2.5, 0.7, 1.3),
11202        ] {
11203            let row = mk_row(seed);
11204            // ours
11205            let mut ours = row.clone();
11206            dsv4_penalize_row(
11207                &mut ours,
11208                &window,
11209                &Dsv4PenaltyCfg {
11210                    last_n,
11211                    repeat: rep,
11212                    freq,
11213                    present,
11214                },
11215            );
11216            let our_pick = ours
11217                .iter()
11218                .enumerate()
11219                .max_by(|a, b| a.1.total_cmp(b.1))
11220                .unwrap()
11221                .0 as u32;
11222            // the house reference: greedy Sampler with penalties + the window as history
11223            let mut sampler = memra_sampling::Sampler::new(memra_sampling::SamplerConfig {
11224                temperature: 0.0,
11225                top_k: 0,
11226                top_p: 1.0,
11227                min_p: 0.0,
11228                penalty_last_n: last_n,
11229                penalty_repeat: rep,
11230                penalty_freq: freq,
11231                penalty_present: present,
11232                seed: 0,
11233            });
11234            for &t in &window {
11235                sampler.accept(t);
11236            }
11237            let ref_pick = sampler.sample(&row);
11238            assert_eq!(
11239                our_pick, ref_pick,
11240                "penalized argmax diverged from memra-sampling (seed {seed}): \
11241                 ours {our_pick} vs reference {ref_pick}"
11242            );
11243        }
11244    }
11245}
11246
11247#[cfg(test)]
11248mod sampled_path_tests {
11249    use super::{Dsv4SampleCfg, dsv4_pos_uniform, dsv4_sample_row};
11250
11251    fn cfg(seed: u64) -> Dsv4SampleCfg {
11252        Dsv4SampleCfg {
11253            temperature: 1.0,
11254            top_p: 0.95,
11255            top_k: 0,
11256            seed,
11257        }
11258    }
11259
11260    /// The identity law's anchor: the draw is a pure function of (row, pos, seed) —
11261    /// same inputs, same token, always; different positions decouple.
11262    #[test]
11263    fn draws_are_position_keyed_and_deterministic() {
11264        let row = [0.1f32, 2.0, -1.0, 1.9, 0.0];
11265        let a = dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap();
11266        for _ in 0..8 {
11267            assert_eq!(dsv4_sample_row(&row, 40, &cfg(20260822)).unwrap(), a);
11268        }
11269        // uniforms at neighboring positions must not be equal (keying is real)
11270        let u0 = dsv4_pos_uniform(20260822, 40);
11271        let u1 = dsv4_pos_uniform(20260822, 41);
11272        let v0 = dsv4_pos_uniform(7, 40);
11273        assert_ne!(u0, u1);
11274        assert_ne!(u0, v0);
11275        assert!((0.0..1.0).contains(&u0));
11276    }
11277
11278    /// temperature <= 0 refuses BY NAME (the penalized-greedy footgun class);
11279    /// bad top_p refuses too.
11280    #[test]
11281    fn t0_and_bad_topp_refuse_by_name() {
11282        let row = [0.0f32, 1.0];
11283        let mut c = cfg(1);
11284        c.temperature = 0.0;
11285        let e = dsv4_sample_row(&row, 0, &c).unwrap_err();
11286        assert!(e.contains("temperature"), "{e}");
11287        let mut c2 = cfg(1);
11288        c2.top_p = 0.0;
11289        assert!(dsv4_sample_row(&row, 0, &c2).is_err());
11290    }
11291
11292    /// top-k 1 and a tight nucleus both collapse to argmax regardless of the draw;
11293    /// ties break by lowest index (the house ordering).
11294    #[test]
11295    fn filters_collapse_to_argmax_and_ties_break_low_index() {
11296        let row = [0.0f32, 5.0, 5.0, -2.0];
11297        let mut c = cfg(99);
11298        c.top_k = 1;
11299        for pos in 0..64 {
11300            assert_eq!(dsv4_sample_row(&row, pos, &c).unwrap(), 1);
11301        }
11302        let mut c2 = cfg(99);
11303        c2.top_p = 1e-9; // nucleus keeps exactly the top-1
11304        for pos in 0..64 {
11305            assert_eq!(dsv4_sample_row(&row, pos, &c2).unwrap(), 1);
11306        }
11307    }
11308
11309    /// The sampled distribution honors the filtered target: over many positions a
11310    /// dominant token wins the majority, and a token outside top-k never appears.
11311    #[test]
11312    fn draw_frequencies_track_the_filtered_target() {
11313        let row = [3.0f32, 1.0, 0.0, -50.0];
11314        let mut c = cfg(20260822);
11315        c.top_k = 3;
11316        c.top_p = 1.0;
11317        let mut counts = [0usize; 4];
11318        for pos in 0..4096 {
11319            counts[dsv4_sample_row(&row, pos, &c).unwrap() as usize] += 1;
11320        }
11321        assert_eq!(counts[3], 0, "outside top-k must never be drawn");
11322        assert!(counts[0] > 2600, "p(tok0) ~ 0.84, got {}/4096", counts[0]);
11323        assert!(counts[1] > 100, "tail token starved: {}", counts[1]);
11324    }
11325}