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