Skip to main content

memra_engine/
lib.rs

1//! memra engine: Stage-1 correctness-first forward-pass kernels + ops, on sm_120 via cudarc.
2
3use cudarc::driver::{
4    CudaContext, CudaFunction, CudaModule, CudaSlice, CudaStream, LaunchConfig, PushKernelArg,
5};
6use cudarc::nvrtc::Ptx;
7use std::sync::{Arc, Mutex};
8
9#[cfg(debug_assertions)]
10pub(crate) fn debug_assert_tensor_stream_device<T>(
11    tensor: &CudaSlice<T>,
12    stream: &CudaStream,
13    site: &str,
14) {
15    let tensor_dev = tensor.ordinal();
16    let stream_dev = stream.context().ordinal();
17    assert_eq!(
18        tensor_dev, stream_dev,
19        "PP cross-device tensor read at {site}: tensor on dev{tensor_dev}, stream on dev{stream_dev}"
20    );
21}
22
23pub use memra_gguf;
24pub use memra_runtime;
25
26pub mod forward;
27pub mod hybrid;
28pub mod hybrid_forward;
29pub mod model;
30pub mod sigrouter_contract;
31pub mod vision;
32pub mod vision_gemma;
33pub mod vision_pre;
34/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
35/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
36pub mod cache {
37    pub use memra_kv::*;
38}
39pub mod decode;
40pub mod decode_batch;
41pub mod dflash;
42pub mod eagle;
43pub mod gemma_spec;
44pub mod graph_update;
45/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
46/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
47/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
48pub mod mla;
49pub mod moesd;
50pub mod parallel;
51pub mod pp;
52pub mod round_stream;
53pub mod spec;
54pub use memra_sampling as sampler;
55
56/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
57/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
58/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
59/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
60/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
61///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
62///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
63///                     stream sync per projection (round-47 ledgered defect).
64///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
65///                     construction, zero syncs, f32 C with the act row-scale folded in.
66/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
67/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
68/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
69/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
70/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
71/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
72///
73/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
74/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
75/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
76/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
77/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
78/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
79/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
80/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
81///
82/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
83/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
84/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
85/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
86/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
87/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
88/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
89///
90/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
91/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
92/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
93/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
94/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
95/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
96/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
97/// the k-quant-only admission survives as the rollback seam, not the default.
98/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
99pub fn moe_f16g_mode() -> u8 {
100    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
101    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
102        Ok("0") => 0,
103        Ok("2") => 2,
104        Ok("3") => 3,
105        Ok(_) => 1,
106        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
107        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
108        Err(_) => 2,
109    })
110}
111/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
112/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
113/// (shape_sel, cross) for the FFI:
114///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
115///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
116///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
117///                         back to 32x64 in-launcher when the device/in_f can't take it).
118///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
119///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
120///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
121///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
122///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
123///                         verdict was stale).
124pub fn moe_f16g_sk_params() -> (i32, i32) {
125    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
126    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
127        Ok("0") => (-1, 0),
128        Ok("32") => (0, i32::MAX),
129        Ok("128") => (0, 1),
130        _ => {
131            let cross = std::env::var("MEMRA_F16G_SK_CROSS")
132                .ok()
133                .and_then(|v| v.parse().ok())
134                .unwrap_or(64);
135            (0, cross)
136        }
137    })
138}
139/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
140/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
141/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
142/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
143/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
144/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
145/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
146/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
147/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
148/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
149pub fn moe_f16g_direct_on(qtype: i32) -> bool {
150    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
151    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
152        Ok("0") => 0,
153        Ok("kq") => 1,
154        _ => 2,
155    });
156    match m {
157        0 => false,
158        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
159        _ => true,
160    }
161}
162/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
163/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
164/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
165/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
166/// stage under q35's routing skew. Bit-identical to every other sk form by construction
167/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
168/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
169/// tail. in_f % 64 != 0 falls back in-launcher.
170pub fn moe_f16g_tail_on() -> bool {
171    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
172    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
173}
174
175/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
176/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
177/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
178/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
179/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
180/// still opens this door for A/B.
181pub fn moe_f16g_gemma_on() -> bool {
182    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
183    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
184}
185
186/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
187/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
188/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
189pub fn moe_fuse_actq_on() -> bool {
190    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
191    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
192}
193
194/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
195/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
196/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
197/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
198/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
199/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
200/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
201/// verify already use (dispatch parity, one router kernel for every t).
202/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
203pub fn router_prefill_exact_on() -> bool {
204    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
205    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
206}
207
208pub fn router_kernel_on() -> bool {
209    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
210    *ON.get_or_init(|| {
211        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
212        if !on {
213            eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)");
214        }
215        on
216    })
217}
218
219/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
220/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
221/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
222/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
223/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
224/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
225/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
226/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
227/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
228/// seam, perf-only: bits are equal by the kernel-check gate).
229/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
230/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
231/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
232/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
233pub const ROUTER_BATCH_MIN_T: usize = 8;
234pub fn router_batch_on() -> bool {
235    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
236    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
237}
238mod cpu_experts;
239#[cfg(memra_cutlass)]
240pub mod cutlass_ffi;
241pub mod dsv4_ffi;
242pub mod dsv4_gpu;
243pub mod f16_ffi;
244pub mod fp8_ffi;
245pub mod mmq_ffi;
246pub mod moe_cache;
247pub mod prime_graph;
248pub mod spill;
249mod spill_pread;
250
251// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
252// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
253// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
254// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
255// broke every machine that wasn't the build machine. Same bytes, same module image;
256// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
257const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
258const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
259const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
260const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
261const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
262const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
263/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
264const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
265
266/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
267/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
268/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
269/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
270/// compile-time default (zero behavior change).
271fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
272    assert!(
273        !(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
274        "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane"
275    );
276    match std::env::var("MEMRA_GEMM_FATBIN") {
277        Ok(path) => std::borrow::Cow::Owned(
278            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}")),
279        ),
280        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
281    }
282}
283
284/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
285/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
286/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
287/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
288/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
289/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
290pub(crate) const fn portable_mma_gated() -> bool {
291    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
292}
293
294/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
295/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
296/// in a pure helper so the dispatch guard can be regression-tested without constructing an
297/// Engine or allocating a GPU tensor.
298const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
299    (!portable_cuda || hopper_mma) && !no_gemm
300}
301
302// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
303// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
304// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
305// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
306// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
307// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
308// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
309const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
310const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
311const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
312const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
313const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
314
315/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
316/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
317pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
318
319/// The flash_attn fatbin matching the selected KV formats.
320fn flash_fatbin_bytes() -> &'static [u8] {
321    match kv_cache_formats() {
322        ("q8_0", "q5_1") => FLASH_FATBIN,
323        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
324        ("q8_0", "fp8") => FLASH_FATBIN_VF8,
325        ("fp8", "q5_1") => FLASH_FATBIN_KF8,
326        ("fp8", "q4_0") => FLASH_FATBIN_KF8VQ4,
327        ("fp8", "fp8") => FLASH_FATBIN_KF8VF8,
328        other => unreachable!("kv_cache_formats returned {other:?}"),
329    }
330}
331
332/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
333/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
334/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
335/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
336/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
337/// defaults (zero behavior change).
338fn k1_launch_override() -> Option<(u32, u32, u32)> {
339    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
340    *K1.get_or_init(|| {
341        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
342        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
343        match p.as_slice() {
344            [bm, bn, w] => Some((*bm, *bn, *w)),
345            _ => None,
346        }
347    })
348}
349
350/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
351/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
352/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
353/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
354/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
355/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
356pub(crate) fn wgmma_gemm_enabled() -> bool {
357    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
358    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
359}
360
361/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
362/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
363/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
364/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
365/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
366/// the split count changes the combine's FP summation order, and the spec verify's batched forward
367/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
368/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
369/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
370/// adaptive retries (any retry MUST pass run-spec self-consistency first).
371/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
372/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
373/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
374/// between eager decode and the verify (the spec-exactness law).
375pub const FA_VEC_MIN_TKV: usize = 96;
376/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
377/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
378/// which moves the crossover — sweep per model, adopt per the battery.
379pub fn fa_vec_min_tkv() -> usize {
380    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
381    *V.get_or_init(|| {
382        std::env::var("MEMRA_FA_VEC_MIN")
383            .ok()
384            .and_then(|v| v.parse().ok())
385            .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
386    })
387}
388
389/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
390/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
391/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
392///
393/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
394/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
395/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
396/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
397/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
398/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
399pub fn fa_f16pv_on() -> bool {
400    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
401    *ON.get_or_init(|| {
402        std::env::var("MEMRA_FA_F16PV")
403            .map(|v| v != "0")
404            .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
405    })
406}
407
408/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
409/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
410/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
411pub fn fa512_hp_on() -> bool {
412    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
413    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
414}
415
416/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
417/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
418/// accumulation. Even n_head and even GQA group required (guarded per call).
419pub fn faw_hp_on() -> bool {
420    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
421    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
422}
423
424/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
425/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
426/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
427pub fn fa512_wide_warps() -> usize {
428    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
429    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
430        Ok("1") => 4,
431        _ => 2,
432    })
433}
434
435/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
436/// and the gemma global-layer rows/parity call sites.
437pub fn fa512_min_tkv() -> usize {
438    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
439    *FA512_MIN.get_or_init(|| {
440        std::env::var("MEMRA_FA512_MIN")
441            .ok()
442            .and_then(|v| v.parse().ok())
443            .unwrap_or(512)
444    })
445}
446/// Per-model crossover default, set at model load BEFORE the first decode (per-model
447/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
448/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
449pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
450    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
451/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
452/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
453/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
454pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(32);
455/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
456/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
457/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
458/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
459/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
460pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
461    std::sync::atomic::AtomicBool::new(false);
462/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
463/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
464/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
465/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
466/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
467/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
468pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
469    std::sync::atomic::AtomicBool::new(true);
470pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
471    std::sync::atomic::AtomicUsize::new(16);
472/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
473/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
474/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
475/// latency-bound at 256 threads — 7us/launch measured).
476pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
477/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
478pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
479/// Per-model stream-k entry override for SPEC serving (-1 = unset → env/default;
480/// 0 = force tiling; 1 = admit the deterministic form selector). The former timing
481/// selector made identical boots choose different fold orders; `MEMRA_MMQ_SK_FORM` is the
482/// explicit numerical-form seam. mmq_ffi reads this before the env.
483pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
484/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
485/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
486pub use memra_kv::KV_FP8_FORCE;
487pub(crate) fn rms_block() -> u32 {
488    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
489    *V.get_or_init(|| {
490        std::env::var("MEMRA_RMS_BLOCK")
491            .ok()
492            .and_then(|v| v.parse().ok())
493            .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
494    })
495}
496
497pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
498    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
499    if let Some(forced) = *S.get_or_init(|| {
500        std::env::var("MEMRA_FA_SPLIT")
501            .ok()
502            .and_then(|v| v.parse().ok())
503            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
504    }) {
505        return forced;
506    }
507    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
508    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
509    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
510    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
511    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
512    //
513    // SM-AWARE SHORT-CTX RUNG (2026-07-06 g7e): the 32-key rung was tuned on the 82-SM 5090.
514    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
515    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on g7e (N=1 sweep + N=3
516    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
517    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
518    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
519    // rig-divergence law: this branch is measured on 188 SMs only).
520    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
521    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
522    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
523    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
524    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
525        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1")
526    {
527        return if t_kv <= 8192 {
528            16
529        } else if t_kv <= 16384 {
530            64
531        } else {
532            128
533        };
534    }
535    let big_rig = fa_sm_count() >= 128;
536    if big_rig {
537        let _ = n_head_kv;
538        if t_kv <= 2048 {
539            16
540        } else if t_kv <= 16384 {
541            64
542        } else {
543            128
544        }
545    } else if n_head_kv <= 4 {
546        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
547        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
548        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
549        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
550        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
551        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
552        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
553        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
554        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
555        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
556        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
557        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
558        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
559        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
560        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
561        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
562        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
563        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
564        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
565        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
566        if t_kv <= 512 {
567            8
568        } else if t_kv <= 16384 {
569            64
570        } else {
571            128
572        }
573    } else {
574        if t_kv <= 8192 {
575            32
576        } else if t_kv <= 16384 {
577            64
578        } else {
579            128
580        }
581    }
582}
583
584/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
585/// same attribute Engine::batched_variant reads).
586fn fa_sm_count() -> i32 {
587    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
588    *N.get_or_init(|| {
589        cudarc::driver::result::init().ok();
590        cudarc::driver::result::device::get(0)
591            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
592                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
593            .unwrap_or(82)
594    })
595}
596
597/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
598/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
599/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
600fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
601    match head_dim {
602        256 => Ok(""),
603        128 => Ok("_hd128"),
604        d => Err(format!(
605            "fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
606                          callers must gate to sdpa_naive"
607        )
608        .into()),
609    }
610}
611
612/// Quant type codes matching qmatvec.cu QType enum.
613pub const QT_Q8_0: i32 = 0;
614pub const QT_Q4_K: i32 = 1;
615pub const QT_Q6_K: i32 = 2;
616pub const QT_Q5_K: i32 = 3;
617pub const QT_Q3_K: i32 = 4;
618pub const QT_IQ4_XS: i32 = 5;
619pub const QT_IQ3_S: i32 = 6;
620pub const QT_NVFP4: i32 = 7;
621/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
622/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
623/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
624/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
625/// — ONE weight copy total, no Q8_0 re-encode duplicate.
626pub const QT_F8_E4M3: i32 = 10;
627/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
628/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
629pub const QT_NVFP4_RP: i32 = 9;
630/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
631pub const QT_F32: i32 = 8;
632pub const QT_BF16: i32 = 11;
633pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
634/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
635/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
636/// dp4a/MMQ implementation exists.
637pub const QT_Q2_K: i32 = 13;
638/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
639/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
640/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
641/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
642/// scalar `scale` field is 1.0 by the layout contract.
643///
644/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
645/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
646/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
647/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
648/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
649/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
650/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
651/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
652/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
653pub const QT_F8_E4M3_BLK: i32 = 14;
654
655/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
656pub struct Engine {
657    pub gpu: memra_runtime::Gpu,
658    module: Arc<CudaModule>,
659    hybrid: Arc<CudaModule>,
660    qmatvec: Arc<CudaModule>,
661    flash: Arc<CudaModule>,
662    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
663    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
664    /// Lazy: loaded on first global-format use; None until then.
665    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
666    gemm: Arc<CudaModule>,
667    router: Arc<CudaModule>,
668    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
669    sample: Arc<CudaModule>,
670    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
671    /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
672    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
673    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
674    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
675    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
676    /// the single largest block. The cache still owns every address for its full lifetime.
677    moe_cache_layout: Mutex<Option<Vec<usize>>>,
678    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
679    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
680    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
681    /// verify between replays) reuse their addresses and the replay reads/writes live memory
682    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
683    capture_keep_on: std::sync::atomic::AtomicBool,
684    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
685    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
686    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
687    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
688    verify_exact: std::sync::atomic::AtomicBool,
689    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
690    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
691    pub copy_stream: Arc<CudaStream>,
692    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
693    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
694    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
695    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
696    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
697    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
698    #[cfg(memra_cutlass)]
699    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
700    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
701    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
702    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
703    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
704    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
705    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
706    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
707    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
708    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
709    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
710    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
711    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
712    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
713    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
714    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
715    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
716    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
717    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
718    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
719    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
720    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
721    /// before capture under the generate_graph tracking-off window so it carries no events).
722    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
723    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
724    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
725    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
726    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
727    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
728    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
729    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
730    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
731    router_stage: Mutex<Option<PinnedStage>>,
732}
733
734/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
735/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
736/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
737/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
738/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
739/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
740/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
741/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
742/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
743fn fa_v2_on() -> bool {
744    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
745    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
746    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
747    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
748    // + graph bit-identity green on all three models.
749    std::env::var("MEMRA_FA_V2")
750        .map(|v| v != "0")
751        .unwrap_or(true)
752}
753
754/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
755/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
756/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
757/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
758/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
759/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
760/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
761fn fa_v3_on() -> bool {
762    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
763    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
764    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
765    std::env::var("MEMRA_FA_V3")
766        .map(|v| v != "0")
767        .unwrap_or(true)
768}
769
770/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
771/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
772/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
773/// predicate so the twins can never diverge.
774fn fa_v4_mode() -> &'static str {
775    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
776    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
777}
778fn fa_v4_on() -> bool {
779    fa_v4_mode() != "0"
780} // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
781/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
782/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
783/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
784/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
785/// stays kernel-family-identical to decode at the same t_kv.
786/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
787/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
788pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
789    std::sync::atomic::AtomicUsize::new(1024);
790pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
791    std::sync::atomic::AtomicUsize::new(usize::MAX);
792pub fn fa_v4_at_pub(t_kv: usize) -> bool {
793    fa_v4_at(t_kv)
794}
795fn fa_v4_at(t_kv: usize) -> bool {
796    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
797    let mx = *M.get_or_init(|| {
798        std::env::var("MEMRA_FA_V4_MAX")
799            .ok()
800            .and_then(|v| v.parse().ok())
801            .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
802    });
803    fa_v4_on() && t_kv < mx
804}
805/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
806/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
807/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
808/// (same split partition, same softmax/accumulation order, same partials/combine) and only
809/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
810/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
811/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
812/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
813/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
814/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
815/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
816/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
817/// within one process (the v2/v3 pattern).
818pub const FA_DEEP_MIN_DEFAULT: usize = 0;
819fn fa_deep_at(t_kv: usize) -> bool {
820    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") {
821        return false;
822    }
823    let min = std::env::var("MEMRA_FA_DEEP_MIN")
824        .ok()
825        .and_then(|v| v.parse().ok())
826        .unwrap_or(FA_DEEP_MIN_DEFAULT);
827    t_kv >= min
828}
829/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
830pub fn fa_deep_at_pub(t_kv: usize) -> bool {
831    fa_deep_at(t_kv)
832}
833
834fn fa_v3_active(head_dim: usize) -> bool {
835    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
836    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
837    fa_v3_on()
838        && head_dim % 128 == 0
839        && kv_cache_formats() == ("q8_0", "q5_1")
840        && !Engine::kv_fp8_on()
841}
842
843/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
844/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
845/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
846/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
847/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
848/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
849/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
850pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
851    std::env::var("MEMRA_NO_FA_VEC").is_err()
852        && t_kv >= fa_vec_min_tkv()
853        && head_dim == 256
854        && fa_v4_at(t_kv)
855        && !matches!(fa_v4_mode(), "noB3" | "stage")
856        && !Engine::kv_fp8_on()
857}
858/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
859pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize {
860    fa_split_keys(t_kv, n_head_kv)
861}
862
863/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
864/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
865/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
866/// so we allocate through `result::malloc_host` with flags=0 directly.
867struct PinnedStage {
868    ptr: *mut u8,
869    cap: usize,
870}
871unsafe impl Send for PinnedStage {}
872impl PinnedStage {
873    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
874        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
875        Ok(PinnedStage { ptr, cap })
876    }
877}
878impl Drop for PinnedStage {
879    fn drop(&mut self) {
880        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
881    }
882}
883
884/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
885/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
886pub const ARGMAX_NB: usize = 256;
887
888/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
889pub(crate) use memra_fa3_vl as fa3_vl_raw;
890
891unsafe extern "C" {
892    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
893    fn memra_fa3_prefill(
894        q16: *const core::ffi::c_void,
895        k16: *const core::ffi::c_void,
896        v16: *const core::ffi::c_void,
897        o: *mut f32,
898        t: i32,
899        h: i32,
900        hkv: i32,
901        d: i32,
902        scale: f32,
903        stream: *mut core::ffi::c_void,
904    ) -> i32;
905    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
906    pub(crate) fn memra_fa3_vl(
907        q16s: *const *const core::ffi::c_void,
908        k16s: *const *const core::ffi::c_void,
909        v16s: *const *const core::ffi::c_void,
910        os: *const *mut f32,
911        ts: *const i32,
912        b: i32,
913        h: i32,
914        hkv: i32,
915        d: i32,
916        scale: f32,
917        stream: *mut core::ffi::c_void,
918    ) -> i32;
919}
920
921/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
922/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
923/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
924/// (slots are never re-allocated), so passing raw values is stable across the launch.
925#[repr(C)]
926#[derive(Clone, Copy)]
927pub struct WPtr8(pub [u64; 8]);
928unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
929
930/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
931/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
932/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
933/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
934#[repr(C)]
935#[derive(Clone, Copy, Default)]
936pub struct GdnSeqVl {
937    pub kb16: u64,
938    pub gcum: u64,
939    pub beta: u64,
940    pub u: u64,
941    pub wb16: u64,
942    pub y: u64,
943    pub ssnap: u64,
944    pub state_in: u64,
945    pub state_out: u64,
946    pub q: u64,
947    pub p: u64,
948    pub o: u64,
949    pub k: u64,
950    pub v: u64,
951    pub g: u64,
952    pub a: u64,
953    pub w: u64,
954    pub t: i32,
955    pub nc: i32,
956}
957unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
958#[repr(C)]
959#[derive(Clone, Copy)]
960pub struct GdnVl8(pub [GdnSeqVl; 8]);
961unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
962
963/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
964/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
965#[repr(C)]
966#[derive(Clone, Copy, Default)]
967pub struct GdnWVl {
968    pub qb16: u64,
969    pub pb16: u64,
970}
971unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
972#[repr(C)]
973#[derive(Clone, Copy)]
974pub struct GdnWVl8(pub [GdnWVl; 8]);
975unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
976
977/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
978#[repr(C)]
979#[derive(Clone, Copy, Default)]
980pub struct GdnPrepVl {
981    pub qkv: u64,
982    pub conv_state: u64,
983    pub conv_out: u64,
984    pub q_g: u64,
985    pub k_g: u64,
986    pub v_g: u64,
987    pub q_l2: u64,
988    pub k_l2: u64,
989    pub beta_raw: u64,
990    pub alpha: u64,
991    pub beta: u64,
992    pub g_log: u64,
993    pub o: u64,
994    pub z: u64,
995    pub gn: u64,
996    pub gn16: u64,
997    pub kb16: u64,
998    pub qb16: u64,
999    pub t: i32,
1000    pub pad: i32,
1001}
1002unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
1003#[repr(C)]
1004#[derive(Clone, Copy)]
1005pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
1006unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
1007
1008/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
1009#[repr(C)]
1010#[derive(Clone, Copy, Default)]
1011pub struct FaSeqVl {
1012    pub q: u64,
1013    pub k16: u64,
1014    pub v16: u64,
1015    pub o: u64,
1016    pub kf: u64,
1017    pub vf: u64,
1018    pub t: i32,
1019    pub pad: i32,
1020}
1021unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
1022#[repr(C)]
1023#[derive(Clone, Copy)]
1024pub struct FaVl8(pub [FaSeqVl; 8]);
1025unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
1026
1027/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
1028#[repr(C)]
1029#[derive(Clone, Copy, Default)]
1030pub struct AttnPreVl {
1031    pub qf: u64,
1032    pub kf: u64,
1033    pub vf: u64,
1034    pub q: u64,
1035    pub gate: u64,
1036    pub qn: u64,
1037    pub kn: u64,
1038    pub kc: u64,
1039    pub vc: u64,
1040    pub t: i32,
1041    pub pad: i32,
1042}
1043unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
1044#[repr(C)]
1045#[derive(Clone, Copy)]
1046pub struct AttnPreVl8(pub [AttnPreVl; 8]);
1047unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
1048
1049/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
1050/// varlen K1-K5 chain fills them).
1051pub struct GdnChunkBufs {
1052    pub gcum: CudaSlice<f32>,
1053    pub a: CudaSlice<f32>,
1054    pub p: CudaSlice<f32>,
1055    pub u: CudaSlice<f32>,
1056    pub w: CudaSlice<f32>,
1057    pub kb16: CudaSlice<u8>,
1058    pub wb16: CudaSlice<u8>,
1059    pub y16: CudaSlice<u8>,
1060    pub ssnap16: CudaSlice<u8>,
1061    pub qb16: CudaSlice<u8>,
1062    pub pb16: CudaSlice<u8>,
1063    pub o: CudaSlice<f32>,
1064    pub t: usize,
1065    pub nc: usize,
1066}
1067
1068/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
1069#[repr(C)]
1070#[derive(Clone, Copy)]
1071pub struct F32x8(pub [f32; 8]);
1072unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
1073
1074/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
1075/// process. Bench binaries read it right after the call to print gen-only throughput without the
1076/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
1077pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1078
1079impl Engine {
1080    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
1081        let gpu = memra_runtime::Gpu::new(ordinal)?;
1082        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
1083        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
1084        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
1085        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
1086            use cudarc::driver::sys::CUdevice_attribute_enum as A;
1087            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
1088                .and_then(|d| unsafe {
1089                    Ok((
1090                        cudarc::driver::result::device::get_attribute(
1091                            d,
1092                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
1093                        )?,
1094                        cudarc::driver::result::device::get_attribute(
1095                            d,
1096                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
1097                        )?,
1098                    ))
1099                })
1100                .unwrap_or((0, 0));
1101            let built = env!("MEMRA_BUILT_CUDA_ARCH");
1102            let ok = matches!(
1103                (built, maj, min),
1104                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9)
1105            );
1106            if !ok {
1107                return Err(format!(
1108                    "memra was built for sm_{built} but device {ordinal} reports compute \
1109                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
1110                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass."
1111                )
1112                .into());
1113            }
1114        }
1115        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
1116        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
1117        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
1118        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
1119        unsafe {
1120            use cudarc::driver::sys;
1121            let dev: sys::CUdevice = ordinal as sys::CUdevice;
1122            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1123            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
1124                let mut thresh: u64 = u64::MAX;
1125                let _ = sys::cuMemPoolSetAttribute(
1126                    pool,
1127                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
1128                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
1129                );
1130            }
1131        }
1132        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
1133        let hybrid = gpu
1134            .ctx
1135            .load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
1136        let qmatvec = gpu
1137            .ctx
1138            .load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
1139        let flash = gpu
1140            .ctx
1141            .load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
1142        let gemm = gpu
1143            .ctx
1144            .load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
1145        let router = gpu
1146            .ctx
1147            .load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
1148        let sample = gpu
1149            .ctx
1150            .load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
1151        let copy_stream = gpu.ctx.new_stream()?;
1152        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
1153        // cudarc is in multi-stream mode (main stream +
1154        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
1155        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
1156        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
1157        // (~7 ms/tok host time, measured nsys 2026-07-04 g7e), and +4.6% measured on 27B decode —
1158        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
1159        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
1160        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
1161        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
1162        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
1163        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
1164        // implicit event tracking.
1165        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
1166        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
1167        if std::env::var("MEMRA_EVT")
1168            .map(|v| v == "1")
1169            .unwrap_or(false)
1170        {
1171            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1172        } else {
1173            unsafe {
1174                gpu.ctx.disable_event_tracking();
1175            }
1176        }
1177        Ok(Self {
1178            gpu,
1179            module,
1180            hybrid,
1181            qmatvec,
1182            flash,
1183            flash_g: std::sync::OnceLock::new(),
1184            gemm,
1185            router,
1186            sample,
1187            moe_cache: Mutex::new(None),
1188            moe_cache_layout: Mutex::new(None),
1189            copy_stream,
1190            capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1191            verify_exact: std::sync::atomic::AtomicBool::new(false),
1192            capture_keep: Mutex::new(Vec::new()),
1193            argmax_partials: Mutex::new(None),
1194            prime_deqw_ws: Mutex::new(None),
1195            router_stage: Mutex::new(None),
1196            fp8_scratch: Mutex::new(None),
1197            fa_vf16_scratch: Mutex::new(None),
1198            fa_part_pool: Mutex::new(None),
1199            fa_part_retired: Mutex::new(Vec::new()),
1200            fn_cache: Mutex::new(Default::default()),
1201            f16_scratch: Mutex::new(None),
1202            #[cfg(memra_cutlass)]
1203            cutlass_scratch: Mutex::new(None),
1204        })
1205    }
1206
1207    pub fn ctx(&self) -> &Arc<CudaContext> {
1208        &self.gpu.ctx
1209    }
1210
1211    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1212    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1213    ///
1214    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1215    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1216    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1217    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1218    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1219    ///
1220    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1221    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1222    /// under-count headroom does not belong in a gate that queues real work, but the honest
1223    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1224    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1225    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1226    ///
1227    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1228    pub fn pool_cached_bytes(&self) -> usize {
1229        let (reserved, used) = self.pool_reserved_used();
1230        reserved.saturating_sub(used)
1231    }
1232
1233    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1234    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1235    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1236    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1237    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1238    /// (0, 0) if the pool cannot be queried.
1239    pub fn pool_reserved_used(&self) -> (usize, usize) {
1240        use cudarc::driver::sys;
1241        unsafe {
1242            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1243            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1244                != sys::CUresult::CUDA_SUCCESS
1245            {
1246                return (0, 0);
1247            }
1248            let (mut reserved, mut used) = (0u64, 0u64);
1249            if sys::cuMemPoolGetAttribute(
1250                pool,
1251                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1252                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1253            ) != sys::CUresult::CUDA_SUCCESS
1254            {
1255                return (0, 0);
1256            }
1257            if sys::cuMemPoolGetAttribute(
1258                pool,
1259                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1260                &mut used as *mut u64 as *mut core::ffi::c_void,
1261            ) != sys::CUresult::CUDA_SUCCESS
1262            {
1263                return (0, 0);
1264            }
1265            (reserved as usize, used as usize)
1266        }
1267    }
1268
1269    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1270    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1271    pub fn stream(&self) -> Arc<CudaStream> {
1272        self.gpu.stream()
1273    }
1274    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1275    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1276    pub fn gkv_on() -> bool {
1277        memra_kv::gkv_on()
1278    }
1279
1280    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1281    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1282    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1283    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1284    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1285    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1286    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1287    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1288    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1289    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1290    /// ON for both — no acceptance cost measured.
1291    pub fn wkv_on() -> bool {
1292        memra_kv::wkv_on()
1293    }
1294
1295    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1296    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1297    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1298    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1299    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1300    pub fn kv_fp8_on() -> bool {
1301        memra_kv::kv_fp8_on()
1302    }
1303
1304    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1305    /// when the fp8-globals arm is on; everything else from the default flash module.
1306    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1307        if head_dim == 512 && Self::gkv_on() {
1308            self.func_g(name)
1309        } else {
1310            self.func(name)
1311        }
1312    }
1313
1314    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1315    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1316    /// per-format fatbins; fall back to the base modules for those.
1317    fn func_g(&self, name: &str) -> CudaFunction {
1318        let m = self.flash_g.get_or_init(|| {
1319            self.gpu
1320                .ctx
1321                .load_module(cudarc::nvrtc::Ptx::from_binary(
1322                    FLASH_FATBIN_KF8VF8.to_vec(),
1323                ))
1324                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1325        });
1326        let key = format!("g:{name}");
1327        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) {
1328            return f.clone();
1329        }
1330        let f = match m.load_function(name) {
1331            Ok(f) => f,
1332            Err(_) => self.func(name),
1333        };
1334        self.fn_cache.lock().unwrap().insert(key, f.clone());
1335        f
1336    }
1337
1338    fn func(&self, name: &str) -> CudaFunction {
1339        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1340        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1341        if let Some(f) = self.fn_cache.lock().unwrap().get(name) {
1342            return f.clone();
1343        }
1344        let f = self
1345            .module
1346            .load_function(name)
1347            .or_else(|_| self.hybrid.load_function(name))
1348            .or_else(|_| self.qmatvec.load_function(name))
1349            .or_else(|_| self.flash.load_function(name))
1350            .or_else(|_| self.gemm.load_function(name))
1351            .or_else(|_| self.router.load_function(name))
1352            .or_else(|_| self.sample.load_function(name))
1353            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1354        self.fn_cache
1355            .lock()
1356            .unwrap()
1357            .insert(name.to_string(), f.clone());
1358        f
1359    }
1360
1361    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1362    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1363    pub fn scatter_trim_logits(
1364        &self,
1365        src: &CudaSlice<f32>,
1366        d2t: &CudaSlice<u32>,
1367        dst: &mut CudaSlice<f32>,
1368        d_vocab: usize,
1369        n_vocab: usize,
1370    ) -> Result<(), Box<dyn std::error::Error>> {
1371        let f1 = self.func("scatter_trim_logits_f32");
1372        let f2 = self.func("scatter_trim_logits_pass2_f32");
1373        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1374        let cfg1 = LaunchConfig {
1375            grid_dim: (256, 1, 1),
1376            block_dim: (256, 1, 1),
1377            shared_mem_bytes: 0,
1378        };
1379        let __s_b1 = self.gpu.stream();
1380        let mut b1 = __s_b1.launch_builder(&f1);
1381        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1382        unsafe {
1383            b1.launch(cfg1)?;
1384        }
1385        let cfg2 = LaunchConfig {
1386            grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1),
1387            block_dim: (256, 1, 1),
1388            shared_mem_bytes: 0,
1389        };
1390        let __s_b2 = self.gpu.stream();
1391        let mut b2 = __s_b2.launch_builder(&f2);
1392        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1393        unsafe {
1394            b2.launch(cfg2)?;
1395        }
1396        Ok(())
1397    }
1398
1399    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1400    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1401
1402    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1403    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1404    #[allow(clippy::too_many_arguments)]
1405    pub fn filter_stats(
1406        &self,
1407        x: &CudaSlice<f32>,
1408        row_stride: usize,
1409        rows: &CudaSlice<i32>,
1410        out_th: &mut CudaSlice<f32>,
1411        out_z: &mut CudaSlice<f32>,
1412        out_max: &mut CudaSlice<f32>,
1413        n: usize,
1414        nrow: usize,
1415        temp: f32,
1416        top_k: i32,
1417        top_p: f32,
1418        min_p: f32,
1419    ) -> Result<(), Box<dyn std::error::Error>> {
1420        // A top-K-selection form of this kernel (3 vocab passes vs the search's ~51) was
1421        // implemented and REFUTED on 2026-08-21 (lane/moebatch-q35moe): the 248k-vocab row is
1422        // L2-resident, so the extra passes are near-free while the per-thread selection list
1423        // spills to local memory — B=8 tick 12.8/11.2 ms (cap 64/32) vs 10.4 ms for this
1424        // kernel, and serve c8 agg ~648 vs ~666. The receipts row is the record; the real
1425        // filtered-sampling win was batching the per-row launches (decode_batch.rs).
1426        let f = self.func("filter_stats_f32");
1427        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1428        let cfg = LaunchConfig {
1429            grid_dim: (nrow as u32, 1, 1),
1430            block_dim: (1024, 1, 1),
1431            shared_mem_bytes: 0,
1432        };
1433        let __s_b = self.gpu.stream();
1434        let mut b = __s_b.launch_builder(&f);
1435        b.arg(x)
1436            .arg(&rs)
1437            .arg(rows)
1438            .arg(&mut *out_th)
1439            .arg(&mut *out_z)
1440            .arg(&mut *out_max)
1441            .arg(&ni)
1442            .arg(&nr)
1443            .arg(&temp)
1444            .arg(&top_k)
1445            .arg(&top_p)
1446            .arg(&min_p);
1447        unsafe {
1448            b.launch(cfg)?;
1449        }
1450        Ok(())
1451    }
1452
1453    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1454    #[allow(clippy::too_many_arguments)]
1455    pub fn softmax_gather_filtered(
1456        &self,
1457        x: &CudaSlice<f32>,
1458        row_stride: usize,
1459        ids: &CudaSlice<u32>,
1460        rows: &CudaSlice<i32>,
1461        th: &CudaSlice<f32>,
1462        z: &CudaSlice<f32>,
1463        out: &mut CudaSlice<f32>,
1464        n: usize,
1465        npair: usize,
1466        temp: f32,
1467    ) -> Result<(), Box<dyn std::error::Error>> {
1468        let f = self.func("softmax_gather_filtered_f32");
1469        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1470        let cfg = LaunchConfig {
1471            grid_dim: (npair as u32, 1, 1),
1472            block_dim: (256, 1, 1),
1473            shared_mem_bytes: 0,
1474        };
1475        let __s_b = self.gpu.stream();
1476        let mut b = __s_b.launch_builder(&f);
1477        b.arg(x)
1478            .arg(&rs)
1479            .arg(ids)
1480            .arg(rows)
1481            .arg(th)
1482            .arg(z)
1483            .arg(&mut *out)
1484            .arg(&ni)
1485            .arg(&np)
1486            .arg(&temp);
1487        unsafe {
1488            b.launch(cfg)?;
1489        }
1490        Ok(())
1491    }
1492
1493    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1494    #[allow(clippy::too_many_arguments)]
1495    pub fn residual_sample_filtered(
1496        &self,
1497        p: &CudaSlice<f32>,
1498        q: Option<&CudaSlice<f32>>,
1499        n: usize,
1500        temp: f32,
1501        seed: u64,
1502        stream_pos: u32,
1503        p_stats: (f32, f32, f32),
1504        q_stats: (f32, f32, f32),
1505        out_tok: &mut CudaSlice<u32>,
1506    ) -> Result<(), Box<dyn std::error::Error>> {
1507        let f = self.func("residual_sample_filtered_f32");
1508        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1509        let has_q: i32 = q.is_some() as i32;
1510        let qbuf = q.unwrap_or(p);
1511        let (pm, pth, pz) = p_stats;
1512        let (qm, qth, qz) = q_stats;
1513        let cfg = LaunchConfig {
1514            grid_dim: (1, 1, 1),
1515            block_dim: (1024, 1, 1),
1516            shared_mem_bytes: 0,
1517        };
1518        let __s_b = self.gpu.stream();
1519        let mut b = __s_b.launch_builder(&f);
1520        b.arg(p)
1521            .arg(qbuf)
1522            .arg(&has_q)
1523            .arg(&ni)
1524            .arg(&temp)
1525            .arg(&slo)
1526            .arg(&shi)
1527            .arg(&stream_pos)
1528            .arg(&pm)
1529            .arg(&pth)
1530            .arg(&pz)
1531            .arg(&qm)
1532            .arg(&qth)
1533            .arg(&qz)
1534            .arg(&mut *out_tok);
1535        unsafe {
1536            b.launch(cfg)?;
1537        }
1538        Ok(())
1539    }
1540
1541    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1542    #[allow(clippy::too_many_arguments)]
1543    pub fn gumbel_perturb_filtered(
1544        &self,
1545        x: &CudaSlice<f32>,
1546        y: &mut CudaSlice<f32>,
1547        n: usize,
1548        seed: u64,
1549        stream_pos: u32,
1550        temp: f32,
1551        row_max: f32,
1552        th: f32,
1553    ) -> Result<(), Box<dyn std::error::Error>> {
1554        let f = self.func("gumbel_perturb_filtered_f32");
1555        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1556        let cfg = LaunchConfig {
1557            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1558            block_dim: (256, 1, 1),
1559            shared_mem_bytes: 0,
1560        };
1561        let __s_b = self.gpu.stream();
1562        let mut b = __s_b.launch_builder(&f);
1563        b.arg(x)
1564            .arg(&mut *y)
1565            .arg(&ni)
1566            .arg(&slo)
1567            .arg(&shi)
1568            .arg(&stream_pos)
1569            .arg(&temp)
1570            .arg(&row_max)
1571            .arg(&th);
1572        unsafe {
1573            b.launch(cfg)?;
1574        }
1575        Ok(())
1576    }
1577
1578    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1579    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1580    /// filtered rejection sampling exact for the penalized target.
1581    #[allow(clippy::too_many_arguments)]
1582    pub fn penalize_logits(
1583        &self,
1584        x: &mut CudaSlice<f32>,
1585        hist: &CudaSlice<u32>,
1586        n_hist: usize,
1587        rep: f32,
1588        freq: f32,
1589        present: f32,
1590        n: usize,
1591    ) -> Result<(), Box<dyn std::error::Error>> {
1592        if n_hist == 0 {
1593            return Ok(());
1594        }
1595        let f = self.func("penalize_logits_f32");
1596        let (nh, ni) = (n_hist as i32, n as i32);
1597        let cfg = LaunchConfig {
1598            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1599            block_dim: (128, 1, 1),
1600            shared_mem_bytes: 0,
1601        };
1602        let __s_b = self.gpu.stream();
1603        let mut b = __s_b.launch_builder(&f);
1604        b.arg(&mut *x)
1605            .arg(hist)
1606            .arg(&nh)
1607            .arg(&rep)
1608            .arg(&freq)
1609            .arg(&present)
1610            .arg(&ni);
1611        unsafe {
1612            b.launch(cfg)?;
1613        }
1614        Ok(())
1615    }
1616
1617    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1618    #[allow(clippy::too_many_arguments)]
1619    pub fn penalize_logits_rows(
1620        &self,
1621        x: &mut CudaSlice<f32>,
1622        hist: &CudaSlice<u32>,
1623        n_hist: usize,
1624        rep: f32,
1625        freq: f32,
1626        present: f32,
1627        n: usize,
1628        nrow: usize,
1629    ) -> Result<(), Box<dyn std::error::Error>> {
1630        if n_hist == 0 || nrow == 0 {
1631            return Ok(());
1632        }
1633        let f = self.func("penalize_logits_rows_f32");
1634        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1635        let cfg = LaunchConfig {
1636            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1637            block_dim: (128, 1, 1),
1638            shared_mem_bytes: 0,
1639        };
1640        let __s_b = self.gpu.stream();
1641        let mut b = __s_b.launch_builder(&f);
1642        b.arg(&mut *x)
1643            .arg(hist)
1644            .arg(&nh)
1645            .arg(&rep)
1646            .arg(&freq)
1647            .arg(&present)
1648            .arg(&ni)
1649            .arg(&nr);
1650        unsafe {
1651            b.launch(cfg)?;
1652        }
1653        Ok(())
1654    }
1655
1656    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1657    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1658    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1659    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1660    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1661    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1662    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1663    pub fn wpf_level() -> u32 {
1664        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1665        *ON.get_or_init(|| {
1666            std::env::var("MEMRA_WPF")
1667                .ok()
1668                .and_then(|v| v.parse().ok())
1669                .unwrap_or(1)
1670        })
1671    }
1672
1673    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1674    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1675    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1676    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1677    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1678    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1679    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1680    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1681    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1682    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1683    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1684    pub fn set_verify_exact(&self, on: bool) {
1685        self.verify_exact
1686            .store(on, std::sync::atomic::Ordering::Relaxed);
1687    }
1688    pub(crate) fn verify_exact_on(&self) -> bool {
1689        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1690    }
1691
1692    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1693    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1694    pub fn qkv_append_on() -> bool {
1695        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1696        *ON.get_or_init(|| {
1697            std::env::var("MEMRA_QKV_APPEND")
1698                .map(|v| v != "0")
1699                .unwrap_or(true)
1700        })
1701    }
1702
1703    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1704    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1705    pub fn pdl_wb_on() -> bool {
1706        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1707        *ON.get_or_init(|| {
1708            std::env::var("MEMRA_PDL_WB")
1709                .map(|v| v != "0")
1710                .unwrap_or(true)
1711        })
1712    }
1713
1714    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1715    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1716    /// per-model no-harm bisect knob.
1717    pub fn pdl_mmvq_on() -> bool {
1718        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1719        *ON.get_or_init(|| {
1720            std::env::var("MEMRA_PDL_MMVQ")
1721                .map(|v| v != "0")
1722                .unwrap_or(true)
1723        })
1724    }
1725
1726    pub fn pdl_on() -> bool {
1727        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1728        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1729    }
1730
1731    /// PDL wave-B seam (gap-diagnosis arc, GAP-DIAGNOSIS.md verdict 8): the gemma
1732    /// NVFP4mix decode chain's hot kernels — nvfp4 fused2/mr2 and the q8_0 `_rp`
1733    /// singles — join the wave-A launch class. Scheduling-only (the entry macro waits
1734    /// on the producer before any read), bit-identical by construction.
1735    /// MEMRA_PDL_NVFP4=0 reverts wave-B alone.
1736    pub fn pdl_nvfp4q8_on() -> bool {
1737        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1738        *ON.get_or_init(|| {
1739            std::env::var("MEMRA_PDL_NVFP4")
1740                .map(|v| v != "0")
1741                .unwrap_or(true)
1742        })
1743    }
1744
1745    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1746    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1747    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1748    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1749    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1750    fn q40_mr1_on() -> bool {
1751        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1752        match *Q40MR.get_or_init(|| {
1753            std::env::var("MEMRA_Q40_MR")
1754                .ok()
1755                .and_then(|v| v.parse().ok())
1756        }) {
1757            Some(v) => v == 1,
1758            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1759        }
1760    }
1761
1762    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1763    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1764    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1765    /// writes wrong bytes silently.
1766    fn pdl_func_flash(
1767        &self,
1768        g: bool,
1769        name: &'static str,
1770    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1771        use cudarc::driver::sys as cu;
1772        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1773        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1774        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1775        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1776        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1777        // this engine's CUcontext; single-context runs behave exactly as before.
1778        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1779            std::sync::Mutex::new(None);
1780        static FNS: std::sync::Mutex<
1781            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
1782        > = std::sync::Mutex::new(None);
1783        let ctx_key = self.ctx().cu_ctx() as usize;
1784        if let Some(&f) = FNS
1785            .lock()
1786            .unwrap()
1787            .get_or_insert_with(Default::default)
1788            .get(&(ctx_key, g, name))
1789        {
1790            return Ok(f as cu::CUfunction);
1791        }
1792        let module = {
1793            let mut mods = MODS.lock().unwrap();
1794            let map = mods.get_or_insert_with(Default::default);
1795            match map.get(&(ctx_key, g)) {
1796                Some(&m) => m,
1797                None => {
1798                    let m = self.pdl_load_module_in_ctx(if g {
1799                        FLASH_FATBIN_KF8VF8
1800                    } else {
1801                        FLASH_FATBIN
1802                    })?;
1803                    map.insert((ctx_key, g), m);
1804                    m
1805                }
1806            }
1807        };
1808        let cname = std::ffi::CString::new(name)?;
1809        let mut f: cu::CUfunction = std::ptr::null_mut();
1810        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1811        if r != cu::CUresult::CUDA_SUCCESS {
1812            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
1813        }
1814        FNS.lock()
1815            .unwrap()
1816            .get_or_insert_with(Default::default)
1817            .insert((ctx_key, g, name), f as usize);
1818        Ok(f)
1819    }
1820
1821    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1822    /// the module to the thread's CURRENT context — a remote-stage engine must not
1823    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1824    /// current context before returning.
1825    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1826        use cudarc::driver::sys as cu;
1827        let mut prev: cu::CUcontext = std::ptr::null_mut();
1828        unsafe {
1829            cu::cuCtxGetCurrent(&mut prev).result()?;
1830        }
1831        self.ctx().bind_to_thread()?;
1832        let mut m: cu::CUmodule = std::ptr::null_mut();
1833        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1834        let restore = if prev.is_null() {
1835            cu::CUresult::CUDA_SUCCESS
1836        } else {
1837            unsafe { cu::cuCtxSetCurrent(prev) }
1838        };
1839        if r != cu::CUresult::CUDA_SUCCESS {
1840            return Err(format!("pdl module load: {r:?}").into());
1841        }
1842        if restore != cu::CUresult::CUDA_SUCCESS {
1843            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1844        }
1845        Ok(m as usize)
1846    }
1847
1848    fn pdl_func(
1849        &self,
1850        name: &'static str,
1851    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1852        use cudarc::driver::sys as cu;
1853        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1854        // are context-scoped; key everything by this engine's CUcontext).
1855        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1856            std::sync::Mutex::new(None);
1857        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1858        // duplicate module, loaded lazily on the first kernels-module miss.
1859        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1860            std::sync::Mutex::new(None);
1861        static FNS: std::sync::Mutex<
1862            Option<std::collections::HashMap<(usize, &'static str), usize>>,
1863        > = std::sync::Mutex::new(None);
1864        let ctx_key = self.ctx().cu_ctx() as usize;
1865        if let Some(&f) = FNS
1866            .lock()
1867            .unwrap()
1868            .get_or_insert_with(Default::default)
1869            .get(&(ctx_key, name))
1870        {
1871            return Ok(f as cu::CUfunction);
1872        }
1873        let module = {
1874            let mut mods = MODULES.lock().unwrap();
1875            let map = mods.get_or_insert_with(Default::default);
1876            match map.get(&ctx_key) {
1877                Some(&m) => m,
1878                None => {
1879                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1880                    map.insert(ctx_key, m);
1881                    m
1882                }
1883            }
1884        };
1885        let cname = std::ffi::CString::new(name)?;
1886        let mut f: cu::CUfunction = std::ptr::null_mut();
1887        let mut r =
1888            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1889        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1890            let qmodule = {
1891                let mut mods = QMODULES.lock().unwrap();
1892                let map = mods.get_or_insert_with(Default::default);
1893                match map.get(&ctx_key) {
1894                    Some(&m) => m,
1895                    None => {
1896                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1897                        map.insert(ctx_key, m);
1898                        m
1899                    }
1900                }
1901            };
1902            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1903        }
1904        if r != cu::CUresult::CUDA_SUCCESS {
1905            return Err(format!("pdl_func {name}: {r:?}").into());
1906        }
1907        FNS.lock()
1908            .unwrap()
1909            .get_or_insert_with(Default::default)
1910            .insert((ctx_key, name), f as usize);
1911        Ok(f)
1912    }
1913
1914    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1915    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1916    ///
1917    /// # Safety
1918    /// `params` must match the kernel's exact parameter list (order, types, count) —
1919    /// a mismatch corrupts the launch silently.
1920    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1921    /// builder path's fa_func/func_g choice exactly).
1922    ///
1923    /// # Safety
1924    /// Same contract as `launch_pdl`.
1925    unsafe fn launch_pdl_flash(
1926        &self,
1927        g: bool,
1928        name: &'static str,
1929        grid: (u32, u32, u32),
1930        block: (u32, u32, u32),
1931        smem: u32,
1932        params: &mut [*mut std::ffi::c_void],
1933    ) -> Result<(), Box<dyn std::error::Error>> {
1934        use cudarc::driver::sys as cu;
1935        let f = self.pdl_func_flash(g, name)?;
1936        if smem > 0 {
1937            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1938            let r =
1939                unsafe {
1940                    cu::cuFuncSetAttribute(f,
1941                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1942                smem as i32)
1943                };
1944            if r != cu::CUresult::CUDA_SUCCESS {
1945                return Err(format!("pdl smem attr {name}: {r:?}").into());
1946            }
1947        }
1948        let mut attr = cu::CUlaunchAttribute {
1949            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1950            pad: [0; 4],
1951            value: cu::CUlaunchAttributeValue {
1952                programmaticStreamSerializationAllowed: 1,
1953            },
1954        };
1955        let cfg = cu::CUlaunchConfig {
1956            gridDimX: grid.0,
1957            gridDimY: grid.1,
1958            gridDimZ: grid.2,
1959            blockDimX: block.0,
1960            blockDimY: block.1,
1961            blockDimZ: block.2,
1962            sharedMemBytes: smem,
1963            hStream: self.gpu.stream().cu_stream(),
1964            attrs: &mut attr,
1965            numAttrs: 1,
1966        };
1967        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1968        if r != cu::CUresult::CUDA_SUCCESS {
1969            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
1970        }
1971        Ok(())
1972    }
1973
1974    unsafe fn launch_pdl(
1975        &self,
1976        name: &'static str,
1977        grid: (u32, u32, u32),
1978        block: (u32, u32, u32),
1979        params: &mut [*mut std::ffi::c_void],
1980    ) -> Result<(), Box<dyn std::error::Error>> {
1981        use cudarc::driver::sys as cu;
1982        let f = self.pdl_func(name)?;
1983        let mut attr = cu::CUlaunchAttribute {
1984            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1985            pad: [0; 4],
1986            value: cu::CUlaunchAttributeValue {
1987                programmaticStreamSerializationAllowed: 1,
1988            },
1989        };
1990        let cfg = cu::CUlaunchConfig {
1991            gridDimX: grid.0,
1992            gridDimY: grid.1,
1993            gridDimZ: grid.2,
1994            blockDimX: block.0,
1995            blockDimY: block.1,
1996            blockDimZ: block.2,
1997            sharedMemBytes: 0,
1998            hStream: self.gpu.stream().cu_stream(),
1999            attrs: &mut attr,
2000            numAttrs: 1,
2001        };
2002        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
2003        if r != cu::CUresult::CUDA_SUCCESS {
2004            return Err(format!("launch_pdl {name}: {r:?}").into());
2005        }
2006        Ok(())
2007    }
2008
2009    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
2010    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
2011    pub fn prefetch_weight_l2(
2012        &self,
2013        w: &crate::model::GpuTensor,
2014    ) -> Result<(), Box<dyn std::error::Error>> {
2015        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
2016            let p = rp4.as_ref().unwrap_or(bytes);
2017            self.prefetch_l2(p, p.len())?;
2018        }
2019        Ok(())
2020    }
2021
2022    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
2023    /// by the DEVICE token id at tok[idx] into f32.
2024    pub fn gather_row_bf16(
2025        &self,
2026        table: &CudaSlice<u8>,
2027        tok: &CudaSlice<u32>,
2028        idx: usize,
2029        dst: &mut CudaSlice<f32>,
2030        ncols: usize,
2031    ) -> Result<(), Box<dyn std::error::Error>> {
2032        let f = self.func("gather_row_bf16_f32");
2033        let cfg = LaunchConfig {
2034            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2035            block_dim: (256, 1, 1),
2036            shared_mem_bytes: 0,
2037        };
2038        let (nc, ix) = (ncols as i32, idx as i32);
2039        let __s_b = self.gpu.stream();
2040        let mut b = __s_b.launch_builder(&f);
2041        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2042        unsafe {
2043            b.launch(cfg)?;
2044        }
2045        Ok(())
2046    }
2047
2048    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2049    pub fn add_row_inplace(
2050        &self,
2051        logits: &mut CudaSlice<f32>,
2052        bias: &CudaSlice<f32>,
2053        n: usize,
2054        row_off: usize,
2055    ) -> Result<(), Box<dyn std::error::Error>> {
2056        let f = self.func("add_row_inplace_f32");
2057        let cfg = LaunchConfig {
2058            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2059            block_dim: (256, 1, 1),
2060            shared_mem_bytes: 0,
2061        };
2062        let (ni, off) = (n as i32, row_off as i64);
2063        let __s_b = self.gpu.stream();
2064        let mut b = __s_b.launch_builder(&f);
2065        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2066        unsafe {
2067            b.launch(cfg)?;
2068        }
2069        Ok(())
2070    }
2071
2072    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2073    pub fn prefetch_l2(
2074        &self,
2075        p: &CudaSlice<u8>,
2076        n: usize,
2077    ) -> Result<(), Box<dyn std::error::Error>> {
2078        let f = self.func("prefetch_l2_bytes");
2079        let lines = n.div_ceil(128);
2080        let ni = n as i64;
2081        let cfg = LaunchConfig {
2082            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2083            block_dim: (256, 1, 1),
2084            shared_mem_bytes: 0,
2085        };
2086        let __s_b = self.gpu.stream();
2087        let mut b = __s_b.launch_builder(&f);
2088        b.arg(p).arg(&ni);
2089        unsafe {
2090            b.launch(cfg)?;
2091        }
2092        Ok(())
2093    }
2094
2095    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2096    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2097    pub fn router_gemv(
2098        &self,
2099        w: &CudaSlice<f32>,
2100        x: &CudaSlice<f32>,
2101        n_embd: usize,
2102        n_experts: usize,
2103        t: usize,
2104    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2105        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2106        // stream differs) — too small to justify a numeric config change; deleted.
2107        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2108        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2109        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2110        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2111            Ok("0") => false,
2112            Ok(_) => true,
2113            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2114        };
2115        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2116        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2117        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2118        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2119        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2120        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2121        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2122        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2123        // (perf-only, bits equal).
2124        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2125        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2126    }
2127
2128    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2129    /// force both forms; `batch` requires `w8`).
2130    pub fn router_gemv_form(
2131        &self,
2132        w: &CudaSlice<f32>,
2133        x: &CudaSlice<f32>,
2134        n_embd: usize,
2135        n_experts: usize,
2136        t: usize,
2137        w8: bool,
2138        batch: bool,
2139    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2140        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2141        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2142        let f = if batch {
2143            self.func("router_gemv_f32_w8_batch")
2144        } else if w8 {
2145            self.func("router_gemv_f32_w8")
2146        } else {
2147            self.func("router_gemv_f32")
2148        };
2149        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2150        let cfg = if batch {
2151            LaunchConfig {
2152                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2153                block_dim: (32, 8, 1),
2154                shared_mem_bytes: 0,
2155            }
2156        } else {
2157            LaunchConfig {
2158                grid_dim: (n_experts as u32, t as u32, 1),
2159                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2160                shared_mem_bytes: 0,
2161            }
2162        };
2163        let __s_b = self.gpu.stream();
2164        let mut b = __s_b.launch_builder(&f);
2165        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2166        unsafe {
2167            b.launch(cfg)?;
2168        }
2169        Ok(y)
2170    }
2171
2172    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2173    pub fn rows_permute(
2174        &self,
2175        src: &CudaSlice<f32>,
2176        idx: &CudaSlice<i32>,
2177        nrows: usize,
2178        ncols: usize,
2179    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2180        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2181        let f = self.func("rows_permute_f32");
2182        let (nc, nr) = (ncols as i32, nrows as i32);
2183        let cfg = LaunchConfig {
2184            grid_dim: (nrows as u32, 1, 1),
2185            block_dim: (256, 1, 1),
2186            shared_mem_bytes: 0,
2187        };
2188        let __s_b = self.gpu.stream();
2189        let mut b = __s_b.launch_builder(&f);
2190        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2191        unsafe {
2192            b.launch(cfg)?;
2193        }
2194        Ok(dst)
2195    }
2196
2197    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2198    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2199    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2200    /// decode chain and the small-t spec-verify chain match per row by construction.
2201    pub fn sigmoid_dot_rows(
2202        &self,
2203        x: &CudaSlice<f32>,
2204        w: &CudaSlice<f32>,
2205        n_embd: usize,
2206        t: usize,
2207    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2208        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2209        // config; same class as MEMRA_ROUTER_V2).
2210        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2211        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2212            let gs = self.linear(x, w, t, n_embd, 1)?;
2213            let mut g = self.uninit(t)?;
2214            self.sigmoid(&gs, &mut g, t)?;
2215            return Ok(g);
2216        }
2217        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2218        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2219        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2220        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2221        // flags doctrine; this per-token form serves every t.
2222        let mut g = self.alloc_uninit::<f32>(t)?;
2223        let f = self.func("sigmoid_dot_rows_f32");
2224        let (ne, ti) = (n_embd as i32, t as i32);
2225        let cfg = LaunchConfig {
2226            grid_dim: (t as u32, 1, 1),
2227            block_dim: (32, 8, 1),
2228            shared_mem_bytes: 0,
2229        };
2230        let __s_b = self.gpu.stream();
2231        let mut b = __s_b.launch_builder(&f);
2232        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2233        unsafe {
2234            b.launch(cfg)?;
2235        }
2236        Ok(g)
2237    }
2238
2239    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2240    pub fn spec_rollback_stream(
2241        &self,
2242        len_ptrs: &CudaSlice<u64>,
2243        pos_start: &CudaSlice<i32>,
2244        acc: &CudaSlice<u32>,
2245        base: usize,
2246        n_rows: usize,
2247    ) -> Result<(), Box<dyn std::error::Error>> {
2248        let f = self.func("spec_rollback_stream");
2249        let (b, nr) = (base as i32, n_rows as i32);
2250        let cfg = LaunchConfig {
2251            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2252            block_dim: (64, 1, 1),
2253            shared_mem_bytes: 0,
2254        };
2255        let __s_bl = self.gpu.stream();
2256        let mut bl = __s_bl.launch_builder(&f);
2257        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2258        unsafe {
2259            bl.launch(cfg)?;
2260        }
2261        Ok(())
2262    }
2263
2264    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2265    pub fn plain_tok_ring(
2266        &self,
2267        vam: &CudaSlice<u32>,
2268        pos_start: &CudaSlice<i32>,
2269        base: usize,
2270        ring: &mut CudaSlice<u32>,
2271    ) -> Result<(), Box<dyn std::error::Error>> {
2272        let f = self.func("plain_tok_ring");
2273        let (b, cap) = (base as i32, ring.len() as i32);
2274        let cfg = LaunchConfig {
2275            grid_dim: (1, 1, 1),
2276            block_dim: (32, 1, 1),
2277            shared_mem_bytes: 0,
2278        };
2279        let __s_bl = self.gpu.stream();
2280        let mut bl = __s_bl.launch_builder(&f);
2281        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2282        unsafe {
2283            bl.launch(cfg)?;
2284        }
2285        Ok(())
2286    }
2287
2288    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2289    pub fn spec_ring_commit(
2290        &self,
2291        vtok: &CudaSlice<u32>,
2292        acc: &CudaSlice<u32>,
2293        brk: &CudaSlice<u32>,
2294        ring: &mut CudaSlice<u32>,
2295        pend: &mut CudaSlice<u32>,
2296    ) -> Result<(), Box<dyn std::error::Error>> {
2297        let f = self.func("spec_ring_commit");
2298        let cfg = LaunchConfig {
2299            grid_dim: (1, 1, 1),
2300            block_dim: (32, 1, 1),
2301            shared_mem_bytes: 0,
2302        };
2303        let __s_b = self.gpu.stream();
2304        let mut b = __s_b.launch_builder(&f);
2305        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2306        unsafe {
2307            b.launch(cfg)?;
2308        }
2309        Ok(())
2310    }
2311    pub fn i32_copy_add(
2312        &self,
2313        src: &CudaSlice<i32>,
2314        dst: &mut CudaSlice<i32>,
2315        delta: i32,
2316    ) -> Result<(), Box<dyn std::error::Error>> {
2317        let f = self.func("i32_copy_add");
2318        let cfg = LaunchConfig {
2319            grid_dim: (1, 1, 1),
2320            block_dim: (32, 1, 1),
2321            shared_mem_bytes: 0,
2322        };
2323        let __s_b = self.gpu.stream();
2324        let mut b = __s_b.launch_builder(&f);
2325        b.arg(src).arg(dst).arg(&delta);
2326        unsafe {
2327            b.launch(cfg)?;
2328        }
2329        Ok(())
2330    }
2331    pub fn u32_copy(
2332        &self,
2333        src: &CudaSlice<u32>,
2334        dst: &mut CudaSlice<u32>,
2335    ) -> Result<(), Box<dyn std::error::Error>> {
2336        let f = self.func("u32_copy");
2337        let cfg = LaunchConfig {
2338            grid_dim: (1, 1, 1),
2339            block_dim: (32, 1, 1),
2340            shared_mem_bytes: 0,
2341        };
2342        let __s_b = self.gpu.stream();
2343        let mut b = __s_b.launch_builder(&f);
2344        b.arg(src).arg(dst);
2345        unsafe {
2346            b.launch(cfg)?;
2347        }
2348        Ok(())
2349    }
2350
2351    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2352    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2353    /// caps acceptance exactly like drafting fewer tokens).
2354    pub fn spec_adapt_k(
2355        &self,
2356        acc: &CudaSlice<u32>,
2357        brk: &mut CudaSlice<u32>,
2358        floor: usize,
2359        cap: usize,
2360    ) -> Result<(), Box<dyn std::error::Error>> {
2361        let f = self.func("spec_adapt_k");
2362        let (fl, cp) = (floor as i32, cap as i32);
2363        let cfg = LaunchConfig {
2364            grid_dim: (1, 1, 1),
2365            block_dim: (32, 1, 1),
2366            shared_mem_bytes: 0,
2367        };
2368        let __s_b = self.gpu.stream();
2369        let mut b = __s_b.launch_builder(&f);
2370        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2371        unsafe {
2372            b.launch(cfg)?;
2373        }
2374        Ok(())
2375    }
2376
2377    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2378    pub fn spec_accept_greedy_dc(
2379        &self,
2380        preds: &CudaSlice<u32>,
2381        vtok: &CudaSlice<u32>,
2382        last_pred: &CudaSlice<u32>,
2383        brk: &CudaSlice<u32>,
2384        out: &mut CudaSlice<u32>,
2385    ) -> Result<(), Box<dyn std::error::Error>> {
2386        let f = self.func("spec_accept_greedy_dc");
2387        let cfg = LaunchConfig {
2388            grid_dim: (1, 1, 1),
2389            block_dim: (32, 1, 1),
2390            shared_mem_bytes: 0,
2391        };
2392        let __s_b = self.gpu.stream();
2393        let mut b = __s_b.launch_builder(&f);
2394        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2395        unsafe {
2396            b.launch(cfg)?;
2397        }
2398        Ok(())
2399    }
2400
2401    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2402    pub fn pos_iota(
2403        &self,
2404        pos0: &CudaSlice<i32>,
2405        out: &mut CudaSlice<i32>,
2406        t: usize,
2407    ) -> Result<(), Box<dyn std::error::Error>> {
2408        let f = self.func("pos_iota_i32");
2409        let ti = t as i32;
2410        let cfg = LaunchConfig {
2411            grid_dim: (1, 1, 1),
2412            block_dim: (t.max(1) as u32, 1, 1),
2413            shared_mem_bytes: 0,
2414        };
2415        let __s_b = self.gpu.stream();
2416        let mut b = __s_b.launch_builder(&f);
2417        b.arg(pos0).arg(out).arg(&ti);
2418        unsafe {
2419            b.launch(cfg)?;
2420        }
2421        Ok(())
2422    }
2423    #[allow(clippy::too_many_arguments)]
2424    pub fn append_kv_quantized_rows_dc(
2425        &self,
2426        k_rows: &CudaSlice<f32>,
2427        v_rows: &CudaSlice<f32>,
2428        kc: &mut CudaSlice<u8>,
2429        vc: &mut CudaSlice<u8>,
2430        t0_dev: &CudaSlice<i32>,
2431        t: usize,
2432        kv_dim_k: usize,
2433        kv_dim_v: usize,
2434        k_tok_bytes: usize,
2435        v_tok_bytes: usize,
2436        g: bool,
2437    ) -> Result<(), Box<dyn std::error::Error>> {
2438        let f = if g {
2439            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2440        } else {
2441            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2442        };
2443        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2444        let cfg = LaunchConfig {
2445            grid_dim: (nblk, t as u32, 1),
2446            block_dim: (32, 1, 1),
2447            shared_mem_bytes: 0,
2448        };
2449        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2450        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2451        let __s_b = self.gpu.stream();
2452        let mut b = __s_b.launch_builder(&f);
2453        b.arg(k_rows)
2454            .arg(v_rows)
2455            .arg(kc)
2456            .arg(vc)
2457            .arg(t0_dev)
2458            .arg(&kdk)
2459            .arg(&kdv)
2460            .arg(&ktb)
2461            .arg(&vtb);
2462        unsafe {
2463            b.launch(cfg)?;
2464        }
2465        Ok(())
2466    }
2467
2468    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2469    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2470    #[allow(clippy::too_many_arguments)]
2471    pub fn append_kv_quantized_row_dc_inc(
2472        &self,
2473        k_row: &CudaSlice<f32>,
2474        v_row: &CudaSlice<f32>,
2475        kc: &mut CudaSlice<u8>,
2476        vc: &mut CudaSlice<u8>,
2477        t0_dev: &mut CudaSlice<i32>,
2478        kv_dim_k: usize,
2479        kv_dim_v: usize,
2480        k_tok_bytes: usize,
2481        v_tok_bytes: usize,
2482        g: bool,
2483    ) -> Result<(), Box<dyn std::error::Error>> {
2484        let f = if g {
2485            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2486        } else {
2487            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2488        };
2489        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2490        let cfg = LaunchConfig {
2491            grid_dim: (1, 1, 1),
2492            block_dim: (nthreads, 1, 1),
2493            shared_mem_bytes: 0,
2494        };
2495        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2496        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2497        let __s_b = self.gpu.stream();
2498        let mut b = __s_b.launch_builder(&f);
2499        b.arg(k_row)
2500            .arg(v_row)
2501            .arg(kc)
2502            .arg(vc)
2503            .arg(t0_dev)
2504            .arg(&kdk)
2505            .arg(&kdv)
2506            .arg(&ktb)
2507            .arg(&vtb);
2508        unsafe {
2509            b.launch(cfg)?;
2510        }
2511        Ok(())
2512    }
2513
2514    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
2515    pub fn pack_tok_p(
2516        &self,
2517        tok: &CudaSlice<u32>,
2518        p: &CudaSlice<f32>,
2519        out: &mut CudaSlice<u32>,
2520        slot: usize,
2521    ) -> Result<(), Box<dyn std::error::Error>> {
2522        let f = self.func("pack_tok_p");
2523        let sl = slot as i32;
2524        let cfg = LaunchConfig {
2525            grid_dim: (1, 1, 1),
2526            block_dim: (32, 1, 1),
2527            shared_mem_bytes: 0,
2528        };
2529        let __s_b = self.gpu.stream();
2530        let mut b = __s_b.launch_builder(&f);
2531        b.arg(tok).arg(p).arg(out).arg(&sl);
2532        unsafe {
2533            b.launch(cfg)?;
2534        }
2535        Ok(())
2536    }
2537    pub fn tok_map_u32(
2538        &self,
2539        tok: &mut CudaSlice<u32>,
2540        map: &CudaSlice<u32>,
2541    ) -> Result<(), Box<dyn std::error::Error>> {
2542        let f = self.func("tok_map_u32");
2543        let cfg = LaunchConfig {
2544            grid_dim: (1, 1, 1),
2545            block_dim: (32, 1, 1),
2546            shared_mem_bytes: 0,
2547        };
2548        let __s_b = self.gpu.stream();
2549        let mut b = __s_b.launch_builder(&f);
2550        b.arg(tok).arg(map);
2551        unsafe {
2552            b.launch(cfg)?;
2553        }
2554        Ok(())
2555    }
2556
2557    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
2558    #[allow(clippy::too_many_arguments)]
2559    pub fn spec_assemble_verify(
2560        &self,
2561        tokp: &CudaSlice<u32>,
2562        pend: &CudaSlice<u32>,
2563        d2t: Option<&CudaSlice<u32>>,
2564        vtok: &mut CudaSlice<u32>,
2565        brk: &mut CudaSlice<u32>,
2566        p_min: f32,
2567        k: usize,
2568        pmin0: bool,
2569    ) -> Result<(), Box<dyn std::error::Error>> {
2570        let f = self.func("spec_assemble_verify");
2571        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
2572        let cfg = LaunchConfig {
2573            grid_dim: (1, 1, 1),
2574            block_dim: (32, 1, 1),
2575            shared_mem_bytes: 0,
2576        };
2577        let __s_b = self.gpu.stream();
2578        let mut b = __s_b.launch_builder(&f);
2579        match d2t {
2580            Some(m) => {
2581                b.arg(tokp)
2582                    .arg(pend)
2583                    .arg(m)
2584                    .arg(vtok)
2585                    .arg(brk)
2586                    .arg(&p_min)
2587                    .arg(&ki)
2588                    .arg(&pm);
2589                unsafe {
2590                    b.launch(cfg)?;
2591                }
2592            }
2593            None => {
2594                let null: u64 = 0;
2595                b.arg(tokp)
2596                    .arg(pend)
2597                    .arg(&null)
2598                    .arg(vtok)
2599                    .arg(brk)
2600                    .arg(&p_min)
2601                    .arg(&ki)
2602                    .arg(&pm);
2603                unsafe {
2604                    b.launch(cfg)?;
2605                }
2606            }
2607        }
2608        Ok(())
2609    }
2610
2611    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
2612    #[allow(clippy::too_many_arguments)]
2613    pub fn ssm_conv_ring_rebuild_dc(
2614        &self,
2615        qkv_tm: &CudaSlice<f32>,
2616        ring_old: &CudaSlice<f32>,
2617        conv_state: &mut CudaSlice<f32>,
2618        conv_dim: usize,
2619        acc: &CudaSlice<u32>,
2620        base: usize,
2621        t_v: usize,
2622        d_conv: usize,
2623    ) -> Result<(), Box<dyn std::error::Error>> {
2624        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
2625        let n = conv_dim * (d_conv - 1);
2626        let cfg = LaunchConfig::for_num_elems(n as u32);
2627        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
2628        let __s_b = self.gpu.stream();
2629        let mut b = __s_b.launch_builder(&f);
2630        b.arg(qkv_tm)
2631            .arg(ring_old)
2632            .arg(conv_state)
2633            .arg(&cd)
2634            .arg(acc)
2635            .arg(&b0)
2636            .arg(&tv)
2637            .arg(&dc);
2638        unsafe {
2639            b.launch(cfg)?;
2640        }
2641        Ok(())
2642    }
2643    #[allow(clippy::too_many_arguments)]
2644    pub fn gdn_scan_s128_dc(
2645        &self,
2646        q: &CudaSlice<f32>,
2647        k: &CudaSlice<f32>,
2648        v: &CudaSlice<f32>,
2649        g: &CudaSlice<f32>,
2650        beta: &CudaSlice<f32>,
2651        state_in: &CudaSlice<f32>,
2652        state_out: &mut CudaSlice<f32>,
2653        o: &mut CudaSlice<f32>,
2654        n_head: usize,
2655        acc: &CudaSlice<u32>,
2656        base: usize,
2657        t_v: usize,
2658        scale: f32,
2659    ) -> Result<(), Box<dyn std::error::Error>> {
2660        let f = self.func("gdn_scan_s128_dc");
2661        const S_V: u32 = 128;
2662        const WARP: u32 = 32;
2663        const COLS_PER_BLOCK: u32 = 4;
2664        let cfg = LaunchConfig {
2665            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
2666            block_dim: (WARP, COLS_PER_BLOCK, 1),
2667            shared_mem_bytes: 0,
2668        };
2669        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
2670        let __s_b = self.gpu.stream();
2671        let mut b = __s_b.launch_builder(&f);
2672        b.arg(q)
2673            .arg(k)
2674            .arg(v)
2675            .arg(g)
2676            .arg(beta)
2677            .arg(state_in)
2678            .arg(state_out)
2679            .arg(o)
2680            .arg(&h)
2681            .arg(acc)
2682            .arg(&b0)
2683            .arg(&tv)
2684            .arg(&scale);
2685        unsafe {
2686            b.launch(cfg)?;
2687        }
2688        Ok(())
2689    }
2690
2691    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
2692    pub fn spec_rollback_kv(
2693        &self,
2694        len_ptrs: &CudaSlice<u64>,
2695        saved: &CudaSlice<i32>,
2696        acc: &CudaSlice<u32>,
2697        base: usize,
2698        n_layer: usize,
2699    ) -> Result<(), Box<dyn std::error::Error>> {
2700        let f = self.func("spec_rollback_kv");
2701        let (b, nl) = (base as i32, n_layer as i32);
2702        let cfg = LaunchConfig {
2703            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2704            block_dim: (64, 1, 1),
2705            shared_mem_bytes: 0,
2706        };
2707        let __s_bl = self.gpu.stream();
2708        let mut bl = __s_bl.launch_builder(&f);
2709        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
2710        unsafe {
2711            bl.launch(cfg)?;
2712        }
2713        Ok(())
2714    }
2715
2716    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
2717    pub fn spec_fork_valid(
2718        &self,
2719        acc: &CudaSlice<u32>,
2720        optimistic_pending: u32,
2721        valid: &mut CudaSlice<u32>,
2722    ) -> Result<(), Box<dyn std::error::Error>> {
2723        let f = self.func("spec_fork_valid");
2724        let cfg = LaunchConfig {
2725            grid_dim: (1, 1, 1),
2726            block_dim: (1, 1, 1),
2727            shared_mem_bytes: 0,
2728        };
2729        let __s_bl = self.gpu.stream();
2730        let mut bl = __s_bl.launch_builder(&f);
2731        bl.arg(acc).arg(&optimistic_pending).arg(valid);
2732        unsafe {
2733            bl.launch(cfg)?;
2734        }
2735        Ok(())
2736    }
2737
2738    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
2739    pub fn spec_fork_reconcile_kv(
2740        &self,
2741        len_ptrs: &CudaSlice<u64>,
2742        saved: &CudaSlice<i32>,
2743        acc: &CudaSlice<u32>,
2744        valid: &CudaSlice<u32>,
2745        base: usize,
2746        n_layer: usize,
2747    ) -> Result<(), Box<dyn std::error::Error>> {
2748        let f = self.func("spec_fork_reconcile_kv");
2749        let (b, nl) = (base as i32, n_layer as i32);
2750        let cfg = LaunchConfig {
2751            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2752            block_dim: (64, 1, 1),
2753            shared_mem_bytes: 0,
2754        };
2755        let __s_bl = self.gpu.stream();
2756        let mut bl = __s_bl.launch_builder(&f);
2757        bl.arg(len_ptrs)
2758            .arg(saved)
2759            .arg(acc)
2760            .arg(valid)
2761            .arg(&b)
2762            .arg(&nl);
2763        unsafe {
2764            bl.launch(cfg)?;
2765        }
2766        Ok(())
2767    }
2768
2769    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
2770    pub fn spec_fork_restore_f32(
2771        &self,
2772        snapshot: &CudaSlice<f32>,
2773        state: &mut CudaSlice<f32>,
2774        valid: &CudaSlice<u32>,
2775    ) -> Result<(), Box<dyn std::error::Error>> {
2776        assert_eq!(
2777            snapshot.len(),
2778            state.len(),
2779            "fork recurrent snapshot shape mismatch"
2780        );
2781        let f = self.func("spec_fork_restore_f32");
2782        let n = state.len() as i32;
2783        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
2784        let cfg = LaunchConfig {
2785            grid_dim: (blocks, 1, 1),
2786            block_dim: (256, 1, 1),
2787            shared_mem_bytes: 0,
2788        };
2789        let __s_bl = self.gpu.stream();
2790        let mut bl = __s_bl.launch_builder(&f);
2791        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
2792        unsafe {
2793            bl.launch(cfg)?;
2794        }
2795        Ok(())
2796    }
2797
2798    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
2799    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
2800    pub fn spec_seed_gather(
2801        &self,
2802        vx: &CudaSlice<f32>,
2803        fill_prev: &CudaSlice<f32>,
2804        acc: &CudaSlice<u32>,
2805        h_seed: &mut CudaSlice<f32>,
2806        base: usize,
2807        n_embd: usize,
2808    ) -> Result<(), Box<dyn std::error::Error>> {
2809        let f = self.func("spec_seed_gather");
2810        let (b, ne) = (base as i32, n_embd as i32);
2811        let cfg = LaunchConfig {
2812            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
2813            block_dim: (256, 1, 1),
2814            shared_mem_bytes: 0,
2815        };
2816        let __s_bl = self.gpu.stream();
2817        let mut bl = __s_bl.launch_builder(&f);
2818        bl.arg(vx)
2819            .arg(fill_prev)
2820            .arg(acc)
2821            .arg(h_seed)
2822            .arg(&b)
2823            .arg(&ne);
2824        unsafe {
2825            bl.launch(cfg)?;
2826        }
2827        Ok(())
2828    }
2829
2830    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2831    pub fn spec_accept_greedy(
2832        &self,
2833        preds: &CudaSlice<u32>,
2834        draft: &CudaSlice<u32>,
2835        last_pred: u32,
2836        base: usize,
2837        k_round: usize,
2838        out: &mut CudaSlice<u32>,
2839    ) -> Result<(), Box<dyn std::error::Error>> {
2840        let f = self.func("spec_accept_greedy");
2841        let (b, k) = (base as i32, k_round as i32);
2842        let cfg = LaunchConfig {
2843            grid_dim: (1, 1, 1),
2844            block_dim: (32, 1, 1),
2845            shared_mem_bytes: 0,
2846        };
2847        let __s_bl = self.gpu.stream();
2848        let mut bl = __s_bl.launch_builder(&f);
2849        bl.arg(preds)
2850            .arg(draft)
2851            .arg(&last_pred)
2852            .arg(&b)
2853            .arg(&k)
2854            .arg(out);
2855        unsafe {
2856            bl.launch(cfg)?;
2857        }
2858        Ok(())
2859    }
2860
2861    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2862    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2863    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2864
2865    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2866    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2867    pub fn gumbel_perturb(
2868        &self,
2869        x: &CudaSlice<f32>,
2870        y: &mut CudaSlice<f32>,
2871        n: usize,
2872        seed: u64,
2873        stream_pos: u32,
2874        temp: f32,
2875    ) -> Result<(), Box<dyn std::error::Error>> {
2876        let f = self.func("gumbel_perturb_f32");
2877        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2878        let cfg = LaunchConfig {
2879            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2880            block_dim: (256, 1, 1),
2881            shared_mem_bytes: 0,
2882        };
2883        let __s_b = self.gpu.stream();
2884        let mut b = __s_b.launch_builder(&f);
2885        b.arg(x)
2886            .arg(&mut *y)
2887            .arg(&ni)
2888            .arg(&slo)
2889            .arg(&shi)
2890            .arg(&stream_pos)
2891            .arg(&temp);
2892        unsafe {
2893            b.launch(cfg)?;
2894        }
2895        Ok(())
2896    }
2897
2898    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2899    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2900    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2901    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2902    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2903    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2904    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2905    pub fn mask_logits_col(
2906        &self,
2907        logits: &mut CudaSlice<f32>,
2908        mask: &CudaSlice<u32>,
2909        col: usize,
2910        n: usize,
2911        mask_words: usize,
2912    ) -> Result<(), Box<dyn std::error::Error>> {
2913        let f = self.func("mask_logits_f32");
2914        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2915        let cfg = LaunchConfig {
2916            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2917            block_dim: (256, 1, 1),
2918            shared_mem_bytes: 0,
2919        };
2920        let __s_b = self.gpu.stream();
2921        let mut b = __s_b.launch_builder(&f);
2922        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2923        unsafe {
2924            b.launch(cfg)?;
2925        }
2926        Ok(())
2927    }
2928
2929    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2930    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2931    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2932    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2933    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2934    /// pointer-invariance IS the serving isolation contract for sampled rows.
2935    pub fn gumbel_perturb_col(
2936        &self,
2937        x: &CudaSlice<f32>,
2938        col: usize,
2939        y: &mut CudaSlice<f32>,
2940        n: usize,
2941        seed: u64,
2942        stream_pos: u32,
2943        temp: f32,
2944    ) -> Result<(), Box<dyn std::error::Error>> {
2945        let f = self.func("gumbel_perturb_f32");
2946        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2947        let col_view = x.slice(col * n..(col + 1) * n);
2948        let cfg = LaunchConfig {
2949            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2950            block_dim: (256, 1, 1),
2951            shared_mem_bytes: 0,
2952        };
2953        let __s_b = self.gpu.stream();
2954        let mut b = __s_b.launch_builder(&f);
2955        b.arg(&col_view)
2956            .arg(&mut *y)
2957            .arg(&ni)
2958            .arg(&slo)
2959            .arg(&shi)
2960            .arg(&stream_pos)
2961            .arg(&temp);
2962        unsafe {
2963            b.launch(cfg)?;
2964        }
2965        Ok(())
2966    }
2967
2968    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
2969    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
2970    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
2971    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
2972    /// the serving isolation contract for sampled rows).
2973    #[allow(clippy::too_many_arguments)]
2974    pub fn gumbel_perturb_filtered_col(
2975        &self,
2976        x: &CudaSlice<f32>,
2977        col: usize,
2978        y: &mut CudaSlice<f32>,
2979        n: usize,
2980        seed: u64,
2981        stream_pos: u32,
2982        temp: f32,
2983        stat_max: &CudaSlice<f32>,
2984        stat_th: &CudaSlice<f32>,
2985        stat_idx: usize,
2986    ) -> Result<(), Box<dyn std::error::Error>> {
2987        let f = self.func("gumbel_perturb_filtered_col_f32");
2988        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2989        let (ci, si) = (col as i32, stat_idx as i32);
2990        let cfg = LaunchConfig {
2991            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2992            block_dim: (256, 1, 1),
2993            shared_mem_bytes: 0,
2994        };
2995        let __s_b = self.gpu.stream();
2996        let mut b = __s_b.launch_builder(&f);
2997        b.arg(x)
2998            .arg(&ci)
2999            .arg(&mut *y)
3000            .arg(&ni)
3001            .arg(&slo)
3002            .arg(&shi)
3003            .arg(&stream_pos)
3004            .arg(&temp)
3005            .arg(stat_max)
3006            .arg(stat_th)
3007            .arg(&si);
3008        unsafe {
3009            b.launch(cfg)?;
3010        }
3011        Ok(())
3012    }
3013
3014    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
3015    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
3016    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
3017    /// reads it (counter is data, not state — graph-replay-safe).
3018    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
3019        let f = self.func("memra_sctr_inc");
3020        let cfg = LaunchConfig {
3021            grid_dim: (1, 1, 1),
3022            block_dim: (1, 1, 1),
3023            shared_mem_bytes: 0,
3024        };
3025        let __s_b = self.gpu.stream();
3026        let mut b = __s_b.launch_builder(&f);
3027        b.arg(&mut *ctr);
3028        unsafe {
3029            b.launch(cfg)?;
3030        }
3031        Ok(())
3032    }
3033
3034    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3035    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3036    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3037    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3038    pub fn gumbel_perturb_ctr(
3039        &self,
3040        x: &CudaSlice<f32>,
3041        y: &mut CudaSlice<f32>,
3042        n: usize,
3043        seed: u64,
3044        ctr: &CudaSlice<u32>,
3045        temp: f32,
3046    ) -> Result<(), Box<dyn std::error::Error>> {
3047        let f = self.func("gumbel_perturb_ctr_f32");
3048        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3049        let cfg = LaunchConfig {
3050            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3051            block_dim: (256, 1, 1),
3052            shared_mem_bytes: 0,
3053        };
3054        let __s_b = self.gpu.stream();
3055        let mut b = __s_b.launch_builder(&f);
3056        b.arg(x)
3057            .arg(&mut *y)
3058            .arg(&ni)
3059            .arg(&slo)
3060            .arg(&shi)
3061            .arg(ctr)
3062            .arg(&temp);
3063        unsafe {
3064            b.launch(cfg)?;
3065        }
3066        Ok(())
3067    }
3068
3069    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3070    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3071    /// (smallest-index tie-break — matches the argmax-gate contract).
3072    pub fn softmax_gather(
3073        &self,
3074        x: &CudaSlice<f32>,
3075        row_stride: usize,
3076        ids: &CudaSlice<u32>,
3077        rows: &CudaSlice<i32>,
3078        out: &mut CudaSlice<f32>,
3079        n: usize,
3080        npair: usize,
3081        temp: f32,
3082    ) -> Result<(), Box<dyn std::error::Error>> {
3083        let f = self.func("softmax_gather_f32");
3084        let (ni, rs) = (n as i32, row_stride as i64);
3085        let np = npair as i32;
3086        let cfg = LaunchConfig {
3087            grid_dim: (npair as u32, 1, 1),
3088            block_dim: (256, 1, 1),
3089            shared_mem_bytes: 0,
3090        };
3091        let __s_b = self.gpu.stream();
3092        let mut b = __s_b.launch_builder(&f);
3093        b.arg(x)
3094            .arg(&rs)
3095            .arg(ids)
3096            .arg(rows)
3097            .arg(&mut *out)
3098            .arg(&ni)
3099            .arg(&np)
3100            .arg(&temp);
3101        unsafe {
3102            b.launch(cfg)?;
3103        }
3104        Ok(())
3105    }
3106
3107    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3108    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3109    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3110    pub fn residual_sample(
3111        &self,
3112        p: &CudaSlice<f32>,
3113        q: Option<&CudaSlice<f32>>,
3114        n: usize,
3115        temp: f32,
3116        seed: u64,
3117        stream_pos: u32,
3118        out_tok: &mut CudaSlice<u32>,
3119    ) -> Result<(), Box<dyn std::error::Error>> {
3120        let f = self.func("residual_sample_f32");
3121        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3122        let nth = 1024u32;
3123        let cfg = LaunchConfig {
3124            grid_dim: (1, 1, 1),
3125            block_dim: (nth, 1, 1),
3126            shared_mem_bytes: 0,
3127        };
3128        let has_q: i32 = q.is_some() as i32;
3129        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3130        let __s_b = self.gpu.stream();
3131        let mut b = __s_b.launch_builder(&f);
3132        b.arg(p)
3133            .arg(qbuf)
3134            .arg(&has_q)
3135            .arg(&ni)
3136            .arg(&temp)
3137            .arg(&slo)
3138            .arg(&shi)
3139            .arg(&stream_pos)
3140            .arg(&mut *out_tok);
3141        unsafe {
3142            b.launch(cfg)?;
3143        }
3144        Ok(())
3145    }
3146
3147    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3148    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3149    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3150    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3151    pub fn with_moe_cache<R>(
3152        &self,
3153        max_block_bytes: usize,
3154        f: impl FnOnce(
3155            &mut crate::moe_cache::MoeSlotCache,
3156            &Engine,
3157        ) -> Result<R, Box<dyn std::error::Error>>,
3158    ) -> Result<R, Box<dyn std::error::Error>> {
3159        let mut guard = self.moe_cache.lock().unwrap();
3160        if guard.is_none() {
3161            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3162        }
3163        let cache = guard.as_mut().unwrap();
3164        f(cache, self)
3165    }
3166
3167    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3168    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3169    pub fn freeze_moe_cache(&self) {
3170        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3171            cache.freeze();
3172        }
3173    }
3174
3175    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3176    /// Never constructs a cache.
3177    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3178        self.moe_cache
3179            .lock()
3180            .unwrap()
3181            .as_ref()
3182            .map(crate::moe_cache::MoeSlotCache::export_residency)
3183    }
3184
3185    pub(crate) fn moe_cache_frozen(&self) -> bool {
3186        self.moe_cache
3187            .lock()
3188            .unwrap()
3189            .as_ref()
3190            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3191    }
3192
3193    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3194    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3195    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3196    /// while leaving the profiling warmup's established batched behavior untouched.
3197    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3198    /// tokenwise arm anyway.)
3199    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3200        crate::cpu_experts::configured()
3201            && self.moe_cache_frozen()
3202            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3203    }
3204
3205    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3206    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3207        assert!(
3208            self.moe_cache.lock().unwrap().is_none(),
3209            "MoE cache layout configured after cache construction"
3210        );
3211        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3212    }
3213
3214    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3215        self.moe_cache_layout.lock().unwrap().clone()
3216    }
3217
3218    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3219    pub fn moe_cache_enabled() -> bool {
3220        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3221    }
3222
3223    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3224    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3225    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3226        let guard = self.moe_cache.lock().unwrap();
3227        guard
3228            .as_ref()
3229            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3230    }
3231
3232    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3233    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3234    /// callers compare a before/after snapshot around a decode window.
3235    pub fn cpu_expert_stats(
3236        &self,
3237    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3238        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3239    }
3240
3241    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3242    /// the backend tail that resident-GPU expert work did not hide.
3243    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3244        crate::cpu_experts::predictor_stats()
3245    }
3246
3247    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3248        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3249    }
3250
3251    /// CPU-routed expert selections grouped by how many of their three projections were already
3252    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3253    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3254        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3255    }
3256
3257    /// Positioned-read proof-backend counters:
3258    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3259    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3260        let guard = self.moe_cache.lock().unwrap();
3261        guard
3262            .as_ref()
3263            .and_then(|cache| cache.pread_stats())
3264            .map(|stats| {
3265                (
3266                    stats.reads,
3267                    stats.bytes,
3268                    stats.read_errors,
3269                    stats.short_reads,
3270                    stats.fallbacks,
3271                    stats.buffer_waits,
3272                    stats.ring_full,
3273                )
3274            })
3275    }
3276
3277    /// Spill configuration values that warned and substituted their documented defaults.
3278    pub fn spill_config_fallbacks(&self) -> u64 {
3279        crate::spill_pread::config_fallbacks()
3280    }
3281
3282    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3283    pub fn moe_cache_reset_counters(&self) {
3284        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3285            c.reset_counters();
3286        }
3287    }
3288
3289    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3290        Ok(self.gpu.stream().clone_htod(v)?)
3291    }
3292
3293    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3294    /// past the final q4_0 block through their aligned window — the bytes never reach a
3295    /// result (funnelshift discards them) but must be mapped memory.
3296    pub fn htod_bytes_padded(
3297        &self,
3298        v: &[u8],
3299        pad: usize,
3300    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3301        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3302        {
3303            let mut view = d.slice_mut(0..v.len());
3304            self.gpu.stream().memcpy_htod(v, &mut view)?;
3305        }
3306        Ok(d)
3307    }
3308
3309    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3310    pub fn copy_into(
3311        &self,
3312        dst: &mut CudaSlice<f32>,
3313        off: usize,
3314        src: &CudaSlice<f32>,
3315        len: usize,
3316    ) -> Result<(), Box<dyn std::error::Error>> {
3317        let mut view = dst.slice_mut(off..off + len);
3318        self.gpu
3319            .stream()
3320            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3321        Ok(())
3322    }
3323
3324    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3325    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3326    pub fn copy_u8_into(
3327        &self,
3328        dst: &mut CudaSlice<u8>,
3329        off: usize,
3330        src: &CudaSlice<u8>,
3331        len: usize,
3332    ) -> Result<(), Box<dyn std::error::Error>> {
3333        let mut view = dst.slice_mut(off..off + len);
3334        self.gpu
3335            .stream()
3336            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3337        Ok(())
3338    }
3339
3340    /// D2D byte-range copy with explicit source and destination offsets.
3341    pub fn copy_u8_range_into(
3342        &self,
3343        dst: &mut CudaSlice<u8>,
3344        dst_off: usize,
3345        src: &CudaSlice<u8>,
3346        src_off: usize,
3347        len: usize,
3348    ) -> Result<(), Box<dyn std::error::Error>> {
3349        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3350        self.gpu
3351            .stream()
3352            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3353        Ok(())
3354    }
3355
3356    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3357    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3358    /// keeping the audited attention range contiguous without changing its absolute start.
3359    pub fn prepare_kv_append(
3360        &self,
3361        kv: &mut crate::cache::KvLayer,
3362        retain_from: usize,
3363        append_rows: usize,
3364    ) -> Result<usize, Box<dyn std::error::Error>> {
3365        let Some(plan) = kv
3366            .ring
3367            .as_ref()
3368            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3369            .transpose()?
3370        else {
3371            return Ok(kv.len);
3372        };
3373        match plan {
3374            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3375            crate::cache::KvRingAppend::Rebase {
3376                src_row,
3377                keep_rows,
3378                new_base,
3379                write_row,
3380            } => {
3381                if keep_rows > 0 {
3382                    let k_len = keep_rows * kv.k_tok_bytes;
3383                    let v_len = keep_rows * kv.v_tok_bytes;
3384                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3385                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3386                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3387                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3388                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3389                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3390                }
3391                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3392                Ok(write_row)
3393            }
3394        }
3395    }
3396
3397    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3398    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3399    pub fn htod_u8_into(
3400        &self,
3401        dst: &mut CudaSlice<u8>,
3402        off: usize,
3403        src: &[u8],
3404    ) -> Result<(), Box<dyn std::error::Error>> {
3405        let mut view = dst.slice_mut(off..off + src.len());
3406        self.gpu.stream().memcpy_htod(src, &mut view)?;
3407        Ok(())
3408    }
3409
3410    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3411        b.slice(0..len)
3412    }
3413
3414    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3415    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3416    pub fn view_u8_range<'a>(
3417        &self,
3418        b: &'a CudaSlice<u8>,
3419        start: usize,
3420        end: usize,
3421    ) -> cudarc::driver::CudaView<'a, u8> {
3422        b.slice(start..end)
3423    }
3424    pub fn view_u8<'a>(
3425        &self,
3426        b: &'a CudaSlice<u8>,
3427        len: usize,
3428    ) -> cudarc::driver::CudaView<'a, u8> {
3429        b.slice(0..len)
3430    }
3431
3432    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3433    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3434    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3435    pub fn append_kv_quantized(
3436        &self,
3437        k_row: &CudaSlice<f32>,
3438        v_row: &CudaSlice<f32>,
3439        kc: &mut CudaSlice<u8>,
3440        vc: &mut CudaSlice<u8>,
3441        t: usize,
3442        kv_dim_k: usize,
3443        kv_dim_v: usize,
3444        k_tok_bytes: usize,
3445        v_tok_bytes: usize,
3446        g: bool,
3447    ) -> Result<(), Box<dyn std::error::Error>> {
3448        let f = if g {
3449            self.func_g("append_quantize_kv_q8_0_q5_1")
3450        } else {
3451            self.func("append_quantize_kv_q8_0_q5_1")
3452        };
3453        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3454        let cfg = LaunchConfig {
3455            grid_dim: (nblk, 1, 1),
3456            block_dim: (32, 1, 1),
3457            shared_mem_bytes: 0,
3458        };
3459        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3460        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3461        let __s_b = self.gpu.stream();
3462        let mut b = __s_b.launch_builder(&f);
3463        b.arg(k_row)
3464            .arg(v_row)
3465            .arg(kc)
3466            .arg(vc)
3467            .arg(&ti)
3468            .arg(&kdk)
3469            .arg(&kdv)
3470            .arg(&ktb)
3471            .arg(&vtb);
3472        unsafe {
3473            b.launch(cfg)?;
3474        }
3475        Ok(())
3476    }
3477
3478    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3479    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3480    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3481    pub fn append_kv_quantized_dc(
3482        &self,
3483        k_row: &CudaSlice<f32>,
3484        v_row: &CudaSlice<f32>,
3485        kc: &mut CudaSlice<u8>,
3486        vc: &mut CudaSlice<u8>,
3487        t_dev: &CudaSlice<i32>,
3488        kv_dim_k: usize,
3489        kv_dim_v: usize,
3490        k_tok_bytes: usize,
3491        v_tok_bytes: usize,
3492        g: bool,
3493    ) -> Result<(), Box<dyn std::error::Error>> {
3494        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3495        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3496        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3497        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3498        if Self::pdl_on() && Self::pdl_wb_on() {
3499            use cudarc::driver::{DevicePtr, DevicePtrMut};
3500            let s = &self.gpu.stream();
3501            let (pk, _g0) = k_row.device_ptr(s);
3502            let (pv, _g1) = v_row.device_ptr(s);
3503            let (pkc, _g2) = kc.device_ptr_mut(s);
3504            let (pvc, _g3) = vc.device_ptr_mut(s);
3505            let (pt, _g4) = t_dev.device_ptr(s);
3506            let mut ps = [
3507                &pk as *const _ as *mut std::ffi::c_void,
3508                &pv as *const _ as *mut _,
3509                &pkc as *const _ as *mut _,
3510                &pvc as *const _ as *mut _,
3511                &pt as *const _ as *mut _,
3512                &kdk as *const _ as *mut _,
3513                &kdv as *const _ as *mut _,
3514                &ktb as *const _ as *mut _,
3515                &vtb as *const _ as *mut _,
3516            ];
3517            unsafe {
3518                self.launch_pdl_flash(
3519                    g,
3520                    "append_quantize_kv_q8_0_q5_1_dc",
3521                    (nblk, 1, 1),
3522                    (32, 1, 1),
3523                    0,
3524                    &mut ps,
3525                )?;
3526            }
3527            return Ok(());
3528        }
3529        let f = if g {
3530            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3531        } else {
3532            self.func("append_quantize_kv_q8_0_q5_1_dc")
3533        };
3534        let cfg = LaunchConfig {
3535            grid_dim: (nblk, 1, 1),
3536            block_dim: (32, 1, 1),
3537            shared_mem_bytes: 0,
3538        };
3539        let __s_b = self.gpu.stream();
3540        let mut b = __s_b.launch_builder(&f);
3541        b.arg(k_row)
3542            .arg(v_row)
3543            .arg(kc)
3544            .arg(vc)
3545            .arg(t_dev)
3546            .arg(&kdk)
3547            .arg(&kdv)
3548            .arg(&ktb)
3549            .arg(&vtb);
3550        unsafe {
3551            b.launch(cfg)?;
3552        }
3553        Ok(())
3554    }
3555
3556    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3557    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3558    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3559    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3560    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3561    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3562    #[allow(clippy::too_many_arguments)]
3563    pub fn append_kv_quantized_rows(
3564        &self,
3565        k_rows: &CudaSlice<f32>,
3566        v_rows: &CudaSlice<f32>,
3567        kc: &mut CudaSlice<u8>,
3568        vc: &mut CudaSlice<u8>,
3569        t0: usize,
3570        t: usize,
3571        kv_dim_k: usize,
3572        kv_dim_v: usize,
3573        k_tok_bytes: usize,
3574        v_tok_bytes: usize,
3575        g: bool,
3576    ) -> Result<(), Box<dyn std::error::Error>> {
3577        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3578            for i in 0..t {
3579                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3580                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3581                self.append_kv_quantized_view(
3582                    &k_row,
3583                    &v_row,
3584                    kc,
3585                    vc,
3586                    t0 + i,
3587                    kv_dim_k,
3588                    kv_dim_v,
3589                    k_tok_bytes,
3590                    v_tok_bytes,
3591                    g,
3592                )?;
3593            }
3594            return Ok(());
3595        }
3596        let f = if g {
3597            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3598        } else {
3599            self.func("append_quantize_kv_q8_0_q5_1_rows")
3600        };
3601        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3602        let cfg = LaunchConfig {
3603            grid_dim: (nblk, t as u32, 1),
3604            block_dim: (32, 1, 1),
3605            shared_mem_bytes: 0,
3606        };
3607        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3608        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3609        let __s_b = self.gpu.stream();
3610        let mut b = __s_b.launch_builder(&f);
3611        b.arg(k_rows)
3612            .arg(v_rows)
3613            .arg(kc)
3614            .arg(vc)
3615            .arg(&t0i)
3616            .arg(&kdk)
3617            .arg(&kdv)
3618            .arg(&ktb)
3619            .arg(&vtb);
3620        unsafe {
3621            b.launch(cfg)?;
3622        }
3623        Ok(())
3624    }
3625
3626    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3627    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3628    /// later, inside a captured graph) without a host round-trip.
3629    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3630        let f = self.func("inc_i32");
3631        let cfg = LaunchConfig {
3632            grid_dim: (1, 1, 1),
3633            block_dim: (1, 1, 1),
3634            shared_mem_bytes: 0,
3635        };
3636        let __s_b = self.gpu.stream();
3637        let mut b = __s_b.launch_builder(&f);
3638        b.arg(p);
3639        unsafe {
3640            b.launch(cfg)?;
3641        }
3642        Ok(())
3643    }
3644
3645    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3646    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3647    pub fn append_kv_quantized_view(
3648        &self,
3649        k_row: &cudarc::driver::CudaView<f32>,
3650        v_row: &cudarc::driver::CudaView<f32>,
3651        kc: &mut CudaSlice<u8>,
3652        vc: &mut CudaSlice<u8>,
3653        t: usize,
3654        kv_dim_k: usize,
3655        kv_dim_v: usize,
3656        k_tok_bytes: usize,
3657        v_tok_bytes: usize,
3658        g: bool,
3659    ) -> Result<(), Box<dyn std::error::Error>> {
3660        let f = if g {
3661            self.func_g("append_quantize_kv_q8_0_q5_1")
3662        } else {
3663            self.func("append_quantize_kv_q8_0_q5_1")
3664        };
3665        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3666        let cfg = LaunchConfig {
3667            grid_dim: (nblk, 1, 1),
3668            block_dim: (32, 1, 1),
3669            shared_mem_bytes: 0,
3670        };
3671        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3672        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3673        let __s_b = self.gpu.stream();
3674        let mut b = __s_b.launch_builder(&f);
3675        b.arg(k_row)
3676            .arg(v_row)
3677            .arg(kc)
3678            .arg(vc)
3679            .arg(&ti)
3680            .arg(&kdk)
3681            .arg(&kdv)
3682            .arg(&ktb)
3683            .arg(&vtb);
3684        unsafe {
3685            b.launch(cfg)?;
3686        }
3687        Ok(())
3688    }
3689
3690    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3691    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3692    pub fn copy_view_into(
3693        &self,
3694        dst: &mut CudaSlice<f32>,
3695        off: usize,
3696        src: &cudarc::driver::CudaView<f32>,
3697        len: usize,
3698    ) -> Result<(), Box<dyn std::error::Error>> {
3699        let mut view = dst.slice_mut(off..off + len);
3700        self.gpu
3701            .stream()
3702            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3703        Ok(())
3704    }
3705
3706    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3707    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3708    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3709    pub fn clone_dtod(
3710        &self,
3711        src: &CudaSlice<f32>,
3712    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3713        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3714        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3715        Ok(dst)
3716    }
3717
3718    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3719    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3720    pub fn dtod_copy_view(
3721        &self,
3722        src: &cudarc::driver::CudaView<f32>,
3723        dst: &mut CudaSlice<f32>,
3724    ) -> Result<(), Box<dyn std::error::Error>> {
3725        self.gpu.stream().memcpy_dtod(src, dst)?;
3726        Ok(())
3727    }
3728
3729    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3730    pub fn dtod_copy_view_i8(
3731        &self,
3732        src: &cudarc::driver::CudaView<i8>,
3733        dst: &mut CudaSlice<i8>,
3734    ) -> Result<(), Box<dyn std::error::Error>> {
3735        self.gpu.stream().memcpy_dtod(src, dst)?;
3736        Ok(())
3737    }
3738
3739    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3740    pub fn dtod_copy_into(
3741        &self,
3742        src: &CudaSlice<f32>,
3743        dst: &mut CudaSlice<f32>,
3744        offset: usize,
3745    ) -> Result<(), Box<dyn std::error::Error>> {
3746        let n = src.len();
3747        let mut dv = dst.slice_mut(offset..offset + n);
3748        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3749        Ok(())
3750    }
3751
3752    /// Batched uniform-size D2D copy (engine-bundle slice 1, DSF-ROUNDCOST-20260820 §1.1):
3753    /// `n` disjoint regions of `words` f32 each; `table` = [src_0..src_{n-1}, dst_0..dst_{n-1}]
3754    /// raw device pointers. ONE kernel launch replaces `n` memcpy_dtod dispatches — the dspark
3755    /// round's snap/commit copy dribble (~0.9 ms/round of dispatch serialization measured).
3756    /// Bytes and stream order are identical to the memcpy sequence it replaces.
3757    pub fn copy_batch_uniform_f32(
3758        &self,
3759        table: &CudaSlice<u64>,
3760        n: usize,
3761        words: usize,
3762    ) -> Result<(), Box<dyn std::error::Error>> {
3763        if n == 0 || words == 0 {
3764            return Ok(());
3765        }
3766        debug_assert!(
3767            table.len() >= 2 * n,
3768            "pointer table must hold n srcs + n dsts"
3769        );
3770        let f = self.func("copy_batch_uniform_f32");
3771        // Enough blocks to stream a multi-MB region, few enough that (chunks x n) stays a
3772        // sane grid: 512K-word ssm regions get 48 grid-striding blocks each.
3773        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
3774        let (ni, wi) = (n as i32, words as i32);
3775        let cfg = LaunchConfig {
3776            grid_dim: (chunks, n as u32, 1),
3777            block_dim: (256, 1, 1),
3778            shared_mem_bytes: 0,
3779        };
3780        let __s = self.gpu.stream();
3781        let mut b = __s.launch_builder(&f);
3782        b.arg(table).arg(&ni).arg(&wi);
3783        unsafe {
3784            b.launch(cfg)?;
3785        }
3786        Ok(())
3787    }
3788
3789    /// H2D refresh of an EXISTING u64 pointer table IN PLACE (stable pointer — the batched
3790    /// state-copy tables are refreshed per round because the GDN ssm handles ping-pong).
3791    pub fn htod_u64_into(
3792        &self,
3793        v: &[u64],
3794        dst: &mut CudaSlice<u64>,
3795    ) -> Result<(), Box<dyn std::error::Error>> {
3796        let mut view = dst.slice_mut(0..v.len());
3797        self.gpu.stream().memcpy_htod(v, &mut view)?;
3798        Ok(())
3799    }
3800
3801    /// Indirect-source copy (engine-bundle slice 3): the src ADDRESS is loaded from a
3802    /// device pointer-table entry at run time, so a captured graph follows the gdn
3803    /// ping-pong through the same table its scan kernels read — a baked memcpy node
3804    /// would keep the capture-time physical buffer. `dst_off`/`words` in f32 elements.
3805    pub fn copy_indirect_src_f32(
3806        &self,
3807        src_entry: &cudarc::driver::CudaView<u64>,
3808        dst: &mut CudaSlice<f32>,
3809        dst_off: usize,
3810        words: usize,
3811    ) -> Result<(), Box<dyn std::error::Error>> {
3812        let f = self.func("copy_indirect_src_f32");
3813        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
3814        let wi = words as i32;
3815        let cfg = LaunchConfig {
3816            grid_dim: (chunks, 1, 1),
3817            block_dim: (256, 1, 1),
3818            shared_mem_bytes: 0,
3819        };
3820        let mut dv = dst.slice_mut(dst_off..dst_off + words);
3821        let __s = self.gpu.stream();
3822        let mut b = __s.launch_builder(&f);
3823        b.arg(src_entry).arg(&mut dv).arg(&wi);
3824        unsafe {
3825            b.launch(cfg)?;
3826        }
3827        Ok(())
3828    }
3829
3830    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3831    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3832        self.alloc_uninit::<i8>(n)
3833    }
3834
3835    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3836    pub fn qmatvec(
3837        &self,
3838        w: &CudaSlice<u8>,
3839        x: &CudaSlice<f32>,
3840        m: usize,
3841        in_f: usize,
3842        out_f: usize,
3843        qtype: i32,
3844        row_bytes: usize,
3845    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3846        let f = self.func("qmatvec_f32");
3847        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3848        let cfg = LaunchConfig {
3849            grid_dim: (out_f as u32, m as u32, 1),
3850            block_dim: (256, 1, 1),
3851            shared_mem_bytes: 0,
3852        };
3853        let (inf, outf, mi, qt, rb) =
3854            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3855        let __s_b = self.gpu.stream();
3856        let mut b = __s_b.launch_builder(&f);
3857        b.arg(w)
3858            .arg(x)
3859            .arg(&mut y)
3860            .arg(&inf)
3861            .arg(&outf)
3862            .arg(&mi)
3863            .arg(&qt)
3864            .arg(&rb);
3865        unsafe {
3866            b.launch(cfg)?;
3867        }
3868        Ok(y)
3869    }
3870
3871    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3872    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3873        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3874        self.keep_if_capturing(&s);
3875        Ok(s)
3876    }
3877
3878    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3879    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3880    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3881    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3882        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3883        self.keep_if_capturing(&s);
3884        Ok(s)
3885    }
3886
3887    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3888    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3889    pub fn memset_zeros_view(
3890        &self,
3891        dst: &mut cudarc::driver::CudaViewMut<f32>,
3892    ) -> Result<(), Box<dyn std::error::Error>> {
3893        self.gpu.stream().memset_zeros(dst)?;
3894        Ok(())
3895    }
3896
3897    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3898    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3899    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3900    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3901    /// stream would require an event).
3902    pub fn stage_expert(
3903        &self,
3904        host_bytes: &[u8],
3905        scratch: &mut CudaSlice<u8>,
3906        off: usize,
3907    ) -> Result<(), Box<dyn std::error::Error>> {
3908        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3909        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3910        Ok(())
3911    }
3912
3913    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3914    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3915    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3916    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3917    /// One CTA per token row, 256 threads (one per expert).
3918    pub fn moe_router_topk(
3919        &self,
3920        logits: &CudaSlice<f32>,
3921        t: usize,
3922        n_expert: usize,
3923        n_used: usize,
3924    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3925        let f = self.func("moe_router_topk_f32");
3926        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3927        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3928        let cfg = LaunchConfig {
3929            grid_dim: (t as u32, 1, 1),
3930            block_dim: (n_expert as u32, 1, 1),
3931            shared_mem_bytes: 0,
3932        };
3933        let (ne, nu) = (n_expert as i32, n_used as i32);
3934        let __s_b = self.gpu.stream();
3935        let mut b = __s_b.launch_builder(&f);
3936        b.arg(logits)
3937            .arg(&mut sel_idx)
3938            .arg(&mut sel_w)
3939            .arg(&ne)
3940            .arg(&nu);
3941        unsafe {
3942            b.launch(cfg)?;
3943        }
3944        Ok((sel_idx, sel_w))
3945    }
3946
3947    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3948    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3949    pub fn moe_router_topk_scaled(
3950        &self,
3951        logits: &CudaSlice<f32>,
3952        t: usize,
3953        n_expert: usize,
3954        n_used: usize,
3955        ex_scale: &CudaSlice<f32>,
3956    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3957        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3958        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3959        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3960        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3961        let f = self.func("moe_router_topk_scaled_f32");
3962        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3963        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3964        let cfg = LaunchConfig {
3965            grid_dim: (t as u32, 1, 1),
3966            block_dim: (n_expert as u32, 1, 1),
3967            shared_mem_bytes: 0,
3968        };
3969        let (ne, nu) = (n_expert as i32, n_used as i32);
3970        let __s_b = self.gpu.stream();
3971        let mut b = __s_b.launch_builder(&f);
3972        b.arg(logits)
3973            .arg(&mut sel_idx)
3974            .arg(&mut sel_w)
3975            .arg(&ne)
3976            .arg(&nu)
3977            .arg(ex_scale);
3978        unsafe {
3979            b.launch(cfg)?;
3980        }
3981        Ok((sel_idx, sel_w))
3982    }
3983
3984    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3985    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3986    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3987    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3988    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3989    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3990    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3991    pub fn moe_router_topk_host(
3992        &self,
3993        logits: &CudaSlice<f32>,
3994        t: usize,
3995        n_expert: usize,
3996        n_used: usize,
3997    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3998        let f = self.func("moe_router_topk_f32");
3999        let n = t * n_used;
4000        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
4001        let mut sel_w = self.alloc_uninit::<f32>(n)?;
4002        let cfg = LaunchConfig {
4003            grid_dim: (t as u32, 1, 1),
4004            block_dim: (n_expert as u32, 1, 1),
4005            shared_mem_bytes: 0,
4006        };
4007        let (ne, nu) = (n_expert as i32, n_used as i32);
4008        let __s_b = self.gpu.stream();
4009        let mut b = __s_b.launch_builder(&f);
4010        b.arg(logits)
4011            .arg(&mut sel_idx)
4012            .arg(&mut sel_w)
4013            .arg(&ne)
4014            .arg(&nu);
4015        unsafe {
4016            b.launch(cfg)?;
4017        }
4018        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
4019        let bytes = n * 8;
4020        let mut guard = self.router_stage.lock().unwrap();
4021        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4022            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4023        }
4024        let stage = guard.as_mut().unwrap();
4025        let (si, sw) = unsafe {
4026            (
4027                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4028                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4029            )
4030        };
4031        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
4032        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
4033        self.gpu.stream().synchronize()?; // ONE sync for both
4034        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4035    }
4036
4037    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
4038    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
4039    /// original expert ids before top-k. Exact key ties choose the smaller original id.
4040    #[allow(clippy::too_many_arguments)]
4041    pub fn moe_router_sigmoid_topk(
4042        &self,
4043        logits: &CudaSlice<f32>,
4044        t: usize,
4045        n_expert: usize,
4046        n_used: usize,
4047        active_count: usize,
4048        correction_bias: &CudaSlice<f32>,
4049        active: &CudaSlice<u8>,
4050        scaling_factor: f32,
4051        route_norm: bool,
4052    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4053        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
4054        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
4055            return Err(format!(
4056                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
4057            )
4058            .into());
4059        }
4060        if logits.len() < t * n_expert
4061            || correction_bias.len() != n_expert
4062            || active.len() != n_expert
4063        {
4064            return Err(format!(
4065                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
4066                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
4067            ).into());
4068        }
4069        let f = self.func("moe_router_sigmoid_topk_f32");
4070        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
4071        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
4072        let threads = n_expert.div_ceil(32) * 32;
4073        let cfg = LaunchConfig {
4074            grid_dim: (t as u32, 1, 1),
4075            block_dim: (threads as u32, 1, 1),
4076            shared_mem_bytes: 0,
4077        };
4078        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
4079        let __s_b = self.gpu.stream();
4080        let mut b = __s_b.launch_builder(&f);
4081        b.arg(logits)
4082            .arg(correction_bias)
4083            .arg(active)
4084            .arg(&mut sel_idx)
4085            .arg(&mut sel_w)
4086            .arg(&ne)
4087            .arg(&nu)
4088            .arg(&scaling_factor)
4089            .arg(&rn);
4090        unsafe {
4091            b.launch(cfg)?;
4092        }
4093        Ok((sel_idx, sel_w))
4094    }
4095
4096    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
4097    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
4098    #[allow(clippy::too_many_arguments)]
4099    pub fn moe_router_sigmoid_topk_host(
4100        &self,
4101        logits: &CudaSlice<f32>,
4102        t: usize,
4103        n_expert: usize,
4104        n_used: usize,
4105        active_count: usize,
4106        correction_bias: &CudaSlice<f32>,
4107        active: &CudaSlice<u8>,
4108        scaling_factor: f32,
4109        route_norm: bool,
4110    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4111        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4112            logits,
4113            t,
4114            n_expert,
4115            n_used,
4116            active_count,
4117            correction_bias,
4118            active,
4119            scaling_factor,
4120            route_norm,
4121        )?;
4122        let n = t * n_used;
4123        let bytes = n * 8;
4124        let mut guard = self.router_stage.lock().unwrap();
4125        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4126            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4127        }
4128        let stage = guard.as_mut().unwrap();
4129        let (si, sw) = unsafe {
4130            (
4131                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4132                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4133            )
4134        };
4135        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4136        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4137        self.gpu.stream().synchronize()?;
4138        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4139    }
4140
4141    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4142    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4143    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4144    pub fn stage_expert_async(
4145        &self,
4146        host_bytes: &[u8],
4147        scratch: &mut CudaSlice<u8>,
4148        off: usize,
4149    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4150        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4151        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4152        Ok(self.copy_stream.record_event(None)?)
4153    }
4154
4155    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4156    pub fn compute_wait(
4157        &self,
4158        ev: &cudarc::driver::CudaEvent,
4159    ) -> Result<(), Box<dyn std::error::Error>> {
4160        self.gpu.stream().wait(ev)?;
4161        Ok(())
4162    }
4163
4164    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4165    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4166    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4167    /// CudaView base+offset pointer is honored by the launch arg.
4168    pub fn qmatvec_view(
4169        &self,
4170        w: &CudaSlice<u8>,
4171        range: std::ops::Range<usize>,
4172        x: &cudarc::driver::CudaView<f32>,
4173        m: usize,
4174        in_f: usize,
4175        out_f: usize,
4176        qtype: i32,
4177        row_bytes: usize,
4178    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4179        let f = self.func("qmatvec_f32");
4180        let wv = w.slice(range); // CudaView<u8>, offset honored
4181        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4182        let cfg = LaunchConfig {
4183            grid_dim: (out_f as u32, m as u32, 1),
4184            block_dim: (256, 1, 1),
4185            shared_mem_bytes: 0,
4186        };
4187        let (inf, outf, mi, qt, rb) =
4188            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4189        let __s_b = self.gpu.stream();
4190        let mut b = __s_b.launch_builder(&f);
4191        b.arg(&wv)
4192            .arg(x)
4193            .arg(&mut y)
4194            .arg(&inf)
4195            .arg(&outf)
4196            .arg(&mi)
4197            .arg(&qt)
4198            .arg(&rb);
4199        unsafe {
4200            b.launch(cfg)?;
4201        }
4202        Ok(y)
4203    }
4204
4205    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4206    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4207    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4208    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4209    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4210    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4211    #[allow(clippy::too_many_arguments)]
4212    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4213    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4214    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4215    pub fn moe_gate_up_silu8_q8(
4216        &self,
4217        gp: WPtr8,
4218        up: WPtr8,
4219        aq: &CudaSlice<i8>,
4220        ad: &CudaSlice<f32>,
4221        in_f: usize,
4222        n_ff: usize,
4223        n_used: usize,
4224        qt_g: i32,
4225        qt_u: i32,
4226        rb_g: usize,
4227        rb_u: usize,
4228    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4229        let f = self.func("moe_gate_up_silu8_q8");
4230        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4231        let cfg = LaunchConfig {
4232            grid_dim: (n_ff as u32, n_used as u32, 1),
4233            block_dim: (32, 1, 1),
4234            shared_mem_bytes: 0,
4235        };
4236        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4237        let __s_b = self.gpu.stream();
4238        let mut b = __s_b.launch_builder(&f);
4239        b.arg(&gp)
4240            .arg(&up)
4241            .arg(aq)
4242            .arg(ad)
4243            .arg(&mut act)
4244            .arg(&inf)
4245            .arg(&nff)
4246            .arg(&qt_g)
4247            .arg(&qt_u)
4248            .arg(&rbg)
4249            .arg(&rbu);
4250        unsafe {
4251            b.launch(cfg)?;
4252        }
4253        Ok(act)
4254    }
4255
4256    #[allow(clippy::too_many_arguments)]
4257    pub fn moe_down8_fma_q8(
4258        &self,
4259        dp: WPtr8,
4260        w: F32x8,
4261        aq2: &CudaSlice<i8>,
4262        ad2: &CudaSlice<f32>,
4263        dst: &mut cudarc::driver::CudaViewMut<f32>,
4264        in_f: usize,
4265        out_f: usize,
4266        n_used: usize,
4267        qt: i32,
4268        rb: usize,
4269    ) -> Result<(), Box<dyn std::error::Error>> {
4270        let f = self.func("moe_down8_fma_q8");
4271        let cfg = LaunchConfig {
4272            grid_dim: (out_f as u32, 1, 1),
4273            block_dim: (32, 1, 1),
4274            shared_mem_bytes: 0,
4275        };
4276        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4277        let __s_b = self.gpu.stream();
4278        let mut b = __s_b.launch_builder(&f);
4279        b.arg(&dp)
4280            .arg(&w)
4281            .arg(aq2)
4282            .arg(ad2)
4283            .arg(dst)
4284            .arg(&inf)
4285            .arg(&outf)
4286            .arg(&nu)
4287            .arg(&qt)
4288            .arg(&rbi);
4289        unsafe {
4290            b.launch(cfg)?;
4291        }
4292        Ok(())
4293    }
4294
4295    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4296    pub fn qmatvec_expert_q8(
4297        &self,
4298        w: &CudaSlice<u8>,
4299        range: std::ops::Range<usize>,
4300        aq: &CudaSlice<i8>,
4301        ad: &CudaSlice<f32>,
4302        m: usize,
4303        in_f: usize,
4304        out_f: usize,
4305        qtype: i32,
4306        row_bytes: usize,
4307    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4308        let f = self.func("qmatvec_expert_q8");
4309        let wv = w.slice(range);
4310        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4311        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4312        let cfg = LaunchConfig {
4313            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4314            block_dim: (32, ROWS, 1),
4315            shared_mem_bytes: 0,
4316        };
4317        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4318        let __s_b = self.gpu.stream();
4319        let mut b = __s_b.launch_builder(&f);
4320        b.arg(&wv)
4321            .arg(aq)
4322            .arg(ad)
4323            .arg(&mut y)
4324            .arg(&inf)
4325            .arg(&outf)
4326            .arg(&mi)
4327            .arg(&qtype)
4328            .arg(&rbi);
4329        unsafe {
4330            b.launch(cfg)?;
4331        }
4332        Ok(y)
4333    }
4334
4335    pub fn moe_gate_up_silu8(
4336        &self,
4337        gp: WPtr8,
4338        up: WPtr8,
4339        x: &cudarc::driver::CudaView<f32>,
4340        in_f: usize,
4341        n_ff: usize,
4342        n_used: usize,
4343        qt_g: i32,
4344        qt_u: i32,
4345        rb_g: usize,
4346        rb_u: usize,
4347    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4348        let f = self.func("moe_gate_up_silu8_f32");
4349        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4350        let cfg = LaunchConfig {
4351            grid_dim: (n_ff as u32, n_used as u32, 1),
4352            block_dim: (256, 1, 1),
4353            shared_mem_bytes: 0,
4354        };
4355        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4356        let __s_b = self.gpu.stream();
4357        let mut b = __s_b.launch_builder(&f);
4358        b.arg(&gp)
4359            .arg(&up)
4360            .arg(x)
4361            .arg(&mut act)
4362            .arg(&inf)
4363            .arg(&nff)
4364            .arg(&qt_g)
4365            .arg(&qt_u)
4366            .arg(&rbg)
4367            .arg(&rbu);
4368        unsafe {
4369            b.launch(cfg)?;
4370        }
4371        Ok(act)
4372    }
4373
4374    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4375    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4376    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4377    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4378    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4379    #[allow(clippy::too_many_arguments)]
4380    pub fn moe_down8_fma_into(
4381        &self,
4382        dp: WPtr8,
4383        w: F32x8,
4384        act: &CudaSlice<f32>,
4385        dst: &mut cudarc::driver::CudaViewMut<f32>,
4386        in_f: usize,
4387        out_f: usize,
4388        n_used: usize,
4389        qt: i32,
4390        rb: usize,
4391    ) -> Result<(), Box<dyn std::error::Error>> {
4392        let f = self.func("moe_down8_fma_f32");
4393        let cfg = LaunchConfig {
4394            grid_dim: (out_f as u32, 1, 1),
4395            block_dim: (256, 1, 1),
4396            shared_mem_bytes: 0,
4397        };
4398        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4399        let __s_b = self.gpu.stream();
4400        let mut b = __s_b.launch_builder(&f);
4401        b.arg(&dp)
4402            .arg(&w)
4403            .arg(act)
4404            .arg(dst)
4405            .arg(&inf)
4406            .arg(&outf)
4407            .arg(&nu)
4408            .arg(&qt)
4409            .arg(&rbv);
4410        unsafe {
4411            b.launch(cfg)?;
4412        }
4413        Ok(())
4414    }
4415
4416    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4417    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4418    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4419    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4420    #[allow(clippy::too_many_arguments)]
4421    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4422    ///
4423    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4424    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4425    /// down's FMA chain stays slot-ordered serial). Seams:
4426    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4427    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4428    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4429    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4430    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4431    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4432    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4433    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4434    ///                       only) | w8h2 (h2 x slot-parallel)
4435    #[allow(clippy::too_many_arguments)]
4436    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4437    #[allow(clippy::too_many_arguments)]
4438    pub fn moe_pairs_matvec_q8(
4439        &self,
4440        table: &CudaSlice<u64>,
4441        proj: i32,
4442        pair_tok: &CudaSlice<i32>,
4443        pair_ex: &CudaSlice<i32>,
4444        aq: &CudaSlice<i8>,
4445        ad: &CudaSlice<f32>,
4446        in_f: usize,
4447        out_f: usize,
4448        n_expert: usize,
4449        n_pairs: usize,
4450        qtype: i32,
4451        row_bytes: usize,
4452    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4453        let f = self.func("moe_pairs_matvec_q8");
4454        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4455        const ROWS: u32 = 4;
4456        let cfg = LaunchConfig {
4457            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4458            block_dim: (32, ROWS, 1),
4459            shared_mem_bytes: 0,
4460        };
4461        let (inf, outf, ne, np, rbi) = (
4462            in_f as i32,
4463            out_f as i32,
4464            n_expert as i32,
4465            n_pairs as i32,
4466            row_bytes as i64,
4467        );
4468        let __s_b = self.gpu.stream();
4469        let mut b = __s_b.launch_builder(&f);
4470        b.arg(table)
4471            .arg(&proj)
4472            .arg(pair_tok)
4473            .arg(pair_ex)
4474            .arg(aq)
4475            .arg(ad)
4476            .arg(&mut y)
4477            .arg(&inf)
4478            .arg(&outf)
4479            .arg(&ne)
4480            .arg(&np)
4481            .arg(&qtype)
4482            .arg(&rbi);
4483        unsafe {
4484            b.launch(cfg)?;
4485        }
4486        Ok(y)
4487    }
4488
4489    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4490    #[allow(clippy::too_many_arguments)]
4491    pub fn moe_pairs_matvec_q8_em(
4492        &self,
4493        table: &CudaSlice<u64>,
4494        proj: i32,
4495        ex_ids: &CudaSlice<i32>,
4496        ex_off: &CudaSlice<i32>,
4497        ex_pairs: &CudaSlice<i32>,
4498        pair_tok: &CudaSlice<i32>,
4499        aq: &CudaSlice<i8>,
4500        ad: &CudaSlice<f32>,
4501        in_f: usize,
4502        out_f: usize,
4503        n_expert: usize,
4504        n_active: usize,
4505        n_pairs: usize,
4506        qtype: i32,
4507        row_bytes: usize,
4508    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4509        let f = self.func("moe_pairs_matvec_q8_em");
4510        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4511        const ROWS: u32 = 4;
4512        let cfg = LaunchConfig {
4513            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4514            block_dim: (32, ROWS, 1),
4515            shared_mem_bytes: 0,
4516        };
4517        let (inf, outf, ne, na, rbi) = (
4518            in_f as i32,
4519            out_f as i32,
4520            n_expert as i32,
4521            n_active as i32,
4522            row_bytes as i64,
4523        );
4524        let __s_b = self.gpu.stream();
4525        let mut b = __s_b.launch_builder(&f);
4526        b.arg(table)
4527            .arg(&proj)
4528            .arg(ex_ids)
4529            .arg(ex_off)
4530            .arg(ex_pairs)
4531            .arg(pair_tok)
4532            .arg(aq)
4533            .arg(ad)
4534            .arg(&mut y)
4535            .arg(&inf)
4536            .arg(&outf)
4537            .arg(&ne)
4538            .arg(&na)
4539            .arg(&qtype)
4540            .arg(&rbi);
4541        unsafe {
4542            b.launch(cfg)?;
4543        }
4544        Ok(y)
4545    }
4546
4547    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4548    // weight group once per (row,group) then dp4a's across the expert's token group.
4549    #[allow(clippy::too_many_arguments)]
4550    pub fn moe_pairs_matvec_q8_dec(
4551        &self,
4552        table: &CudaSlice<u64>,
4553        proj: i32,
4554        ex_ids: &CudaSlice<i32>,
4555        ex_off: &CudaSlice<i32>,
4556        ex_pairs: &CudaSlice<i32>,
4557        pair_tok: &CudaSlice<i32>,
4558        aq: &CudaSlice<i8>,
4559        ad: &CudaSlice<f32>,
4560        in_f: usize,
4561        out_f: usize,
4562        n_expert: usize,
4563        n_active: usize,
4564        n_pairs: usize,
4565        qtype: i32,
4566        row_bytes: usize,
4567    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4568        let f = self.func("moe_pairs_matvec_q8_dec");
4569        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4570        const ROWS: u32 = 4;
4571        let cfg = LaunchConfig {
4572            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4573            block_dim: (32, ROWS, 1),
4574            shared_mem_bytes: 0,
4575        };
4576        let (inf, outf, ne, na, rbi) = (
4577            in_f as i32,
4578            out_f as i32,
4579            n_expert as i32,
4580            n_active as i32,
4581            row_bytes as i64,
4582        );
4583        let __s_b = self.gpu.stream();
4584        let mut b = __s_b.launch_builder(&f);
4585        b.arg(table)
4586            .arg(&proj)
4587            .arg(ex_ids)
4588            .arg(ex_off)
4589            .arg(ex_pairs)
4590            .arg(pair_tok)
4591            .arg(aq)
4592            .arg(ad)
4593            .arg(&mut y)
4594            .arg(&inf)
4595            .arg(&outf)
4596            .arg(&ne)
4597            .arg(&na)
4598            .arg(&qtype)
4599            .arg(&rbi);
4600        unsafe {
4601            b.launch(cfg)?;
4602        }
4603        Ok(y)
4604    }
4605
4606    pub fn moe_pairs_gelu_mul(
4607        &self,
4608        gate: &CudaSlice<f32>,
4609        up: &CudaSlice<f32>,
4610        n: usize,
4611    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4612        let f = self.func("moe_pairs_gelu_mul");
4613        let mut act = self.alloc_uninit::<f32>(n)?;
4614        let cfg = LaunchConfig::for_num_elems(n as u32);
4615        let nl = n as i64;
4616        let __s_b = self.gpu.stream();
4617        let mut b = __s_b.launch_builder(&f);
4618        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4619        unsafe {
4620            b.launch(cfg)?;
4621        }
4622        Ok(act)
4623    }
4624
4625    pub fn moe_pairs_silu_mul(
4626        &self,
4627        gate: &CudaSlice<f32>,
4628        up: &CudaSlice<f32>,
4629        n: usize,
4630    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4631        let f = self.func("moe_pairs_silu_mul");
4632        let mut act = self.alloc_uninit::<f32>(n)?;
4633        let cfg = LaunchConfig::for_num_elems(n as u32);
4634        let nl = n as i64;
4635        let __s_b = self.gpu.stream();
4636        let mut b = __s_b.launch_builder(&f);
4637        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4638        unsafe {
4639            b.launch(cfg)?;
4640        }
4641        Ok(act)
4642    }
4643
4644    #[allow(clippy::too_many_arguments)]
4645    pub fn moe_pairs_scatter(
4646        &self,
4647        y_down: &CudaSlice<f32>,
4648        pair_w: &CudaSlice<f32>,
4649        tok_pair_off: &CudaSlice<i32>,
4650        tok_pair_ids: &CudaSlice<i32>,
4651        moe_out: &mut CudaSlice<f32>,
4652        t: usize,
4653        n_embd: usize,
4654    ) -> Result<(), Box<dyn std::error::Error>> {
4655        let f = self.func("moe_pairs_scatter");
4656        let cfg = LaunchConfig {
4657            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4658            block_dim: (256, 1, 1),
4659            shared_mem_bytes: 0,
4660        };
4661        let ne = n_embd as i32;
4662        let __s_b = self.gpu.stream();
4663        let mut b = __s_b.launch_builder(&f);
4664        b.arg(y_down)
4665            .arg(pair_w)
4666            .arg(tok_pair_off)
4667            .arg(tok_pair_ids)
4668            .arg(moe_out)
4669            .arg(&ne);
4670        unsafe {
4671            b.launch(cfg)?;
4672        }
4673        Ok(())
4674    }
4675
4676    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4677    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4678    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4679    #[allow(clippy::too_many_arguments)]
4680    pub fn moe_gate_up_gelu8_dev_q8(
4681        &self,
4682        table: &CudaSlice<u64>,
4683        sel: &cudarc::driver::CudaView<i32>,
4684        aq: &CudaSlice<i8>,
4685        ad: &CudaSlice<f32>,
4686        in_f: usize,
4687        n_ff: usize,
4688        n_used: usize,
4689        n_expert: usize,
4690        qt_g: i32,
4691        qt_u: i32,
4692        rb_g: usize,
4693        rb_u: usize,
4694    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4695        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4696        let (inf, nff, ne, rbg, rbu) = (
4697            in_f as i32,
4698            n_ff as i32,
4699            n_expert as i32,
4700            rb_g as i64,
4701            rb_u as i64,
4702        );
4703        let f = self.func("moe_gate_up_gelu8_dev_q8");
4704        let cfg = LaunchConfig {
4705            grid_dim: (n_ff as u32, n_used as u32, 1),
4706            block_dim: (32, 1, 1),
4707            shared_mem_bytes: 0,
4708        };
4709        let __s_b = self.gpu.stream();
4710        let mut b = __s_b.launch_builder(&f);
4711        b.arg(table)
4712            .arg(sel)
4713            .arg(aq)
4714            .arg(ad)
4715            .arg(&mut act)
4716            .arg(&inf)
4717            .arg(&nff)
4718            .arg(&ne)
4719            .arg(&qt_g)
4720            .arg(&qt_u)
4721            .arg(&rbg)
4722            .arg(&rbu);
4723        unsafe {
4724            b.launch(cfg)?;
4725        }
4726        Ok(act)
4727    }
4728
4729    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4730    #[allow(clippy::too_many_arguments)]
4731    pub fn moe_gate_up_gelu8_dev_q8_rows(
4732        &self,
4733        table: &CudaSlice<u64>,
4734        sel: &CudaSlice<i32>,
4735        aq: &CudaSlice<i8>,
4736        ad: &CudaSlice<f32>,
4737        t: usize,
4738        in_f: usize,
4739        n_ff: usize,
4740        n_used: usize,
4741        n_expert: usize,
4742        qt_g: i32,
4743        qt_u: i32,
4744        rb_g: usize,
4745        rb_u: usize,
4746    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4747        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4748        let (inf, nff, ne, rbg, rbu, nu) = (
4749            in_f as i32,
4750            n_ff as i32,
4751            n_expert as i32,
4752            rb_g as i64,
4753            rb_u as i64,
4754            n_used as i32,
4755        );
4756        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4757        let cfg = LaunchConfig {
4758            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4759            block_dim: (32, 1, 1),
4760            shared_mem_bytes: 0,
4761        };
4762        let __s_b = self.gpu.stream();
4763        let mut b = __s_b.launch_builder(&f);
4764        b.arg(table)
4765            .arg(sel)
4766            .arg(aq)
4767            .arg(ad)
4768            .arg(&mut act)
4769            .arg(&inf)
4770            .arg(&nff)
4771            .arg(&ne)
4772            .arg(&qt_g)
4773            .arg(&qt_u)
4774            .arg(&rbg)
4775            .arg(&rbu)
4776            .arg(&nu);
4777        unsafe {
4778            b.launch(cfg)?;
4779        }
4780        Ok(act)
4781    }
4782
4783    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4784    #[allow(clippy::too_many_arguments)]
4785    pub fn moe_gate_up_gelu8_dev_q8_csr(
4786        &self,
4787        table: &CudaSlice<u64>,
4788        sel: &CudaSlice<i32>,
4789        aq: &CudaSlice<i8>,
4790        ad: &CudaSlice<f32>,
4791        n_pairs: usize,
4792        in_f: usize,
4793        n_ff: usize,
4794        n_used: usize,
4795        n_expert: usize,
4796        qt_g: i32,
4797        qt_u: i32,
4798        rb_g: usize,
4799        rb_u: usize,
4800    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4801        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4802        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4803            in_f as i32,
4804            n_ff as i32,
4805            n_expert as i32,
4806            rb_g as i64,
4807            rb_u as i64,
4808            n_used as i32,
4809            n_pairs as i32,
4810        );
4811        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4812        let cfg = LaunchConfig {
4813            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4814            block_dim: (32, 1, 1),
4815            shared_mem_bytes: 0,
4816        };
4817        let __s_b = self.gpu.stream();
4818        let mut b = __s_b.launch_builder(&f);
4819        b.arg(table)
4820            .arg(sel)
4821            .arg(aq)
4822            .arg(ad)
4823            .arg(&mut act)
4824            .arg(&inf)
4825            .arg(&nff)
4826            .arg(&ne)
4827            .arg(&qt_g)
4828            .arg(&qt_u)
4829            .arg(&rbg)
4830            .arg(&rbu)
4831            .arg(&nu)
4832            .arg(&npi);
4833        unsafe {
4834            b.launch(cfg)?;
4835        }
4836        Ok(act)
4837    }
4838
4839    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4840    #[allow(clippy::too_many_arguments)]
4841    pub fn moe_down8_fma_dev_q8_rows_g(
4842        &self,
4843        table: &CudaSlice<u64>,
4844        sel: &CudaSlice<i32>,
4845        w: &CudaSlice<f32>,
4846        aq2: &CudaSlice<i8>,
4847        ad2: &CudaSlice<f32>,
4848        dst: &mut CudaSlice<f32>,
4849        t: usize,
4850        in_f: usize,
4851        out_f: usize,
4852        n_used: usize,
4853        n_expert: usize,
4854        qt: i32,
4855        rb: usize,
4856    ) -> Result<(), Box<dyn std::error::Error>> {
4857        let (inf, outf, nu, ne, rbi) = (
4858            in_f as i32,
4859            out_f as i32,
4860            n_used as i32,
4861            n_expert as i32,
4862            rb as i64,
4863        );
4864        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4865        // eight warps, then replay the original slot-ordered FMA chain. Every
4866        // other shape retains the generic one-warp rows kernel.
4867        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4868        let f = self.func(if step_b1_w8 {
4869            "moe_down8_fma_dev_q8_rows_w8"
4870        } else {
4871            "moe_down8_fma_dev_q8_rows_g"
4872        });
4873        let cfg = LaunchConfig {
4874            grid_dim: (out_f as u32, 1, t as u32),
4875            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4876            shared_mem_bytes: 0,
4877        };
4878        let __s_b = self.gpu.stream();
4879        let mut b = __s_b.launch_builder(&f);
4880        b.arg(table)
4881            .arg(sel)
4882            .arg(w)
4883            .arg(aq2)
4884            .arg(ad2)
4885            .arg(dst)
4886            .arg(&inf)
4887            .arg(&outf)
4888            .arg(&nu)
4889            .arg(&ne)
4890            .arg(&qt)
4891            .arg(&rbi);
4892        unsafe {
4893            b.launch(cfg)?;
4894        }
4895        Ok(())
4896    }
4897
4898    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4899    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4900    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4901    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4902        let (out_f, in_f) = (2048usize, 2816usize);
4903        let nblk = in_f / 32;
4904        let mut seed = 0x9E3779B97F4A7C15u64;
4905        let mut rng = move || {
4906            seed = seed
4907                .wrapping_mul(6364136223846793005)
4908                .wrapping_add(1442695040888963407);
4909            (seed >> 33) as u8
4910        };
4911        let mut w = vec![0u8; out_f * nblk * 18];
4912        for b in w.iter_mut() {
4913            *b = rng();
4914        }
4915        for r in 0..out_f {
4916            for g in 0..nblk {
4917                let off = (r * nblk + g) * 18;
4918                w[off] = 0x00;
4919                w[off + 1] = 0x2C; // sane half d
4920            }
4921        }
4922        let qplane = out_f * nblk * 16;
4923        let mut wrp = vec![0u8; w.len()];
4924        for r in 0..out_f {
4925            for g in 0..nblk {
4926                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4927                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4928                    .copy_from_slice(&src[0..2]);
4929                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4930            }
4931        }
4932        let w_d = self.htod_bytes(&w)?;
4933        let wrp_d = self.htod_bytes(&wrp)?;
4934        let mut aq = vec![0i8; m * in_f];
4935        for v in aq.iter_mut() {
4936            *v = rng() as i8;
4937        }
4938        let aq_d = self.htod_i8(&aq)?;
4939        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4940        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4941        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4942        const RPB: u32 = 4;
4943        let cfg = LaunchConfig {
4944            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4945            block_dim: (32, RPB, 1),
4946            shared_mem_bytes: 0,
4947        };
4948        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4949        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4950        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4951        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4952        {
4953            let __s_b = self.gpu.stream();
4954            let mut b = __s_b.launch_builder(&fb);
4955            b.arg(&w_d)
4956                .arg(&aq_d)
4957                .arg(&ad_d)
4958                .arg(&mut y0)
4959                .arg(&inf)
4960                .arg(&outf)
4961                .arg(&mi)
4962                .arg(&rb);
4963            unsafe {
4964                b.launch(cfg)?;
4965            }
4966            let __s_b = self.gpu.stream();
4967            let mut b = __s_b.launch_builder(&fr);
4968            b.arg(&wrp_d)
4969                .arg(&aq_d)
4970                .arg(&ad_d)
4971                .arg(&mut y1)
4972                .arg(&inf)
4973                .arg(&outf)
4974                .arg(&mi)
4975                .arg(&qp);
4976            unsafe {
4977                b.launch(cfg)?;
4978            }
4979        }
4980        self.gpu.stream().synchronize()?;
4981        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4982        let nd = h0
4983            .iter()
4984            .zip(&h1)
4985            .filter(|(a, b)| a.to_bits() != b.to_bits())
4986            .count();
4987        if nd != 0 {
4988            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4989        }
4990        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4991            self.gpu.stream().synchronize()?;
4992            let t0 = std::time::Instant::now();
4993            for _ in 0..500 {
4994                if rp {
4995                    let __s_b = self.gpu.stream();
4996                    let mut b = __s_b.launch_builder(&fr);
4997                    b.arg(&wrp_d)
4998                        .arg(&aq_d)
4999                        .arg(&ad_d)
5000                        .arg(&mut y1)
5001                        .arg(&inf)
5002                        .arg(&outf)
5003                        .arg(&mi)
5004                        .arg(&qp);
5005                    unsafe {
5006                        b.launch(cfg)?;
5007                    }
5008                } else {
5009                    let __s_b = self.gpu.stream();
5010                    let mut b = __s_b.launch_builder(&fb);
5011                    b.arg(&w_d)
5012                        .arg(&aq_d)
5013                        .arg(&ad_d)
5014                        .arg(&mut y0)
5015                        .arg(&inf)
5016                        .arg(&outf)
5017                        .arg(&mi)
5018                        .arg(&rb);
5019                    unsafe {
5020                        b.launch(cfg)?;
5021                    }
5022                }
5023            }
5024            self.gpu.stream().synchronize()?;
5025            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
5026        };
5027        let _ = time(false)?;
5028        let _ = time(true)?; // warm
5029        Ok((time(false)?, time(true)?))
5030    }
5031
5032    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
5033    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
5034    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
5035    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
5036    pub fn build_q4_rp4(
5037        &self,
5038        t: &mut crate::model::GpuTensor,
5039    ) -> Result<(), Box<dyn std::error::Error>> {
5040        use crate::model::GpuTensor;
5041        let GpuTensor::Quant {
5042            bytes,
5043            qtype,
5044            row_bytes,
5045            ne,
5046            rp4,
5047            ..
5048        } = t
5049        else {
5050            return Ok(());
5051        };
5052        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
5053            return Ok(());
5054        }
5055        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5056        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
5057            return Ok(());
5058        }
5059        let nblk = in_f / 32;
5060        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
5061        let f = self.func("q4_0_split_rp_build");
5062        let n = (out_f * nblk) as i32;
5063        let cfg = LaunchConfig {
5064            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5065            block_dim: (256, 1, 1),
5066            shared_mem_bytes: 0,
5067        };
5068        let (of, nb) = (out_f as i32, nblk as i32);
5069        let _ = n;
5070        let __s_b = self.gpu.stream();
5071        let mut b = __s_b.launch_builder(&f);
5072        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5073        unsafe {
5074            b.launch(cfg)?;
5075        }
5076        *rp4 = Some(dst);
5077        Ok(())
5078    }
5079
5080    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
5081    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
5082    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
5083    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5084    pub fn build_q8_rp4(
5085        &self,
5086        t: &mut crate::model::GpuTensor,
5087    ) -> Result<(), Box<dyn std::error::Error>> {
5088        use crate::model::GpuTensor;
5089        let GpuTensor::Quant {
5090            bytes,
5091            qtype,
5092            row_bytes,
5093            ne,
5094            rp4,
5095            ..
5096        } = t
5097        else {
5098            return Ok(());
5099        };
5100        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
5101            return Ok(());
5102        }
5103        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5104        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5105            return Ok(());
5106        }
5107        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5108        Ok(())
5109    }
5110
5111    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5112    /// mirror without a GpuTensor (same kernel the loader path above uses).
5113    pub fn build_q8_rp4_raw(
5114        &self,
5115        bytes: &CudaSlice<u8>,
5116        in_f: usize,
5117        out_f: usize,
5118    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5119        assert!(in_f % 32 == 0);
5120        let nblk = in_f / 32;
5121        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5122        let f = self.func("q8_0_split_rp_build");
5123        let cfg = LaunchConfig {
5124            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5125            block_dim: (256, 1, 1),
5126            shared_mem_bytes: 0,
5127        };
5128        let (of, nb) = (out_f as i32, nblk as i32);
5129        let __s_b = self.gpu.stream();
5130        let mut b = __s_b.launch_builder(&f);
5131        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5132        unsafe {
5133            b.launch(cfg)?;
5134        }
5135        Ok(dst)
5136    }
5137
5138    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5139    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5140    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5141    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5142    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5143    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5144    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5145    pub fn build_q4k_rp4(
5146        &self,
5147        t: &mut crate::model::GpuTensor,
5148    ) -> Result<(), Box<dyn std::error::Error>> {
5149        use crate::model::GpuTensor;
5150        let GpuTensor::Quant {
5151            bytes,
5152            qtype,
5153            row_bytes,
5154            ne,
5155            rp4,
5156            ..
5157        } = t
5158        else {
5159            return Ok(());
5160        };
5161        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5162            return Ok(());
5163        }
5164        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5165        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5166            return Ok(());
5167        }
5168        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5169        Ok(())
5170    }
5171
5172    pub fn build_q6k_rp4(
5173        &self,
5174        t: &mut crate::model::GpuTensor,
5175    ) -> Result<(), Box<dyn std::error::Error>> {
5176        use crate::model::GpuTensor;
5177        let GpuTensor::Quant {
5178            bytes,
5179            qtype,
5180            row_bytes,
5181            ne,
5182            rp4,
5183            ..
5184        } = t
5185        else {
5186            return Ok(());
5187        };
5188        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5189            return Ok(());
5190        }
5191        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5192        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5193            return Ok(());
5194        }
5195        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5196        Ok(())
5197    }
5198
5199    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5200    pub fn build_kq_rp4_raw(
5201        &self,
5202        bytes: &CudaSlice<u8>,
5203        in_f: usize,
5204        out_f: usize,
5205        qtype: i32,
5206    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5207        assert!(in_f % 256 == 0);
5208        let nsbk = in_f / 256;
5209        let (sb_bytes, kname) = match qtype {
5210            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5211            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5212            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5213        };
5214        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5215        let f = self.func(kname);
5216        let cfg = LaunchConfig {
5217            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5218            block_dim: (256, 1, 1),
5219            shared_mem_bytes: 0,
5220        };
5221        let (of, nb) = (out_f as i32, nsbk as i32);
5222        let __s_b = self.gpu.stream();
5223        let mut b = __s_b.launch_builder(&f);
5224        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5225        unsafe {
5226            b.launch(cfg)?;
5227        }
5228        Ok(dst)
5229    }
5230
5231    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5232    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5233    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5234    pub fn kqrp_enabled() -> bool {
5235        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5236        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5237            Ok("0") => false,
5238            Ok(_) => true,
5239            Err(_) => cfg!(memra_hopper_mma),
5240        })
5241    }
5242
5243    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5244    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5245    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5246    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5247    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5248    pub fn build_q4_rp_swap(
5249        &self,
5250        t: &mut crate::model::GpuTensor,
5251    ) -> Result<bool, Box<dyn std::error::Error>> {
5252        use crate::model::GpuTensor;
5253        // QTYPE GUARD IN THE SWAP ITSELF (gemma4 NVFP4mix prefill-NaN, 2026-08-17):
5254        // `rp4` is a SHARED field — the Q8RP walk parks Q8_0 MIRRORS there, and this
5255        // fn used to `take()` whatever it found. On a Q8_0-carrying gemma4-dense trunk
5256        // the swap hijacked those mirrors: `bytes` became split-plane in place, the
5257        // m<=16 `_rp` dispatch read them correctly (masking the corruption from every
5258        // decode pin), and every GGUF-layout prefill consumer (MMQ + GEMM) read the
5259        // fp16 d-plane as weights -> layer-0 NaN, <pad>-spam serving. Only a tensor
5260        // this fn's OWN builder serves may ever be swapped; everything else refuses
5261        // here, regardless of walk ordering.
5262        if !matches!(t, GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0) {
5263            return Ok(false);
5264        }
5265        self.build_q4_rp4(t)?;
5266        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5267        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5268            return Ok(false);
5269        };
5270        match rp4.take() {
5271            Some(split) => {
5272                *bytes = split; // the GGUF-layout buffer drops here
5273                *rp = true;
5274                Ok(true)
5275            }
5276            None => Ok(false),
5277        }
5278    }
5279
5280    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5281    pub fn q4rp_enabled() -> bool {
5282        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5283        *ON.get_or_init(|| {
5284            std::env::var("MEMRA_Q4RP")
5285                .map(|v| v != "0")
5286                .unwrap_or(true)
5287        })
5288    }
5289
5290    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5291    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5292    pub fn copy_rows_strided(
5293        &self,
5294        src: &CudaSlice<f32>,
5295        dst: &mut CudaSlice<f32>,
5296        row_elems: usize,
5297        n_rows: usize,
5298        src_stride: usize,
5299        src_off: usize,
5300    ) -> Result<(), Box<dyn std::error::Error>> {
5301        let f = self.func("copy_rows_strided_f32");
5302        let cfg = LaunchConfig {
5303            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5304            block_dim: (256, 1, 1),
5305            shared_mem_bytes: 0,
5306        };
5307        let (re, nr) = (row_elems as i32, n_rows as i32);
5308        let (st, off) = (src_stride as i64, src_off as i64);
5309        let __s_b = self.gpu.stream();
5310        let mut b = __s_b.launch_builder(&f);
5311        b.arg(src)
5312            .arg(&mut *dst)
5313            .arg(&re)
5314            .arg(&nr)
5315            .arg(&st)
5316            .arg(&off);
5317        unsafe {
5318            b.launch(cfg)?;
5319        }
5320        Ok(())
5321    }
5322
5323    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5324    pub fn u32_set_k(
5325        &self,
5326        dst: &mut CudaSlice<u32>,
5327        v: u32,
5328        idx: usize,
5329    ) -> Result<(), Box<dyn std::error::Error>> {
5330        let f = self.func("u32_set_k");
5331        let cfg = LaunchConfig {
5332            grid_dim: (1, 1, 1),
5333            block_dim: (1, 1, 1),
5334            shared_mem_bytes: 0,
5335        };
5336        let ii = idx as i32;
5337        let __s_b = self.gpu.stream();
5338        let mut b = __s_b.launch_builder(&f);
5339        b.arg(dst).arg(&v).arg(&ii);
5340        unsafe {
5341            b.launch(cfg)?;
5342        }
5343        Ok(())
5344    }
5345
5346    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5347    pub fn i32_add_k(
5348        &self,
5349        d: &mut CudaSlice<i32>,
5350        v: i32,
5351    ) -> Result<(), Box<dyn std::error::Error>> {
5352        let f = self.func("i32_add_k");
5353        let cfg = LaunchConfig {
5354            grid_dim: (1, 1, 1),
5355            block_dim: (32, 1, 1),
5356            shared_mem_bytes: 0,
5357        };
5358        let __s_b = self.gpu.stream();
5359        let mut b = __s_b.launch_builder(&f);
5360        b.arg(d).arg(&v);
5361        unsafe {
5362            b.launch(cfg)?;
5363        }
5364        Ok(())
5365    }
5366
5367    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5368    pub fn i32_iota_from(
5369        &self,
5370        ctr: &CudaSlice<i32>,
5371        dst: &mut CudaSlice<i32>,
5372        n: usize,
5373    ) -> Result<(), Box<dyn std::error::Error>> {
5374        let f = self.func("i32_iota_from");
5375        let cfg = LaunchConfig::for_num_elems(n as u32);
5376        let ni = n as i32;
5377        let __s_b = self.gpu.stream();
5378        let mut b = __s_b.launch_builder(&f);
5379        b.arg(ctr).arg(dst).arg(&ni);
5380        unsafe {
5381            b.launch(cfg)?;
5382        }
5383        Ok(())
5384    }
5385
5386    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5387    pub fn u32_map_k(
5388        &self,
5389        buf: &mut CudaSlice<u32>,
5390        map: &CudaSlice<u32>,
5391        idx: usize,
5392    ) -> Result<(), Box<dyn std::error::Error>> {
5393        let f = self.func("u32_map_k");
5394        let cfg = LaunchConfig {
5395            grid_dim: (1, 1, 1),
5396            block_dim: (1, 1, 1),
5397            shared_mem_bytes: 0,
5398        };
5399        let ii = idx as i32;
5400        let __s_b = self.gpu.stream();
5401        let mut b = __s_b.launch_builder(&f);
5402        b.arg(buf).arg(map).arg(&ii);
5403        unsafe {
5404            b.launch(cfg)?;
5405        }
5406        Ok(())
5407    }
5408
5409    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5410    #[allow(clippy::too_many_arguments)]
5411    pub fn u32_pack2(
5412        &self,
5413        a: &CudaSlice<u32>,
5414        off_a: usize,
5415        n1: usize,
5416        b_in: &CudaSlice<u32>,
5417        n2: usize,
5418        out: &mut CudaSlice<u32>,
5419    ) -> Result<(), Box<dyn std::error::Error>> {
5420        let f = self.func("u32_pack2");
5421        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5422        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5423        let __s_b = self.gpu.stream();
5424        let mut b = __s_b.launch_builder(&f);
5425        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5426        unsafe {
5427            b.launch(cfg)?;
5428        }
5429        Ok(())
5430    }
5431
5432    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5433    pub fn moe_w_exscale(
5434        &self,
5435        w: &mut CudaSlice<f32>,
5436        sel: &CudaSlice<i32>,
5437        s: &CudaSlice<f32>,
5438        n: usize,
5439    ) -> Result<(), Box<dyn std::error::Error>> {
5440        let f = self.func("moe_w_exscale");
5441        let cfg = LaunchConfig::for_num_elems(n as u32);
5442        let ni = n as i32;
5443        let __s_b = self.gpu.stream();
5444        let mut b = __s_b.launch_builder(&f);
5445        b.arg(w).arg(sel).arg(s).arg(&ni);
5446        unsafe {
5447            b.launch(cfg)?;
5448        }
5449        Ok(())
5450    }
5451
5452    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5453    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5454    pub fn moe_w_scale_by_expert(
5455        &self,
5456        w: &mut CudaSlice<f32>,
5457        sel: &CudaSlice<i32>,
5458        macros: &CudaSlice<f32>,
5459        n_expert: usize,
5460        n: usize,
5461    ) -> Result<(), Box<dyn std::error::Error>> {
5462        let f = self.func("moe_w_scale_by_expert");
5463        let cfg = LaunchConfig {
5464            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5465            block_dim: (64, 1, 1),
5466            shared_mem_bytes: 0,
5467        };
5468        let (ne, nn) = (n_expert as i32, n as i32);
5469        let __s_b = self.gpu.stream();
5470        let mut b = __s_b.launch_builder(&f);
5471        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5472        unsafe {
5473            b.launch(cfg)?;
5474        }
5475        Ok(())
5476    }
5477
5478    pub fn moe_gate_up_silu8_dev_q8(
5479        &self,
5480        table: &CudaSlice<u64>,
5481        sel: &cudarc::driver::CudaView<i32>,
5482        aq: &CudaSlice<i8>,
5483        ad: &CudaSlice<f32>,
5484        in_f: usize,
5485        n_ff: usize,
5486        n_used: usize,
5487        n_expert: usize,
5488        qt_g: i32,
5489        qt_u: i32,
5490        rb_g: usize,
5491        rb_u: usize,
5492        macros: &CudaSlice<f32>,
5493    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5494        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5495        let (mode, wpb) = GU.get_or_init(|| {
5496            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5497            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5498                .ok()
5499                .and_then(|v| v.parse().ok())
5500                .unwrap_or(4u32)
5501                .clamp(1, 16);
5502            (mode, wpb)
5503        });
5504        let (mode, wpb) = (mode.as_str(), *wpb);
5505        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5506        let (inf, nff, ne, rbg, rbu) = (
5507            in_f as i32,
5508            n_ff as i32,
5509            n_expert as i32,
5510            rb_g as i64,
5511            rb_u as i64,
5512        );
5513        let (f, cfg) = match mode {
5514            "1" | "2" | "4" => {
5515                let rpw: u32 = mode.parse().unwrap();
5516                let f = self.func(match rpw {
5517                    1 => "moe_gate_up_silu8_dev_q8_r1",
5518                    2 => "moe_gate_up_silu8_dev_q8_r2",
5519                    _ => "moe_gate_up_silu8_dev_q8_r4",
5520                });
5521                let rows_per_block = (rpw * wpb) as usize;
5522                let gx = n_ff.div_ceil(rows_per_block) as u32;
5523                (
5524                    f,
5525                    LaunchConfig {
5526                        grid_dim: (gx, n_used as u32, 1),
5527                        block_dim: (32, wpb, 1),
5528                        shared_mem_bytes: 0,
5529                    },
5530                )
5531            }
5532            "j8" if n_used <= 32 => (
5533                self.func("moe_gate_up_silu8_dev_q8_j8"),
5534                LaunchConfig {
5535                    grid_dim: (n_ff as u32, 1, 1),
5536                    block_dim: (32, n_used as u32, 1),
5537                    shared_mem_bytes: 0,
5538                },
5539            ),
5540            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5541            "vsm2" => {
5542                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5543                let sh = (rb_g + rb_u) as u32;
5544                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5545                f.set_attribute(
5546                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5547                    sh as i32,
5548                )?;
5549                (
5550                    f,
5551                    LaunchConfig {
5552                        grid_dim: (n_ff as u32, n_used as u32, 1),
5553                        block_dim: (32, 1, 1),
5554                        shared_mem_bytes: sh,
5555                    },
5556                )
5557            }
5558            "vsm" => {
5559                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5560                let sh = (rb_g + rb_u) as u32;
5561                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5562                f.set_attribute(
5563                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5564                    sh as i32,
5565                )?;
5566                (
5567                    f,
5568                    LaunchConfig {
5569                        grid_dim: (n_ff as u32, n_used as u32, 1),
5570                        block_dim: (32, 1, 1),
5571                        shared_mem_bytes: sh,
5572                    },
5573                )
5574            }
5575            "sg" => (
5576                self.func("moe_gate_up_silu8_dev_q8_sg"),
5577                LaunchConfig {
5578                    grid_dim: (n_ff as u32, n_used as u32, 1),
5579                    block_dim: (32, 1, 1),
5580                    shared_mem_bytes: 0,
5581                },
5582            ),
5583            "j8sg" if n_used <= 32 => (
5584                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5585                LaunchConfig {
5586                    grid_dim: (n_ff as u32, 1, 1),
5587                    block_dim: (32, n_used as u32, 1),
5588                    shared_mem_bytes: 0,
5589                },
5590            ),
5591            "u64" if in_f == 2048 => (
5592                self.func("moe_gate_up_silu8_dev_q8_u64"),
5593                LaunchConfig {
5594                    grid_dim: (n_ff as u32, n_used as u32, 1),
5595                    block_dim: (32, 1, 1),
5596                    shared_mem_bytes: 0,
5597                },
5598            ),
5599            "gs4" if in_f == 2048 => (
5600                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5601                LaunchConfig {
5602                    grid_dim: (n_ff as u32, n_used as u32, 1),
5603                    block_dim: (32, 4, 1),
5604                    shared_mem_bytes: 0,
5605                },
5606            ),
5607            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5608            "v" | "" => (
5609                self.func("moe_gate_up_silu8_dev_q8_v"),
5610                LaunchConfig {
5611                    grid_dim: (n_ff as u32, n_used as u32, 1),
5612                    block_dim: (32, 1, 1),
5613                    shared_mem_bytes: 0,
5614                },
5615            ),
5616            "s2" => (
5617                self.func("moe_gate_up_silu8_dev_q8_s2"),
5618                LaunchConfig {
5619                    grid_dim: (n_ff as u32, n_used as u32, 1),
5620                    block_dim: (32, 2, 1),
5621                    shared_mem_bytes: 0,
5622                },
5623            ),
5624            "s2z" => {
5625                let rz = wpb.min(16); // s2z smem tile is [16][2]
5626                (
5627                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5628                    LaunchConfig {
5629                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5630                        block_dim: (32, 2, rz),
5631                        shared_mem_bytes: 0,
5632                    },
5633                )
5634            }
5635            _ => (
5636                self.func("moe_gate_up_silu8_dev_q8"),
5637                LaunchConfig {
5638                    grid_dim: (n_ff as u32, n_used as u32, 1),
5639                    block_dim: (32, 1, 1),
5640                    shared_mem_bytes: 0,
5641                },
5642            ),
5643        };
5644        let __s_b = self.gpu.stream();
5645        let mut b = __s_b.launch_builder(&f);
5646        b.arg(table)
5647            .arg(sel)
5648            .arg(aq)
5649            .arg(ad)
5650            .arg(&mut act)
5651            .arg(&inf)
5652            .arg(&nff)
5653            .arg(&ne)
5654            .arg(&qt_g)
5655            .arg(&qt_u)
5656            .arg(&rbg)
5657            .arg(&rbu)
5658            .arg(macros);
5659        unsafe {
5660            b.launch(cfg)?;
5661        }
5662        Ok(act)
5663    }
5664
5665    #[allow(clippy::too_many_arguments)]
5666    pub fn moe_down8_fma_dev_q8(
5667        &self,
5668        table: &CudaSlice<u64>,
5669        sel: &cudarc::driver::CudaView<i32>,
5670        w: &cudarc::driver::CudaView<f32>,
5671        aq2: &CudaSlice<i8>,
5672        ad2: &CudaSlice<f32>,
5673        dst: &mut cudarc::driver::CudaViewMut<f32>,
5674        in_f: usize,
5675        out_f: usize,
5676        n_used: usize,
5677        n_expert: usize,
5678        qt: i32,
5679        rb: usize,
5680    ) -> Result<(), Box<dyn std::error::Error>> {
5681        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5682        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5683        let (inf, outf, nu, ne, rbi) = (
5684            in_f as i32,
5685            out_f as i32,
5686            n_used as i32,
5687            n_expert as i32,
5688            rb as i64,
5689        );
5690        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5691        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5692        let (f, cfg) = match mode.as_str() {
5693            m @ ("1" | "2" | "4") if n_used <= 8 => {
5694                let rpw: usize = m.parse().unwrap();
5695                let f = self.func(match rpw {
5696                    1 => "moe_down8_fma_dev_q8_w8r1",
5697                    2 => "moe_down8_fma_dev_q8_w8r2",
5698                    _ => "moe_down8_fma_dev_q8_w8r4",
5699                });
5700                (
5701                    f,
5702                    LaunchConfig {
5703                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5704                        block_dim: (32, n_used as u32, 1),
5705                        shared_mem_bytes: 0,
5706                    },
5707                )
5708            }
5709            "h2" if in_f == 512 => (
5710                self.func("moe_down8_fma_dev_q8_h2"),
5711                LaunchConfig {
5712                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5713                    block_dim: (32, 1, 1),
5714                    shared_mem_bytes: 0,
5715                },
5716            ),
5717            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5718            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5719            "" if in_f == 704 && n_used <= 8 => (
5720                self.func("moe_down8_fma_dev_q8_w8r2"),
5721                LaunchConfig {
5722                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5723                    block_dim: (32, n_used as u32, 1),
5724                    shared_mem_bytes: 0,
5725                },
5726            ),
5727            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5728            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5729            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5730            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5731                self.func("moe_down8_fma_dev_q8_w8h2v"),
5732                LaunchConfig {
5733                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5734                    block_dim: (32, n_used as u32, 1),
5735                    shared_mem_bytes: 0,
5736                },
5737            ),
5738            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5739                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5740                LaunchConfig {
5741                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5742                    block_dim: (32, n_used as u32, 1),
5743                    shared_mem_bytes: 0,
5744                },
5745            ),
5746            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5747                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5748                LaunchConfig {
5749                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5750                    block_dim: (32, n_used as u32, 1),
5751                    shared_mem_bytes: 0,
5752                },
5753            ),
5754            "w8h2" if in_f == 512 && n_used <= 8 => (
5755                self.func("moe_down8_fma_dev_q8_w8h2"),
5756                LaunchConfig {
5757                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5758                    block_dim: (32, n_used as u32, 1),
5759                    shared_mem_bytes: 0,
5760                },
5761            ),
5762            _ => (
5763                self.func("moe_down8_fma_dev_q8"),
5764                LaunchConfig {
5765                    grid_dim: (out_f as u32, 1, 1),
5766                    block_dim: (32, 1, 1),
5767                    shared_mem_bytes: 0,
5768                },
5769            ),
5770        };
5771        let __s_b = self.gpu.stream();
5772        let mut b = __s_b.launch_builder(&f);
5773        b.arg(table)
5774            .arg(sel)
5775            .arg(w)
5776            .arg(aq2)
5777            .arg(ad2)
5778            .arg(dst)
5779            .arg(&inf)
5780            .arg(&outf)
5781            .arg(&nu)
5782            .arg(&ne)
5783            .arg(&qt)
5784            .arg(&rbi);
5785        unsafe {
5786            b.launch(cfg)?;
5787        }
5788        Ok(())
5789    }
5790
5791    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5792    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5793    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5794    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5795    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5796    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5797    #[allow(clippy::too_many_arguments)]
5798    pub fn moe_gate_up_silu8_dev_q8_rows(
5799        &self,
5800        table: &CudaSlice<u64>,
5801        sel: &CudaSlice<i32>,
5802        aq: &CudaSlice<i8>,
5803        ad: &CudaSlice<f32>,
5804        t: usize,
5805        in_f: usize,
5806        n_ff: usize,
5807        n_used: usize,
5808        n_expert: usize,
5809        qt_g: i32,
5810        qt_u: i32,
5811        rb_g: usize,
5812        rb_u: usize,
5813        macros: &CudaSlice<f32>,
5814    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5815        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5816        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5817        let cfg = LaunchConfig {
5818            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5819            block_dim: (32, 1, 1),
5820            shared_mem_bytes: 0,
5821        };
5822        let (inf, nff, ne, nu, rbg, rbu) = (
5823            in_f as i32,
5824            n_ff as i32,
5825            n_expert as i32,
5826            n_used as i32,
5827            rb_g as i64,
5828            rb_u as i64,
5829        );
5830        let __s_b = self.gpu.stream();
5831        let mut b = __s_b.launch_builder(&f);
5832        b.arg(table)
5833            .arg(sel)
5834            .arg(aq)
5835            .arg(ad)
5836            .arg(&mut act)
5837            .arg(&inf)
5838            .arg(&nff)
5839            .arg(&ne)
5840            .arg(&qt_g)
5841            .arg(&qt_u)
5842            .arg(&rbg)
5843            .arg(&rbu)
5844            .arg(&nu)
5845            .arg(macros);
5846        unsafe {
5847            b.launch(cfg)?;
5848        }
5849        Ok(act)
5850    }
5851
5852    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5853    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5854    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5855    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5856    #[allow(clippy::too_many_arguments)]
5857    pub fn moe_down8_fma_dev_q8_rows(
5858        &self,
5859        table: &CudaSlice<u64>,
5860        sel: &CudaSlice<i32>,
5861        w: &CudaSlice<f32>,
5862        aq2: &CudaSlice<i8>,
5863        ad2: &CudaSlice<f32>,
5864        dst: &mut CudaSlice<f32>,
5865        t: usize,
5866        in_f: usize,
5867        out_f: usize,
5868        n_used: usize,
5869        n_expert: usize,
5870        qt: i32,
5871        rb: usize,
5872    ) -> Result<(), Box<dyn std::error::Error>> {
5873        assert!(
5874            in_f == 512 && n_used <= 8,
5875            "down rows twin is w8h2v shape-gated"
5876        );
5877        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5878        let cfg = LaunchConfig {
5879            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5880            block_dim: (32, n_used as u32, 1),
5881            shared_mem_bytes: 0,
5882        };
5883        let (inf, outf, nu, ne, rbi) = (
5884            in_f as i32,
5885            out_f as i32,
5886            n_used as i32,
5887            n_expert as i32,
5888            rb as i64,
5889        );
5890        let __s_b = self.gpu.stream();
5891        let mut b = __s_b.launch_builder(&f);
5892        b.arg(table)
5893            .arg(sel)
5894            .arg(w)
5895            .arg(aq2)
5896            .arg(ad2)
5897            .arg(dst)
5898            .arg(&inf)
5899            .arg(&outf)
5900            .arg(&nu)
5901            .arg(&ne)
5902            .arg(&qt)
5903            .arg(&rbi);
5904        unsafe {
5905            b.launch(cfg)?;
5906        }
5907        Ok(())
5908    }
5909
5910    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5911    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5912    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5913    #[allow(clippy::too_many_arguments)]
5914    pub fn moe_gate_up_silu8_dev_q8_csr(
5915        &self,
5916        table: &CudaSlice<u64>,
5917        sel: &CudaSlice<i32>,
5918        aq: &CudaSlice<i8>,
5919        ad: &CudaSlice<f32>,
5920        n_pairs: usize,
5921        in_f: usize,
5922        n_ff: usize,
5923        n_used: usize,
5924        n_expert: usize,
5925        qt_g: i32,
5926        qt_u: i32,
5927        rb_g: usize,
5928        rb_u: usize,
5929    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5930        // NVFP4 experts take the NVFP4-specialized owner-scan twin (lane/moebatch-q35moe);
5931        // host gate guarantees qt_g == qt_u within a supported class.
5932        let f = if qt_g == crate::QT_NVFP4 {
5933            self.func("moe_gate_up_silu8_dev_q8_csr_nvfp4")
5934        } else {
5935            self.func("moe_gate_up_silu8_dev_q8_csr_iq4")
5936        };
5937        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5938        let cfg = LaunchConfig {
5939            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5940            block_dim: (32, 1, 1),
5941            shared_mem_bytes: 0,
5942        };
5943        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5944            in_f as i32,
5945            n_ff as i32,
5946            n_expert as i32,
5947            n_used as i32,
5948            n_pairs as i32,
5949            rb_g as i64,
5950            rb_u as i64,
5951        );
5952        let __s_b = self.gpu.stream();
5953        let mut b = __s_b.launch_builder(&f);
5954        b.arg(table)
5955            .arg(sel)
5956            .arg(aq)
5957            .arg(ad)
5958            .arg(&mut act)
5959            .arg(&inf)
5960            .arg(&nff)
5961            .arg(&ne)
5962            .arg(&qt_g)
5963            .arg(&qt_u)
5964            .arg(&rbg)
5965            .arg(&rbu)
5966            .arg(&nu)
5967            .arg(&npi);
5968        unsafe {
5969            b.launch(cfg)?;
5970        }
5971        Ok(act)
5972    }
5973
5974    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5975    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5976    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5977    #[allow(clippy::too_many_arguments)]
5978    pub fn moe_down8_fma_dev_q8_variant(
5979        &self,
5980        variant: &str,
5981        table: &CudaSlice<u64>,
5982        sel: &cudarc::driver::CudaView<i32>,
5983        w: &cudarc::driver::CudaView<f32>,
5984        aq2: &CudaSlice<i8>,
5985        ad2: &CudaSlice<f32>,
5986        dst: &mut cudarc::driver::CudaViewMut<f32>,
5987        in_f: usize,
5988        out_f: usize,
5989        n_used: usize,
5990        n_expert: usize,
5991        qt: i32,
5992        rb: usize,
5993    ) -> Result<(), Box<dyn std::error::Error>> {
5994        let (inf, outf, nu, ne, rbi) = (
5995            in_f as i32,
5996            out_f as i32,
5997            n_used as i32,
5998            n_expert as i32,
5999            rb as i64,
6000        );
6001        let (f, cfg) = match variant {
6002            "w8h2" | "w8h2v" => (
6003                self.func(if variant == "w8h2" {
6004                    "moe_down8_fma_dev_q8_w8h2"
6005                } else {
6006                    "moe_down8_fma_dev_q8_w8h2v"
6007                }),
6008                LaunchConfig {
6009                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6010                    block_dim: (32, n_used as u32, 1),
6011                    shared_mem_bytes: 0,
6012                },
6013            ),
6014            "w8h2r2" | "w8h2r2v" => (
6015                self.func(if variant == "w8h2r2" {
6016                    "moe_down8_fma_dev_q8_w8h2r2"
6017                } else {
6018                    "moe_down8_fma_dev_q8_w8h2r2v"
6019                }),
6020                LaunchConfig {
6021                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
6022                    block_dim: (32, n_used as u32, 1),
6023                    shared_mem_bytes: 0,
6024                },
6025            ),
6026            _ => (
6027                self.func("moe_down8_fma_dev_q8"),
6028                LaunchConfig {
6029                    grid_dim: (out_f as u32, 1, 1),
6030                    block_dim: (32, 1, 1),
6031                    shared_mem_bytes: 0,
6032                },
6033            ),
6034        };
6035        let __s_b = self.gpu.stream();
6036        let mut b = __s_b.launch_builder(&f);
6037        b.arg(table)
6038            .arg(sel)
6039            .arg(w)
6040            .arg(aq2)
6041            .arg(ad2)
6042            .arg(dst)
6043            .arg(&inf)
6044            .arg(&outf)
6045            .arg(&nu)
6046            .arg(&ne)
6047            .arg(&qt)
6048            .arg(&rbi);
6049        unsafe {
6050            b.launch(cfg)?;
6051        }
6052        Ok(())
6053    }
6054
6055    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
6056    #[allow(clippy::too_many_arguments)]
6057    pub fn moe_gate_up_silu8_dev_q8_variant(
6058        &self,
6059        variant: &str,
6060        table: &CudaSlice<u64>,
6061        sel: &cudarc::driver::CudaView<i32>,
6062        aq: &CudaSlice<i8>,
6063        ad: &CudaSlice<f32>,
6064        in_f: usize,
6065        n_ff: usize,
6066        n_used: usize,
6067        n_expert: usize,
6068        qt_g: i32,
6069        qt_u: i32,
6070        rb_g: usize,
6071        rb_u: usize,
6072    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6073        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
6074        let (inf, nff, ne, rbg, rbu) = (
6075            in_f as i32,
6076            n_ff as i32,
6077            n_expert as i32,
6078            rb_g as i64,
6079            rb_u as i64,
6080        );
6081        let f = self.func(if variant == "v" {
6082            "moe_gate_up_silu8_dev_q8_v"
6083        } else {
6084            "moe_gate_up_silu8_dev_q8"
6085        });
6086        let cfg = LaunchConfig {
6087            grid_dim: (n_ff as u32, n_used as u32, 1),
6088            block_dim: (32, 1, 1),
6089            shared_mem_bytes: 0,
6090        };
6091        let __s_b = self.gpu.stream();
6092        let mut b = __s_b.launch_builder(&f);
6093        b.arg(table)
6094            .arg(sel)
6095            .arg(aq)
6096            .arg(ad)
6097            .arg(&mut act)
6098            .arg(&inf)
6099            .arg(&nff)
6100            .arg(&ne)
6101            .arg(&qt_g)
6102            .arg(&qt_u)
6103            .arg(&rbg)
6104            .arg(&rbu);
6105        unsafe {
6106            b.launch(cfg)?;
6107        }
6108        Ok(act)
6109    }
6110
6111    pub fn moe_gate_up_silu8_dev(
6112        &self,
6113        table: &CudaSlice<u64>,
6114        sel: &cudarc::driver::CudaView<i32>,
6115        x: &cudarc::driver::CudaView<f32>,
6116        in_f: usize,
6117        n_ff: usize,
6118        n_used: usize,
6119        n_expert: usize,
6120        qt_g: i32,
6121        qt_u: i32,
6122        rb_g: usize,
6123        rb_u: usize,
6124        macros: &CudaSlice<f32>,
6125    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6126        let f = self.func("moe_gate_up_silu8_dev");
6127        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6128        let cfg = LaunchConfig {
6129            grid_dim: (n_ff as u32, n_used as u32, 1),
6130            block_dim: (256, 1, 1),
6131            shared_mem_bytes: 0,
6132        };
6133        let (inf, nff, ne, rbg, rbu) = (
6134            in_f as i32,
6135            n_ff as i32,
6136            n_expert as i32,
6137            rb_g as i64,
6138            rb_u as i64,
6139        );
6140        let __s_b = self.gpu.stream();
6141        let mut b = __s_b.launch_builder(&f);
6142        b.arg(table)
6143            .arg(sel)
6144            .arg(x)
6145            .arg(&mut act)
6146            .arg(&inf)
6147            .arg(&nff)
6148            .arg(&ne)
6149            .arg(&qt_g)
6150            .arg(&qt_u)
6151            .arg(&rbg)
6152            .arg(&rbu)
6153            .arg(macros);
6154        unsafe {
6155            b.launch(cfg)?;
6156        }
6157        Ok(act)
6158    }
6159
6160    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6161    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6162    #[allow(clippy::too_many_arguments)]
6163    pub fn moe_down8_fma_dev(
6164        &self,
6165        table: &CudaSlice<u64>,
6166        sel: &cudarc::driver::CudaView<i32>,
6167        w: &cudarc::driver::CudaView<f32>,
6168        act: &CudaSlice<f32>,
6169        dst: &mut cudarc::driver::CudaViewMut<f32>,
6170        in_f: usize,
6171        out_f: usize,
6172        n_used: usize,
6173        n_expert: usize,
6174        qt: i32,
6175        rb: usize,
6176    ) -> Result<(), Box<dyn std::error::Error>> {
6177        let f = self.func("moe_down8_fma_dev");
6178        let cfg = LaunchConfig {
6179            grid_dim: (out_f as u32, 1, 1),
6180            block_dim: (256, 1, 1),
6181            shared_mem_bytes: 0,
6182        };
6183        let (inf, outf, nu, ne, rbv) = (
6184            in_f as i32,
6185            out_f as i32,
6186            n_used as i32,
6187            n_expert as i32,
6188            rb as i64,
6189        );
6190        let __s_b = self.gpu.stream();
6191        let mut b = __s_b.launch_builder(&f);
6192        b.arg(table)
6193            .arg(sel)
6194            .arg(w)
6195            .arg(act)
6196            .arg(dst)
6197            .arg(&inf)
6198            .arg(&outf)
6199            .arg(&nu)
6200            .arg(&ne)
6201            .arg(&qt)
6202            .arg(&rbv);
6203        unsafe {
6204            b.launch(cfg)?;
6205        }
6206        Ok(())
6207    }
6208
6209    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6210    pub fn axpy_into(
6211        &self,
6212        src: &CudaSlice<f32>,
6213        alpha: f32,
6214        dst: &mut cudarc::driver::CudaViewMut<f32>,
6215        n: usize,
6216    ) -> Result<(), Box<dyn std::error::Error>> {
6217        let f = self.func("axpy_f32");
6218        let cfg = LaunchConfig::for_num_elems(n as u32);
6219        let (a, ni) = (alpha, n as i32);
6220        let __s_b = self.gpu.stream();
6221        let mut b = __s_b.launch_builder(&f);
6222        b.arg(src).arg(dst).arg(&a).arg(&ni);
6223        unsafe {
6224            b.launch(cfg)?;
6225        }
6226        Ok(())
6227    }
6228
6229    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6230    pub fn add_scaled_rows(
6231        &self,
6232        src: &CudaSlice<f32>,
6233        scale: &CudaSlice<f32>,
6234        dst: &mut CudaSlice<f32>,
6235        ncols: usize,
6236        nrows: usize,
6237    ) -> Result<(), Box<dyn std::error::Error>> {
6238        let f = self.func("add_scaled_rows_f32");
6239        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6240        let (nc, nr) = (ncols as i32, nrows as i32);
6241        let __s_b = self.gpu.stream();
6242        let mut b = __s_b.launch_builder(&f);
6243        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6244        unsafe {
6245            b.launch(cfg)?;
6246        }
6247        Ok(())
6248    }
6249
6250    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6251
6252    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6253    pub fn gather_rows(
6254        &self,
6255        src: &CudaSlice<f32>,
6256        idx: &CudaSlice<i32>,
6257        dst: &mut CudaSlice<f32>,
6258        ncols: usize,
6259        m_e: usize,
6260    ) -> Result<(), Box<dyn std::error::Error>> {
6261        let f = self.func("gather_rows_f32");
6262        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6263        let (nc, me) = (ncols as i32, m_e as i32);
6264        let __s_b = self.gpu.stream();
6265        let mut b = __s_b.launch_builder(&f);
6266        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6267        unsafe {
6268            b.launch(cfg)?;
6269        }
6270        Ok(())
6271    }
6272
6273    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6274    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6275    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6276    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6277    pub fn scatter_slot(
6278        &self,
6279        src: &CudaSlice<f32>,
6280        tok_idx: &CudaSlice<i32>,
6281        slot_idx: &CudaSlice<i32>,
6282        weight: &CudaSlice<f32>,
6283        dst: &mut CudaSlice<f32>,
6284        wbuf: &mut CudaSlice<f32>,
6285        ncols: usize,
6286        n_used: usize,
6287        m_e: usize,
6288    ) -> Result<(), Box<dyn std::error::Error>> {
6289        let f = self.func("scatter_add_slot_f32");
6290        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6291        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6292        let __s_b = self.gpu.stream();
6293        let mut b = __s_b.launch_builder(&f);
6294        b.arg(src)
6295            .arg(tok_idx)
6296            .arg(slot_idx)
6297            .arg(weight)
6298            .arg(dst)
6299            .arg(wbuf)
6300            .arg(&nc)
6301            .arg(&nu)
6302            .arg(&me);
6303        unsafe {
6304            b.launch(cfg)?;
6305        }
6306        Ok(())
6307    }
6308
6309    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6310    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6311    /// Uses FMA for bit-identity with the sequential axpy path.
6312    pub fn reduce_slots(
6313        &self,
6314        slots: &CudaSlice<f32>,
6315        wbuf: &CudaSlice<f32>,
6316        dst: &mut CudaSlice<f32>,
6317        ncols: usize,
6318        n_used: usize,
6319        t: usize,
6320    ) -> Result<(), Box<dyn std::error::Error>> {
6321        let f = self.func("reduce_slots_f32");
6322        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6323        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6324        let __s_b = self.gpu.stream();
6325        let mut b = __s_b.launch_builder(&f);
6326        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6327        unsafe {
6328            b.launch(cfg)?;
6329        }
6330        Ok(())
6331    }
6332
6333    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6334    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6335    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6336    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6337    /// GPU time, ~half of it redundant re-quantization of the same row.
6338    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6339    pub fn quantize_q8_1_view(
6340        &self,
6341        x: &cudarc::driver::CudaView<f32>,
6342        m: usize,
6343        in_f: usize,
6344    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6345        let f = self.func("quantize_q8_1");
6346        let nblk = in_f / 32;
6347        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6348        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6349        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6350        let (inf, mi) = (in_f as i32, m as i32);
6351        let __s_b = self.gpu.stream();
6352        let mut b = __s_b.launch_builder(&f);
6353        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6354        unsafe {
6355            b.launch(cfg)?;
6356        }
6357        Ok((q, d))
6358    }
6359
6360    pub fn quantize_q8_1(
6361        &self,
6362        x: &CudaSlice<f32>,
6363        m: usize,
6364        in_f: usize,
6365    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6366        let nblk = in_f / 32;
6367        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6368        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6369        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6370        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6371        let (inf, mi) = (in_f as i32, m as i32);
6372        if Self::pdl_on() && Self::pdl_wb_on() {
6373            {
6374                use cudarc::driver::{DevicePtr, DevicePtrMut};
6375                let s = &self.gpu.stream();
6376                let (px, _g0) = x.device_ptr(s);
6377                let (pq, _g1) = q.device_ptr_mut(s);
6378                let (pd, _g2) = d.device_ptr_mut(s);
6379                let mut ps = [
6380                    &px as *const _ as *mut std::ffi::c_void,
6381                    &pq as *const _ as *mut _,
6382                    &pd as *const _ as *mut _,
6383                    &inf as *const _ as *mut _,
6384                    &mi as *const _ as *mut _,
6385                ];
6386                unsafe {
6387                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6388                }
6389            }
6390            return Ok((q, d));
6391        }
6392        let f = self.func("quantize_q8_1");
6393        let __s_b = self.gpu.stream();
6394        let mut b = __s_b.launch_builder(&f);
6395        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6396        unsafe {
6397            b.launch(cfg)?;
6398        }
6399        Ok((q, d))
6400    }
6401
6402    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6403    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6404    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6405    pub fn quantize_fp4_act(
6406        &self,
6407        x: &CudaSlice<f32>,
6408        m: usize,
6409        in_f: usize,
6410    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6411        let f = self.func("quantize_fp4_act");
6412        let nb16 = in_f / 16;
6413        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6414        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6415        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6416        let (inf, mi) = (in_f as i32, m as i32);
6417        let __s_b = self.gpu.stream();
6418        let mut b = __s_b.launch_builder(&f);
6419        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6420        unsafe {
6421            b.launch(cfg)?;
6422        }
6423        Ok((aq4, ad4))
6424    }
6425
6426    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6427    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6428    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6429    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6430    pub fn qmatvec_gemm_nvfp4_fp4(
6431        &self,
6432        bytes: &CudaSlice<u8>,
6433        x: &CudaSlice<f32>,
6434        m: usize,
6435        in_f: usize,
6436        out_f: usize,
6437        row_bytes: usize,
6438        scale: f32,
6439    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6440        assert!(
6441            in_f % 64 == 0,
6442            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6443        );
6444        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6445        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6446        if scale != 1.0 {
6447            self.scale_inplace(&mut y, scale, m * out_f)?;
6448        }
6449        Ok(y)
6450    }
6451
6452    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6453    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6454    fn fp4_gemm_launch(
6455        &self,
6456        bytes: &CudaSlice<u8>,
6457        aq4: &CudaSlice<u32>,
6458        ad4: &CudaSlice<u8>,
6459        m: usize,
6460        in_f: usize,
6461        out_f: usize,
6462        row_bytes: usize,
6463    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6464        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6465        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6466        const BM: u32 = 64;
6467        const BN: u32 = 256;
6468        let cfg = LaunchConfig {
6469            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6470            block_dim: (32, 4, 1),
6471            shared_mem_bytes: 0,
6472        };
6473        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6474        let __s_b = self.gpu.stream();
6475        let mut b = __s_b.launch_builder(&f);
6476        b.arg(bytes)
6477            .arg(aq4)
6478            .arg(ad4)
6479            .arg(&mut y)
6480            .arg(&inf)
6481            .arg(&outf)
6482            .arg(&mi)
6483            .arg(&rb);
6484        unsafe {
6485            b.launch(cfg)?;
6486        }
6487        Ok(y)
6488    }
6489
6490    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6491    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6492        &self,
6493        bytes: &CudaSlice<u8>,
6494        x: &CudaSlice<f32>,
6495        m: usize,
6496        in_f: usize,
6497        out_f: usize,
6498        row_bytes: usize,
6499    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6500        assert!(
6501            in_f % 64 == 0,
6502            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6503        );
6504        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6505        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6506    }
6507
6508    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6509    pub fn qmatvec_q8_0_fast(
6510        &self,
6511        w: &CudaSlice<u8>,
6512        x: &CudaSlice<f32>,
6513        m: usize,
6514        in_f: usize,
6515        out_f: usize,
6516        row_bytes: usize,
6517    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6518        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6519        let f = self.func("qmatvec_q8_0_dp4a");
6520        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6521        let cfg = LaunchConfig {
6522            grid_dim: (out_f as u32, m as u32, 1),
6523            block_dim: (128, 1, 1),
6524            shared_mem_bytes: 0,
6525        };
6526        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6527        let __s_b = self.gpu.stream();
6528        let mut b = __s_b.launch_builder(&f);
6529        b.arg(w)
6530            .arg(&aq)
6531            .arg(&ad)
6532            .arg(&mut y)
6533            .arg(&inf)
6534            .arg(&outf)
6535            .arg(&mi)
6536            .arg(&rb);
6537        unsafe {
6538            b.launch(cfg)?;
6539        }
6540        Ok(y)
6541    }
6542
6543    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6544    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6545    pub fn qmatvec_q4_K_fast(
6546        &self,
6547        w: &CudaSlice<u8>,
6548        x: &CudaSlice<f32>,
6549        m: usize,
6550        in_f: usize,
6551        out_f: usize,
6552        row_bytes: usize,
6553    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6554        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6555        let f = self.func("qmatvec_q4_K_dp4a");
6556        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6557        let cfg = LaunchConfig {
6558            grid_dim: (out_f as u32, m as u32, 1),
6559            block_dim: (128, 1, 1),
6560            shared_mem_bytes: 0,
6561        };
6562        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6563        let __s_b = self.gpu.stream();
6564        let mut b = __s_b.launch_builder(&f);
6565        b.arg(w)
6566            .arg(&aq)
6567            .arg(&ad)
6568            .arg(&mut y)
6569            .arg(&inf)
6570            .arg(&outf)
6571            .arg(&mi)
6572            .arg(&rb);
6573        unsafe {
6574            b.launch(cfg)?;
6575        }
6576        Ok(y)
6577    }
6578
6579    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6580    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6581    pub fn qmatvec_q6_K_fast(
6582        &self,
6583        w: &CudaSlice<u8>,
6584        x: &CudaSlice<f32>,
6585        m: usize,
6586        in_f: usize,
6587        out_f: usize,
6588        row_bytes: usize,
6589    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6590        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6591        let f = self.func("qmatvec_q6_K_dp4a");
6592        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6593        let cfg = LaunchConfig {
6594            grid_dim: (out_f as u32, m as u32, 1),
6595            block_dim: (128, 1, 1),
6596            shared_mem_bytes: 0,
6597        };
6598        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6599        let __s_b = self.gpu.stream();
6600        let mut b = __s_b.launch_builder(&f);
6601        b.arg(w)
6602            .arg(&aq)
6603            .arg(&ad)
6604            .arg(&mut y)
6605            .arg(&inf)
6606            .arg(&outf)
6607            .arg(&mi)
6608            .arg(&rb);
6609        unsafe {
6610            b.launch(cfg)?;
6611        }
6612        Ok(y)
6613    }
6614
6615    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6616    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6617    pub fn qmatvec_q5_K_fast(
6618        &self,
6619        w: &CudaSlice<u8>,
6620        x: &CudaSlice<f32>,
6621        m: usize,
6622        in_f: usize,
6623        out_f: usize,
6624        row_bytes: usize,
6625    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6626        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6627    }
6628    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6629    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6630    pub fn qmatvec_q3_K_fast(
6631        &self,
6632        w: &CudaSlice<u8>,
6633        x: &CudaSlice<f32>,
6634        m: usize,
6635        in_f: usize,
6636        out_f: usize,
6637        row_bytes: usize,
6638    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6639        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6640    }
6641    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6642    pub fn qmatvec_nvfp4_fast_rp(
6643        &self,
6644        w: &CudaSlice<u8>,
6645        x: &CudaSlice<f32>,
6646        m: usize,
6647        in_f: usize,
6648        out_f: usize,
6649        row_bytes: usize,
6650    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6651        assert!(
6652            in_f % 64 == 0,
6653            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6654        );
6655        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6656    }
6657    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6658    pub fn qmatvec_nvfp4_fast(
6659        &self,
6660        w: &CudaSlice<u8>,
6661        x: &CudaSlice<f32>,
6662        m: usize,
6663        in_f: usize,
6664        out_f: usize,
6665        row_bytes: usize,
6666    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6667        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6668        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6669        assert!(
6670            in_f % 64 == 0,
6671            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6672        );
6673        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6674    }
6675    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6676    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6677    pub fn qmatvec_iq4_XS_fast(
6678        &self,
6679        w: &CudaSlice<u8>,
6680        x: &CudaSlice<f32>,
6681        m: usize,
6682        in_f: usize,
6683        out_f: usize,
6684        row_bytes: usize,
6685    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6686        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6687    }
6688
6689    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6690    fn qmatvec_dp4a_named(
6691        &self,
6692        name: &str,
6693        w: &CudaSlice<u8>,
6694        x: &CudaSlice<f32>,
6695        m: usize,
6696        in_f: usize,
6697        out_f: usize,
6698        row_bytes: usize,
6699    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6700        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6701        let f = self.func(name);
6702        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6703        let cfg = LaunchConfig {
6704            grid_dim: (out_f as u32, m as u32, 1),
6705            block_dim: (128, 1, 1),
6706            shared_mem_bytes: 0,
6707        };
6708        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6709        let __s_b = self.gpu.stream();
6710        let mut b = __s_b.launch_builder(&f);
6711        b.arg(w)
6712            .arg(&aq)
6713            .arg(&ad)
6714            .arg(&mut y)
6715            .arg(&inf)
6716            .arg(&outf)
6717            .arg(&mi)
6718            .arg(&rb);
6719        unsafe {
6720            b.launch(cfg)?;
6721        }
6722        Ok(y)
6723    }
6724
6725    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6726        Ok(self.gpu.stream().clone_htod(v)?)
6727    }
6728    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6729        Ok(self.gpu.stream().clone_htod(v)?)
6730    }
6731    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6732    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6733        Ok(self.gpu.stream().clone_htod(v)?)
6734    }
6735    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6736        Ok(self.gpu.stream().clone_htod(v)?)
6737    }
6738    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6739    pub fn dtoh_view(
6740        &self,
6741        d: &cudarc::driver::CudaView<f32>,
6742    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6743        let v = self.gpu.stream().clone_dtoh(d)?;
6744        self.gpu.stream().synchronize()?;
6745        Ok(v)
6746    }
6747    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6748        let v = self.gpu.stream().clone_dtoh(d)?;
6749        self.gpu.stream().synchronize()?;
6750        Ok(v)
6751    }
6752    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6753    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6754    /// issuing them together avoids a second stream synchronization in every trunk layer.
6755    pub fn dtoh_pair(
6756        &self,
6757        a: &CudaSlice<f32>,
6758        b: &CudaSlice<f32>,
6759    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6760        let av = self.gpu.stream().clone_dtoh(a)?;
6761        let bv = self.gpu.stream().clone_dtoh(b)?;
6762        self.gpu.stream().synchronize()?;
6763        Ok((av, bv))
6764    }
6765    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6766    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6767        let v = self.gpu.stream().clone_dtoh(d)?;
6768        self.gpu.stream().synchronize()?;
6769        Ok(v)
6770    }
6771    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6772    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6773        let v = self.gpu.stream().clone_dtoh(d)?;
6774        self.gpu.stream().synchronize()?;
6775        Ok(v)
6776    }
6777    pub fn dtoh_u8_view(
6778        &self,
6779        d: &cudarc::driver::CudaView<u8>,
6780    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6781        let v = self.gpu.stream().clone_dtoh(d)?;
6782        self.gpu.stream().synchronize()?;
6783        Ok(v)
6784    }
6785    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6786        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6787        self.keep_if_capturing(&s);
6788        Ok(s)
6789    }
6790
6791    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6792    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6793    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6794    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6795    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6796    /// back (or kept resident for graph replay). Returns the device token buffer.
6797    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6798    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6799    pub fn prob_of_token_device(
6800        &self,
6801        logits: &CudaSlice<f32>,
6802        tok: &CudaSlice<u32>,
6803        n_vocab: usize,
6804    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6805        let nb = ARGMAX_NB;
6806        let mut part = self.alloc_uninit::<f32>(nb)?;
6807        let mut p = self.alloc_uninit::<f32>(1)?;
6808        let f1 = self.func("prob_of_token_partial_f32");
6809        let cfg1 = LaunchConfig {
6810            grid_dim: (nb as u32, 1, 1),
6811            block_dim: (256, 1, 1),
6812            shared_mem_bytes: 0,
6813        };
6814        let nv = n_vocab as i32;
6815        let __s_b1 = self.gpu.stream();
6816        let mut b1 = __s_b1.launch_builder(&f1);
6817        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6818        unsafe {
6819            b1.launch(cfg1)?;
6820        }
6821        let f2 = self.func("prob_of_token_final_f32");
6822        let cfg2 = LaunchConfig {
6823            grid_dim: (1, 1, 1),
6824            block_dim: (256, 1, 1),
6825            shared_mem_bytes: 0,
6826        };
6827        let nbi = nb as i32;
6828        let __s_b2 = self.gpu.stream();
6829        let mut b2 = __s_b2.launch_builder(&f2);
6830        b2.arg(&part).arg(&mut p).arg(&nbi);
6831        unsafe {
6832            b2.launch(cfg2)?;
6833        }
6834        Ok(p)
6835    }
6836
6837    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6838    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6839    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6840    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6841    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6842    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6843    pub fn prob_of_token_device_col(
6844        &self,
6845        logits: &CudaSlice<f32>,
6846        tok_all: &CudaSlice<u32>,
6847        tok_idx: usize,
6848        p_out: &mut CudaSlice<f32>,
6849        p_idx: usize,
6850        n_vocab: usize,
6851    ) -> Result<(), Box<dyn std::error::Error>> {
6852        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6853        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6854        let nb = ARGMAX_NB;
6855        let mut part = self.alloc_uninit::<f32>(nb)?;
6856        let f1 = self.func("prob_of_token_partial_f32");
6857        let cfg1 = LaunchConfig {
6858            grid_dim: (nb as u32, 1, 1),
6859            block_dim: (256, 1, 1),
6860            shared_mem_bytes: 0,
6861        };
6862        let nv = n_vocab as i32;
6863        let __s_b1 = self.gpu.stream();
6864        let mut b1 = __s_b1.launch_builder(&f1);
6865        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6866        unsafe {
6867            b1.launch(cfg1)?;
6868        }
6869        let f2 = self.func("prob_of_token_final_f32");
6870        let cfg2 = LaunchConfig {
6871            grid_dim: (1, 1, 1),
6872            block_dim: (256, 1, 1),
6873            shared_mem_bytes: 0,
6874        };
6875        let nbi = nb as i32;
6876        let __s_b2 = self.gpu.stream();
6877        let mut b2 = __s_b2.launch_builder(&f2);
6878        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6879        unsafe {
6880            b2.launch(cfg2)?;
6881        }
6882        Ok(())
6883    }
6884
6885    pub fn prob_of_token_device_into(
6886        &self,
6887        logits: &CudaSlice<f32>,
6888        tok: &CudaSlice<u32>,
6889        p_out: &mut CudaSlice<f32>,
6890        n_vocab: usize,
6891    ) -> Result<(), Box<dyn std::error::Error>> {
6892        let nb = ARGMAX_NB;
6893        let mut part = self.alloc_uninit::<f32>(nb)?;
6894        let f1 = self.func("prob_of_token_partial_f32");
6895        let cfg1 = LaunchConfig {
6896            grid_dim: (nb as u32, 1, 1),
6897            block_dim: (256, 1, 1),
6898            shared_mem_bytes: 0,
6899        };
6900        let nv = n_vocab as i32;
6901        let __s_b1 = self.gpu.stream();
6902        let mut b1 = __s_b1.launch_builder(&f1);
6903        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6904        unsafe {
6905            b1.launch(cfg1)?;
6906        }
6907        let f2 = self.func("prob_of_token_final_f32");
6908        let cfg2 = LaunchConfig {
6909            grid_dim: (1, 1, 1),
6910            block_dim: (256, 1, 1),
6911            shared_mem_bytes: 0,
6912        };
6913        let nbi = nb as i32;
6914        let __s_b2 = self.gpu.stream();
6915        let mut b2 = __s_b2.launch_builder(&f2);
6916        b2.arg(&part).arg(p_out).arg(&nbi);
6917        unsafe {
6918            b2.launch(cfg2)?;
6919        }
6920        Ok(())
6921    }
6922
6923    pub fn argmax_token_device(
6924        &self,
6925        logits: &CudaSlice<f32>,
6926        n_vocab: usize,
6927    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6928        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6929        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6930        Ok(tok)
6931    }
6932    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6933    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6934    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6935    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6936    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6937    /// captured passes bake fixed addresses.
6938    pub fn argmax_token_device_into(
6939        &self,
6940        logits: &CudaSlice<f32>,
6941        tok: &mut CudaSlice<u32>,
6942        n_vocab: usize,
6943    ) -> Result<(), Box<dyn std::error::Error>> {
6944        let nb = ARGMAX_NB;
6945        let f1 = self.func("argmax_partial_f32");
6946        let f2 = self.func("argmax_final_f32");
6947        let mut guard = self.argmax_partials.lock().unwrap();
6948        if guard.is_none() {
6949            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6950            // buffers carry no cudarc events (illegal inside capture).
6951            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6952            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6953            *guard = Some((pv, pi));
6954        }
6955        let (part_v, part_i) = guard.as_mut().unwrap();
6956        let nv = n_vocab as i32;
6957        let nbi = nb as i32;
6958        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6959        let cfg1 = LaunchConfig {
6960            grid_dim: (nb as u32, 1, 1),
6961            block_dim: (256, 1, 1),
6962            shared_mem_bytes: 0,
6963        };
6964        let __s_b1 = self.gpu.stream();
6965        let mut b1 = __s_b1.launch_builder(&f1);
6966        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6967        unsafe {
6968            b1.launch(cfg1)?;
6969        }
6970        // pass 2: one block reduces NB partials -> token_out[0].
6971        let cfg2 = LaunchConfig {
6972            grid_dim: (1, 1, 1),
6973            block_dim: (256, 1, 1),
6974            shared_mem_bytes: 0,
6975        };
6976        let __s_b2 = self.gpu.stream();
6977        let mut b2 = __s_b2.launch_builder(&f2);
6978        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6979        unsafe {
6980            b2.launch(cfg2)?;
6981        }
6982        Ok(())
6983    }
6984    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6985    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6986    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6987    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6988    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6989    pub fn argmax_token_device_col(
6990        &self,
6991        logits: &CudaSlice<f32>,
6992        col: usize,
6993        n_vocab: usize,
6994        toks: &mut CudaSlice<u32>,
6995        out_idx: usize,
6996    ) -> Result<(), Box<dyn std::error::Error>> {
6997        let nb = ARGMAX_NB;
6998        let f1 = self.func("argmax_partial_f32");
6999        let f2 = self.func("argmax_final_f32");
7000        let mut guard = self.argmax_partials.lock().unwrap();
7001        if guard.is_none() {
7002            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
7003            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
7004            *guard = Some((pv, pi));
7005        }
7006        let (part_v, part_i) = guard.as_mut().unwrap();
7007        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
7008        let nv = n_vocab as i32;
7009        let nbi = nb as i32;
7010        let cfg1 = LaunchConfig {
7011            grid_dim: (nb as u32, 1, 1),
7012            block_dim: (256, 1, 1),
7013            shared_mem_bytes: 0,
7014        };
7015        let __s_b1 = self.gpu.stream();
7016        let mut b1 = __s_b1.launch_builder(&f1);
7017        b1.arg(&col_view)
7018            .arg(&mut *part_v)
7019            .arg(&mut *part_i)
7020            .arg(&nv);
7021        unsafe {
7022            b1.launch(cfg1)?;
7023        }
7024        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
7025        let cfg2 = LaunchConfig {
7026            grid_dim: (1, 1, 1),
7027            block_dim: (256, 1, 1),
7028            shared_mem_bytes: 0,
7029        };
7030        let __s_b2 = self.gpu.stream();
7031        let mut b2 = __s_b2.launch_builder(&f2);
7032        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
7033        unsafe {
7034            b2.launch(cfg2)?;
7035        }
7036        Ok(())
7037    }
7038    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
7039    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
7040        Ok(self.gpu.stream().clone_htod(v)?)
7041    }
7042    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
7043        let v = self.gpu.stream().clone_dtoh(d)?;
7044        self.gpu.stream().synchronize()?;
7045        Ok(v)
7046    }
7047    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
7048    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
7049    /// contents change every step, the address must not, so a captured graph can read it).
7050    pub fn htod_u32_into(
7051        &self,
7052        dst: &mut CudaSlice<u32>,
7053        src: &[u32],
7054    ) -> Result<(), Box<dyn std::error::Error>> {
7055        let mut view = dst.slice_mut(0..src.len());
7056        self.gpu.stream().memcpy_htod(src, &mut view)?;
7057        Ok(())
7058    }
7059
7060    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
7061    /// table without changing the device address its reconcile kernel consumes.
7062    pub fn htod_i32_into(
7063        &self,
7064        dst: &mut CudaSlice<i32>,
7065        src: &[i32],
7066    ) -> Result<(), Box<dyn std::error::Error>> {
7067        let mut view = dst.slice_mut(0..src.len());
7068        self.gpu.stream().memcpy_htod(src, &mut view)?;
7069        Ok(())
7070    }
7071
7072    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
7073        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
7074        self.keep_if_capturing(&s);
7075        Ok(s)
7076    }
7077    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
7078    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
7079    pub fn embed_gather_device_into(
7080        &self,
7081        embd: &CudaSlice<u8>,
7082        token_d: &CudaSlice<u32>,
7083        x_out: &mut CudaSlice<f32>,
7084        n_embd: usize,
7085        qtype: i32,
7086        row_bytes: usize,
7087    ) -> Result<(), Box<dyn std::error::Error>> {
7088        let f = self.func("embed_gather_u32");
7089        let cfg = LaunchConfig {
7090            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7091            block_dim: (256, 1, 1),
7092            shared_mem_bytes: 0,
7093        };
7094        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7095        let __s_b = self.gpu.stream();
7096        let mut b = __s_b.launch_builder(&f);
7097        b.arg(embd)
7098            .arg(token_d)
7099            .arg(x_out)
7100            .arg(&ne)
7101            .arg(&qt)
7102            .arg(&rb);
7103        unsafe {
7104            b.launch(cfg)?;
7105        }
7106        Ok(())
7107    }
7108    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
7109    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
7110        let v = self.gpu.stream().clone_dtoh(d)?;
7111        self.gpu.stream().synchronize()?;
7112        Ok(v[0])
7113    }
7114    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
7115    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
7116    /// the counter value after the throwaway capture warmups corrupt it.
7117    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
7118    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
7119    /// copy (fine at stream-idle boundaries, poison mid-round).
7120    pub fn i32_set_k(
7121        &self,
7122        dst: &mut CudaSlice<i32>,
7123        v: i32,
7124    ) -> Result<(), Box<dyn std::error::Error>> {
7125        let f = self.func("i32_set_k");
7126        let cfg = LaunchConfig {
7127            grid_dim: (1, 1, 1),
7128            block_dim: (1, 1, 1),
7129            shared_mem_bytes: 0,
7130        };
7131        let idx = 0i32;
7132        let __s_b = self.gpu.stream();
7133        let mut b = __s_b.launch_builder(&f);
7134        b.arg(dst).arg(&v).arg(&idx);
7135        unsafe {
7136            b.launch(cfg)?;
7137        }
7138        Ok(())
7139    }
7140
7141    pub fn set_i32_one(
7142        &self,
7143        d: &mut CudaSlice<i32>,
7144        v: i32,
7145    ) -> Result<(), Box<dyn std::error::Error>> {
7146        self.gpu.stream().memcpy_htod(&[v], d)?;
7147        Ok(())
7148    }
7149    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
7150    /// during priming / capture-state restore.
7151    pub fn set_u32_one(
7152        &self,
7153        d: &mut CudaSlice<u32>,
7154        v: u32,
7155    ) -> Result<(), Box<dyn std::error::Error>> {
7156        self.gpu.stream().memcpy_htod(&[v], d)?;
7157        Ok(())
7158    }
7159    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
7160    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
7161        let v = self.gpu.stream().clone_dtoh(d)?;
7162        self.gpu.stream().synchronize()?;
7163        Ok(v[0])
7164    }
7165    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
7166    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
7167        Ok(self.gpu.stream().clone_htod(bytes)?)
7168    }
7169    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7170    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7171    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7172    pub fn embed_gather_device(
7173        &self,
7174        embd: &CudaSlice<u8>,
7175        token_d: &CudaSlice<u32>,
7176        n_embd: usize,
7177        qtype: i32,
7178        row_bytes: usize,
7179    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7180        let f = self.func("embed_gather_u32");
7181        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7182        let cfg = LaunchConfig {
7183            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7184            block_dim: (256, 1, 1),
7185            shared_mem_bytes: 0,
7186        };
7187        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7188        let __s_b = self.gpu.stream();
7189        let mut b = __s_b.launch_builder(&f);
7190        b.arg(embd)
7191            .arg(token_d)
7192            .arg(&mut x)
7193            .arg(&ne)
7194            .arg(&qt)
7195            .arg(&rb);
7196        unsafe {
7197            b.launch(cfg)?;
7198        }
7199        Ok(x)
7200    }
7201
7202    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7203    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7204    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7205    pub fn embed_gather_device_t(
7206        &self,
7207        embd: &CudaSlice<u8>,
7208        tokens: &[u32],
7209        n_embd: usize,
7210        qtype: i32,
7211        row_bytes: usize,
7212    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7213        let t = tokens.len();
7214        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7215        let f = self.func("embed_gather_u32_t");
7216        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7217        let cfg = LaunchConfig {
7218            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7219            block_dim: (256, 1, 1),
7220            shared_mem_bytes: 0,
7221        };
7222        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7223        let __s_b = self.gpu.stream();
7224        let mut b = __s_b.launch_builder(&f);
7225        b.arg(embd)
7226            .arg(&tok_d)
7227            .arg(&mut x)
7228            .arg(&ne)
7229            .arg(&qt)
7230            .arg(&rb)
7231            .arg(&ti);
7232        unsafe {
7233            b.launch(cfg)?;
7234        }
7235        Ok(x)
7236    }
7237
7238    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7239    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7240    /// as embed_gather_device_t — bit-identical rows.
7241    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7242    pub fn embed_gather_device_tv(
7243        &self,
7244        embd: &CudaSlice<u8>,
7245        tok_v: &cudarc::driver::CudaView<u32>,
7246        t: usize,
7247        n_embd: usize,
7248        qtype: i32,
7249        row_bytes: usize,
7250    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7251        let f = self.func("embed_gather_u32_t");
7252        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7253        let cfg = LaunchConfig {
7254            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7255            block_dim: (256, 1, 1),
7256            shared_mem_bytes: 0,
7257        };
7258        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7259        let __s_b = self.gpu.stream();
7260        let mut b = __s_b.launch_builder(&f);
7261        b.arg(embd)
7262            .arg(tok_v)
7263            .arg(&mut x)
7264            .arg(&ne)
7265            .arg(&qt)
7266            .arg(&rb)
7267            .arg(&ti);
7268        unsafe {
7269            b.launch(cfg)?;
7270        }
7271        Ok(x)
7272    }
7273
7274    pub fn embed_gather_device_td(
7275        &self,
7276        embd: &CudaSlice<u8>,
7277        tok_d: &CudaSlice<u32>,
7278        t: usize,
7279        n_embd: usize,
7280        qtype: i32,
7281        row_bytes: usize,
7282    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7283        let f = self.func("embed_gather_u32_t");
7284        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7285        let cfg = LaunchConfig {
7286            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7287            block_dim: (256, 1, 1),
7288            shared_mem_bytes: 0,
7289        };
7290        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7291        let __s_b = self.gpu.stream();
7292        let mut b = __s_b.launch_builder(&f);
7293        b.arg(embd)
7294            .arg(tok_d)
7295            .arg(&mut x)
7296            .arg(&ne)
7297            .arg(&qt)
7298            .arg(&rb)
7299            .arg(&ti);
7300        unsafe {
7301            b.launch(cfg)?;
7302        }
7303        Ok(x)
7304    }
7305
7306    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7307    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7308    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7309    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7310    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7311    #[inline]
7312    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7313    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7314        if self
7315            .capture_keep_on
7316            .load(std::sync::atomic::Ordering::Relaxed)
7317        {
7318            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7319        }
7320    }
7321
7322    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7323        &self,
7324        n: usize,
7325    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7326        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7327        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7328        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7329        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7330        {
7331            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7332            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7333                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7334                use cudarc::driver::DevicePtrMut;
7335                let n_bytes = s.len() * std::mem::size_of::<T>();
7336                let stream = self.gpu.stream();
7337                let (p_, _g) = s.device_ptr_mut(&stream);
7338                unsafe {
7339                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7340                        .result()?;
7341                }
7342            }
7343        }
7344        self.keep_if_capturing(&s);
7345        Ok(s)
7346    }
7347
7348    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7349    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7350    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7351    /// consumers alloc through this (m=1 decode arms).
7352    pub fn uninit_q8_pair(
7353        &self,
7354        n: usize,
7355    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7356        Ok((
7357            self.alloc_uninit::<i8>(n)?,
7358            self.alloc_uninit::<f32>(n / 32)?,
7359        ))
7360    }
7361
7362    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7363        self.alloc_uninit::<f32>(n)
7364    }
7365
7366    /// i8 uninitialized scratch (same contract as `uninit`).
7367    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7368        self.alloc_uninit::<i8>(n)
7369    }
7370
7371    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7372    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7373    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7374    #[allow(clippy::too_many_arguments)]
7375    pub fn rms_norm3(
7376        &self,
7377        x: &CudaSlice<f32>,
7378        w0: &CudaSlice<f32>,
7379        w1: &CudaSlice<f32>,
7380        w2: &CudaSlice<f32>,
7381        d0: &mut CudaSlice<f32>,
7382        d1: &mut CudaSlice<f32>,
7383        d2: &mut CudaSlice<f32>,
7384        ncols: usize,
7385        nrows: usize,
7386        eps: f32,
7387    ) -> Result<(), Box<dyn std::error::Error>> {
7388        let f = self.func("rms_norm3_f32");
7389        let cfg = LaunchConfig {
7390            grid_dim: (nrows as u32, 1, 1),
7391            block_dim: (rms_block(), 1, 1),
7392            shared_mem_bytes: 0,
7393        };
7394        let (nc, e) = (ncols as i32, eps);
7395        let __s_b = self.gpu.stream();
7396        let mut b = __s_b.launch_builder(&f);
7397        b.arg(x)
7398            .arg(w0)
7399            .arg(w1)
7400            .arg(w2)
7401            .arg(d0)
7402            .arg(d1)
7403            .arg(d2)
7404            .arg(&nc)
7405            .arg(&e);
7406        unsafe {
7407            b.launch(cfg)?;
7408        }
7409        Ok(())
7410    }
7411
7412    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7413    #[allow(clippy::too_many_arguments)]
7414    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7415    /// piggybacks on the same conditions.
7416    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7417        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7418        *WARP_ON.get_or_init(|| {
7419            std::env::var("MEMRA_QKVNORM_W")
7420                .map(|v| v != "0")
7421                .unwrap_or(true)
7422        }) && ncols % 4 == 0
7423            && rows >= 64
7424    }
7425
7426    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7427    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7428    #[allow(clippy::too_many_arguments)]
7429    pub fn rms_norm_qkv_w4b(
7430        &self,
7431        q: &CudaSlice<f32>,
7432        k: &CudaSlice<f32>,
7433        v: &CudaSlice<f32>,
7434        wq: &CudaSlice<f32>,
7435        wk: &CudaSlice<f32>,
7436        wv: &CudaSlice<f32>,
7437        dq: &mut CudaSlice<f32>,
7438        dk: &mut CudaSlice<f32>,
7439        dv: &mut CudaSlice<f32>,
7440        dvb: &mut CudaSlice<u8>,
7441        ncols: usize,
7442        rq: usize,
7443        rk: usize,
7444        eps: f32,
7445        vf16: bool,
7446    ) -> Result<(), Box<dyn std::error::Error>> {
7447        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7448        let f = self.func("rms_norm_qkv_w4b_f32");
7449        let rows = (rq + 2 * rk) as u32;
7450        let cfg = LaunchConfig {
7451            grid_dim: (rows.div_ceil(8), 1, 1),
7452            block_dim: (256, 1, 1),
7453            shared_mem_bytes: 0,
7454        };
7455        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7456        let vf = vf16 as i32;
7457        let __s_b = self.gpu.stream();
7458        let mut b = __s_b.launch_builder(&f);
7459        b.arg(q)
7460            .arg(k)
7461            .arg(v)
7462            .arg(wq)
7463            .arg(wk)
7464            .arg(wv)
7465            .arg(dq)
7466            .arg(dk)
7467            .arg(dv)
7468            .arg(&mut *dvb)
7469            .arg(&nc)
7470            .arg(&rqi)
7471            .arg(&rki)
7472            .arg(&rvi)
7473            .arg(&e)
7474            .arg(&vf);
7475        unsafe {
7476            b.launch(cfg)?;
7477        }
7478        Ok(())
7479    }
7480
7481    pub fn rms_norm_qkv(
7482        &self,
7483        q: &CudaSlice<f32>,
7484        k: &CudaSlice<f32>,
7485        v: &CudaSlice<f32>,
7486        wq: &CudaSlice<f32>,
7487        wk: &CudaSlice<f32>,
7488        wv: &CudaSlice<f32>,
7489        dq: &mut CudaSlice<f32>,
7490        dk: &mut CudaSlice<f32>,
7491        dv: &mut CudaSlice<f32>,
7492        ncols: usize,
7493        rq: usize,
7494        rk: usize,
7495        eps: f32,
7496    ) -> Result<(), Box<dyn std::error::Error>> {
7497        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7498        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7499        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7500        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7501        let warp_on = *WARP_ON.get_or_init(|| {
7502            std::env::var("MEMRA_QKVNORM_W")
7503                .map(|v| v != "0")
7504                .unwrap_or(true)
7505        });
7506        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7507        // replay numerics are untouched on every model; only prefill depth takes the new config.
7508        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7509            let f = self.func("rms_norm_qkv_w4_f32");
7510            let rows = (rq + 2 * rk) as u32;
7511            let cfg = LaunchConfig {
7512                grid_dim: (rows.div_ceil(8), 1, 1),
7513                block_dim: (256, 1, 1),
7514                shared_mem_bytes: 0,
7515            };
7516            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7517            let __s_b = self.gpu.stream();
7518            let mut b = __s_b.launch_builder(&f);
7519            b.arg(q)
7520                .arg(k)
7521                .arg(v)
7522                .arg(wq)
7523                .arg(wk)
7524                .arg(wv)
7525                .arg(dq)
7526                .arg(dk)
7527                .arg(dv)
7528                .arg(&nc)
7529                .arg(&rqi)
7530                .arg(&rki)
7531                .arg(&rvi)
7532                .arg(&e);
7533            unsafe {
7534                b.launch(cfg)?;
7535            }
7536            return Ok(());
7537        }
7538        let f = self.func("rms_norm_qkv_f32");
7539        let grid = (rq + 2 * rk) as u32;
7540        let cfg = LaunchConfig {
7541            grid_dim: (grid, 1, 1),
7542            block_dim: (rms_block(), 1, 1),
7543            shared_mem_bytes: 0,
7544        };
7545        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7546        let __s_b = self.gpu.stream();
7547        let mut b = __s_b.launch_builder(&f);
7548        b.arg(q)
7549            .arg(k)
7550            .arg(v)
7551            .arg(wq)
7552            .arg(wk)
7553            .arg(wv)
7554            .arg(dq)
7555            .arg(dk)
7556            .arg(dv)
7557            .arg(&nc)
7558            .arg(&rqi)
7559            .arg(&rki)
7560            .arg(&e);
7561        unsafe {
7562            b.launch(cfg)?;
7563        }
7564        Ok(())
7565    }
7566
7567    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7568    #[allow(clippy::too_many_arguments)]
7569    pub fn rms_norm2x(
7570        &self,
7571        a: &CudaSlice<f32>,
7572        bb: &CudaSlice<f32>,
7573        wa: &CudaSlice<f32>,
7574        wb: &CudaSlice<f32>,
7575        da: &mut CudaSlice<f32>,
7576        db: &mut CudaSlice<f32>,
7577        ncols: usize,
7578        nrows: usize,
7579        eps: f32,
7580    ) -> Result<(), Box<dyn std::error::Error>> {
7581        let f = self.func("rms_norm2x_f32");
7582        let cfg = LaunchConfig {
7583            grid_dim: (2 * nrows as u32, 1, 1),
7584            block_dim: (rms_block(), 1, 1),
7585            shared_mem_bytes: 0,
7586        };
7587        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7588        let __s_b = self.gpu.stream();
7589        let mut b = __s_b.launch_builder(&f);
7590        b.arg(a)
7591            .arg(bb)
7592            .arg(wa)
7593            .arg(wb)
7594            .arg(da)
7595            .arg(db)
7596            .arg(&nc)
7597            .arg(&nr)
7598            .arg(&e);
7599        unsafe {
7600            b.launch(cfg)?;
7601        }
7602        Ok(())
7603    }
7604
7605    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7606    pub fn softcap(
7607        &self,
7608        y: &mut CudaSlice<f32>,
7609        cap: f32,
7610        n: usize,
7611    ) -> Result<(), Box<dyn std::error::Error>> {
7612        let f = self.func("softcap_f32");
7613        let cfg = LaunchConfig::for_num_elems(n as u32);
7614        let ni = n as i32;
7615        let __s_b = self.gpu.stream();
7616        let mut b = __s_b.launch_builder(&f);
7617        b.arg(y).arg(&cap).arg(&ni);
7618        unsafe {
7619            b.launch(cfg)?;
7620        }
7621        Ok(())
7622    }
7623
7624    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7625    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7626    pub fn mask_ids_rows(
7627        &self,
7628        y: &mut CudaSlice<f32>,
7629        ids: &CudaSlice<i32>,
7630        n_ids: usize,
7631        n_vocab: usize,
7632        t: usize,
7633    ) -> Result<(), Box<dyn std::error::Error>> {
7634        let f = self.func("mask_ids_rows_f32");
7635        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7636        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7637        let __s_b = self.gpu.stream();
7638        let mut b = __s_b.launch_builder(&f);
7639        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7640        unsafe {
7641            b.launch(cfg)?;
7642        }
7643        Ok(())
7644    }
7645
7646    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7647    #[allow(clippy::too_many_arguments)]
7648    pub fn add_scale_rms_norm(
7649        &self,
7650        a: &CudaSlice<f32>,
7651        b_in: &CudaSlice<f32>,
7652        c: f32,
7653        w: &CudaSlice<f32>,
7654        res: &mut CudaSlice<f32>,
7655        dst: &mut CudaSlice<f32>,
7656        ncols: usize,
7657        nrows: usize,
7658        eps: f32,
7659    ) -> Result<(), Box<dyn std::error::Error>> {
7660        let f = self.func("add_scale_rms_norm_f32");
7661        let cfg = LaunchConfig {
7662            grid_dim: (nrows as u32, 1, 1),
7663            block_dim: (rms_block(), 1, 1),
7664            shared_mem_bytes: 0,
7665        };
7666        let (nc, e2) = (ncols as i32, eps);
7667        let __s_b = self.gpu.stream();
7668        let mut b = __s_b.launch_builder(&f);
7669        b.arg(a)
7670            .arg(b_in)
7671            .arg(&c)
7672            .arg(w)
7673            .arg(res)
7674            .arg(dst)
7675            .arg(&nc)
7676            .arg(&e2);
7677        unsafe {
7678            b.launch(cfg)?;
7679        }
7680        Ok(())
7681    }
7682
7683    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7684    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7685    #[allow(clippy::too_many_arguments)]
7686    pub fn add_scale_rms_norm_q8_1(
7687        &self,
7688        a: &CudaSlice<f32>,
7689        b_in: &CudaSlice<f32>,
7690        c: f32,
7691        w: &CudaSlice<f32>,
7692        res: &mut CudaSlice<f32>,
7693        ncols: usize,
7694        nrows: usize,
7695        eps: f32,
7696    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7697        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7698        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7699        let (nc, e2) = (ncols as i32, eps);
7700        if Self::pdl_on() && Self::pdl_wb_on() {
7701            {
7702                use cudarc::driver::{DevicePtr, DevicePtrMut};
7703                let s = &self.gpu.stream();
7704                let (pa, _g0) = a.device_ptr(s);
7705                let (pb, _g1) = b_in.device_ptr(s);
7706                let (pw, _g2) = w.device_ptr(s);
7707                let (pr, _g3) = res.device_ptr_mut(s);
7708                let (pq, _g4) = out_q.device_ptr_mut(s);
7709                let (pd, _g5) = out_d.device_ptr_mut(s);
7710                let mut ps = [
7711                    &pa as *const _ as *mut std::ffi::c_void,
7712                    &pb as *const _ as *mut _,
7713                    &c as *const _ as *mut _,
7714                    &pw as *const _ as *mut _,
7715                    &pr as *const _ as *mut _,
7716                    &pq as *const _ as *mut _,
7717                    &pd as *const _ as *mut _,
7718                    &nc as *const _ as *mut _,
7719                    &e2 as *const _ as *mut _,
7720                ];
7721                unsafe {
7722                    self.launch_pdl(
7723                        "add_scale_rms_norm_q8_1",
7724                        (nrows as u32, 1, 1),
7725                        (rms_block(), 1, 1),
7726                        &mut ps,
7727                    )?;
7728                }
7729            }
7730            return Ok((out_q, out_d));
7731        }
7732        let f = self.func("add_scale_rms_norm_q8_1");
7733        let cfg = LaunchConfig {
7734            grid_dim: (nrows as u32, 1, 1),
7735            block_dim: (rms_block(), 1, 1),
7736            shared_mem_bytes: 0,
7737        };
7738        let __s_b = self.gpu.stream();
7739        let mut b = __s_b.launch_builder(&f);
7740        b.arg(a)
7741            .arg(b_in)
7742            .arg(&c)
7743            .arg(w)
7744            .arg(res)
7745            .arg(&mut out_q)
7746            .arg(&mut out_d)
7747            .arg(&nc)
7748            .arg(&e2);
7749        unsafe {
7750            b.launch(cfg)?;
7751        }
7752        Ok((out_q, out_d))
7753    }
7754
7755    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7756    #[allow(clippy::too_many_arguments)]
7757    pub fn add_scale_rms_norm_q8_1_into(
7758        &self,
7759        a: &CudaSlice<f32>,
7760        b_in: &CudaSlice<f32>,
7761        c: f32,
7762        w: &CudaSlice<f32>,
7763        res: &mut CudaSlice<f32>,
7764        ncols: usize,
7765        nrows: usize,
7766        eps: f32,
7767        out_q: &mut CudaSlice<i8>,
7768        out_d: &mut CudaSlice<f32>,
7769    ) -> Result<(), Box<dyn std::error::Error>> {
7770        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7771        let (nc, e2) = (ncols as i32, eps);
7772        if Self::pdl_on() && Self::pdl_wb_on() {
7773            use cudarc::driver::{DevicePtr, DevicePtrMut};
7774            let s = &self.gpu.stream();
7775            let (pa, _g0) = a.device_ptr(s);
7776            let (pb, _g1) = b_in.device_ptr(s);
7777            let (pw, _g2) = w.device_ptr(s);
7778            let (pr, _g3) = res.device_ptr_mut(s);
7779            let (pq, _g4) = out_q.device_ptr_mut(s);
7780            let (pd, _g5) = out_d.device_ptr_mut(s);
7781            let mut ps = [
7782                &pa as *const _ as *mut std::ffi::c_void,
7783                &pb as *const _ as *mut _,
7784                &c as *const _ as *mut _,
7785                &pw as *const _ as *mut _,
7786                &pr as *const _ as *mut _,
7787                &pq as *const _ as *mut _,
7788                &pd as *const _ as *mut _,
7789                &nc as *const _ as *mut _,
7790                &e2 as *const _ as *mut _,
7791            ];
7792            unsafe {
7793                self.launch_pdl(
7794                    "add_scale_rms_norm_q8_1",
7795                    (nrows as u32, 1, 1),
7796                    (rms_block(), 1, 1),
7797                    &mut ps,
7798                )?;
7799            }
7800            return Ok(());
7801        }
7802        let f = self.func("add_scale_rms_norm_q8_1");
7803        let cfg = LaunchConfig {
7804            grid_dim: (nrows as u32, 1, 1),
7805            block_dim: (rms_block(), 1, 1),
7806            shared_mem_bytes: 0,
7807        };
7808        let __s_b = self.gpu.stream();
7809        let mut b = __s_b.launch_builder(&f);
7810        b.arg(a)
7811            .arg(b_in)
7812            .arg(&c)
7813            .arg(w)
7814            .arg(res)
7815            .arg(&mut *out_q)
7816            .arg(&mut *out_d)
7817            .arg(&nc)
7818            .arg(&e2);
7819        unsafe {
7820            b.launch(cfg)?;
7821        }
7822        Ok(())
7823    }
7824
7825    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7826    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7827    #[allow(clippy::too_many_arguments)]
7828    pub fn rms_pre_add_scale_rms_norm_q8_1(
7829        &self,
7830        a: &CudaSlice<f32>,
7831        wa: &CudaSlice<f32>,
7832        b_in: &CudaSlice<f32>,
7833        c: f32,
7834        w: &CudaSlice<f32>,
7835        res: &mut CudaSlice<f32>,
7836        ncols: usize,
7837        nrows: usize,
7838        eps: f32,
7839    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7840        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7841        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7842        let (nc, e2) = (ncols as i32, eps);
7843        if Self::pdl_on() {
7844            {
7845                use cudarc::driver::{DevicePtr, DevicePtrMut};
7846                let s = &self.gpu.stream();
7847                let (pa, _g0) = a.device_ptr(s);
7848                let (pwa, _g1) = wa.device_ptr(s);
7849                let (pb, _g2) = b_in.device_ptr(s);
7850                let (pw, _g3) = w.device_ptr(s);
7851                let (pr, _g4) = res.device_ptr_mut(s);
7852                let (pq, _g5) = out_q.device_ptr_mut(s);
7853                let (pd, _g6) = out_d.device_ptr_mut(s);
7854                let mut ps = [
7855                    &pa as *const _ as *mut std::ffi::c_void,
7856                    &pwa as *const _ as *mut _,
7857                    &pb as *const _ as *mut _,
7858                    &c as *const _ as *mut _,
7859                    &pw as *const _ as *mut _,
7860                    &pr as *const _ as *mut _,
7861                    &pq as *const _ as *mut _,
7862                    &pd as *const _ as *mut _,
7863                    &nc as *const _ as *mut _,
7864                    &e2 as *const _ as *mut _,
7865                ];
7866                unsafe {
7867                    self.launch_pdl(
7868                        "rms_pre_add_scale_rms_norm_q8_1",
7869                        (nrows as u32, 1, 1),
7870                        (rms_block(), 1, 1),
7871                        &mut ps,
7872                    )?;
7873                }
7874            }
7875            return Ok((out_q, out_d));
7876        }
7877        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7878        let cfg = LaunchConfig {
7879            grid_dim: (nrows as u32, 1, 1),
7880            block_dim: (rms_block(), 1, 1),
7881            shared_mem_bytes: 0,
7882        };
7883        let __s_b = self.gpu.stream();
7884        let mut b = __s_b.launch_builder(&f);
7885        b.arg(a)
7886            .arg(wa)
7887            .arg(b_in)
7888            .arg(&c)
7889            .arg(w)
7890            .arg(res)
7891            .arg(&mut out_q)
7892            .arg(&mut out_d)
7893            .arg(&nc)
7894            .arg(&e2);
7895        unsafe {
7896            b.launch(cfg)?;
7897        }
7898        Ok((out_q, out_d))
7899    }
7900
7901    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7902    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7903    pub fn gelu_tanh_mul_q8_1(
7904        &self,
7905        gate: &CudaSlice<f32>,
7906        up: &cudarc::driver::CudaView<f32>,
7907        act: &mut CudaSlice<f32>,
7908        ncols: usize,
7909        nrows: usize,
7910    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7911        debug_assert!(ncols % 128 == 0);
7912        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7913        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7914        let nc = ncols as i32;
7915        if Self::pdl_on() {
7916            {
7917                use cudarc::driver::{DevicePtr, DevicePtrMut};
7918                let s = &self.gpu.stream();
7919                let (pg, _g0) = gate.device_ptr(s);
7920                let (pu, _g1) = up.device_ptr(s);
7921                let (pact, _g2) = act.device_ptr_mut(s);
7922                let (pq, _g3) = out_q.device_ptr_mut(s);
7923                let (pd, _g4) = out_d.device_ptr_mut(s);
7924                let mut ps = [
7925                    &pg as *const _ as *mut std::ffi::c_void,
7926                    &pu as *const _ as *mut _,
7927                    &pact as *const _ as *mut _,
7928                    &pq as *const _ as *mut _,
7929                    &pd as *const _ as *mut _,
7930                    &nc as *const _ as *mut _,
7931                ];
7932                unsafe {
7933                    self.launch_pdl(
7934                        "gelu_tanh_mul_q8_1",
7935                        (nrows as u32, 1, 1),
7936                        (rms_block(), 1, 1),
7937                        &mut ps,
7938                    )?;
7939                }
7940            }
7941            return Ok((out_q, out_d));
7942        }
7943        let f = self.func("gelu_tanh_mul_q8_1");
7944        let cfg = LaunchConfig {
7945            grid_dim: (nrows as u32, 1, 1),
7946            block_dim: (rms_block(), 1, 1),
7947            shared_mem_bytes: 0,
7948        };
7949        let __s_b = self.gpu.stream();
7950        let mut b = __s_b.launch_builder(&f);
7951        b.arg(gate)
7952            .arg(up)
7953            .arg(act)
7954            .arg(&mut out_q)
7955            .arg(&mut out_d)
7956            .arg(&nc);
7957        unsafe {
7958            b.launch(cfg)?;
7959        }
7960        Ok((out_q, out_d))
7961    }
7962
7963    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7964    #[allow(clippy::too_many_arguments)]
7965    pub fn gelu_tanh_mul_q8_1_into(
7966        &self,
7967        gate: &CudaSlice<f32>,
7968        up: &cudarc::driver::CudaView<f32>,
7969        act: &mut CudaSlice<f32>,
7970        ncols: usize,
7971        nrows: usize,
7972        out_q: &mut CudaSlice<i8>,
7973        out_d: &mut CudaSlice<f32>,
7974    ) -> Result<(), Box<dyn std::error::Error>> {
7975        debug_assert!(ncols % 128 == 0);
7976        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7977        let nc = ncols as i32;
7978        if Self::pdl_on() {
7979            use cudarc::driver::{DevicePtr, DevicePtrMut};
7980            let s = &self.gpu.stream();
7981            let (pg, _g0) = gate.device_ptr(s);
7982            let (pu, _g1) = up.device_ptr(s);
7983            let (pact, _g2) = act.device_ptr_mut(s);
7984            let (pq, _g3) = out_q.device_ptr_mut(s);
7985            let (pd, _g4) = out_d.device_ptr_mut(s);
7986            let mut ps = [
7987                &pg as *const _ as *mut std::ffi::c_void,
7988                &pu as *const _ as *mut _,
7989                &pact as *const _ as *mut _,
7990                &pq as *const _ as *mut _,
7991                &pd as *const _ as *mut _,
7992                &nc as *const _ as *mut _,
7993            ];
7994            unsafe {
7995                self.launch_pdl(
7996                    "gelu_tanh_mul_q8_1",
7997                    (nrows as u32, 1, 1),
7998                    (rms_block(), 1, 1),
7999                    &mut ps,
8000                )?;
8001            }
8002            return Ok(());
8003        }
8004        let f = self.func("gelu_tanh_mul_q8_1");
8005        let cfg = LaunchConfig {
8006            grid_dim: (nrows as u32, 1, 1),
8007            block_dim: (rms_block(), 1, 1),
8008            shared_mem_bytes: 0,
8009        };
8010        let __s_b = self.gpu.stream();
8011        let mut b = __s_b.launch_builder(&f);
8012        b.arg(gate)
8013            .arg(up)
8014            .arg(&mut *act)
8015            .arg(&mut *out_q)
8016            .arg(&mut *out_d)
8017            .arg(&nc);
8018        unsafe {
8019            b.launch(cfg)?;
8020        }
8021        Ok(())
8022    }
8023
8024    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
8025    #[allow(clippy::too_many_arguments)]
8026    pub fn add_rms_norm3_q8z(
8027        &self,
8028        a: &CudaSlice<f32>,
8029        b_in: &CudaSlice<f32>,
8030        w0: &CudaSlice<f32>,
8031        w1: &CudaSlice<f32>,
8032        w2: &CudaSlice<f32>,
8033        res: &mut CudaSlice<f32>,
8034        out1: &mut CudaSlice<f32>,
8035        ncols: usize,
8036        nrows: usize,
8037        eps: f32,
8038    ) -> Result<
8039        (
8040            (CudaSlice<i8>, CudaSlice<f32>),
8041            (CudaSlice<i8>, CudaSlice<f32>),
8042        ),
8043        Box<dyn std::error::Error>,
8044    > {
8045        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
8046        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8047        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
8048        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8049        let f = self.func("add_rms_norm3_q8z_f32");
8050        let cfg = LaunchConfig {
8051            grid_dim: (nrows as u32, 1, 1),
8052            block_dim: (rms_block(), 1, 1),
8053            shared_mem_bytes: 0,
8054        };
8055        let (nc, e2) = (ncols as i32, eps);
8056        let __s_b = self.gpu.stream();
8057        let mut b = __s_b.launch_builder(&f);
8058        b.arg(a)
8059            .arg(b_in)
8060            .arg(w0)
8061            .arg(w1)
8062            .arg(w2)
8063            .arg(res)
8064            .arg(&mut q0)
8065            .arg(&mut d0)
8066            .arg(out1)
8067            .arg(&mut q2)
8068            .arg(&mut d2)
8069            .arg(&nc)
8070            .arg(&e2);
8071        unsafe {
8072            b.launch(cfg)?;
8073        }
8074        Ok(((q0, d0), (q2, d2)))
8075    }
8076
8077    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
8078    #[allow(clippy::too_many_arguments)]
8079    pub fn add_rms_norm3(
8080        &self,
8081        a: &CudaSlice<f32>,
8082        b_in: &CudaSlice<f32>,
8083        w0: &CudaSlice<f32>,
8084        w1: &CudaSlice<f32>,
8085        w2: &CudaSlice<f32>,
8086        res: &mut CudaSlice<f32>,
8087        d0: &mut CudaSlice<f32>,
8088        d1: &mut CudaSlice<f32>,
8089        d2: &mut CudaSlice<f32>,
8090        ncols: usize,
8091        nrows: usize,
8092        eps: f32,
8093    ) -> Result<(), Box<dyn std::error::Error>> {
8094        let f = self.func("add_rms_norm3_f32");
8095        let cfg = LaunchConfig {
8096            grid_dim: (nrows as u32, 1, 1),
8097            block_dim: (rms_block(), 1, 1),
8098            shared_mem_bytes: 0,
8099        };
8100        let (nc, e2) = (ncols as i32, eps);
8101        let __s_b = self.gpu.stream();
8102        let mut b = __s_b.launch_builder(&f);
8103        b.arg(a)
8104            .arg(b_in)
8105            .arg(w0)
8106            .arg(w1)
8107            .arg(w2)
8108            .arg(res)
8109            .arg(d0)
8110            .arg(d1)
8111            .arg(d2)
8112            .arg(&nc)
8113            .arg(&e2);
8114        unsafe {
8115            b.launch(cfg)?;
8116        }
8117        Ok(())
8118    }
8119
8120    /// dst = (a + b) * c (residual add + layer scale, one launch).
8121    pub fn add_scale(
8122        &self,
8123        a: &CudaSlice<f32>,
8124        b_in: &CudaSlice<f32>,
8125        c: f32,
8126        dst: &mut CudaSlice<f32>,
8127        n: usize,
8128    ) -> Result<(), Box<dyn std::error::Error>> {
8129        let f = self.func("add_scale_f32");
8130        let cfg = LaunchConfig::for_num_elems(n as u32);
8131        let ni = n as i32;
8132        let __s_b = self.gpu.stream();
8133        let mut b = __s_b.launch_builder(&f);
8134        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
8135        unsafe {
8136            b.launch(cfg)?;
8137        }
8138        Ok(())
8139    }
8140
8141    /// Vision-tower LayerNorm (with bias) over [nrows, ncols] — lane/vision.
8142    pub fn layer_norm_bias(
8143        &self,
8144        x: &CudaSlice<f32>,
8145        w: &CudaSlice<f32>,
8146        b: &CudaSlice<f32>,
8147        dst: &mut CudaSlice<f32>,
8148        ncols: usize,
8149        nrows: usize,
8150        eps: f32,
8151    ) -> Result<(), Box<dyn std::error::Error>> {
8152        let f = self.func("layer_norm_bias_f32");
8153        let (nc, e) = (ncols as i32, eps);
8154        let cfg = LaunchConfig {
8155            grid_dim: (nrows as u32, 1, 1),
8156            block_dim: (256, 1, 1),
8157            shared_mem_bytes: 0,
8158        };
8159        let __s_b = self.gpu.stream();
8160        let mut lb = __s_b.launch_builder(&f);
8161        lb.arg(x).arg(w).arg(b).arg(&mut *dst).arg(&nc).arg(&e);
8162        unsafe {
8163            lb.launch(cfg)?;
8164        }
8165        Ok(())
8166    }
8167
8168    /// gelu_pytorch_tanh elementwise (vision tower MLP activation).
8169    pub fn gelu_tanh(
8170        &self,
8171        x: &CudaSlice<f32>,
8172        dst: &mut CudaSlice<f32>,
8173        n: usize,
8174    ) -> Result<(), Box<dyn std::error::Error>> {
8175        let f = self.func("gelu_tanh_f32");
8176        let ni = n as i64;
8177        let cfg = LaunchConfig {
8178            grid_dim: (n.div_ceil(256) as u32, 1, 1),
8179            block_dim: (256, 1, 1),
8180            shared_mem_bytes: 0,
8181        };
8182        let __s_b = self.gpu.stream();
8183        let mut lb = __s_b.launch_builder(&f);
8184        lb.arg(x).arg(&mut *dst).arg(&ni);
8185        unsafe {
8186            lb.launch(cfg)?;
8187        }
8188        Ok(())
8189    }
8190
8191    /// In-place row softmax over [nrows, ncols] (bidirectional vision attention).
8192    pub fn row_softmax(
8193        &self,
8194        x: &mut CudaSlice<f32>,
8195        ncols: usize,
8196        nrows: usize,
8197    ) -> Result<(), Box<dyn std::error::Error>> {
8198        let f = self.func("row_softmax_f32");
8199        let nc = ncols as i32;
8200        let cfg = LaunchConfig {
8201            grid_dim: (nrows as u32, 1, 1),
8202            block_dim: (256, 1, 1),
8203            shared_mem_bytes: 0,
8204        };
8205        let __s_b = self.gpu.stream();
8206        let mut lb = __s_b.launch_builder(&f);
8207        lb.arg(&mut *x).arg(&nc);
8208        unsafe {
8209            lb.launch(cfg)?;
8210        }
8211        Ok(())
8212    }
8213
8214    pub fn rms_norm(
8215        &self,
8216        x: &CudaSlice<f32>,
8217        w: &CudaSlice<f32>,
8218        dst: &mut CudaSlice<f32>,
8219        ncols: usize,
8220        nrows: usize,
8221        eps: f32,
8222    ) -> Result<(), Box<dyn std::error::Error>> {
8223        let (nc, e) = (ncols as i32, eps);
8224        if Self::pdl_on() && Self::pdl_wb_on() {
8225            use cudarc::driver::{DevicePtr, DevicePtrMut};
8226            let s = &self.gpu.stream();
8227            let (px, _g0) = x.device_ptr(s);
8228            let (pw, _g1) = w.device_ptr(s);
8229            let (pd, _g2) = dst.device_ptr_mut(s);
8230            let mut ps = [
8231                &px as *const _ as *mut std::ffi::c_void,
8232                &pw as *const _ as *mut _,
8233                &pd as *const _ as *mut _,
8234                &nc as *const _ as *mut _,
8235                &e as *const _ as *mut _,
8236            ];
8237            unsafe {
8238                self.launch_pdl(
8239                    "rms_norm_f32",
8240                    (nrows as u32, 1, 1),
8241                    (rms_block(), 1, 1),
8242                    &mut ps,
8243                )?;
8244            }
8245            return Ok(());
8246        }
8247        let f = self.func("rms_norm_f32");
8248        let cfg = LaunchConfig {
8249            grid_dim: (nrows as u32, 1, 1),
8250            block_dim: (rms_block(), 1, 1),
8251            shared_mem_bytes: 0,
8252        };
8253        let __s_b = self.gpu.stream();
8254        let mut b = __s_b.launch_builder(&f);
8255        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8256        unsafe {
8257            b.launch(cfg)?;
8258        }
8259        Ok(())
8260    }
8261
8262    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8263    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8264    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8265    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8266    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8267    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8268    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8269    pub fn rms_norm_decode(
8270        &self,
8271        x: &CudaSlice<f32>,
8272        w: &CudaSlice<f32>,
8273        dst: &mut CudaSlice<f32>,
8274        ncols: usize,
8275        nrows: usize,
8276        eps: f32,
8277    ) -> Result<(), Box<dyn std::error::Error>> {
8278        let f = self.func("rms_norm_f32");
8279        let cfg = LaunchConfig {
8280            grid_dim: (nrows as u32, 1, 1),
8281            block_dim: (1024, 1, 1),
8282            shared_mem_bytes: 0,
8283        };
8284        let (nc, e) = (ncols as i32, eps);
8285        let __s_b = self.gpu.stream();
8286        let mut b = __s_b.launch_builder(&f);
8287        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8288        unsafe {
8289            b.launch(cfg)?;
8290        }
8291        Ok(())
8292    }
8293
8294    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8295    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8296    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8297    pub fn rms_norm_q8_1(
8298        &self,
8299        x: &CudaSlice<f32>,
8300        w: &CudaSlice<f32>,
8301        ncols: usize,
8302        nrows: usize,
8303        eps: f32,
8304    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8305        let nblk = ncols / 32;
8306        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8307        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8308        let (nc, e) = (ncols as i32, eps);
8309        if Self::pdl_on() {
8310            {
8311                use cudarc::driver::{DevicePtr, DevicePtrMut};
8312                let s = &self.gpu.stream();
8313                let (px, _g0) = x.device_ptr(s);
8314                let (pw, _g1) = w.device_ptr(s);
8315                let (pq, _g2) = q.device_ptr_mut(s);
8316                let (pd, _g3) = d.device_ptr_mut(s);
8317                let mut ps = [
8318                    &px as *const _ as *mut std::ffi::c_void,
8319                    &pw as *const _ as *mut _,
8320                    &pq as *const _ as *mut _,
8321                    &pd as *const _ as *mut _,
8322                    &nc as *const _ as *mut _,
8323                    &e as *const _ as *mut _,
8324                ];
8325                unsafe {
8326                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8327                }
8328            }
8329            return Ok((q, d));
8330        }
8331        let f = self.func("rms_norm_q8_1");
8332        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8333        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8334        let cfg = LaunchConfig {
8335            grid_dim: (nrows as u32, 1, 1),
8336            block_dim: (1024, 1, 1),
8337            shared_mem_bytes: 0,
8338        };
8339        let __s_b = self.gpu.stream();
8340        let mut b = __s_b.launch_builder(&f);
8341        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8342        unsafe {
8343            b.launch(cfg)?;
8344        }
8345        Ok((q, d))
8346    }
8347
8348    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8349    /// PDL arm), caller-owned outputs.
8350    pub fn rms_norm_q8_1_into(
8351        &self,
8352        x: &CudaSlice<f32>,
8353        w: &CudaSlice<f32>,
8354        ncols: usize,
8355        nrows: usize,
8356        eps: f32,
8357        q: &mut CudaSlice<i8>,
8358        d: &mut CudaSlice<f32>,
8359    ) -> Result<(), Box<dyn std::error::Error>> {
8360        let nblk = ncols / 32;
8361        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8362        let (nc, e) = (ncols as i32, eps);
8363        if Self::pdl_on() {
8364            use cudarc::driver::{DevicePtr, DevicePtrMut};
8365            let s = &self.gpu.stream();
8366            let (px, _g0) = x.device_ptr(s);
8367            let (pw, _g1) = w.device_ptr(s);
8368            let (pq, _g2) = q.device_ptr_mut(s);
8369            let (pd, _g3) = d.device_ptr_mut(s);
8370            let mut ps = [
8371                &px as *const _ as *mut std::ffi::c_void,
8372                &pw as *const _ as *mut _,
8373                &pq as *const _ as *mut _,
8374                &pd as *const _ as *mut _,
8375                &nc as *const _ as *mut _,
8376                &e as *const _ as *mut _,
8377            ];
8378            unsafe {
8379                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8380            }
8381            return Ok(());
8382        }
8383        let f = self.func("rms_norm_q8_1");
8384        let cfg = LaunchConfig {
8385            grid_dim: (nrows as u32, 1, 1),
8386            block_dim: (1024, 1, 1),
8387            shared_mem_bytes: 0,
8388        };
8389        let __s_b = self.gpu.stream();
8390        let mut b = __s_b.launch_builder(&f);
8391        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8392        unsafe {
8393            b.launch(cfg)?;
8394        }
8395        Ok(())
8396    }
8397
8398    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8399    pub fn quantize_q8_1_into(
8400        &self,
8401        x: &CudaSlice<f32>,
8402        m: usize,
8403        in_f: usize,
8404        q: &mut CudaSlice<i8>,
8405        d: &mut CudaSlice<f32>,
8406    ) -> Result<(), Box<dyn std::error::Error>> {
8407        let nblk = in_f / 32;
8408        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8409        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8410        let (inf, mi) = (in_f as i32, m as i32);
8411        if Self::pdl_on() && Self::pdl_wb_on() {
8412            use cudarc::driver::{DevicePtr, DevicePtrMut};
8413            let s = &self.gpu.stream();
8414            let (px, _g0) = x.device_ptr(s);
8415            let (pq, _g1) = q.device_ptr_mut(s);
8416            let (pd, _g2) = d.device_ptr_mut(s);
8417            let mut ps = [
8418                &px as *const _ as *mut std::ffi::c_void,
8419                &pq as *const _ as *mut _,
8420                &pd as *const _ as *mut _,
8421                &inf as *const _ as *mut _,
8422                &mi as *const _ as *mut _,
8423            ];
8424            unsafe {
8425                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8426            }
8427            return Ok(());
8428        }
8429        let f = self.func("quantize_q8_1");
8430        let __s_b = self.gpu.stream();
8431        let mut b = __s_b.launch_builder(&f);
8432        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8433        unsafe {
8434            b.launch(cfg)?;
8435        }
8436        Ok(())
8437    }
8438
8439    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8440    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8441    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8442    pub fn add_rms_norm_q8_1(
8443        &self,
8444        a: &CudaSlice<f32>,
8445        b_in: &CudaSlice<f32>,
8446        w: &CudaSlice<f32>,
8447        res: &mut CudaSlice<f32>,
8448        ncols: usize,
8449        nrows: usize,
8450        eps: f32,
8451    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8452        let nblk = ncols / 32;
8453        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8454        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8455        let f = self.func("add_rms_norm_q8_1");
8456        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8457        let cfg = LaunchConfig {
8458            grid_dim: (nrows as u32, 1, 1),
8459            block_dim: (1024, 1, 1),
8460            shared_mem_bytes: 0,
8461        };
8462        let (nc, e) = (ncols as i32, eps);
8463        let __s_bld = self.gpu.stream();
8464        let mut bld = __s_bld.launch_builder(&f);
8465        bld.arg(a)
8466            .arg(b_in)
8467            .arg(w)
8468            .arg(res)
8469            .arg(&mut q)
8470            .arg(&mut d)
8471            .arg(&nc)
8472            .arg(&e);
8473        unsafe {
8474            bld.launch(cfg)?;
8475        }
8476        Ok((q, d))
8477    }
8478
8479    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8480    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8481    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8482    pub fn add_rms_norm(
8483        &self,
8484        a: &CudaSlice<f32>,
8485        b: &CudaSlice<f32>,
8486        w: &CudaSlice<f32>,
8487        res: &mut CudaSlice<f32>,
8488        dst: &mut CudaSlice<f32>,
8489        ncols: usize,
8490        nrows: usize,
8491        eps: f32,
8492    ) -> Result<(), Box<dyn std::error::Error>> {
8493        let (nc, e) = (ncols as i32, eps);
8494        if Self::pdl_on() && Self::pdl_wb_on() {
8495            use cudarc::driver::{DevicePtr, DevicePtrMut};
8496            let s = &self.gpu.stream();
8497            let (pa, _g0) = a.device_ptr(s);
8498            let (pb, _g1) = b.device_ptr(s);
8499            let (pw, _g2) = w.device_ptr(s);
8500            let (pr, _g3) = res.device_ptr_mut(s);
8501            let (pd, _g4) = dst.device_ptr_mut(s);
8502            let mut ps = [
8503                &pa as *const _ as *mut std::ffi::c_void,
8504                &pb as *const _ as *mut _,
8505                &pw as *const _ as *mut _,
8506                &pr as *const _ as *mut _,
8507                &pd as *const _ as *mut _,
8508                &nc as *const _ as *mut _,
8509                &e as *const _ as *mut _,
8510            ];
8511            unsafe {
8512                self.launch_pdl(
8513                    "add_rms_norm_f32",
8514                    (nrows as u32, 1, 1),
8515                    (rms_block(), 1, 1),
8516                    &mut ps,
8517                )?;
8518            }
8519            return Ok(());
8520        }
8521        let f = self.func("add_rms_norm_f32");
8522        let cfg = LaunchConfig {
8523            grid_dim: (nrows as u32, 1, 1),
8524            block_dim: (rms_block(), 1, 1),
8525            shared_mem_bytes: 0,
8526        };
8527        let __s_b2 = self.gpu.stream();
8528        let mut b2 = __s_b2.launch_builder(&f);
8529        b2.arg(a)
8530            .arg(b)
8531            .arg(w)
8532            .arg(&mut *res)
8533            .arg(&mut *dst)
8534            .arg(&nc)
8535            .arg(&e);
8536        unsafe {
8537            b2.launch(cfg)?;
8538        }
8539        Ok(())
8540    }
8541
8542    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8543    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8544    #[allow(clippy::too_many_arguments)]
8545    pub fn rms_pre_add_rms_norm(
8546        &self,
8547        a: &CudaSlice<f32>,
8548        wa: &CudaSlice<f32>,
8549        b: &CudaSlice<f32>,
8550        w: &CudaSlice<f32>,
8551        res: &mut CudaSlice<f32>,
8552        dst: &mut CudaSlice<f32>,
8553        ncols: usize,
8554        nrows: usize,
8555        eps: f32,
8556    ) -> Result<(), Box<dyn std::error::Error>> {
8557        let f = self.func("rms_pre_add_rms_norm_f32");
8558        let cfg = LaunchConfig {
8559            grid_dim: (nrows as u32, 1, 1),
8560            block_dim: (rms_block(), 1, 1),
8561            shared_mem_bytes: 0,
8562        };
8563        let (nc, e) = (ncols as i32, eps);
8564        let __s_b2 = self.gpu.stream();
8565        let mut b2 = __s_b2.launch_builder(&f);
8566        b2.arg(a)
8567            .arg(wa)
8568            .arg(b)
8569            .arg(w)
8570            .arg(&mut *res)
8571            .arg(&mut *dst)
8572            .arg(&nc)
8573            .arg(&e);
8574        unsafe {
8575            b2.launch(cfg)?;
8576        }
8577        Ok(())
8578    }
8579
8580    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8581    #[allow(clippy::too_many_arguments)]
8582    pub fn rms_pre_add_rms_norm_q8z(
8583        &self,
8584        a: &CudaSlice<f32>,
8585        wa: &CudaSlice<f32>,
8586        b: &CudaSlice<f32>,
8587        w: &CudaSlice<f32>,
8588        res: &mut CudaSlice<f32>,
8589        dst: &mut CudaSlice<f32>,
8590        ncols: usize,
8591        nrows: usize,
8592        eps: f32,
8593    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8594        debug_assert!(ncols % 128 == 0);
8595        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8596        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8597        let (nc, e) = (ncols as i32, eps);
8598        if Self::pdl_on() {
8599            {
8600                use cudarc::driver::{DevicePtr, DevicePtrMut};
8601                let s = &self.gpu.stream();
8602                let (pa, _g0) = a.device_ptr(s);
8603                let (pwa, _g1) = wa.device_ptr(s);
8604                let (pb, _g2) = b.device_ptr(s);
8605                let (pw, _g3) = w.device_ptr(s);
8606                let (pr, _g4) = res.device_ptr_mut(s);
8607                let (pdst, _g5) = dst.device_ptr_mut(s);
8608                let (pq, _g6) = out_q.device_ptr_mut(s);
8609                let (pd, _g7) = out_d.device_ptr_mut(s);
8610                let mut ps = [
8611                    &pa as *const _ as *mut std::ffi::c_void,
8612                    &pwa as *const _ as *mut _,
8613                    &pb as *const _ as *mut _,
8614                    &pw as *const _ as *mut _,
8615                    &pr as *const _ as *mut _,
8616                    &pdst as *const _ as *mut _,
8617                    &pq as *const _ as *mut _,
8618                    &pd as *const _ as *mut _,
8619                    &nc as *const _ as *mut _,
8620                    &e as *const _ as *mut _,
8621                ];
8622                unsafe {
8623                    self.launch_pdl(
8624                        "rms_pre_add_rms_norm_q8z_f32",
8625                        (nrows as u32, 1, 1),
8626                        (rms_block(), 1, 1),
8627                        &mut ps,
8628                    )?;
8629                }
8630            }
8631            return Ok((out_q, out_d));
8632        }
8633        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8634        let cfg = LaunchConfig {
8635            grid_dim: (nrows as u32, 1, 1),
8636            block_dim: (rms_block(), 1, 1),
8637            shared_mem_bytes: 0,
8638        };
8639        let __s_b2 = self.gpu.stream();
8640        let mut b2 = __s_b2.launch_builder(&f);
8641        b2.arg(a)
8642            .arg(wa)
8643            .arg(b)
8644            .arg(w)
8645            .arg(&mut *res)
8646            .arg(&mut *dst)
8647            .arg(&mut out_q)
8648            .arg(&mut out_d)
8649            .arg(&nc)
8650            .arg(&e);
8651        unsafe {
8652            b2.launch(cfg)?;
8653        }
8654        Ok((out_q, out_d))
8655    }
8656
8657    /// Slot-fed twin of `rms_pre_add_rms_norm_q8z` (gemma4 pn-fold, slotted/graph arm):
8658    /// identical kernel, caller-owned outputs, PLAIN launch only — the dc_slotted capture
8659    /// body must stay attribute-free (the fused2_into precedent).
8660    #[allow(clippy::too_many_arguments)]
8661    pub fn rms_pre_add_rms_norm_q8z_into(
8662        &self,
8663        a: &CudaSlice<f32>,
8664        wa: &CudaSlice<f32>,
8665        b: &CudaSlice<f32>,
8666        w: &CudaSlice<f32>,
8667        res: &mut CudaSlice<f32>,
8668        dst: &mut CudaSlice<f32>,
8669        ncols: usize,
8670        nrows: usize,
8671        eps: f32,
8672        out_q: &mut CudaSlice<i8>,
8673        out_d: &mut CudaSlice<f32>,
8674    ) -> Result<(), Box<dyn std::error::Error>> {
8675        debug_assert!(ncols % 128 == 0);
8676        let (nc, e) = (ncols as i32, eps);
8677        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8678        let cfg = LaunchConfig {
8679            grid_dim: (nrows as u32, 1, 1),
8680            block_dim: (rms_block(), 1, 1),
8681            shared_mem_bytes: 0,
8682        };
8683        let __s_b = self.gpu.stream();
8684        let mut b2 = __s_b.launch_builder(&f);
8685        b2.arg(a)
8686            .arg(wa)
8687            .arg(b)
8688            .arg(w)
8689            .arg(&mut *res)
8690            .arg(&mut *dst)
8691            .arg(&mut *out_q)
8692            .arg(&mut *out_d)
8693            .arg(&nc)
8694            .arg(&e);
8695        unsafe {
8696            b2.launch(cfg)?;
8697        }
8698        Ok(())
8699    }
8700
8701    /// Slot-fed twin of `rms_pre_add_scale_rms_norm_q8_1` (gemma4 pn-fold exit, slotted
8702    /// arm): identical kernel, caller-owned outputs, PLAIN launch (capture-safe).
8703    #[allow(clippy::too_many_arguments)]
8704    pub fn rms_pre_add_scale_rms_norm_q8_1_into(
8705        &self,
8706        a: &CudaSlice<f32>,
8707        wa: &CudaSlice<f32>,
8708        b_in: &CudaSlice<f32>,
8709        c: f32,
8710        w: &CudaSlice<f32>,
8711        res: &mut CudaSlice<f32>,
8712        ncols: usize,
8713        nrows: usize,
8714        eps: f32,
8715        out_q: &mut CudaSlice<i8>,
8716        out_d: &mut CudaSlice<f32>,
8717    ) -> Result<(), Box<dyn std::error::Error>> {
8718        debug_assert!(ncols % 128 == 0);
8719        let (nc, e2) = (ncols as i32, eps);
8720        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
8721        let cfg = LaunchConfig {
8722            grid_dim: (nrows as u32, 1, 1),
8723            block_dim: (rms_block(), 1, 1),
8724            shared_mem_bytes: 0,
8725        };
8726        let __s_b = self.gpu.stream();
8727        let mut b2 = __s_b.launch_builder(&f);
8728        b2.arg(a)
8729            .arg(wa)
8730            .arg(b_in)
8731            .arg(&c)
8732            .arg(w)
8733            .arg(&mut *res)
8734            .arg(&mut *out_q)
8735            .arg(&mut *out_d)
8736            .arg(&nc)
8737            .arg(&e2);
8738        unsafe {
8739            b2.launch(cfg)?;
8740        }
8741        Ok(())
8742    }
8743
8744    /// gemma4 pn-fold seam (GAP-DIAGNOSIS verdict 7, the E4B glue backport): the dense
8745    /// decode/verify/slotted trio folds post_attn_norm into the tail entry
8746    /// (rms_pre_add_rms_norm[_q8z]) and post_ffw_norm into the residual exit
8747    /// (rms_pre_add_scale_rms_norm_q8_1). BITS-CHANGING vs the two-launch chain (the
8748    /// single-phase reduction's expansion rounding — E4B receipts); every arm moves
8749    /// together so decode == verify == graph parity holds BY CONSTRUCTION within either
8750    /// seam value. MEMRA_G4_PNFOLD=0 restores the unfused chain everywhere.
8751    pub fn g4_pnfold_on() -> bool {
8752        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8753        *ON.get_or_init(|| {
8754            std::env::var("MEMRA_G4_PNFOLD")
8755                .map(|v| v != "0")
8756                .unwrap_or(true)
8757        })
8758    }
8759
8760    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8761    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8762    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8763    pub fn build_q4_out_concat3(
8764        &self,
8765        w0: &crate::model::GpuTensor,
8766        w1: &crate::model::GpuTensor,
8767        w2: &crate::model::GpuTensor,
8768    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8769        use crate::model::GpuTensor;
8770        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8771            match w {
8772                GpuTensor::Quant {
8773                    qtype,
8774                    row_bytes,
8775                    rp,
8776                    ..
8777                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8778                _ => None,
8779            }
8780        };
8781        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8782        else {
8783            return Ok(None);
8784        };
8785        if rb0 != rb1
8786            || rb0 != rb2
8787            || w0.in_features() != w1.in_features()
8788            || w0.in_features() != w2.in_features()
8789        {
8790            return Ok(None);
8791        }
8792        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8793            match w {
8794                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8795                _ => unreachable!(),
8796            }
8797        }
8798        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8799        let total = rb0 * (o0 + o1 + o2);
8800        let mut cat = self.alloc_u8(total)?;
8801        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8802        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8803        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8804        Ok(Some(GpuTensor::Quant {
8805            bytes: cat,
8806            qtype: QT_Q4_0,
8807            row_bytes: rb0,
8808            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8809            scale: 1.0,
8810            rp: false,
8811            #[cfg(memra_cutlass)]
8812            cutlass: None,
8813            fp8: None,
8814            blk: None,
8815            rp4: None,
8816            f16: None,
8817        }))
8818    }
8819
8820    /// FULL-WIDTH-ROPE CONTRACT for the fused rms_norm+qkv+rope kernels
8821    /// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up).
8822    ///
8823    /// `rms_norm_qkv_rope_f32` / `_cat_f32` (`cu/kernels.cu`) and
8824    /// `rms_norm_qkv_rope_append_body` (`cu/flash_attn.cu`) take NO `n_dims`/`n_rot` argument.
8825    /// They compute `int half = ncols / 2` and rotate the FULL head width by construction — the
8826    /// standalone `rope_neox*` kernels take `n_dims` and early-return above it, these do not.
8827    ///
8828    /// Every call site today is a gemma-4 arm, where `n_rot == head_dim`, so nothing is wrong
8829    /// now. But this is the n_rot bug ONE FUSION away: fuse a partial-rotary arch onto these
8830    /// kernels (qwen3.5 = 64 rotary dims of a 256-wide head; step35 full-attn = 64 of 128) and
8831    /// 192 dims that must pass through unrotated get rotated silently — no error, no NaN, just a
8832    /// wrong model. The n_rot lane already paid for that class once, in the config derivation.
8833    ///
8834    /// So the fusions now take the layer's DERIVED rope width and refuse anything but full
8835    /// width. A future partial-rotary caller fails at its first launch with the geometry named
8836    /// instead of serving quietly wrong logits.
8837    fn full_width_rope_only(
8838        kernel: &str,
8839        n_rot: usize,
8840        head_dim: usize,
8841    ) -> Result<(), Box<dyn std::error::Error>> {
8842        if n_rot == head_dim {
8843            return Ok(());
8844        }
8845        Err(format!(
8846            "{kernel}: PARTIAL ROTARY REFUSED — n_rot {n_rot} != head_dim {head_dim}. This fused \
8847             rms_norm+qkv+rope kernel carries no n_dims parameter and rotates the full head \
8848             width (half = ncols/2), so it would rotate dims {n_rot}..{head_dim} that must pass \
8849             through unrotated. Use the split path (rms_norm_qkv + rope_neox/rope_neox2 with \
8850             n_dims={n_rot}), or add an n_dims early-return to the kernel and widen this guard."
8851        )
8852        .into())
8853    }
8854
8855    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8856    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8857    /// ([`Engine::full_width_rope_only`]).
8858    #[allow(clippy::too_many_arguments)]
8859    pub fn rms_norm_qkv_rope_cat(
8860        &self,
8861        qkv: &CudaSlice<f32>,
8862        wq: &CudaSlice<f32>,
8863        wk: &CudaSlice<f32>,
8864        wv: &CudaSlice<f32>,
8865        q: &mut CudaSlice<f32>,
8866        k: &mut CudaSlice<f32>,
8867        v: &mut CudaSlice<f32>,
8868        head_dim: usize,
8869        n_rot: usize,
8870        rq: usize,
8871        rk: usize,
8872        pos: &CudaSlice<i32>,
8873        nh_q: usize,
8874        nh_k: usize,
8875        base: f32,
8876        freq_scale: f32,
8877        ff: Option<&CudaSlice<f32>>,
8878        eps: f32,
8879    ) -> Result<(), Box<dyn std::error::Error>> {
8880        Self::full_width_rope_only("rms_norm_qkv_rope_cat", n_rot, head_dim)?;
8881        let rows = rq + rk + rk;
8882        let theta_scale = base.powf(-2.0 / head_dim as f32);
8883        let (nc, rqi, rki, nhq, nhk) = (
8884            head_dim as i32,
8885            rq as i32,
8886            rk as i32,
8887            nh_q as i32,
8888            nh_k as i32,
8889        );
8890        if Self::pdl_on() {
8891            use cudarc::driver::{DevicePtr, DevicePtrMut};
8892            let s = &self.gpu.stream();
8893            let (pqkv, _g0) = qkv.device_ptr(s);
8894            let (pwq, _g1) = wq.device_ptr(s);
8895            let (pwk, _g2) = wk.device_ptr(s);
8896            let (pwv, _g3) = wv.device_ptr(s);
8897            let (pq, _g4) = q.device_ptr_mut(s);
8898            let (pk, _g5) = k.device_ptr_mut(s);
8899            let (pv, _g6) = v.device_ptr_mut(s);
8900            let (ppos, _g7) = pos.device_ptr(s);
8901            let (pff, _g8) = match ff {
8902                Some(t) => {
8903                    let (p, g) = t.device_ptr(s);
8904                    (p, Some(g))
8905                }
8906                None => (0, None),
8907            };
8908            let mut ps = [
8909                &pqkv as *const _ as *mut std::ffi::c_void,
8910                &pwq as *const _ as *mut _,
8911                &pwk as *const _ as *mut _,
8912                &pwv as *const _ as *mut _,
8913                &pq as *const _ as *mut _,
8914                &pk as *const _ as *mut _,
8915                &pv as *const _ as *mut _,
8916                &nc as *const _ as *mut _,
8917                &rqi as *const _ as *mut _,
8918                &rki as *const _ as *mut _,
8919                &ppos as *const _ as *mut _,
8920                &nhq as *const _ as *mut _,
8921                &nhk as *const _ as *mut _,
8922                &theta_scale as *const _ as *mut _,
8923                &freq_scale as *const _ as *mut _,
8924                &pff as *const _ as *mut _,
8925                &eps as *const _ as *mut _,
8926            ];
8927            unsafe {
8928                self.launch_pdl(
8929                    "rms_norm_qkv_rope_cat_f32",
8930                    (rows as u32, 1, 1),
8931                    (rms_block(), 1, 1),
8932                    &mut ps,
8933                )?;
8934            }
8935            return Ok(());
8936        }
8937        let f = self.func("rms_norm_qkv_rope_cat_f32");
8938        let cfg = LaunchConfig {
8939            grid_dim: (rows as u32, 1, 1),
8940            block_dim: (rms_block(), 1, 1),
8941            shared_mem_bytes: 0,
8942        };
8943        let __s_b = self.gpu.stream();
8944        let mut b = __s_b.launch_builder(&f);
8945        match ff {
8946            Some(t) => {
8947                b.arg(qkv)
8948                    .arg(wq)
8949                    .arg(wk)
8950                    .arg(wv)
8951                    .arg(&mut *q)
8952                    .arg(&mut *k)
8953                    .arg(&mut *v)
8954                    .arg(&nc)
8955                    .arg(&rqi)
8956                    .arg(&rki)
8957                    .arg(pos)
8958                    .arg(&nhq)
8959                    .arg(&nhk)
8960                    .arg(&theta_scale)
8961                    .arg(&freq_scale)
8962                    .arg(t)
8963                    .arg(&eps);
8964                unsafe {
8965                    b.launch(cfg)?;
8966                }
8967            }
8968            None => {
8969                let null: u64 = 0;
8970                b.arg(qkv)
8971                    .arg(wq)
8972                    .arg(wk)
8973                    .arg(wv)
8974                    .arg(&mut *q)
8975                    .arg(&mut *k)
8976                    .arg(&mut *v)
8977                    .arg(&nc)
8978                    .arg(&rqi)
8979                    .arg(&rki)
8980                    .arg(pos)
8981                    .arg(&nhq)
8982                    .arg(&nhk)
8983                    .arg(&theta_scale)
8984                    .arg(&freq_scale)
8985                    .arg(&null)
8986                    .arg(&eps);
8987                unsafe {
8988                    b.launch(cfg)?;
8989                }
8990            }
8991        }
8992        Ok(())
8993    }
8994
8995    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8996    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8997    /// ([`Engine::full_width_rope_only`]).
8998    #[allow(clippy::too_many_arguments)]
8999    pub fn rms_norm_qkv_rope(
9000        &self,
9001        q0: &CudaSlice<f32>,
9002        k0: &CudaSlice<f32>,
9003        v0: &CudaSlice<f32>,
9004        wq: &CudaSlice<f32>,
9005        wk: &CudaSlice<f32>,
9006        wv: &CudaSlice<f32>,
9007        q: &mut CudaSlice<f32>,
9008        k: &mut CudaSlice<f32>,
9009        v: &mut CudaSlice<f32>,
9010        head_dim: usize,
9011        n_rot: usize,
9012        rq: usize,
9013        rk: usize,
9014        pos: &CudaSlice<i32>,
9015        nh_q: usize,
9016        nh_k: usize,
9017        base: f32,
9018        freq_scale: f32,
9019        ff: Option<&CudaSlice<f32>>,
9020        eps: f32,
9021    ) -> Result<(), Box<dyn std::error::Error>> {
9022        Self::full_width_rope_only("rms_norm_qkv_rope", n_rot, head_dim)?;
9023        let f = self.func("rms_norm_qkv_rope_f32");
9024        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
9025        let cfg = LaunchConfig {
9026            grid_dim: (rows as u32, 1, 1),
9027            block_dim: (rms_block(), 1, 1),
9028            shared_mem_bytes: 0,
9029        };
9030        let theta_scale = base.powf(-2.0 / head_dim as f32);
9031        let (nc, rqi, rki, nhq, nhk) = (
9032            head_dim as i32,
9033            rq as i32,
9034            rk as i32,
9035            nh_q as i32,
9036            nh_k as i32,
9037        );
9038        let __s_b = self.gpu.stream();
9039        let mut b = __s_b.launch_builder(&f);
9040        match ff {
9041            Some(t) => {
9042                b.arg(q0)
9043                    .arg(k0)
9044                    .arg(v0)
9045                    .arg(wq)
9046                    .arg(wk)
9047                    .arg(wv)
9048                    .arg(&mut *q)
9049                    .arg(&mut *k)
9050                    .arg(&mut *v)
9051                    .arg(&nc)
9052                    .arg(&rqi)
9053                    .arg(&rki)
9054                    .arg(pos)
9055                    .arg(&nhq)
9056                    .arg(&nhk)
9057                    .arg(&theta_scale)
9058                    .arg(&freq_scale)
9059                    .arg(t)
9060                    .arg(&eps);
9061                unsafe {
9062                    b.launch(cfg)?;
9063                }
9064            }
9065            None => {
9066                let null: u64 = 0;
9067                b.arg(q0)
9068                    .arg(k0)
9069                    .arg(v0)
9070                    .arg(wq)
9071                    .arg(wk)
9072                    .arg(wv)
9073                    .arg(&mut *q)
9074                    .arg(&mut *k)
9075                    .arg(&mut *v)
9076                    .arg(&nc)
9077                    .arg(&rqi)
9078                    .arg(&rki)
9079                    .arg(pos)
9080                    .arg(&nhq)
9081                    .arg(&nhk)
9082                    .arg(&theta_scale)
9083                    .arg(&freq_scale)
9084                    .arg(&null)
9085                    .arg(&eps);
9086                unsafe {
9087                    b.launch(cfg)?;
9088                }
9089            }
9090        }
9091        Ok(())
9092    }
9093
9094    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
9095    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
9096    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
9097    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
9098    /// ([`Engine::full_width_rope_only`]).
9099    #[allow(clippy::too_many_arguments)]
9100    pub fn rms_norm_qkv_rope_append_dc(
9101        &self,
9102        q0: &CudaSlice<f32>,
9103        k0: &CudaSlice<f32>,
9104        v0: &CudaSlice<f32>,
9105        wq: &CudaSlice<f32>,
9106        wk: &CudaSlice<f32>,
9107        wv: &CudaSlice<f32>,
9108        q: &mut CudaSlice<f32>,
9109        k: &mut CudaSlice<f32>,
9110        v: &mut CudaSlice<f32>,
9111        head_dim: usize,
9112        n_rot: usize,
9113        rq: usize,
9114        rk: usize,
9115        pos: &CudaSlice<i32>,
9116        nh_q: usize,
9117        nh_k: usize,
9118        base: f32,
9119        freq_scale: f32,
9120        ff: Option<&CudaSlice<f32>>,
9121        eps: f32,
9122        kc: &mut CudaSlice<u8>,
9123        vc: &mut CudaSlice<u8>,
9124        t_dev: &CudaSlice<i32>,
9125        k_tok_bytes: usize,
9126        v_tok_bytes: usize,
9127        g: bool,
9128    ) -> Result<(), Box<dyn std::error::Error>> {
9129        Self::full_width_rope_only("rms_norm_qkv_rope_append_dc", n_rot, head_dim)?;
9130        let rows = rq + rk + rk;
9131        let theta_scale = base.powf(-2.0 / head_dim as f32);
9132        let (nc, rqi, rki, nhq, nhk) = (
9133            head_dim as i32,
9134            rq as i32,
9135            rk as i32,
9136            nh_q as i32,
9137            nh_k as i32,
9138        );
9139        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9140        if Self::pdl_on() && Self::pdl_wb_on() {
9141            use cudarc::driver::{DevicePtr, DevicePtrMut};
9142            let s = &self.gpu.stream();
9143            let (p0, _a0) = q0.device_ptr(s);
9144            let (p1, _a1) = k0.device_ptr(s);
9145            let (p2, _a2) = v0.device_ptr(s);
9146            let (pwq, _a3) = wq.device_ptr(s);
9147            let (pwk, _a4) = wk.device_ptr(s);
9148            let (pwv, _a5) = wv.device_ptr(s);
9149            let (pq, _a6) = q.device_ptr_mut(s);
9150            let (pk, _a7) = k.device_ptr_mut(s);
9151            let (pv, _a8) = v.device_ptr_mut(s);
9152            let (pp, _a9) = pos.device_ptr(s);
9153            let pff: u64 = match ff {
9154                Some(t) => {
9155                    let (p, _gg) = t.device_ptr(s);
9156                    p as u64
9157                }
9158                None => 0,
9159            };
9160            let (pkc, _a10) = kc.device_ptr_mut(s);
9161            let (pvc, _a11) = vc.device_ptr_mut(s);
9162            let (pt, _a12) = t_dev.device_ptr(s);
9163            let mut ps = [
9164                &p0 as *const _ as *mut std::ffi::c_void,
9165                &p1 as *const _ as *mut _,
9166                &p2 as *const _ as *mut _,
9167                &pwq as *const _ as *mut _,
9168                &pwk as *const _ as *mut _,
9169                &pwv as *const _ as *mut _,
9170                &pq as *const _ as *mut _,
9171                &pk as *const _ as *mut _,
9172                &pv as *const _ as *mut _,
9173                &nc as *const _ as *mut _,
9174                &rqi as *const _ as *mut _,
9175                &rki as *const _ as *mut _,
9176                &pp as *const _ as *mut _,
9177                &nhq as *const _ as *mut _,
9178                &nhk as *const _ as *mut _,
9179                &theta_scale as *const _ as *mut _,
9180                &freq_scale as *const _ as *mut _,
9181                &pff as *const _ as *mut _,
9182                &eps as *const _ as *mut _,
9183                &pkc as *const _ as *mut _,
9184                &pvc as *const _ as *mut _,
9185                &pt as *const _ as *mut _,
9186                &ktb as *const _ as *mut _,
9187                &vtb as *const _ as *mut _,
9188            ];
9189            unsafe {
9190                self.launch_pdl_flash(
9191                    g,
9192                    "rms_norm_qkv_rope_append_dc_f32",
9193                    (rows as u32, 1, 1),
9194                    (rms_block(), 1, 1),
9195                    0,
9196                    &mut ps,
9197                )?;
9198            }
9199            return Ok(());
9200        }
9201        let f = if g {
9202            self.func_g("rms_norm_qkv_rope_append_dc_f32")
9203        } else {
9204            self.func("rms_norm_qkv_rope_append_dc_f32")
9205        };
9206        let cfg = LaunchConfig {
9207            grid_dim: (rows as u32, 1, 1),
9208            block_dim: (rms_block(), 1, 1),
9209            shared_mem_bytes: 0,
9210        };
9211        let __s_b = self.gpu.stream();
9212        let mut b = __s_b.launch_builder(&f);
9213        match ff {
9214            Some(t) => {
9215                b.arg(q0)
9216                    .arg(k0)
9217                    .arg(v0)
9218                    .arg(wq)
9219                    .arg(wk)
9220                    .arg(wv)
9221                    .arg(&mut *q)
9222                    .arg(&mut *k)
9223                    .arg(&mut *v)
9224                    .arg(&nc)
9225                    .arg(&rqi)
9226                    .arg(&rki)
9227                    .arg(pos)
9228                    .arg(&nhq)
9229                    .arg(&nhk)
9230                    .arg(&theta_scale)
9231                    .arg(&freq_scale)
9232                    .arg(t)
9233                    .arg(&eps)
9234                    .arg(&mut *kc)
9235                    .arg(&mut *vc)
9236                    .arg(t_dev)
9237                    .arg(&ktb)
9238                    .arg(&vtb);
9239                unsafe {
9240                    b.launch(cfg)?;
9241                }
9242            }
9243            None => {
9244                let null: u64 = 0;
9245                b.arg(q0)
9246                    .arg(k0)
9247                    .arg(v0)
9248                    .arg(wq)
9249                    .arg(wk)
9250                    .arg(wv)
9251                    .arg(&mut *q)
9252                    .arg(&mut *k)
9253                    .arg(&mut *v)
9254                    .arg(&nc)
9255                    .arg(&rqi)
9256                    .arg(&rki)
9257                    .arg(pos)
9258                    .arg(&nhq)
9259                    .arg(&nhk)
9260                    .arg(&theta_scale)
9261                    .arg(&freq_scale)
9262                    .arg(&null)
9263                    .arg(&eps)
9264                    .arg(&mut *kc)
9265                    .arg(&mut *vc)
9266                    .arg(t_dev)
9267                    .arg(&ktb)
9268                    .arg(&vtb);
9269                unsafe {
9270                    b.launch(cfg)?;
9271                }
9272            }
9273        }
9274        Ok(())
9275    }
9276
9277    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
9278    /// Host-len twin of `rms_norm_qkv_rope_append_dc` (zoo-fusion arc): the eager decode
9279    /// arm tracks kv length on host (`kvl.len`), so the append slot rides the launch arg
9280    /// instead of the device counter. Kernel body is SHARED with the _dc entry (one
9281    /// inlined body — bit-identical to the rms_norm_qkv_rope + append pair it replaces,
9282    /// same law as the dc fold). `n_rot` is the layer's derived rotary width and MUST equal
9283    /// `head_dim` ([`Engine::full_width_rope_only`]).
9284    #[allow(clippy::too_many_arguments)]
9285    pub fn rms_norm_qkv_rope_append(
9286        &self,
9287        q0: &CudaSlice<f32>,
9288        k0: &CudaSlice<f32>,
9289        v0: &CudaSlice<f32>,
9290        wq: &CudaSlice<f32>,
9291        wk: &CudaSlice<f32>,
9292        wv: &CudaSlice<f32>,
9293        q: &mut CudaSlice<f32>,
9294        k: &mut CudaSlice<f32>,
9295        v: &mut CudaSlice<f32>,
9296        head_dim: usize,
9297        n_rot: usize,
9298        rq: usize,
9299        rk: usize,
9300        pos: &CudaSlice<i32>,
9301        nh_q: usize,
9302        nh_k: usize,
9303        base: f32,
9304        freq_scale: f32,
9305        ff: Option<&CudaSlice<f32>>,
9306        eps: f32,
9307        kc: &mut CudaSlice<u8>,
9308        vc: &mut CudaSlice<u8>,
9309        t: usize,
9310        k_tok_bytes: usize,
9311        v_tok_bytes: usize,
9312        g: bool,
9313    ) -> Result<(), Box<dyn std::error::Error>> {
9314        Self::full_width_rope_only("rms_norm_qkv_rope_append", n_rot, head_dim)?;
9315        let rows = rq + rk + rk;
9316        let theta_scale = base.powf(-2.0 / head_dim as f32);
9317        let (nc, rqi, rki, nhq, nhk) = (
9318            head_dim as i32,
9319            rq as i32,
9320            rk as i32,
9321            nh_q as i32,
9322            nh_k as i32,
9323        );
9324        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9325        let ti = t as i32;
9326        if Self::pdl_on() && Self::pdl_wb_on() {
9327            use cudarc::driver::{DevicePtr, DevicePtrMut};
9328            let s = &self.gpu.stream();
9329            let (p0, _a0) = q0.device_ptr(s);
9330            let (p1, _a1) = k0.device_ptr(s);
9331            let (p2, _a2) = v0.device_ptr(s);
9332            let (pwq, _a3) = wq.device_ptr(s);
9333            let (pwk, _a4) = wk.device_ptr(s);
9334            let (pwv, _a5) = wv.device_ptr(s);
9335            let (pq, _a6) = q.device_ptr_mut(s);
9336            let (pk, _a7) = k.device_ptr_mut(s);
9337            let (pv, _a8) = v.device_ptr_mut(s);
9338            let (pp, _a9) = pos.device_ptr(s);
9339            let pff: u64 = match ff {
9340                Some(t) => {
9341                    let (p, _gg) = t.device_ptr(s);
9342                    p as u64
9343                }
9344                None => 0,
9345            };
9346            let (pkc, _a10) = kc.device_ptr_mut(s);
9347            let (pvc, _a11) = vc.device_ptr_mut(s);
9348            let mut ps = [
9349                &p0 as *const _ as *mut std::ffi::c_void,
9350                &p1 as *const _ as *mut _,
9351                &p2 as *const _ as *mut _,
9352                &pwq as *const _ as *mut _,
9353                &pwk as *const _ as *mut _,
9354                &pwv as *const _ as *mut _,
9355                &pq as *const _ as *mut _,
9356                &pk as *const _ as *mut _,
9357                &pv as *const _ as *mut _,
9358                &nc as *const _ as *mut _,
9359                &rqi as *const _ as *mut _,
9360                &rki as *const _ as *mut _,
9361                &pp as *const _ as *mut _,
9362                &nhq as *const _ as *mut _,
9363                &nhk as *const _ as *mut _,
9364                &theta_scale as *const _ as *mut _,
9365                &freq_scale as *const _ as *mut _,
9366                &pff as *const _ as *mut _,
9367                &eps as *const _ as *mut _,
9368                &pkc as *const _ as *mut _,
9369                &pvc as *const _ as *mut _,
9370                &ti as *const _ as *mut _,
9371                &ktb as *const _ as *mut _,
9372                &vtb as *const _ as *mut _,
9373            ];
9374            unsafe {
9375                self.launch_pdl_flash(
9376                    g,
9377                    "rms_norm_qkv_rope_append_f32",
9378                    (rows as u32, 1, 1),
9379                    (rms_block(), 1, 1),
9380                    0,
9381                    &mut ps,
9382                )?;
9383            }
9384            return Ok(());
9385        }
9386        let f = if g {
9387            self.func_g("rms_norm_qkv_rope_append_f32")
9388        } else {
9389            self.func("rms_norm_qkv_rope_append_f32")
9390        };
9391        let cfg = LaunchConfig {
9392            grid_dim: (rows as u32, 1, 1),
9393            block_dim: (rms_block(), 1, 1),
9394            shared_mem_bytes: 0,
9395        };
9396        let __s_b = self.gpu.stream();
9397        let mut b = __s_b.launch_builder(&f);
9398        let null: u64 = 0;
9399        b.arg(q0)
9400            .arg(k0)
9401            .arg(v0)
9402            .arg(wq)
9403            .arg(wk)
9404            .arg(wv)
9405            .arg(&mut *q)
9406            .arg(&mut *k)
9407            .arg(&mut *v)
9408            .arg(&nc)
9409            .arg(&rqi)
9410            .arg(&rki)
9411            .arg(pos)
9412            .arg(&nhq)
9413            .arg(&nhk)
9414            .arg(&theta_scale)
9415            .arg(&freq_scale);
9416        match ff {
9417            Some(t) => {
9418                b.arg(t);
9419            }
9420            None => {
9421                b.arg(&null);
9422            }
9423        }
9424        b.arg(&eps)
9425            .arg(&mut *kc)
9426            .arg(&mut *vc)
9427            .arg(&ti)
9428            .arg(&ktb)
9429            .arg(&vtb);
9430        unsafe {
9431            b.launch(cfg)?;
9432        }
9433        Ok(())
9434    }
9435
9436    pub fn add_q8_1(
9437        &self,
9438        a: &CudaSlice<f32>,
9439        b: &CudaSlice<f32>,
9440        res: &mut CudaSlice<f32>,
9441        ncols: usize,
9442        nrows: usize,
9443    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9444        debug_assert!(ncols % 128 == 0);
9445        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9446        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9447        let f = self.func("add_q8_1_f32");
9448        let cfg = LaunchConfig {
9449            grid_dim: (nrows as u32, 1, 1),
9450            block_dim: (rms_block(), 1, 1),
9451            shared_mem_bytes: 0,
9452        };
9453        let nc = ncols as i32;
9454        let __s_b2 = self.gpu.stream();
9455        let mut b2 = __s_b2.launch_builder(&f);
9456        b2.arg(a)
9457            .arg(b)
9458            .arg(&mut *res)
9459            .arg(&mut out_q)
9460            .arg(&mut out_d)
9461            .arg(&nc);
9462        unsafe {
9463            b2.launch(cfg)?;
9464        }
9465        Ok((out_q, out_d))
9466    }
9467
9468    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
9469    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
9470    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
9471    pub fn rms_pre_add_q8_1(
9472        &self,
9473        a: &CudaSlice<f32>,
9474        wa: &CudaSlice<f32>,
9475        b: &CudaSlice<f32>,
9476        res: &mut CudaSlice<f32>,
9477        ncols: usize,
9478        nrows: usize,
9479        eps: f32,
9480    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9481        debug_assert!(ncols % 128 == 0);
9482        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9483        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9484        let f = self.func("rms_pre_add_q8_1_f32");
9485        let cfg = LaunchConfig {
9486            grid_dim: (nrows as u32, 1, 1),
9487            block_dim: (rms_block(), 1, 1),
9488            shared_mem_bytes: 0,
9489        };
9490        let (nc, ep) = (ncols as i32, eps);
9491        let __s_b2 = self.gpu.stream();
9492        let mut b2 = __s_b2.launch_builder(&f);
9493        b2.arg(a)
9494            .arg(wa)
9495            .arg(b)
9496            .arg(&mut *res)
9497            .arg(&mut out_q)
9498            .arg(&mut out_d)
9499            .arg(&nc)
9500            .arg(&ep);
9501        unsafe {
9502            b2.launch(cfg)?;
9503        }
9504        Ok((out_q, out_d))
9505    }
9506
9507    /// L2 norm per row (head_dim), no weight.
9508    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
9509    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
9510    pub fn l2_v2_on(ncols: usize) -> bool {
9511        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
9512    }
9513
9514    pub fn l2_norm_pp(
9515        &self,
9516        x: &CudaSlice<f32>,
9517        dst: &mut CudaSlice<f32>,
9518        dst16: Option<&mut CudaSlice<u8>>,
9519        ncols: usize,
9520        nrows: usize,
9521        eps: f32,
9522    ) -> Result<(), Box<dyn std::error::Error>> {
9523        if Self::l2_v2_on(ncols) {
9524            let f = self.func("l2_norm_pp_v2_f32");
9525            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
9526            let cfg = LaunchConfig {
9527                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
9528                block_dim: (256, 1, 1),
9529                shared_mem_bytes: 0,
9530            };
9531            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9532            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
9533            let d16: u64 = match dst16 {
9534                Some(d) => self.addr_u8(d),
9535                None => 0,
9536            };
9537            let __s_b = self.gpu.stream();
9538            let mut b = __s_b.launch_builder(&f);
9539            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
9540            unsafe {
9541                b.launch(cfg)?;
9542            }
9543            return Ok(());
9544        }
9545        self.l2_norm(x, dst, ncols, nrows, eps)
9546    }
9547
9548    pub fn l2_norm(
9549        &self,
9550        x: &CudaSlice<f32>,
9551        dst: &mut CudaSlice<f32>,
9552        ncols: usize,
9553        nrows: usize,
9554        eps: f32,
9555    ) -> Result<(), Box<dyn std::error::Error>> {
9556        let f = self.func("l2_norm_f32");
9557        let cfg = LaunchConfig {
9558            grid_dim: (nrows as u32, 1, 1),
9559            block_dim: (256, 1, 1),
9560            shared_mem_bytes: 0,
9561        };
9562        let (nc, e) = (ncols as i32, eps);
9563        let __s_b = self.gpu.stream();
9564        let mut b = __s_b.launch_builder(&f);
9565        b.arg(x).arg(dst).arg(&nc).arg(&e);
9566        unsafe {
9567            b.launch(cfg)?;
9568        }
9569        Ok(())
9570    }
9571
9572    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9573    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9574    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9575    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9576    /// propagate through gdn_scan and flip argmax on marginal logits.
9577    pub fn l2_norm_decode(
9578        &self,
9579        x: &CudaSlice<f32>,
9580        dst: &mut CudaSlice<f32>,
9581        ncols: usize,
9582        nrows: usize,
9583        eps: f32,
9584    ) -> Result<(), Box<dyn std::error::Error>> {
9585        let f = self.func("l2_norm_f32");
9586        let cfg = LaunchConfig {
9587            grid_dim: (nrows as u32, 1, 1),
9588            block_dim: (32, 1, 1),
9589            shared_mem_bytes: 0,
9590        };
9591        let (nc, e) = (ncols as i32, eps);
9592        let __s_b = self.gpu.stream();
9593        let mut b = __s_b.launch_builder(&f);
9594        b.arg(x).arg(dst).arg(&nc).arg(&e);
9595        unsafe {
9596            b.launch(cfg)?;
9597        }
9598        Ok(())
9599    }
9600
9601    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9602    pub fn rope_neox(
9603        &self,
9604        x: &mut CudaSlice<f32>,
9605        pos: &CudaSlice<i32>,
9606        head_dim: usize,
9607        n_dims: usize,
9608        n_heads: usize,
9609        n_tokens: usize,
9610        freq_base: f32,
9611        freq_scale: f32,
9612    ) -> Result<(), Box<dyn std::error::Error>> {
9613        let f = self.func("rope_neox_f32");
9614        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9615        let grid = (n_heads * n_tokens) as u32;
9616        let cfg = LaunchConfig {
9617            grid_dim: (grid, 1, 1),
9618            block_dim: ((head_dim / 2) as u32, 1, 1),
9619            shared_mem_bytes: 0,
9620        };
9621        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9622        let __s_b = self.gpu.stream();
9623        let mut b = __s_b.launch_builder(&f);
9624        b.arg(x)
9625            .arg(pos)
9626            .arg(&hd)
9627            .arg(&nd)
9628            .arg(&nh)
9629            .arg(&theta_scale)
9630            .arg(&freq_scale);
9631        unsafe {
9632            b.launch(cfg)?;
9633        }
9634        Ok(())
9635    }
9636
9637    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9638    pub fn rope_neox_ff(
9639        &self,
9640        x: &mut CudaSlice<f32>,
9641        pos: &CudaSlice<i32>,
9642        head_dim: usize,
9643        n_dims: usize,
9644        n_heads: usize,
9645        n_tokens: usize,
9646        freq_base: f32,
9647        freq_scale: f32,
9648        ff: &CudaSlice<f32>,
9649    ) -> Result<(), Box<dyn std::error::Error>> {
9650        let f = self.func("rope_neox_ff_f32");
9651        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9652        let grid = (n_heads * n_tokens) as u32;
9653        let cfg = LaunchConfig {
9654            grid_dim: (grid, 1, 1),
9655            block_dim: ((head_dim / 2) as u32, 1, 1),
9656            shared_mem_bytes: 0,
9657        };
9658        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9659        let __s_b = self.gpu.stream();
9660        let mut b = __s_b.launch_builder(&f);
9661        b.arg(x)
9662            .arg(pos)
9663            .arg(&hd)
9664            .arg(&nd)
9665            .arg(&nh)
9666            .arg(&theta_scale)
9667            .arg(&freq_scale)
9668            .arg(ff);
9669        unsafe {
9670            b.launch(cfg)?;
9671        }
9672        Ok(())
9673    }
9674
9675    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9676    #[allow(clippy::too_many_arguments)]
9677    pub fn rope_neox2(
9678        &self,
9679        q: &mut CudaSlice<f32>,
9680        k: &mut CudaSlice<f32>,
9681        pos: &CudaSlice<i32>,
9682        head_dim: usize,
9683        n_dims: usize,
9684        nh_q: usize,
9685        nh_k: usize,
9686        n_tokens: usize,
9687        freq_base: f32,
9688        freq_scale: f32,
9689        ff: Option<&CudaSlice<f32>>,
9690    ) -> Result<(), Box<dyn std::error::Error>> {
9691        let f = self.func("rope_neox2_f32");
9692        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9693        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9694        let cfg = LaunchConfig {
9695            grid_dim: (grid, 1, 1),
9696            block_dim: ((head_dim / 2) as u32, 1, 1),
9697            shared_mem_bytes: 0,
9698        };
9699        let (hd, nd, nq, nk, nt) = (
9700            head_dim as i32,
9701            n_dims as i32,
9702            nh_q as i32,
9703            nh_k as i32,
9704            n_tokens as i32,
9705        );
9706        let __s_b = self.gpu.stream();
9707        let mut b = __s_b.launch_builder(&f);
9708        b.arg(q)
9709            .arg(k)
9710            .arg(pos)
9711            .arg(&hd)
9712            .arg(&nd)
9713            .arg(&nq)
9714            .arg(&nk)
9715            .arg(&nt)
9716            .arg(&theta_scale)
9717            .arg(&freq_scale);
9718        match ff {
9719            Some(ffv) => {
9720                b.arg(ffv);
9721                unsafe {
9722                    b.launch(cfg)?;
9723                }
9724            }
9725            None => {
9726                let null: u64 = 0;
9727                b.arg(&null);
9728                unsafe {
9729                    b.launch(cfg)?;
9730                }
9731            }
9732        }
9733        Ok(())
9734    }
9735
9736    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9737    pub fn gelu_tanh_mul(
9738        &self,
9739        gate: &CudaSlice<f32>,
9740        up: &CudaSlice<f32>,
9741        dst: &mut CudaSlice<f32>,
9742        n: usize,
9743    ) -> Result<(), Box<dyn std::error::Error>> {
9744        let f = self.func("gelu_tanh_mul_f32");
9745        let cfg = LaunchConfig::for_num_elems(n as u32);
9746        let ni = n as i32;
9747        let __s_b = self.gpu.stream();
9748        let mut b = __s_b.launch_builder(&f);
9749        b.arg(gate).arg(up).arg(dst).arg(&ni);
9750        unsafe {
9751            b.launch(cfg)?;
9752        }
9753        Ok(())
9754    }
9755
9756    pub fn silu_mul(
9757        &self,
9758        gate: &CudaSlice<f32>,
9759        up: &CudaSlice<f32>,
9760        dst: &mut CudaSlice<f32>,
9761        n: usize,
9762    ) -> Result<(), Box<dyn std::error::Error>> {
9763        let f = self.func("silu_mul_f32");
9764        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9765        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9766        let ni = n as i32;
9767        let __s_b = self.gpu.stream();
9768        let mut b = __s_b.launch_builder(&f);
9769        b.arg(gate).arg(up).arg(dst).arg(&ni);
9770        unsafe {
9771            b.launch(cfg)?;
9772        }
9773        Ok(())
9774    }
9775
9776    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9777    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9778    pub fn silu_mul_f16out(
9779        &self,
9780        gate: &CudaSlice<f32>,
9781        up: &CudaSlice<f32>,
9782        dst: &mut CudaSlice<f32>,
9783        dst16: &mut CudaSlice<u8>,
9784        n: usize,
9785    ) -> Result<(), Box<dyn std::error::Error>> {
9786        let f = self.func("silu_mul_f16out_f32");
9787        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9788        let ni = n as i32;
9789        let __s_b = self.gpu.stream();
9790        let mut b = __s_b.launch_builder(&f);
9791        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9792        unsafe {
9793            b.launch(cfg)?;
9794        }
9795        Ok(())
9796    }
9797
9798    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9799    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9800    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9801    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9802    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9803    /// launches per dense FFN layer (the gate+up post-matmul scales).
9804    pub fn silu_mul_scaled(
9805        &self,
9806        gate: &CudaSlice<f32>,
9807        up: &CudaSlice<f32>,
9808        gs: f32,
9809        us: f32,
9810        dst: &mut CudaSlice<f32>,
9811        n: usize,
9812    ) -> Result<(), Box<dyn std::error::Error>> {
9813        let f = self.func("silu_mul_scaled_f32");
9814        let cfg = LaunchConfig::for_num_elems(n as u32);
9815        let ni = n as i32;
9816        let (gsf, usf) = (gs, us);
9817        let __s_b = self.gpu.stream();
9818        let mut b = __s_b.launch_builder(&f);
9819        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9820        unsafe {
9821            b.launch(cfg)?;
9822        }
9823        Ok(())
9824    }
9825
9826    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9827    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9828    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9829    #[allow(clippy::too_many_arguments)]
9830    pub fn swigluoai_mul_scaled(
9831        &self,
9832        gate: &CudaSlice<f32>,
9833        up: &CudaSlice<f32>,
9834        gs: f32,
9835        us: f32,
9836        alpha: f32,
9837        limit: f32,
9838        dst: &mut CudaSlice<f32>,
9839        n: usize,
9840    ) -> Result<(), Box<dyn std::error::Error>> {
9841        let f = self.func("swigluoai_mul_scaled_f32");
9842        let cfg = LaunchConfig::for_num_elems(n as u32);
9843        let ni = n as i32;
9844        let __s_b = self.gpu.stream();
9845        let mut b = __s_b.launch_builder(&f);
9846        b.arg(gate)
9847            .arg(up)
9848            .arg(&gs)
9849            .arg(&us)
9850            .arg(&alpha)
9851            .arg(&limit)
9852            .arg(dst)
9853            .arg(&ni);
9854        unsafe {
9855            b.launch(cfg)?;
9856        }
9857        Ok(())
9858    }
9859
9860    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9861    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9862    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9863    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9864    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9865    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9866    /// n must be a multiple of 32 (n_ff always is).
9867    pub fn silu_mul_scaled_q8_1(
9868        &self,
9869        gate: &CudaSlice<f32>,
9870        up: &CudaSlice<f32>,
9871        gs: f32,
9872        us: f32,
9873        n: usize,
9874    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9875        let f = self.func("silu_mul_scaled_q8_1");
9876        let nblk = n / 32;
9877        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9878        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9879        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9880        let cfg = LaunchConfig::for_num_elems(n as u32);
9881        let (gsf, usf, ni) = (gs, us, n as i32);
9882        let __s_b = self.gpu.stream();
9883        let mut b = __s_b.launch_builder(&f);
9884        b.arg(gate)
9885            .arg(up)
9886            .arg(&gsf)
9887            .arg(&usf)
9888            .arg(&mut aq)
9889            .arg(&mut ad)
9890            .arg(&ni);
9891        unsafe {
9892            b.launch(cfg)?;
9893        }
9894        Ok((aq, ad))
9895    }
9896
9897    pub fn add(
9898        &self,
9899        a: &CudaSlice<f32>,
9900        b_in: &CudaSlice<f32>,
9901        dst: &mut CudaSlice<f32>,
9902        n: usize,
9903    ) -> Result<(), Box<dyn std::error::Error>> {
9904        let f = self.func("add_f32");
9905        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9906        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9907        let ni = n as i32;
9908        let __s_bld = self.gpu.stream();
9909        let mut bld = __s_bld.launch_builder(&f);
9910        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9911        unsafe {
9912            bld.launch(cfg)?;
9913        }
9914        Ok(())
9915    }
9916
9917    pub fn mul(
9918        &self,
9919        a: &CudaSlice<f32>,
9920        b_in: &CudaSlice<f32>,
9921        dst: &mut CudaSlice<f32>,
9922        n: usize,
9923    ) -> Result<(), Box<dyn std::error::Error>> {
9924        let f = self.func("mul_f32");
9925        let cfg = LaunchConfig::for_num_elems(n as u32);
9926        let ni = n as i32;
9927        let __s_bld = self.gpu.stream();
9928        let mut bld = __s_bld.launch_builder(&f);
9929        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9930        unsafe {
9931            bld.launch(cfg)?;
9932        }
9933        Ok(())
9934    }
9935
9936    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9937    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9938    pub fn matmul(
9939        &self,
9940        w: &crate::model::GpuTensor,
9941        x: &CudaSlice<f32>,
9942        m: usize,
9943    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9944        use crate::model::GpuTensor;
9945        let in_f = w.in_features();
9946        let out_f = w.out_features();
9947        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9948        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9949        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9950        // gives nothing). Quantize the activation once here then call the GEMM.
9951        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9952        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9953        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9954        #[allow(non_snake_case)]
9955        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9956        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9957        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9958            usize::MAX
9959        } else {
9960            16usize
9961        };
9962
9963        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9964        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9965        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9966        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9967        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9968        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9969        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9970        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9971        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9972        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9973        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9974        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9975        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9976        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9977        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9978        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9979        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9980        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9981        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9982        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9983        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9984        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9985        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9986        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9987        if m >= GEMM_M_THRESHOLD {
9988            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9989                return Ok(y);
9990            }
9991            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9992            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9993            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9994            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9995            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9996            // tile defaults differently by operand source.
9997            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9998                return Ok(y);
9999            }
10000            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
10001            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
10002            if let Some(y) = self.try_f16_gemm(w, x, m)? {
10003                return Ok(y);
10004            }
10005        }
10006        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
10007        // m threshold the rest of this method uses:
10008        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
10009        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
10010        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
10011        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
10012        //     across every tier by construction with no batched twin needed.
10013        //
10014        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
10015        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
10016        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
10017        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
10018        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
10019        // arms is what makes sure it never gets there.
10020        if let GpuTensor::Quant { qtype, .. } = w {
10021            if *qtype == QT_F8_E4M3_BLK {
10022                if m >= GEMM_M_THRESHOLD {
10023                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
10024                        return Ok(y);
10025                    }
10026                }
10027                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10028                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10029                    return Ok(y);
10030                }
10031            }
10032        }
10033        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
10034            return self.qmatvec_mmq(w, x, m);
10035        }
10036        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
10037            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10038            return self.qmatvec_gemm(w, &aq, &ad, m);
10039        }
10040        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
10041        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
10042        if m >= GEMM_M_THRESHOLD {
10043            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
10044                return Ok(y);
10045            }
10046        }
10047        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
10048        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
10049        // to Stage-A f32-dequant (the correctness oracle path).
10050        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
10051        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
10052        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
10053        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
10054        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
10055        if m == 1 && fast {
10056            if let GpuTensor::Quant {
10057                bytes,
10058                qtype,
10059                row_bytes,
10060                rp,
10061                rp4,
10062                scale,
10063                ..
10064            } = w
10065            {
10066                if self.mmvq_supports(*qtype) {
10067                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
10068                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
10069                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
10070                    let (bytes, rp) = match rp4 {
10071                        Some(m4) => (m4, true),
10072                        None => (bytes, *rp),
10073                    };
10074                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10075                    return self.qmatvec_mmvq(
10076                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
10077                    );
10078                }
10079            }
10080        }
10081        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
10082        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
10083        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
10084        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
10085        // block below. MEMRA_NO_BATCHED -> per-m path.
10086        //
10087        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
10088        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
10089        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
10090        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
10091        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
10092        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
10093        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
10094        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
10095        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
10096        if (2..=16).contains(&m)
10097            && fast
10098            && std::env::var("MEMRA_NO_BATCHED").is_err()
10099            && (m <= 4 || Self::b8_enabled())
10100        {
10101            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
10102            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
10103            // is present (rp4) — the mirror pick below then routes to the _rp family.
10104            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
10105            // because the native e4m3 row layout is already aligned and needs no mirror.
10106            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
10107            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
10108            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
10109            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
10110            let m_ok = m <= 8
10111                || matches!(w, GpuTensor::Quant { qtype, .. }
10112                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
10113                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
10114            if m_ok {
10115                if let GpuTensor::Quant {
10116                    bytes,
10117                    qtype,
10118                    row_bytes,
10119                    rp,
10120                    rp4,
10121                    ..
10122                } = w
10123                {
10124                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
10125                        let (bytes, rp) = match rp4 {
10126                            Some(m4) => (m4, true),
10127                            None => (bytes, *rp),
10128                        };
10129                        let mcols = Self::batched_mcols(m);
10130                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10131                        let mut y = self.qmatvec_mmvq_batched(
10132                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
10133                        )?;
10134                        if let GpuTensor::Quant { scale, .. } = w {
10135                            if *scale != 1.0 {
10136                                self.scale_inplace(&mut y, *scale, m * out_f)?;
10137                            }
10138                        }
10139                        return Ok(y);
10140                    }
10141                }
10142            }
10143        }
10144        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
10145        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
10146        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
10147        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
10148        // for this dtype, so the generic match below must never see it under `fast`.
10149        if fast {
10150            if let GpuTensor::Quant {
10151                bytes,
10152                qtype,
10153                row_bytes,
10154                scale,
10155                ..
10156            } = w
10157            {
10158                if *qtype == QT_F8_E4M3 {
10159                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10160                    return self.qmatvec_mmvq(
10161                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
10162                    );
10163                }
10164            }
10165        }
10166        let mut y = match w {
10167            GpuTensor::Quant {
10168                bytes,
10169                qtype,
10170                row_bytes,
10171                ..
10172            } if fast && *qtype == QT_Q8_0 => {
10173                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10174            }
10175            GpuTensor::Quant {
10176                bytes,
10177                qtype,
10178                row_bytes,
10179                ..
10180            } if fast && *qtype == QT_Q4_K => {
10181                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10182            }
10183            GpuTensor::Quant {
10184                bytes,
10185                qtype,
10186                row_bytes,
10187                ..
10188            } if fast && *qtype == QT_Q6_K => {
10189                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10190            }
10191            GpuTensor::Quant {
10192                bytes,
10193                qtype,
10194                row_bytes,
10195                ..
10196            } if fast && *qtype == QT_Q5_K => {
10197                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10198            }
10199            GpuTensor::Quant {
10200                bytes,
10201                qtype,
10202                row_bytes,
10203                ..
10204            } if fast && *qtype == QT_Q3_K => {
10205                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10206            }
10207            GpuTensor::Quant {
10208                bytes,
10209                qtype,
10210                row_bytes,
10211                rp,
10212                ..
10213            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
10214                if *rp {
10215                    "qmatvec_nvfp4_dp4a_rp"
10216                } else {
10217                    "qmatvec_nvfp4_dp4a"
10218                },
10219                bytes,
10220                x,
10221                m,
10222                in_f,
10223                out_f,
10224                *row_bytes,
10225            )?,
10226            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
10227            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
10228            // anomaly (research/kat-anomaly-20260802/).
10229            GpuTensor::Quant {
10230                bytes,
10231                qtype,
10232                row_bytes,
10233                ..
10234            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
10235                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10236            }
10237            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
10238            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
10239            // without first writing the matching kernel, or func() will panic
10240            // "kernel ... not in any fatbin".
10241            GpuTensor::Quant {
10242                bytes,
10243                qtype,
10244                row_bytes,
10245                rp,
10246                ..
10247            } =>
10248            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
10249            // deq(row,j) form cannot address the planes; same value/product order).
10250            {
10251                self.qmatvec(
10252                    bytes,
10253                    x,
10254                    m,
10255                    in_f,
10256                    out_f,
10257                    if *rp && *qtype == QT_NVFP4 {
10258                        QT_NVFP4_RP
10259                    } else {
10260                        *qtype
10261                    },
10262                    *row_bytes,
10263                )?
10264            }
10265            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
10266            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
10267            // cuBLASLt f32 GEMV as the Float arm.
10268            GpuTensor::FloatBf16 { data, .. } => {
10269                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
10270            }
10271        };
10272        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
10273        if let GpuTensor::Quant { scale, .. } = w {
10274            if *scale != 1.0 {
10275                self.scale_inplace(&mut y, *scale, m * out_f)?;
10276            }
10277        }
10278        Ok(y)
10279    }
10280
10281    /// True when `MEMRA_FAST=0`, i.e. the Stage-A f32 oracle is the requested arithmetic and every
10282    /// `matmul_pre` call will take the raw-f32 escape rather than the q8_1 pair.
10283    ///
10284    /// WHY THIS EXISTS AS ITS OWN PREDICATE: `uses_q8_1_fast` needs a weight, but the callers that
10285    /// have to DECIDE WHETHER TO MATERIALIZE an f32 activation sit one level above any weight — the
10286    /// gemma-4 decode trunk emits a q8_1 pair per layer and hands it down, so the f32 has to be
10287    /// produced (or not) by the loop that owns the residual. Cached in a OnceLock like every other
10288    /// `*_on()` flag here: this is read once per layer per token on the decode path, and the daily
10289    /// path must not pay an env lookup for a flag that is off.
10290    pub fn stage_a_raw_needed() -> bool {
10291        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10292        *ON.get_or_init(|| std::env::var("MEMRA_FAST").as_deref() == Ok("0"))
10293    }
10294
10295    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
10296    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
10297    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
10298        use crate::model::GpuTensor;
10299        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
10300            return false;
10301        }
10302        match w {
10303            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
10304            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
10305            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
10306            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
10307            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
10308            // block class has no fused twin yet, so each of its projections takes its own launch.
10309            GpuTensor::Quant { qtype, .. } => {
10310                matches!(
10311                    *qtype,
10312                    QT_Q8_0
10313                        | QT_Q4_K
10314                        | QT_Q6_K
10315                        | QT_Q5_K
10316                        | QT_Q3_K
10317                        | QT_NVFP4
10318                        | QT_F8_E4M3
10319                        | QT_F8_E4M3_BLK
10320                        | QT_Q4_0
10321                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
10322            }
10323            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
10324        }
10325    }
10326
10327    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
10328    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
10329    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
10330    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
10331    pub fn matmul_pre(
10332        &self,
10333        w: &crate::model::GpuTensor,
10334        aq: &CudaSlice<i8>,
10335        ad: &CudaSlice<f32>,
10336        x_fallback: &CudaSlice<f32>,
10337        m: usize,
10338    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10339        use crate::model::GpuTensor;
10340        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
10341        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
10342        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
10343        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
10344        // rc=30013 dig, 2026-07-31).
10345        let x_raw_ok = x_fallback.len() >= m * w.in_features();
10346        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
10347        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
10348        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10349            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
10350                return Ok(y);
10351            }
10352            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
10353            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
10354            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
10355                return Ok(y);
10356            }
10357            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
10358            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
10359                return Ok(y);
10360            }
10361        }
10362        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
10363        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
10364        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
10365        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
10366        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
10367        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10368            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
10369                return Ok(y);
10370            }
10371        }
10372        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10373            return Ok(y);
10374        }
10375        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
10376        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
10377        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
10378        // aq/ad.
10379        if m >= 16
10380            && w.out_features() >= 128
10381            && self.mmq_supports(w)
10382            && !self.verify_exact_on()
10383            && x_raw_ok
10384        {
10385            return self.qmatvec_mmq(w, x_fallback, m);
10386        }
10387        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
10388        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
10389        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10390            if let Some(y) =
10391                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
10392            {
10393                return Ok(y);
10394            }
10395        }
10396        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
10397        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
10398        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
10399            return self.qmatvec_gemm(w, aq, ad, m);
10400        }
10401        // THE SEVENTH ARM. Every raw-f32 arm above is guarded by `x_raw_ok`; this one — the
10402        // Stage-A / Float escape — was not, and it is the ONLY one `MEMRA_FAST=0` opens. So the
10403        // 2026-07-31 E4B rc=30013 fix (add the length guard, keep empty-fallback callers off the
10404        // raw-f32 arms) protected the six arms the FAST path can reach and missed the one the
10405        // ORACLE path reaches. Consequence before this guard: the gemma-4 decode arms, which pass
10406        // `e.zeros(0)` because their trunk emits only a q8_1 pair and never materializes an f32
10407        // attn-normed activation, fell through to Stage-A `qmatvec_f32` (cu/qmatvec.cu:5442),
10408        // which reads `m * in_f` floats out of a 0-byte allocation ->
10409        // CUDA_ERROR_ILLEGAL_ADDRESS at layer 0 of the first decode token. The fault is STICKY:
10410        // it poisons the context, so every LATER request in that process fails with an unrelated
10411        // message ("cache alloc failed: ...") and the true cause appears exactly once, in the
10412        // first failure. That is what made `MEMRA_FAST=0` — the reference named in
10413        // `tools/argmax-margin-gate.sh`'s own header — return an opaque HTTP 500 on the gemma
10414        // dense artifact and left the arm with no working truth instrument.
10415        //
10416        // Refuse loudly instead of reading out of bounds. A named error at the true call site is
10417        // strictly better than an illegal address surfacing later at an unrelated sync point, and
10418        // an oracle that cannot run must say so rather than corrupt the context it runs in.
10419        if !self.uses_q8_1_fast(w) {
10420            if !x_raw_ok {
10421                return Err(format!(
10422                    "matmul_pre: q8_1-fast is off for this weight but x_fallback holds {} f32 \
10423                     (need m*in_f = {}*{} = {}). This call site pre-quantized its activation and \
10424                     dropped the f32, so there is nothing to fall back to — pass the real f32 \
10425                     activation (see Engine::rms_norm_decode, which is bit-identical to \
10426                     rms_norm_q8_1's reduction) or keep the weight on the q8_1 path.",
10427                    x_fallback.len(),
10428                    m,
10429                    w.in_features(),
10430                    m * w.in_features()
10431                )
10432                .into());
10433            }
10434            return self.matmul(w, x_fallback, m);
10435        }
10436        let in_f = w.in_features();
10437        let out_f = w.out_features();
10438        let (bytes, qtype, row_bytes, scale, rp) = match w {
10439            GpuTensor::Quant {
10440                bytes,
10441                qtype,
10442                row_bytes,
10443                scale,
10444                rp,
10445                ..
10446            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10447            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
10448        };
10449        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
10450        // the dp4a/oracle tails below keep the raw GGUF bytes.
10451        let (mbytes, mrp) = match w {
10452            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10453            _ => (bytes, rp),
10454        };
10455        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
10456        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
10457        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
10458        if m == 1 && self.mmvq_supports(qtype) {
10459            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
10460        }
10461        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
10462        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
10463        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
10464        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
10465        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
10466        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
10467        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
10468        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
10469        // m=5..8 on the old per-m path (b8-tier-only seam).
10470        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
10471        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
10472        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
10473        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10474            && std::env::var("MEMRA_NO_BATCHED").is_err()
10475            && (m <= 4 || Self::b8_enabled())
10476            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
10477            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
10478            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
10479            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
10480                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
10481        {
10482            let mcols = Self::batched_mcols(m);
10483            return self.qmatvec_mmvq_batched(
10484                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
10485            );
10486        }
10487        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
10488        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
10489        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
10490        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
10491        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
10492        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
10493            let (b2, r2) = if qtype == QT_Q4_0 {
10494                (mbytes, mrp)
10495            } else {
10496                (bytes, rp)
10497            };
10498            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
10499        }
10500        let name = match qtype {
10501            QT_Q8_0 => "qmatvec_q8_0_dp4a",
10502            QT_Q4_K => "qmatvec_q4_K_dp4a",
10503            QT_Q6_K => "qmatvec_q6_K_dp4a",
10504            QT_Q5_K => "qmatvec_q5_K_dp4a",
10505            QT_Q3_K => "qmatvec_q3_K_dp4a",
10506            QT_NVFP4 => {
10507                if rp {
10508                    "qmatvec_nvfp4_dp4a_rp"
10509                } else {
10510                    "qmatvec_nvfp4_dp4a"
10511                }
10512            }
10513            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
10514            _ => unreachable!(),
10515        };
10516        let f = self.func(name);
10517        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
10518        let cfg = LaunchConfig {
10519            grid_dim: (out_f as u32, m as u32, 1),
10520            block_dim: (128, 1, 1),
10521            shared_mem_bytes: 0,
10522        };
10523        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10524        let __s_b = self.gpu.stream();
10525        let mut b = __s_b.launch_builder(&f);
10526        b.arg(bytes)
10527            .arg(aq)
10528            .arg(ad)
10529            .arg(&mut y)
10530            .arg(&inf)
10531            .arg(&outf)
10532            .arg(&mi)
10533            .arg(&rb);
10534        unsafe {
10535            b.launch(cfg)?;
10536        }
10537        if scale != 1.0 {
10538            self.scale_inplace(&mut y, scale, m * out_f)?;
10539        }
10540        Ok(y)
10541    }
10542
10543    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
10544    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
10545    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
10546    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
10547    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
10548    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
10549    /// reduce as m=1); this method just forces that path unconditionally.
10550    pub fn matmul_decode_exact(
10551        &self,
10552        w: &crate::model::GpuTensor,
10553        x: &CudaSlice<f32>,
10554        m: usize,
10555    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10556        use crate::model::GpuTensor;
10557        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
10558        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
10559        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
10560        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
10561        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
10562        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
10563        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
10564        if let GpuTensor::Float { data, .. } = w {
10565            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
10566        }
10567        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
10568        // float linear (same n-independent reduction contract as the Float arm above).
10569        if let GpuTensor::FloatBf16 { data, .. } = w {
10570            let (in_f, out_f) = (w.in_features(), w.out_features());
10571            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
10572        }
10573        if !self.uses_q8_1_fast(w) {
10574            return self.matmul(w, x, m);
10575        }
10576        let in_f = w.in_features();
10577        let out_f = w.out_features();
10578        let (bytes, qtype, row_bytes, scale, rp) = match w {
10579            GpuTensor::Quant {
10580                bytes,
10581                qtype,
10582                row_bytes,
10583                scale,
10584                rp,
10585                ..
10586            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10587            _ => return self.matmul(w, x, m),
10588        };
10589        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
10590        // which does its own mirror pick).
10591        let (bytes, rp) = match w {
10592            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10593            _ => (bytes, rp),
10594        };
10595        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10596        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10597        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10598        // (token,row) by construction, which is exactly what this method exists to guarantee.
10599        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10600            return Ok(y);
10601        }
10602        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10603        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10604        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10605        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10606        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10607        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10608        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10609        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10610        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10611            && std::env::var("MEMRA_NO_BATCHED").is_err()
10612            && (m <= 4 || Self::b8_enabled())
10613            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10614            // no mirror precondition, `rp` selects the layout only.
10615            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10616                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10617        {
10618            let mcols = Self::batched_mcols(m);
10619            return self.qmatvec_mmvq_batched(
10620                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10621            );
10622        }
10623        if self.mmvq_supports(qtype) {
10624            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10625            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10626            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10627        }
10628        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10629        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10630        self.matmul_pre(w, &aq, &ad, x, m)
10631    }
10632
10633    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10634    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10635    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10636    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10637    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10638    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10639    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10640    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10641    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10642    pub fn matmul_decode_exact_pre(
10643        &self,
10644        w: &crate::model::GpuTensor,
10645        aq: &CudaSlice<i8>,
10646        ad: &CudaSlice<f32>,
10647        m: usize,
10648    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10649        use crate::model::GpuTensor;
10650        debug_assert!(
10651            self.uses_q8_1_fast(w),
10652            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10653        );
10654        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10655        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10656            return Ok(y);
10657        }
10658        let in_f = w.in_features();
10659        let out_f = w.out_features();
10660        let (bytes, qtype, row_bytes, scale, rp) = match w {
10661            GpuTensor::Quant {
10662                bytes,
10663                qtype,
10664                row_bytes,
10665                scale,
10666                rp,
10667                ..
10668            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10669            _ => {
10670                return Err(
10671                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10672                );
10673            }
10674        };
10675        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10676        let (bytes, rp) = match w {
10677            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10678            _ => (bytes, rp),
10679        };
10680        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10681        if (2..=16).contains(&m)
10682            && self.batched_supports(qtype)
10683            && self.mmvq_supports(qtype)
10684            && std::env::var("MEMRA_NO_BATCHED").is_err()
10685            && (m <= 4 || Self::b8_enabled())
10686            && (m <= 8
10687                || qtype == QT_Q4_0
10688                || qtype == QT_Q6_K
10689                || qtype == QT_F8_E4M3
10690                || qtype == QT_NVFP4
10691                || qtype == QT_Q4_K
10692                || qtype == QT_Q5_K
10693                || qtype == QT_Q8_0)
10694        {
10695            let mcols = Self::batched_mcols(m);
10696            return self.qmatvec_mmvq_batched(
10697                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10698            );
10699        }
10700        if self.mmvq_supports(qtype) {
10701            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10702        }
10703        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10704        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10705        let x0 = self.zeros(0)?;
10706        self.matmul_pre(w, aq, ad, &x0, m)
10707    }
10708
10709    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10710    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10711    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10712    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10713    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10714    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10715    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10716    /// per-tensor path.
10717    pub fn matmul_decode_exact_dual_pre(
10718        &self,
10719        w0: &crate::model::GpuTensor,
10720        w1: &crate::model::GpuTensor,
10721        aq: &CudaSlice<i8>,
10722        ad: &CudaSlice<f32>,
10723        m: usize,
10724    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10725    {
10726        use crate::model::GpuTensor;
10727        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10728        let on = *ON.get_or_init(|| {
10729            std::env::var("MEMRA_SPEC_DUAL_T")
10730                .map(|v| v != "0")
10731                .unwrap_or(true)
10732        });
10733        if !on
10734            || !(2..=7).contains(&m)
10735            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10736            || !self.uses_q8_1_fast(w0)
10737            || !self.uses_q8_1_fast(w1)
10738        {
10739            return Ok(None);
10740        }
10741        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10742        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10743        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10744        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10745        if !self.mmvq_supports(QT_NVFP4) {
10746            return Ok(None);
10747        }
10748        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10749        if w1.in_features() != in_f || w1.out_features() != out_f {
10750            return Ok(None);
10751        }
10752        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10753            (
10754                GpuTensor::Quant {
10755                    bytes: b0,
10756                    qtype: q0,
10757                    row_bytes: rb0,
10758                    scale: s0,
10759                    rp: rp0,
10760                    rp4: None,
10761                    ..
10762                },
10763                GpuTensor::Quant {
10764                    bytes: b1,
10765                    qtype: q1,
10766                    row_bytes: rb1,
10767                    scale: s1,
10768                    rp: rp1,
10769                    rp4: None,
10770                    ..
10771                },
10772            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10773                (b0, b1, *rb0, *s0, *s1, *rp0)
10774            }
10775            _ => return Ok(None),
10776        };
10777        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10778        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10779        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10780        {
10781            return Ok(None);
10782        }
10783        let (y0, y1) =
10784            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10785        Ok(Some(((y0, s0), (y1, s1))))
10786    }
10787
10788    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10789    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10790    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10791    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10792    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10793    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10794    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10795    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10796    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10797    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10798    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10799    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10800    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10801    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10802    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10803    pub fn matmul_decode_exact_dual(
10804        &self,
10805        w0: &crate::model::GpuTensor,
10806        w1: &crate::model::GpuTensor,
10807        x: &CudaSlice<f32>,
10808        m: usize,
10809    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10810        use crate::model::GpuTensor;
10811        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10812        let on = *ON.get_or_init(|| {
10813            std::env::var("MEMRA_SPEC_DUAL_T")
10814                .map(|v| v != "0")
10815                .unwrap_or(true)
10816        });
10817        if !on
10818            || !(2..=4).contains(&m)
10819            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10820            || !self.uses_q8_1_fast(w0)
10821            || !self.uses_q8_1_fast(w1)
10822        {
10823            return Ok(None);
10824        }
10825        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10826        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10827        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10828        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10829        if !self.mmvq_supports(QT_NVFP4) {
10830            return Ok(None);
10831        }
10832        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10833        if w1.in_features() != in_f || w1.out_features() != out_f {
10834            return Ok(None);
10835        }
10836        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10837            (
10838                GpuTensor::Quant {
10839                    bytes: b0,
10840                    qtype: q0,
10841                    row_bytes: rb0,
10842                    scale: s0,
10843                    rp: rp0,
10844                    rp4: None,
10845                    ..
10846                },
10847                GpuTensor::Quant {
10848                    bytes: b1,
10849                    qtype: q1,
10850                    row_bytes: rb1,
10851                    scale: s1,
10852                    rp: rp1,
10853                    rp4: None,
10854                    ..
10855                },
10856            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10857                (b0, b1, *rb0, *s0, *s1, *rp0)
10858            }
10859            _ => return Ok(None),
10860        };
10861        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10862        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10863        if std::env::var("MEMRA_DEBUG").is_ok() {
10864            static ONCE: std::sync::Once = std::sync::Once::new();
10865            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10866        }
10867        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10868        let (y0, y1) =
10869            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10870        let mut y0 = y0;
10871        let mut y1 = y1;
10872        if s0 != 1.0 {
10873            self.scale_inplace(&mut y0, s0, m * out_f)?;
10874        }
10875        if s1 != 1.0 {
10876            self.scale_inplace(&mut y1, s1, m * out_f)?;
10877        }
10878        Ok(Some((y0, y1)))
10879    }
10880
10881    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10882    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10883    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10884    /// twins (both buffers must be the repacked layout).
10885    #[allow(clippy::too_many_arguments)]
10886    pub fn qmatvec_batched_dual_raw(
10887        &self,
10888        b0: &CudaSlice<u8>,
10889        b1: &CudaSlice<u8>,
10890        aq: &CudaSlice<i8>,
10891        ad: &CudaSlice<f32>,
10892        m: usize,
10893        in_f: usize,
10894        out_f: usize,
10895        row_bytes: usize,
10896        rp: bool,
10897    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10898        const ROWS_PER_BLOCK: u32 = 4;
10899        let mcols = Self::batched_mcols(m);
10900        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10901        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10902        let tiny_rp1 = rp
10903            && mcols == 4
10904            && out_f <= 128
10905            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10906        let (name, rows_per_block) = if tiny_rp1 {
10907            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10908        } else {
10909            match (mcols, rp, m) {
10910                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10911                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10912                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10913                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10914                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10915                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10916                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10917                _ => {
10918                    return Err(
10919                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10920                    );
10921                }
10922            }
10923        };
10924        let f = self.func(name);
10925        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10926        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10927        let cfg = LaunchConfig {
10928            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10929            block_dim: (32, ROWS_PER_BLOCK, 1),
10930            shared_mem_bytes: 0,
10931        };
10932        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10933        let __s_b = self.gpu.stream();
10934        let mut b = __s_b.launch_builder(&f);
10935        b.arg(b0)
10936            .arg(b1)
10937            .arg(aq)
10938            .arg(ad)
10939            .arg(&mut y0)
10940            .arg(&mut y1)
10941            .arg(&inf)
10942            .arg(&outf)
10943            .arg(&mi)
10944            .arg(&rb);
10945        unsafe {
10946            b.launch(cfg)?;
10947        }
10948        Ok((y0, y1))
10949    }
10950
10951    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10952    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10953    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10954    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10955    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10956    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10957    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10958    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10959    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10960    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10961    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10962    pub fn matmul_pre_dual_noscale(
10963        &self,
10964        w0: &crate::model::GpuTensor,
10965        w1: &crate::model::GpuTensor,
10966        aq: &CudaSlice<i8>,
10967        ad: &CudaSlice<f32>,
10968        m: usize,
10969    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10970    {
10971        use crate::model::GpuTensor;
10972        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10973            return Ok(None);
10974        }
10975        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10976        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10977        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10978        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10979        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10980        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10981        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10982        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10983        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10984        if !self.mmvq_supports(QT_NVFP4) {
10985            return Ok(None);
10986        }
10987        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10988        if w1.in_features() != in_f || w1.out_features() != out_f {
10989            return Ok(None);
10990        }
10991        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10992        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10993        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10994        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10995        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10996        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10997        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10998        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10999        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
11000        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
11001        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
11002        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
11003        let no_mirror =
11004            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
11005        if self.q8_ffn_fuse2_on()
11006            && no_mirror(w0)
11007            && no_mirror(w1)
11008            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
11009        {
11010            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
11011            return Ok(Some(((y0, 1.0), (y1, 1.0))));
11012        }
11013        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
11014        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
11015        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
11016        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
11017        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
11018        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
11019        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
11020        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
11021        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
11022        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11023            let (y0, y1) =
11024                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
11025            return Ok(Some(((y0, p0.3), (y1, p1.3))));
11026        }
11027        let (b0, q0, rb0, s0, rp0) = match w0 {
11028            GpuTensor::Quant {
11029                bytes,
11030                qtype,
11031                row_bytes,
11032                scale,
11033                rp,
11034                ..
11035            } => (bytes, *qtype, *row_bytes, *scale, *rp),
11036            _ => return Ok(None),
11037        };
11038        let (b1, q1, rb1, s1, rp1) = match w1 {
11039            GpuTensor::Quant {
11040                bytes,
11041                qtype,
11042                row_bytes,
11043                scale,
11044                rp,
11045                ..
11046            } => (bytes, *qtype, *row_bytes, *scale, *rp),
11047            _ => return Ok(None),
11048        };
11049        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
11050            return Ok(None);
11051        }
11052        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11053        const RPW: u32 = 2;
11054        let rows_per_block = ROWS_PER_BLOCK * RPW;
11055        let f = self.func(if rp0 {
11056            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
11057        } else {
11058            "qmatvec_nvfp4_mmvq_dual_mr2"
11059        });
11060        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
11061        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
11062        let cfg = LaunchConfig {
11063            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
11064            block_dim: (32, ROWS_PER_BLOCK, 1),
11065            shared_mem_bytes: 0,
11066        };
11067        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
11068        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
11069        // yscale args stay 1.0 here (they exist for the single-tensor callers).
11070        let one = 1.0f32;
11071        let __s_b = self.gpu.stream();
11072        let mut b = __s_b.launch_builder(&f);
11073        b.arg(b0)
11074            .arg(b1)
11075            .arg(aq)
11076            .arg(ad)
11077            .arg(&mut y0)
11078            .arg(&mut y1)
11079            .arg(&inf)
11080            .arg(&outf)
11081            .arg(&mi)
11082            .arg(&rb)
11083            .arg(&one)
11084            .arg(&one);
11085        unsafe {
11086            b.launch(cfg)?;
11087        }
11088        Ok(Some(((y0, s0), (y1, s1))))
11089    }
11090
11091    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
11092    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
11093    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
11094    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
11095    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
11096    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
11097    /// back to the three singles.
11098    #[allow(clippy::too_many_arguments)]
11099    pub fn matmul_nvfp4_fused3(
11100        &self,
11101        w0: &crate::model::GpuTensor,
11102        w1: &crate::model::GpuTensor,
11103        w2: &crate::model::GpuTensor,
11104        aq: &CudaSlice<i8>,
11105        ad: &CudaSlice<f32>,
11106        m: usize,
11107    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11108    {
11109        use crate::model::GpuTensor;
11110        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11111        // read serves all m rows); the fused segments would re-read the weight per row. The
11112        // fusion win is the B=1 decode tick.
11113        if m != 1
11114            || !self.mmvq_supports(QT_NVFP4)
11115            || !self.uses_q8_1_fast(w0)
11116            || !self.uses_q8_1_fast(w1)
11117            || !self.uses_q8_1_fast(w2)
11118        {
11119            return Ok(None);
11120        }
11121        let unpack = |w: &crate::model::GpuTensor| match w {
11122            GpuTensor::Quant {
11123                bytes,
11124                qtype,
11125                scale,
11126                rp,
11127                ..
11128            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11129            _ => None,
11130        };
11131        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
11132            return Ok(None);
11133        };
11134        let in_f = w0.in_features();
11135        if w1.in_features() != in_f || w2.in_features() != in_f {
11136            return Ok(None);
11137        }
11138        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
11139        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11140        const RPW: u32 = 2;
11141        let rows_pb = ROWS_PER_BLOCK * RPW;
11142        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11143        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
11144        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11145        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11146        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11147        let cfg = LaunchConfig {
11148            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
11149            block_dim: (32, ROWS_PER_BLOCK, 1),
11150            shared_mem_bytes: 0,
11151        };
11152        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
11153        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11154        // only dereferenced for the launch-arg build inside this call.
11155        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
11156        let __s_b = self.gpu.stream();
11157        let mut b = __s_b.launch_builder(&f);
11158        b.arg(b0)
11159            .arg(b1)
11160            .arg(b2)
11161            .arg(aq)
11162            .arg(ad)
11163            .arg(&mut y0)
11164            .arg(&mut y1)
11165            .arg(&mut y2)
11166            .arg(&inf)
11167            .arg(&oi0)
11168            .arg(&oi1)
11169            .arg(&oi2)
11170            .arg(&mi)
11171            .arg(&p0.1)
11172            .arg(&p1.1)
11173            .arg(&p2.1);
11174        unsafe {
11175            b.launch(cfg)?;
11176        }
11177        Ok(Some((y0, y1, y2)))
11178    }
11179
11180    /// fused2 twin of `matmul_nvfp4_fused3`, for MIXED-type weight groups: the gemma4
11181    /// dense NVFP4mix recipe keeps `attn_v` and `ffn_down` at Q8_0 (full-NVFP4 was
11182    /// measured garbage on this dense class), so its q/k/v trio and gate/up/down never
11183    /// satisfy an all-NVFP4 fused3 — the pairs that ARE uniformly NVFP4 (q,k and
11184    /// gate,up) fuse here instead. m==1 only, same law as fused3/fused4: per
11185    /// (tensor,row) the kernel seg body is VERBATIM, so the fusion is bit-identical to
11186    /// two separate launches. `MEMRA_NVFP4_FUSED2=0` is the rollback seam and the
11187    /// same-binary interleaved A/B arm.
11188    pub fn matmul_nvfp4_fused2(
11189        &self,
11190        w0: &crate::model::GpuTensor,
11191        w1: &crate::model::GpuTensor,
11192        aq: &CudaSlice<i8>,
11193        ad: &CudaSlice<f32>,
11194        m: usize,
11195    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11196        use crate::model::GpuTensor;
11197        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11198        let off =
11199            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11200        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11201        // read serves all m rows); the fused segments would re-read the weight per row.
11202        if off
11203            || m != 1
11204            || !self.mmvq_supports(QT_NVFP4)
11205            || !self.uses_q8_1_fast(w0)
11206            || !self.uses_q8_1_fast(w1)
11207        {
11208            return Ok(None);
11209        }
11210        let unpack = |w: &crate::model::GpuTensor| match w {
11211            GpuTensor::Quant {
11212                bytes,
11213                qtype,
11214                scale,
11215                rp,
11216                ..
11217            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11218            _ => None,
11219        };
11220        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11221            return Ok(None);
11222        };
11223        let in_f = w0.in_features();
11224        if w1.in_features() != in_f {
11225            return Ok(None);
11226        }
11227        let (o0, o1) = (w0.out_features(), w1.out_features());
11228        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11229        const RPW: u32 = 2;
11230        let rows_pb = ROWS_PER_BLOCK * RPW;
11231        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11232        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11233        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11234        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11235        let cfg = LaunchConfig {
11236            grid_dim: (nb(o0) + nb(o1), m as u32, 1),
11237            block_dim: (32, ROWS_PER_BLOCK, 1),
11238            shared_mem_bytes: 0,
11239        };
11240        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, m as i32);
11241        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11242        // only dereferenced for the launch-arg build inside this call.
11243        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11244        // PDL wave-B: the kernel carries MEMRA_PDL_ENTRY — launch overlaps the producer's
11245        // drain (quantize/norm emitting aq/ad). Same math, same order, bit-identical.
11246        if Self::pdl_on() && Self::pdl_mmvq_on() && Self::pdl_nvfp4q8_on() {
11247            {
11248                use cudarc::driver::{DevicePtr, DevicePtrMut};
11249                let s = &self.gpu.stream();
11250                let (pw0, _g0) = b0.device_ptr(s);
11251                let (pw1, _g1) = b1.device_ptr(s);
11252                let (paq, _g2) = aq.device_ptr(s);
11253                let (pad, _g3) = ad.device_ptr(s);
11254                let (py0, _g4) = y0.device_ptr_mut(s);
11255                let (py1, _g5) = y1.device_ptr_mut(s);
11256                let (s0, s1) = (p0.1, p1.1);
11257                let mut ps = [
11258                    &pw0 as *const _ as *mut std::ffi::c_void,
11259                    &pw1 as *const _ as *mut _,
11260                    &paq as *const _ as *mut _,
11261                    &pad as *const _ as *mut _,
11262                    &py0 as *const _ as *mut _,
11263                    &py1 as *const _ as *mut _,
11264                    &inf as *const _ as *mut _,
11265                    &oi0 as *const _ as *mut _,
11266                    &oi1 as *const _ as *mut _,
11267                    &mi as *const _ as *mut _,
11268                    &s0 as *const _ as *mut _,
11269                    &s1 as *const _ as *mut _,
11270                ];
11271                unsafe {
11272                    self.launch_pdl(
11273                        "qmatvec_nvfp4_mmvq_fused2_rp",
11274                        cfg.grid_dim,
11275                        cfg.block_dim,
11276                        &mut ps,
11277                    )?;
11278                }
11279            }
11280            return Ok(Some((y0, y1)));
11281        }
11282        let __s_b = self.gpu.stream();
11283        let mut b = __s_b.launch_builder(&f);
11284        b.arg(b0)
11285            .arg(b1)
11286            .arg(aq)
11287            .arg(ad)
11288            .arg(&mut y0)
11289            .arg(&mut y1)
11290            .arg(&inf)
11291            .arg(&oi0)
11292            .arg(&oi1)
11293            .arg(&mi)
11294            .arg(&p0.1)
11295            .arg(&p1.1);
11296        unsafe {
11297            b.launch(cfg)?;
11298        }
11299        Ok(Some((y0, y1)))
11300    }
11301
11302    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch to
11303    /// `matmul_nvfp4_fused2`, caller-owned outputs — the gemma4 dc_slotted graph body
11304    /// needs zero mem nodes, so the allocating wrapper can't serve it. Returns false
11305    /// (decline) on any non-NVFP4/rp pair; the caller chains or refuses.
11306    pub fn matmul_nvfp4_fused2_into(
11307        &self,
11308        w0: &crate::model::GpuTensor,
11309        w1: &crate::model::GpuTensor,
11310        aq: &CudaSlice<i8>,
11311        ad: &CudaSlice<f32>,
11312        y0: &mut CudaSlice<f32>,
11313        y1: &mut CudaSlice<f32>,
11314    ) -> Result<bool, Box<dyn std::error::Error>> {
11315        use crate::model::GpuTensor;
11316        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11317        let off =
11318            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11319        if off
11320            || !self.mmvq_supports(QT_NVFP4)
11321            || !self.uses_q8_1_fast(w0)
11322            || !self.uses_q8_1_fast(w1)
11323        {
11324            return Ok(false);
11325        }
11326        let unpack = |w: &crate::model::GpuTensor| match w {
11327            GpuTensor::Quant {
11328                bytes,
11329                qtype,
11330                scale,
11331                rp,
11332                ..
11333            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11334            _ => None,
11335        };
11336        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11337            return Ok(false);
11338        };
11339        let in_f = w0.in_features();
11340        if w1.in_features() != in_f {
11341            return Ok(false);
11342        }
11343        let (o0, o1) = (w0.out_features(), w1.out_features());
11344        if y0.len() < o0 || y1.len() < o1 {
11345            return Ok(false);
11346        }
11347        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11348        const RPW: u32 = 2;
11349        let rows_pb = ROWS_PER_BLOCK * RPW;
11350        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11351        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11352        let cfg = LaunchConfig {
11353            grid_dim: (nb(o0) + nb(o1), 1, 1),
11354            block_dim: (32, ROWS_PER_BLOCK, 1),
11355            shared_mem_bytes: 0,
11356        };
11357        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, 1i32);
11358        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11359        // only dereferenced for the launch-arg build inside this call.
11360        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11361        let __s_b = self.gpu.stream();
11362        let mut b = __s_b.launch_builder(&f);
11363        b.arg(b0)
11364            .arg(b1)
11365            .arg(aq)
11366            .arg(ad)
11367            .arg(&mut *y0)
11368            .arg(&mut *y1)
11369            .arg(&inf)
11370            .arg(&oi0)
11371            .arg(&oi1)
11372            .arg(&mi)
11373            .arg(&p0.1)
11374            .arg(&p1.1);
11375        unsafe {
11376            b.launch(cfg)?;
11377        }
11378        Ok(true)
11379    }
11380
11381    /// fused4 twin of `matmul_nvfp4_fused3`: the Linear-mixer projection quartet
11382    /// (wqkv + wqkv_gate + ssm_beta + ssm_alpha) in one launch, m==1 only. Per
11383    /// (tensor,row) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM — bit-identical
11384    /// to four separate launches (rig-native decode increment 2, RIG-NATIVE-DECODE.md).
11385    #[allow(clippy::type_complexity)]
11386    pub fn matmul_nvfp4_fused4(
11387        &self,
11388        w0: &crate::model::GpuTensor,
11389        w1: &crate::model::GpuTensor,
11390        w2: &crate::model::GpuTensor,
11391        w3: &crate::model::GpuTensor,
11392        aq: &CudaSlice<i8>,
11393        ad: &CudaSlice<f32>,
11394        m: usize,
11395    ) -> Result<
11396        Option<(
11397            CudaSlice<f32>,
11398            CudaSlice<f32>,
11399            CudaSlice<f32>,
11400            CudaSlice<f32>,
11401        )>,
11402        Box<dyn std::error::Error>,
11403    > {
11404        use crate::model::GpuTensor;
11405        // MEMRA_NVFP4_FUSED4=0: rollback seam + the same-binary interleaved A/B arm.
11406        if m != 1
11407            || std::env::var("MEMRA_NVFP4_FUSED4").as_deref() == Ok("0")
11408            || !self.mmvq_supports(QT_NVFP4)
11409            || !self.uses_q8_1_fast(w0)
11410            || !self.uses_q8_1_fast(w1)
11411            || !self.uses_q8_1_fast(w2)
11412            || !self.uses_q8_1_fast(w3)
11413        {
11414            return Ok(None);
11415        }
11416        let unpack = |w: &crate::model::GpuTensor| match w {
11417            GpuTensor::Quant {
11418                bytes,
11419                qtype,
11420                scale,
11421                rp,
11422                ..
11423            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11424            _ => None,
11425        };
11426        let (Some(p0), Some(p1), Some(p2), Some(p3)) =
11427            (unpack(w0), unpack(w1), unpack(w2), unpack(w3))
11428        else {
11429            return Ok(None);
11430        };
11431        let in_f = w0.in_features();
11432        if w1.in_features() != in_f || w2.in_features() != in_f || w3.in_features() != in_f {
11433            return Ok(None);
11434        }
11435        let (o0, o1, o2, o3) = (
11436            w0.out_features(),
11437            w1.out_features(),
11438            w2.out_features(),
11439            w3.out_features(),
11440        );
11441        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11442        const RPW: u32 = 2;
11443        let rows_pb = ROWS_PER_BLOCK * RPW;
11444        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11445        let f = self.func("qmatvec_nvfp4_mmvq_fused4_rp");
11446        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11447        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11448        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11449        let mut y3 = self.alloc_uninit::<f32>(m * o3)?;
11450        let cfg = LaunchConfig {
11451            grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), m as u32, 1),
11452            block_dim: (32, ROWS_PER_BLOCK, 1),
11453            shared_mem_bytes: 0,
11454        };
11455        let (inf, oi0, oi1, oi2, oi3, mi) = (
11456            in_f as i32,
11457            o0 as i32,
11458            o1 as i32,
11459            o2 as i32,
11460            o3 as i32,
11461            m as i32,
11462        );
11463        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11464        // only dereferenced for the launch-arg build inside this call.
11465        let (b0, b1, b2, b3) = unsafe { (&*p0.0, &*p1.0, &*p2.0, &*p3.0) };
11466        let __s_b = self.gpu.stream();
11467        let mut b = __s_b.launch_builder(&f);
11468        b.arg(b0)
11469            .arg(b1)
11470            .arg(b2)
11471            .arg(b3)
11472            .arg(aq)
11473            .arg(ad)
11474            .arg(&mut y0)
11475            .arg(&mut y1)
11476            .arg(&mut y2)
11477            .arg(&mut y3)
11478            .arg(&inf)
11479            .arg(&oi0)
11480            .arg(&oi1)
11481            .arg(&oi2)
11482            .arg(&oi3)
11483            .arg(&mi)
11484            .arg(&p0.1)
11485            .arg(&p1.1)
11486            .arg(&p2.1)
11487            .arg(&p3.1);
11488        unsafe {
11489            b.launch(cfg)?;
11490        }
11491        Ok(Some((y0, y1, y2, y3)))
11492    }
11493
11494    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
11495    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
11496    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
11497    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
11498    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
11499    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
11500    /// back to the per-tensor path.
11501    pub fn matmul_q8_fused2(
11502        &self,
11503        w0: &crate::model::GpuTensor,
11504        w1: &crate::model::GpuTensor,
11505        aq: &CudaSlice<i8>,
11506        ad: &CudaSlice<f32>,
11507    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11508        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
11509        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
11510        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
11511        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
11512        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
11513        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11514            return Ok(Some(self.e4m3_fused2_core(
11515                p0.0,
11516                p1.0,
11517                aq,
11518                ad,
11519                w0.in_features(),
11520                p0.1,
11521                p1.1,
11522                p0.2,
11523                p0.3,
11524                p1.3,
11525            )?));
11526        }
11527        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11528            return Ok(None);
11529        };
11530        Ok(Some(self.q8_fused2_core(
11531            p0.0,
11532            p1.0,
11533            aq,
11534            ad,
11535            w0.in_features(),
11536            p0.1,
11537            p1.1,
11538            p0.2,
11539        )?))
11540    }
11541
11542    #[allow(clippy::too_many_arguments)]
11543    fn q8_fused2_core(
11544        &self,
11545        b0: &CudaSlice<u8>,
11546        b1: &CudaSlice<u8>,
11547        aq: &CudaSlice<i8>,
11548        ad: &CudaSlice<f32>,
11549        in_f: usize,
11550        out0: usize,
11551        out1: usize,
11552        row_bytes: usize,
11553    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11554        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11555        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11556        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11557        let f = self.func("qmatvec_q8_0_mmvq_fused2");
11558        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11559        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11560        let cfg = LaunchConfig {
11561            grid_dim: (nb0 + nb1, 1, 1),
11562            block_dim: (32, ROWS_PER_BLOCK, 1),
11563            shared_mem_bytes: 0,
11564        };
11565        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
11566        let __s_b = self.gpu.stream();
11567        let mut b = __s_b.launch_builder(&f);
11568        b.arg(b0)
11569            .arg(b1)
11570            .arg(aq)
11571            .arg(ad)
11572            .arg(&mut y0)
11573            .arg(&mut y1)
11574            .arg(&inf)
11575            .arg(&o0)
11576            .arg(&o1)
11577            .arg(&rbl);
11578        unsafe {
11579            b.launch(cfg)?;
11580        }
11581        Ok((y0, y1))
11582    }
11583
11584    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
11585    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
11586    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
11587    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
11588    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
11589    pub fn matmul_q8_fused2_x(
11590        &self,
11591        w0: &crate::model::GpuTensor,
11592        w1: &crate::model::GpuTensor,
11593        x: &CudaSlice<f32>,
11594    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11595        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
11596            return Ok(None);
11597        }
11598        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11599            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11600            return Ok(Some(self.e4m3_fused2_core(
11601                p0.0,
11602                p1.0,
11603                &aq,
11604                &ad,
11605                w0.in_features(),
11606                p0.1,
11607                p1.1,
11608                p0.2,
11609                p0.3,
11610                p1.3,
11611            )?));
11612        }
11613        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11614            return Ok(None);
11615        };
11616        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11617        Ok(Some(self.q8_fused2_core(
11618            p0.0,
11619            p1.0,
11620            &aq,
11621            &ad,
11622            w0.in_features(),
11623            p0.1,
11624            p1.1,
11625            p0.2,
11626        )?))
11627    }
11628
11629    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
11630    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
11631    #[allow(clippy::too_many_arguments)]
11632    pub fn qmatvec_q8_fused2_raw(
11633        &self,
11634        b0: &CudaSlice<u8>,
11635        b1: &CudaSlice<u8>,
11636        x: &CudaSlice<f32>,
11637        in_f: usize,
11638        out0: usize,
11639        out1: usize,
11640        row_bytes: usize,
11641    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11642        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11643        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
11644    }
11645
11646    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
11647    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
11648    /// (tensor,row) to three separate m=1 MMVQ launches.
11649    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
11650    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
11651    pub fn matmul_q4_fused3(
11652        &self,
11653        w0: &crate::model::GpuTensor,
11654        w1: &crate::model::GpuTensor,
11655        w2: &crate::model::GpuTensor,
11656        aq: &CudaSlice<i8>,
11657        ad: &CudaSlice<f32>,
11658    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11659    {
11660        use crate::model::GpuTensor;
11661        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11662            match w {
11663                GpuTensor::Quant {
11664                    qtype, row_bytes, ..
11665                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11666                _ => None,
11667            }
11668        };
11669        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11670            return Ok(None);
11671        };
11672        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11673            return Ok(None);
11674        }
11675        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
11676        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
11677        // the separate matvecs (each routes its own rp).
11678        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11679            match w {
11680                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11681                    Some(m) => (m, true),
11682                    None => (bytes, *rp),
11683                },
11684                _ => unreachable!(),
11685            }
11686        }
11687        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11688        if rp0 != rp1 || rp1 != rp2 {
11689            return Ok(None);
11690        }
11691        let rp = rp0;
11692        let rpb: u32 = 4;
11693        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
11694        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
11695        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
11696        let mr1 = rp && Self::q40_mr1_on();
11697        let nb = |o: usize| {
11698            if mr1 {
11699                (o as u32).div_ceil(rpb)
11700            } else {
11701                (o as u32).div_ceil(2).div_ceil(rpb)
11702            }
11703        };
11704        let grid = nb(o0) + nb(o1) + nb(o2);
11705        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11706        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11707        let mut y2 = self.alloc_uninit::<f32>(o2)?;
11708        let f = self.func(if mr1 {
11709            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11710        } else if rp {
11711            "qmatvec_q4_0_mmvq_fused3_rp"
11712        } else {
11713            "qmatvec_q4_0_mmvq_fused3"
11714        });
11715        let cfg = LaunchConfig {
11716            grid_dim: (grid, 1, 1),
11717            block_dim: (32, rpb, 1),
11718            shared_mem_bytes: 0,
11719        };
11720        let inf = w0.in_features() as i32;
11721        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11722        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11723        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
11724        // variant may take the programmatic-serialization launch.
11725        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11726            {
11727                use cudarc::driver::{DevicePtr, DevicePtrMut};
11728                let s = &self.gpu.stream();
11729                let (p0, _g0) = b0.device_ptr(s);
11730                let (p1, _g1) = b1.device_ptr(s);
11731                let (p2, _g2) = b2.device_ptr(s);
11732                let (paq, _g3) = aq.device_ptr(s);
11733                let (pad, _g4) = ad.device_ptr(s);
11734                let (py0, _g5) = y0.device_ptr_mut(s);
11735                let (py1, _g6) = y1.device_ptr_mut(s);
11736                let (py2, _g7) = y2.device_ptr_mut(s);
11737                let mut ps = [
11738                    &p0 as *const _ as *mut std::ffi::c_void,
11739                    &p1 as *const _ as *mut _,
11740                    &p2 as *const _ as *mut _,
11741                    &paq as *const _ as *mut _,
11742                    &pad as *const _ as *mut _,
11743                    &py0 as *const _ as *mut _,
11744                    &py1 as *const _ as *mut _,
11745                    &py2 as *const _ as *mut _,
11746                    &inf as *const _ as *mut _,
11747                    &oo0 as *const _ as *mut _,
11748                    &oo1 as *const _ as *mut _,
11749                    &oo2 as *const _ as *mut _,
11750                    &r0 as *const _ as *mut _,
11751                    &r1 as *const _ as *mut _,
11752                    &r2 as *const _ as *mut _,
11753                ];
11754                unsafe {
11755                    self.launch_pdl(
11756                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11757                        (grid, 1, 1),
11758                        (32, rpb, 1),
11759                        &mut ps,
11760                    )?;
11761                }
11762            }
11763            return Ok(Some((y0, y1, y2)));
11764        }
11765        let __s_b = self.gpu.stream();
11766        let mut b = __s_b.launch_builder(&f);
11767        b.arg(b0)
11768            .arg(b1)
11769            .arg(b2)
11770            .arg(aq)
11771            .arg(ad)
11772            .arg(&mut y0)
11773            .arg(&mut y1)
11774            .arg(&mut y2)
11775            .arg(&inf)
11776            .arg(&oo0)
11777            .arg(&oo1)
11778            .arg(&oo2)
11779            .arg(&r0)
11780            .arg(&r1)
11781            .arg(&r2);
11782        unsafe {
11783            b.launch(cfg)?;
11784        }
11785        Ok(Some((y0, y1, y2)))
11786    }
11787
11788    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11789    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
11790    #[allow(clippy::too_many_arguments)]
11791    pub fn matmul_q4_fused3_into(
11792        &self,
11793        w0: &crate::model::GpuTensor,
11794        w1: &crate::model::GpuTensor,
11795        w2: &crate::model::GpuTensor,
11796        aq: &CudaSlice<i8>,
11797        ad: &CudaSlice<f32>,
11798        y0: &mut CudaSlice<f32>,
11799        y1: &mut CudaSlice<f32>,
11800        y2: &mut CudaSlice<f32>,
11801    ) -> Result<bool, Box<dyn std::error::Error>> {
11802        use crate::model::GpuTensor;
11803        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11804            match w {
11805                GpuTensor::Quant {
11806                    qtype, row_bytes, ..
11807                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11808                _ => None,
11809            }
11810        };
11811        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11812            return Ok(false);
11813        };
11814        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11815            return Ok(false);
11816        }
11817        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11818            match w {
11819                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11820                    Some(m) => (m, true),
11821                    None => (bytes, *rp),
11822                },
11823                _ => unreachable!(),
11824            }
11825        }
11826        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11827        if rp0 != rp1 || rp1 != rp2 {
11828            return Ok(false);
11829        }
11830        let rp = rp0;
11831        let rpb: u32 = 4;
11832        let mr1 = rp && Self::q40_mr1_on();
11833        let nb = |o: usize| {
11834            if mr1 {
11835                (o as u32).div_ceil(rpb)
11836            } else {
11837                (o as u32).div_ceil(2).div_ceil(rpb)
11838            }
11839        };
11840        let grid = nb(o0) + nb(o1) + nb(o2);
11841        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
11842        let f = self.func(if mr1 {
11843            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11844        } else if rp {
11845            "qmatvec_q4_0_mmvq_fused3_rp"
11846        } else {
11847            "qmatvec_q4_0_mmvq_fused3"
11848        });
11849        let cfg = LaunchConfig {
11850            grid_dim: (grid, 1, 1),
11851            block_dim: (32, rpb, 1),
11852            shared_mem_bytes: 0,
11853        };
11854        let inf = w0.in_features() as i32;
11855        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11856        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11857        // PDL wave-A: identical to the owned twin (capture-lane parity).
11858        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11859            use cudarc::driver::{DevicePtr, DevicePtrMut};
11860            let s = &self.gpu.stream();
11861            let (p0, _g0) = b0.device_ptr(s);
11862            let (p1, _g1) = b1.device_ptr(s);
11863            let (p2, _g2) = b2.device_ptr(s);
11864            let (paq, _g3) = aq.device_ptr(s);
11865            let (pad, _g4) = ad.device_ptr(s);
11866            let (py0, _g5) = y0.device_ptr_mut(s);
11867            let (py1, _g6) = y1.device_ptr_mut(s);
11868            let (py2, _g7) = y2.device_ptr_mut(s);
11869            let mut ps = [
11870                &p0 as *const _ as *mut std::ffi::c_void,
11871                &p1 as *const _ as *mut _,
11872                &p2 as *const _ as *mut _,
11873                &paq as *const _ as *mut _,
11874                &pad as *const _ as *mut _,
11875                &py0 as *const _ as *mut _,
11876                &py1 as *const _ as *mut _,
11877                &py2 as *const _ as *mut _,
11878                &inf as *const _ as *mut _,
11879                &oo0 as *const _ as *mut _,
11880                &oo1 as *const _ as *mut _,
11881                &oo2 as *const _ as *mut _,
11882                &r0 as *const _ as *mut _,
11883                &r1 as *const _ as *mut _,
11884                &r2 as *const _ as *mut _,
11885            ];
11886            unsafe {
11887                self.launch_pdl(
11888                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11889                    (grid, 1, 1),
11890                    (32, rpb, 1),
11891                    &mut ps,
11892                )?;
11893            }
11894            return Ok(true);
11895        }
11896        let __s_b = self.gpu.stream();
11897        let mut b = __s_b.launch_builder(&f);
11898        b.arg(b0)
11899            .arg(b1)
11900            .arg(b2)
11901            .arg(aq)
11902            .arg(ad)
11903            .arg(&mut *y0)
11904            .arg(&mut *y1)
11905            .arg(&mut *y2)
11906            .arg(&inf)
11907            .arg(&oo0)
11908            .arg(&oo1)
11909            .arg(&oo2)
11910            .arg(&r0)
11911            .arg(&r1)
11912            .arg(&r2);
11913        unsafe {
11914            b.launch(cfg)?;
11915        }
11916        Ok(true)
11917    }
11918
11919    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
11920    pub fn matmul_q4_fused2(
11921        &self,
11922        w0: &crate::model::GpuTensor,
11923        w1: &crate::model::GpuTensor,
11924        aq: &CudaSlice<i8>,
11925        ad: &CudaSlice<f32>,
11926    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11927        use crate::model::GpuTensor;
11928        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11929            match w {
11930                GpuTensor::Quant {
11931                    qtype, row_bytes, ..
11932                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11933                _ => None,
11934            }
11935        };
11936        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11937            return Ok(None);
11938        };
11939        if w0.in_features() != w1.in_features() {
11940            return Ok(None);
11941        }
11942        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
11943        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11944            match w {
11945                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11946                    Some(m) => (m, true),
11947                    None => (bytes, *rp),
11948                },
11949                _ => unreachable!(),
11950            }
11951        }
11952        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11953        if rp0 != rp1 {
11954            return Ok(None);
11955        }
11956        let rp = rp0;
11957        let rpb: u32 = 4;
11958        // mr1 twin — see matmul_q4_fused3.
11959        let mr1 = rp && Self::q40_mr1_on();
11960        let nb = |o: usize| {
11961            if mr1 {
11962                (o as u32).div_ceil(rpb)
11963            } else {
11964                (o as u32).div_ceil(2).div_ceil(rpb)
11965            }
11966        };
11967        let grid = nb(o0) + nb(o1);
11968        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11969        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11970        let f = self.func(if mr1 {
11971            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11972        } else if rp {
11973            "qmatvec_q4_0_mmvq_fused2_rp"
11974        } else {
11975            "qmatvec_q4_0_mmvq_fused2"
11976        });
11977        let cfg = LaunchConfig {
11978            grid_dim: (grid, 1, 1),
11979            block_dim: (32, rpb, 1),
11980            shared_mem_bytes: 0,
11981        };
11982        let inf = w0.in_features() as i32;
11983        let (oo0, oo1) = (o0 as i32, o1 as i32);
11984        let (r0, r1) = (rb0 as i64, rb1 as i64);
11985        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
11986        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11987            {
11988                use cudarc::driver::{DevicePtr, DevicePtrMut};
11989                let s = &self.gpu.stream();
11990                let (p0, _g0) = b0.device_ptr(s);
11991                let (p1, _g1) = b1.device_ptr(s);
11992                let (paq, _g2) = aq.device_ptr(s);
11993                let (pad, _g3) = ad.device_ptr(s);
11994                let (py0, _g4) = y0.device_ptr_mut(s);
11995                let (py1, _g5) = y1.device_ptr_mut(s);
11996                let mut ps = [
11997                    &p0 as *const _ as *mut std::ffi::c_void,
11998                    &p1 as *const _ as *mut _,
11999                    &paq as *const _ as *mut _,
12000                    &pad as *const _ as *mut _,
12001                    &py0 as *const _ as *mut _,
12002                    &py1 as *const _ as *mut _,
12003                    &inf as *const _ as *mut _,
12004                    &oo0 as *const _ as *mut _,
12005                    &oo1 as *const _ as *mut _,
12006                    &r0 as *const _ as *mut _,
12007                    &r1 as *const _ as *mut _,
12008                ];
12009                unsafe {
12010                    self.launch_pdl(
12011                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
12012                        (grid, 1, 1),
12013                        (32, rpb, 1),
12014                        &mut ps,
12015                    )?;
12016                }
12017            }
12018            return Ok(Some((y0, y1)));
12019        }
12020        let __s_b = self.gpu.stream();
12021        let mut b = __s_b.launch_builder(&f);
12022        b.arg(b0)
12023            .arg(b1)
12024            .arg(aq)
12025            .arg(ad)
12026            .arg(&mut y0)
12027            .arg(&mut y1)
12028            .arg(&inf)
12029            .arg(&oo0)
12030            .arg(&oo1)
12031            .arg(&r0)
12032            .arg(&r1);
12033        unsafe {
12034            b.launch(cfg)?;
12035        }
12036        Ok(Some((y0, y1)))
12037    }
12038
12039    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
12040    pub fn matmul_q4_fused2_into(
12041        &self,
12042        w0: &crate::model::GpuTensor,
12043        w1: &crate::model::GpuTensor,
12044        aq: &CudaSlice<i8>,
12045        ad: &CudaSlice<f32>,
12046        y0: &mut CudaSlice<f32>,
12047        y1: &mut CudaSlice<f32>,
12048    ) -> Result<bool, Box<dyn std::error::Error>> {
12049        use crate::model::GpuTensor;
12050        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
12051            match w {
12052                GpuTensor::Quant {
12053                    qtype, row_bytes, ..
12054                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
12055                _ => None,
12056            }
12057        };
12058        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
12059            return Ok(false);
12060        };
12061        if w0.in_features() != w1.in_features() {
12062            return Ok(false);
12063        }
12064        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12065            match w {
12066                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12067                    Some(m) => (m, true),
12068                    None => (bytes, *rp),
12069                },
12070                _ => unreachable!(),
12071            }
12072        }
12073        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
12074        if rp0 != rp1 {
12075            return Ok(false);
12076        }
12077        let rp = rp0;
12078        let rpb: u32 = 4;
12079        let mr1 = rp && Self::q40_mr1_on();
12080        let nb = |o: usize| {
12081            if mr1 {
12082                (o as u32).div_ceil(rpb)
12083            } else {
12084                (o as u32).div_ceil(2).div_ceil(rpb)
12085            }
12086        };
12087        let grid = nb(o0) + nb(o1);
12088        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
12089        let f = self.func(if mr1 {
12090            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
12091        } else if rp {
12092            "qmatvec_q4_0_mmvq_fused2_rp"
12093        } else {
12094            "qmatvec_q4_0_mmvq_fused2"
12095        });
12096        let cfg = LaunchConfig {
12097            grid_dim: (grid, 1, 1),
12098            block_dim: (32, rpb, 1),
12099            shared_mem_bytes: 0,
12100        };
12101        let inf = w0.in_features() as i32;
12102        let (oo0, oo1) = (o0 as i32, o1 as i32);
12103        let (r0, r1) = (rb0 as i64, rb1 as i64);
12104        // PDL wave-A: identical to the owned twin (capture-lane parity).
12105        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
12106            use cudarc::driver::{DevicePtr, DevicePtrMut};
12107            let s = &self.gpu.stream();
12108            let (p0, _g0) = b0.device_ptr(s);
12109            let (p1, _g1) = b1.device_ptr(s);
12110            let (paq, _g2) = aq.device_ptr(s);
12111            let (pad, _g3) = ad.device_ptr(s);
12112            let (py0, _g4) = y0.device_ptr_mut(s);
12113            let (py1, _g5) = y1.device_ptr_mut(s);
12114            let mut ps = [
12115                &p0 as *const _ as *mut std::ffi::c_void,
12116                &p1 as *const _ as *mut _,
12117                &paq as *const _ as *mut _,
12118                &pad as *const _ as *mut _,
12119                &py0 as *const _ as *mut _,
12120                &py1 as *const _ as *mut _,
12121                &inf as *const _ as *mut _,
12122                &oo0 as *const _ as *mut _,
12123                &oo1 as *const _ as *mut _,
12124                &r0 as *const _ as *mut _,
12125                &r1 as *const _ as *mut _,
12126            ];
12127            unsafe {
12128                self.launch_pdl(
12129                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
12130                    (grid, 1, 1),
12131                    (32, rpb, 1),
12132                    &mut ps,
12133                )?;
12134            }
12135            return Ok(true);
12136        }
12137        let __s_b = self.gpu.stream();
12138        let mut b = __s_b.launch_builder(&f);
12139        b.arg(b0)
12140            .arg(b1)
12141            .arg(aq)
12142            .arg(ad)
12143            .arg(&mut *y0)
12144            .arg(&mut *y1)
12145            .arg(&inf)
12146            .arg(&oo0)
12147            .arg(&oo1)
12148            .arg(&r0)
12149            .arg(&r1);
12150        unsafe {
12151            b.launch(cfg)?;
12152        }
12153        Ok(true)
12154    }
12155
12156    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
12157    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
12158    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
12159    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
12160    pub fn matmul_q4_fused2_batched(
12161        &self,
12162        w0: &crate::model::GpuTensor,
12163        w1: &crate::model::GpuTensor,
12164        aq: &CudaSlice<i8>,
12165        ad: &CudaSlice<f32>,
12166        m: usize,
12167    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12168        use crate::model::GpuTensor;
12169        if m < 2 || m > 8 {
12170            return Ok(None);
12171        }
12172        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
12173            match w {
12174                GpuTensor::Quant {
12175                    qtype, row_bytes, ..
12176                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
12177                _ => None,
12178            }
12179        };
12180        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
12181            return Ok(None);
12182        };
12183        if w0.in_features() != w1.in_features() {
12184            return Ok(None);
12185        }
12186        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12187            match w {
12188                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12189                    Some(mr) => (mr, true),
12190                    None => (bytes, *rp),
12191                },
12192                _ => unreachable!(),
12193            }
12194        }
12195        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
12196        if !rp0 || !rp1 {
12197            return Ok(None);
12198        }
12199        let mcols = Self::batched_mcols(m);
12200        let rpb: u32 = 4;
12201        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12202        let grid = nb(o0) + nb(o1);
12203        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12204        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12205        let f = self.func(match mcols {
12206            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
12207            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
12208            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
12209        });
12210        let cfg = LaunchConfig {
12211            grid_dim: (grid, 1, 1),
12212            block_dim: (32, rpb, 1),
12213            shared_mem_bytes: 0,
12214        };
12215        let inf = w0.in_features() as i32;
12216        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
12217        let rb = rb0 as i64;
12218        let __s_b = self.gpu.stream();
12219        let mut b = __s_b.launch_builder(&f);
12220        b.arg(b0)
12221            .arg(b1)
12222            .arg(aq)
12223            .arg(ad)
12224            .arg(&mut y0)
12225            .arg(&mut y1)
12226            .arg(&inf)
12227            .arg(&oo0)
12228            .arg(&oo1)
12229            .arg(&mi)
12230            .arg(&rb);
12231        unsafe {
12232            b.launch(cfg)?;
12233        }
12234        Ok(Some((y0, y1)))
12235    }
12236
12237    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
12238    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
12239    #[allow(clippy::too_many_arguments)]
12240    pub fn matmul_q4_fused3_batched(
12241        &self,
12242        w0: &crate::model::GpuTensor,
12243        w1: &crate::model::GpuTensor,
12244        w2: &crate::model::GpuTensor,
12245        aq: &CudaSlice<i8>,
12246        ad: &CudaSlice<f32>,
12247        m: usize,
12248    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12249    {
12250        use crate::model::GpuTensor;
12251        if m < 2 || m > 8 {
12252            return Ok(None);
12253        }
12254        let q4 = |w: &GpuTensor| -> Option<usize> {
12255            match w {
12256                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
12257                _ => None,
12258            }
12259        };
12260        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
12261            return Ok(None);
12262        };
12263        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
12264            return Ok(None);
12265        }
12266        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12267            match w {
12268                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12269                    Some(mr) => (mr, true),
12270                    None => (bytes, *rp),
12271                },
12272                _ => unreachable!(),
12273            }
12274        }
12275        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
12276        if !rp0 || !rp1 || !rp2 {
12277            return Ok(None);
12278        }
12279        let mcols = Self::batched_mcols(m);
12280        let rpb: u32 = 4;
12281        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12282        let grid = nb(o0) + nb(o1) + nb(o2);
12283        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12284        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12285        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
12286        let f = self.func(match mcols {
12287            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
12288            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
12289            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
12290        });
12291        let cfg = LaunchConfig {
12292            grid_dim: (grid, 1, 1),
12293            block_dim: (32, rpb, 1),
12294            shared_mem_bytes: 0,
12295        };
12296        let inf = w0.in_features() as i32;
12297        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
12298        let rb = 0i64;
12299        let __s_b = self.gpu.stream();
12300        let mut b = __s_b.launch_builder(&f);
12301        b.arg(b0)
12302            .arg(b1)
12303            .arg(b2)
12304            .arg(aq)
12305            .arg(ad)
12306            .arg(&mut y0)
12307            .arg(&mut y1)
12308            .arg(&mut y2)
12309            .arg(&inf)
12310            .arg(&oo0)
12311            .arg(&oo1)
12312            .arg(&oo2)
12313            .arg(&mi)
12314            .arg(&rb);
12315        unsafe {
12316            b.launch(cfg)?;
12317        }
12318        Ok(Some((y0, y1, y2)))
12319    }
12320
12321    pub fn matmul_q8_fused3(
12322        &self,
12323        w0: &crate::model::GpuTensor,
12324        w1: &crate::model::GpuTensor,
12325        w2: &crate::model::GpuTensor,
12326        aq: &CudaSlice<i8>,
12327        ad: &CudaSlice<f32>,
12328    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12329    {
12330        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
12331        // are per-tensor FP8, so native residency without this arm meant three separate launches.
12332        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12333            return Ok(Some(self.e4m3_fused3_core(
12334                p0.0,
12335                p1.0,
12336                p2.0,
12337                aq,
12338                ad,
12339                w0.in_features(),
12340                p0.1,
12341                p1.1,
12342                p2.1,
12343                p0.2,
12344                p0.3,
12345                p1.3,
12346                p2.3,
12347            )?));
12348        }
12349        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12350            return Ok(None);
12351        };
12352        Ok(Some(self.q8_fused3_core(
12353            p0.0,
12354            p1.0,
12355            p2.0,
12356            aq,
12357            ad,
12358            w0.in_features(),
12359            p0.1,
12360            p1.1,
12361            p2.1,
12362            p0.2,
12363        )?))
12364    }
12365
12366    #[allow(clippy::too_many_arguments)]
12367    fn q8_fused3_core(
12368        &self,
12369        b0: &CudaSlice<u8>,
12370        b1: &CudaSlice<u8>,
12371        b2: &CudaSlice<u8>,
12372        aq: &CudaSlice<i8>,
12373        ad: &CudaSlice<f32>,
12374        in_f: usize,
12375        out0: usize,
12376        out1: usize,
12377        out2: usize,
12378        row_bytes: usize,
12379    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12380        const ROWS_PER_BLOCK: u32 = 4;
12381        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12382        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12383        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12384        let f = self.func("qmatvec_q8_0_mmvq_fused3");
12385        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12386        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12387        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12388        let cfg = LaunchConfig {
12389            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12390            block_dim: (32, ROWS_PER_BLOCK, 1),
12391            shared_mem_bytes: 0,
12392        };
12393        let (inf, o0, o1, o2, rbl) = (
12394            in_f as i32,
12395            out0 as i32,
12396            out1 as i32,
12397            out2 as i32,
12398            row_bytes as i64,
12399        );
12400        let __s_b = self.gpu.stream();
12401        let mut b = __s_b.launch_builder(&f);
12402        b.arg(b0)
12403            .arg(b1)
12404            .arg(b2)
12405            .arg(aq)
12406            .arg(ad)
12407            .arg(&mut y0)
12408            .arg(&mut y1)
12409            .arg(&mut y2)
12410            .arg(&inf)
12411            .arg(&o0)
12412            .arg(&o1)
12413            .arg(&o2)
12414            .arg(&rbl);
12415        unsafe {
12416            b.launch(cfg)?;
12417        }
12418        Ok((y0, y1, y2))
12419    }
12420
12421    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
12422    #[allow(clippy::too_many_arguments)]
12423    pub fn qmatvec_q8_fused3_raw(
12424        &self,
12425        b0: &CudaSlice<u8>,
12426        b1: &CudaSlice<u8>,
12427        b2: &CudaSlice<u8>,
12428        x: &CudaSlice<f32>,
12429        in_f: usize,
12430        out0: usize,
12431        out1: usize,
12432        out2: usize,
12433        row_bytes: usize,
12434    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12435        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12436        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
12437    }
12438
12439    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
12440    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
12441    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
12442    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
12443    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
12444    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
12445    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
12446    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
12447    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
12448    /// twin must not introduce a batched program the reference path would not run).
12449    pub fn matmul_q8_fused2_t(
12450        &self,
12451        w0: &crate::model::GpuTensor,
12452        w1: &crate::model::GpuTensor,
12453        aq: &CudaSlice<i8>,
12454        ad: &CudaSlice<f32>,
12455        m: usize,
12456    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12457        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
12458        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
12459        // fuses too — same template body, still bit-identical to the two _b8 launches.
12460        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12461            return Ok(None);
12462        }
12463        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
12464        // so the fused b8 launch would introduce a batched program the reference path would not run.
12465        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
12466            if m > 4 && !Self::b8_enabled() {
12467                return Ok(None);
12468            }
12469            return Ok(Some(self.e4m3_fused2_t_core(
12470                p0.0,
12471                p1.0,
12472                aq,
12473                ad,
12474                m,
12475                w0.in_features(),
12476                p0.1,
12477                p1.1,
12478                p0.2,
12479                p0.3,
12480                p1.3,
12481            )?));
12482        }
12483        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
12484            return Ok(None);
12485        };
12486        Ok(Some(self.q8_fused2_t_core(
12487            p0.0,
12488            p1.0,
12489            aq,
12490            ad,
12491            m,
12492            w0.in_features(),
12493            p0.1,
12494            p1.1,
12495            p0.2,
12496        )?))
12497    }
12498
12499    #[allow(clippy::too_many_arguments)]
12500    fn q8_fused2_t_core(
12501        &self,
12502        b0: &CudaSlice<u8>,
12503        b1: &CudaSlice<u8>,
12504        aq: &CudaSlice<i8>,
12505        ad: &CudaSlice<f32>,
12506        m: usize,
12507        in_f: usize,
12508        out0: usize,
12509        out1: usize,
12510        row_bytes: usize,
12511    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12512        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12513        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12514        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12515        let f = self.func(match Self::batched_mcols(m) {
12516            2 => "qmatvec_q8_0_mmvq_fused2_b2",
12517            4 => "qmatvec_q8_0_mmvq_fused2_b4",
12518            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
12519            _ => "qmatvec_q8_0_mmvq_fused2_b8",
12520        });
12521        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12522        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12523        let cfg = LaunchConfig {
12524            grid_dim: (nb0 + nb1, 1, 1),
12525            block_dim: (32, ROWS_PER_BLOCK, 1),
12526            shared_mem_bytes: 0,
12527        };
12528        let (inf, o0, o1, mi, rbl) = (
12529            in_f as i32,
12530            out0 as i32,
12531            out1 as i32,
12532            m as i32,
12533            row_bytes as i64,
12534        );
12535        let __s_b = self.gpu.stream();
12536        let mut b = __s_b.launch_builder(&f);
12537        b.arg(b0)
12538            .arg(b1)
12539            .arg(aq)
12540            .arg(ad)
12541            .arg(&mut y0)
12542            .arg(&mut y1)
12543            .arg(&inf)
12544            .arg(&o0)
12545            .arg(&o1)
12546            .arg(&mi)
12547            .arg(&rbl);
12548        unsafe {
12549            b.launch(cfg)?;
12550        }
12551        Ok((y0, y1))
12552    }
12553
12554    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
12555    /// q8_1 quant of the [m, in_f] activation), no env gating.
12556    #[allow(clippy::too_many_arguments)]
12557    pub fn qmatvec_q8_fused2_t_raw(
12558        &self,
12559        b0: &CudaSlice<u8>,
12560        b1: &CudaSlice<u8>,
12561        x: &CudaSlice<f32>,
12562        m: usize,
12563        in_f: usize,
12564        out0: usize,
12565        out1: usize,
12566        row_bytes: usize,
12567    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12568        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12569        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
12570    }
12571
12572    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
12573    /// `matmul_q8_fused2_t` with three ranges.
12574    #[allow(clippy::too_many_arguments)]
12575    pub fn matmul_q8_fused3_t(
12576        &self,
12577        w0: &crate::model::GpuTensor,
12578        w1: &crate::model::GpuTensor,
12579        w2: &crate::model::GpuTensor,
12580        aq: &CudaSlice<i8>,
12581        ad: &CudaSlice<f32>,
12582        m: usize,
12583    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12584    {
12585        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12586            return Ok(None);
12587        }
12588        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12589            return Ok(Some(self.e4m3_fused3_t_core(
12590                p0.0,
12591                p1.0,
12592                p2.0,
12593                aq,
12594                ad,
12595                m,
12596                w0.in_features(),
12597                p0.1,
12598                p1.1,
12599                p2.1,
12600                p0.2,
12601                p0.3,
12602                p1.3,
12603                p2.3,
12604            )?));
12605        }
12606        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12607            return Ok(None);
12608        };
12609        Ok(Some(self.q8_fused3_t_core(
12610            p0.0,
12611            p1.0,
12612            p2.0,
12613            aq,
12614            ad,
12615            m,
12616            w0.in_features(),
12617            p0.1,
12618            p1.1,
12619            p2.1,
12620            p0.2,
12621        )?))
12622    }
12623
12624    #[allow(clippy::too_many_arguments)]
12625    fn q8_fused3_t_core(
12626        &self,
12627        b0: &CudaSlice<u8>,
12628        b1: &CudaSlice<u8>,
12629        b2: &CudaSlice<u8>,
12630        aq: &CudaSlice<i8>,
12631        ad: &CudaSlice<f32>,
12632        m: usize,
12633        in_f: usize,
12634        out0: usize,
12635        out1: usize,
12636        out2: usize,
12637        row_bytes: usize,
12638    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12639        const ROWS_PER_BLOCK: u32 = 4;
12640        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12641        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12642        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12643        let f = self.func(if Self::batched_mcols(m) == 2 {
12644            "qmatvec_q8_0_mmvq_fused3_b2"
12645        } else {
12646            "qmatvec_q8_0_mmvq_fused3_b4"
12647        });
12648        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12649        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12650        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12651        let cfg = LaunchConfig {
12652            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12653            block_dim: (32, ROWS_PER_BLOCK, 1),
12654            shared_mem_bytes: 0,
12655        };
12656        let (inf, o0, o1, o2, mi, rbl) = (
12657            in_f as i32,
12658            out0 as i32,
12659            out1 as i32,
12660            out2 as i32,
12661            m as i32,
12662            row_bytes as i64,
12663        );
12664        let __s_b = self.gpu.stream();
12665        let mut b = __s_b.launch_builder(&f);
12666        b.arg(b0)
12667            .arg(b1)
12668            .arg(b2)
12669            .arg(aq)
12670            .arg(ad)
12671            .arg(&mut y0)
12672            .arg(&mut y1)
12673            .arg(&mut y2)
12674            .arg(&inf)
12675            .arg(&o0)
12676            .arg(&o1)
12677            .arg(&o2)
12678            .arg(&mi)
12679            .arg(&rbl);
12680        unsafe {
12681            b.launch(cfg)?;
12682        }
12683        Ok((y0, y1, y2))
12684    }
12685
12686    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
12687    #[allow(clippy::too_many_arguments)]
12688    pub fn qmatvec_q8_fused3_t_raw(
12689        &self,
12690        b0: &CudaSlice<u8>,
12691        b1: &CudaSlice<u8>,
12692        b2: &CudaSlice<u8>,
12693        x: &CudaSlice<f32>,
12694        m: usize,
12695        in_f: usize,
12696        out0: usize,
12697        out1: usize,
12698        out2: usize,
12699        row_bytes: usize,
12700    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12701        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12702        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
12703    }
12704
12705    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
12706    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
12707    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
12708    pub fn q8_ffn_fuse2_on(&self) -> bool {
12709        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12710        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
12711    }
12712
12713    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
12714    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
12715    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
12716    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
12717    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
12718    #[allow(clippy::type_complexity)]
12719    fn q8_fused_params<'w, const N: usize>(
12720        &self,
12721        ws: &[&'w crate::model::GpuTensor; N],
12722    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
12723        use crate::model::GpuTensor;
12724        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12725            return None;
12726        }
12727        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
12728            return None;
12729        }
12730        let in_f = ws[0].in_features();
12731        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
12732        for (i, w) in ws.iter().enumerate() {
12733            match w {
12734                GpuTensor::Quant {
12735                    bytes,
12736                    qtype,
12737                    row_bytes,
12738                    scale,
12739                    ..
12740                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
12741                    out[i] = Some((bytes, w.out_features(), *row_bytes))
12742                }
12743                _ => return None,
12744            }
12745        }
12746        Some(out.map(|o| o.unwrap()))
12747    }
12748
12749    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
12750    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
12751    pub fn e4m3_dual_on(&self) -> bool {
12752        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12753        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
12754    }
12755
12756    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
12757    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
12758    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
12759    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
12760    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
12761    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
12762    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
12763    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
12764    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
12765    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
12766    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
12767    #[allow(clippy::type_complexity)]
12768    fn e4m3_fused_params<'w, const N: usize>(
12769        &self,
12770        ws: &[&'w crate::model::GpuTensor; N],
12771    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
12772        use crate::model::GpuTensor;
12773        if !self.e4m3_dual_on() {
12774            return None;
12775        }
12776        let in_f = ws[0].in_features();
12777        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
12778        for (i, w) in ws.iter().enumerate() {
12779            match w {
12780                GpuTensor::Quant {
12781                    bytes,
12782                    qtype,
12783                    row_bytes,
12784                    scale,
12785                    rp,
12786                    rp4,
12787                    ..
12788                } if *qtype == QT_F8_E4M3
12789                    && w.in_features() == in_f
12790                    && *row_bytes == in_f
12791                    && !*rp
12792                    && rp4.is_none() =>
12793                {
12794                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
12795                }
12796                _ => return None,
12797            }
12798        }
12799        Some(out.map(|o| o.unwrap()))
12800    }
12801
12802    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
12803    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
12804    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
12805    #[allow(clippy::too_many_arguments)]
12806    fn e4m3_fused2_core(
12807        &self,
12808        b0: &CudaSlice<u8>,
12809        b1: &CudaSlice<u8>,
12810        aq: &CudaSlice<i8>,
12811        ad: &CudaSlice<f32>,
12812        in_f: usize,
12813        out0: usize,
12814        out1: usize,
12815        row_bytes: usize,
12816        ws0: f32,
12817        ws1: f32,
12818    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12819        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12820        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12821        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12822        let f = self.func("qmatvec_e4m3_mmvq_fused2");
12823        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12824        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12825        let cfg = LaunchConfig {
12826            grid_dim: (nb0 + nb1, 1, 1),
12827            block_dim: (32, ROWS_PER_BLOCK, 1),
12828            shared_mem_bytes: 0,
12829        };
12830        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
12831        let __s_b = self.gpu.stream();
12832        let mut b = __s_b.launch_builder(&f);
12833        b.arg(b0)
12834            .arg(b1)
12835            .arg(aq)
12836            .arg(ad)
12837            .arg(&mut y0)
12838            .arg(&mut y1)
12839            .arg(&inf)
12840            .arg(&o0)
12841            .arg(&o1)
12842            .arg(&rbl)
12843            .arg(&ws0)
12844            .arg(&ws1);
12845        unsafe {
12846            b.launch(cfg)?;
12847        }
12848        Ok((y0, y1))
12849    }
12850
12851    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
12852    #[allow(clippy::too_many_arguments)]
12853    fn e4m3_fused3_core(
12854        &self,
12855        b0: &CudaSlice<u8>,
12856        b1: &CudaSlice<u8>,
12857        b2: &CudaSlice<u8>,
12858        aq: &CudaSlice<i8>,
12859        ad: &CudaSlice<f32>,
12860        in_f: usize,
12861        out0: usize,
12862        out1: usize,
12863        out2: usize,
12864        row_bytes: usize,
12865        ws0: f32,
12866        ws1: f32,
12867        ws2: f32,
12868    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12869        const ROWS_PER_BLOCK: u32 = 4;
12870        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12871        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12872        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12873        let f = self.func("qmatvec_e4m3_mmvq_fused3");
12874        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12875        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12876        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12877        let cfg = LaunchConfig {
12878            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12879            block_dim: (32, ROWS_PER_BLOCK, 1),
12880            shared_mem_bytes: 0,
12881        };
12882        let (inf, o0, o1, o2, rbl) = (
12883            in_f as i32,
12884            out0 as i32,
12885            out1 as i32,
12886            out2 as i32,
12887            row_bytes as i64,
12888        );
12889        let __s_b = self.gpu.stream();
12890        let mut b = __s_b.launch_builder(&f);
12891        b.arg(b0)
12892            .arg(b1)
12893            .arg(b2)
12894            .arg(aq)
12895            .arg(ad)
12896            .arg(&mut y0)
12897            .arg(&mut y1)
12898            .arg(&mut y2)
12899            .arg(&inf)
12900            .arg(&o0)
12901            .arg(&o1)
12902            .arg(&o2)
12903            .arg(&rbl)
12904            .arg(&ws0)
12905            .arg(&ws1)
12906            .arg(&ws2);
12907        unsafe {
12908            b.launch(cfg)?;
12909        }
12910        Ok((y0, y1, y2))
12911    }
12912
12913    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
12914    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
12915    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
12916    #[allow(clippy::too_many_arguments)]
12917    fn e4m3_fused2_t_core(
12918        &self,
12919        b0: &CudaSlice<u8>,
12920        b1: &CudaSlice<u8>,
12921        aq: &CudaSlice<i8>,
12922        ad: &CudaSlice<f32>,
12923        m: usize,
12924        in_f: usize,
12925        out0: usize,
12926        out1: usize,
12927        row_bytes: usize,
12928        ws0: f32,
12929        ws1: f32,
12930    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12931        const ROWS_PER_BLOCK: u32 = 4;
12932        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12933        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12934        let f = self.func(match Self::batched_mcols(m) {
12935            2 => "qmatvec_e4m3_mmvq_fused2_b2",
12936            4 => "qmatvec_e4m3_mmvq_fused2_b4",
12937            _ => "qmatvec_e4m3_mmvq_fused2_b8",
12938        });
12939        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12940        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12941        let cfg = LaunchConfig {
12942            grid_dim: (nb0 + nb1, 1, 1),
12943            block_dim: (32, ROWS_PER_BLOCK, 1),
12944            shared_mem_bytes: 0,
12945        };
12946        let (inf, o0, o1, mi, rbl) = (
12947            in_f as i32,
12948            out0 as i32,
12949            out1 as i32,
12950            m as i32,
12951            row_bytes as i64,
12952        );
12953        let __s_b = self.gpu.stream();
12954        let mut b = __s_b.launch_builder(&f);
12955        b.arg(b0)
12956            .arg(b1)
12957            .arg(aq)
12958            .arg(ad)
12959            .arg(&mut y0)
12960            .arg(&mut y1)
12961            .arg(&inf)
12962            .arg(&o0)
12963            .arg(&o1)
12964            .arg(&mi)
12965            .arg(&rbl);
12966        unsafe {
12967            b.launch(cfg)?;
12968        }
12969        if ws0 != 1.0 {
12970            self.scale_inplace(&mut y0, ws0, m * out0)?;
12971        }
12972        if ws1 != 1.0 {
12973            self.scale_inplace(&mut y1, ws1, m * out1)?;
12974        }
12975        Ok((y0, y1))
12976    }
12977
12978    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
12979    #[allow(clippy::too_many_arguments)]
12980    fn e4m3_fused3_t_core(
12981        &self,
12982        b0: &CudaSlice<u8>,
12983        b1: &CudaSlice<u8>,
12984        b2: &CudaSlice<u8>,
12985        aq: &CudaSlice<i8>,
12986        ad: &CudaSlice<f32>,
12987        m: usize,
12988        in_f: usize,
12989        out0: usize,
12990        out1: usize,
12991        out2: usize,
12992        row_bytes: usize,
12993        ws0: f32,
12994        ws1: f32,
12995        ws2: f32,
12996    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12997        const ROWS_PER_BLOCK: u32 = 4;
12998        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12999        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
13000        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
13001        let f = self.func(if Self::batched_mcols(m) == 2 {
13002            "qmatvec_e4m3_mmvq_fused3_b2"
13003        } else {
13004            "qmatvec_e4m3_mmvq_fused3_b4"
13005        });
13006        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
13007        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
13008        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
13009        let cfg = LaunchConfig {
13010            grid_dim: (nb0 + nb1 + nb2, 1, 1),
13011            block_dim: (32, ROWS_PER_BLOCK, 1),
13012            shared_mem_bytes: 0,
13013        };
13014        let (inf, o0, o1, o2, mi, rbl) = (
13015            in_f as i32,
13016            out0 as i32,
13017            out1 as i32,
13018            out2 as i32,
13019            m as i32,
13020            row_bytes as i64,
13021        );
13022        let __s_b = self.gpu.stream();
13023        let mut b = __s_b.launch_builder(&f);
13024        b.arg(b0)
13025            .arg(b1)
13026            .arg(b2)
13027            .arg(aq)
13028            .arg(ad)
13029            .arg(&mut y0)
13030            .arg(&mut y1)
13031            .arg(&mut y2)
13032            .arg(&inf)
13033            .arg(&o0)
13034            .arg(&o1)
13035            .arg(&o2)
13036            .arg(&mi)
13037            .arg(&rbl);
13038        unsafe {
13039            b.launch(cfg)?;
13040        }
13041        if ws0 != 1.0 {
13042            self.scale_inplace(&mut y0, ws0, m * out0)?;
13043        }
13044        if ws1 != 1.0 {
13045            self.scale_inplace(&mut y1, ws1, m * out1)?;
13046        }
13047        if ws2 != 1.0 {
13048            self.scale_inplace(&mut y2, ws2, m * out2)?;
13049        }
13050        Ok((y0, y1, y2))
13051    }
13052
13053    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
13054    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
13055    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
13056    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
13057    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
13058    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
13059    ///
13060    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
13061    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
13062    pub fn qmatvec_e4m3_blk_mmvq(
13063        &self,
13064        bytes: &CudaSlice<u8>,
13065        aq: &CudaSlice<i8>,
13066        ad: &CudaSlice<f32>,
13067        scales: &CudaSlice<f32>,
13068        m: usize,
13069        in_f: usize,
13070        out_f: usize,
13071        row_bytes: usize,
13072        scale_cols: usize,
13073    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13074        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
13075        self.qmatvec_e4m3_blk_mmvq_into(
13076            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
13077        )?;
13078        Ok(y)
13079    }
13080
13081    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
13082    #[allow(clippy::too_many_arguments)]
13083    pub fn qmatvec_e4m3_blk_mmvq_into(
13084        &self,
13085        bytes: &CudaSlice<u8>,
13086        aq: &CudaSlice<i8>,
13087        ad: &CudaSlice<f32>,
13088        scales: &CudaSlice<f32>,
13089        m: usize,
13090        in_f: usize,
13091        out_f: usize,
13092        row_bytes: usize,
13093        scale_cols: usize,
13094        y: &mut CudaSlice<f32>,
13095    ) -> Result<(), Box<dyn std::error::Error>> {
13096        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13097        let f = self.func("qmatvec_e4m3_blk_mmvq");
13098        let cfg = LaunchConfig {
13099            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
13100            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
13101            shared_mem_bytes: 0,                // warp-only reduce
13102        };
13103        let (inf, outf, mi, rb, sc) = (
13104            in_f as i32,
13105            out_f as i32,
13106            m as i32,
13107            row_bytes as i64,
13108            scale_cols as i32,
13109        );
13110        let __s_b = self.gpu.stream();
13111        let mut b = __s_b.launch_builder(&f);
13112        b.arg(bytes)
13113            .arg(aq)
13114            .arg(ad)
13115            .arg(scales)
13116            .arg(&mut *y)
13117            .arg(&inf)
13118            .arg(&outf)
13119            .arg(&mi)
13120            .arg(&rb)
13121            .arg(&sc);
13122        unsafe {
13123            b.launch(cfg)?;
13124        }
13125        Ok(())
13126    }
13127
13128    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
13129    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
13130    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
13131    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
13132    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
13133    #[allow(clippy::too_many_arguments)]
13134    pub fn qmatvec_e4m3_blk_mmvq_batched(
13135        &self,
13136        bytes: &CudaSlice<u8>,
13137        aq: &CudaSlice<i8>,
13138        ad: &CudaSlice<f32>,
13139        scales: &CudaSlice<f32>,
13140        m: usize,
13141        in_f: usize,
13142        out_f: usize,
13143        row_bytes: usize,
13144        scale_cols: usize,
13145        mcols: usize,
13146    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13147        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13148        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
13149        let name = match mcols {
13150            2 => "qmatvec_e4m3_blk_mmvq_b2",
13151            4 => "qmatvec_e4m3_blk_mmvq_b4",
13152            8 => "qmatvec_e4m3_blk_mmvq_b8",
13153            16 => "qmatvec_e4m3_blk_mmvq_b16",
13154            _ => {
13155                return Err(
13156                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
13157                );
13158            }
13159        };
13160        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13161        let f = self.func(name);
13162        let cfg = LaunchConfig {
13163            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
13164            block_dim: (32, ROWS_PER_BLOCK, 1),
13165            shared_mem_bytes: 0,
13166        };
13167        let (inf, outf, mi, rb, sc) = (
13168            in_f as i32,
13169            out_f as i32,
13170            m as i32,
13171            row_bytes as i64,
13172            scale_cols as i32,
13173        );
13174        let __s_b = self.gpu.stream();
13175        let mut b = __s_b.launch_builder(&f);
13176        b.arg(bytes)
13177            .arg(aq)
13178            .arg(ad)
13179            .arg(scales)
13180            .arg(&mut y)
13181            .arg(&inf)
13182            .arg(&outf)
13183            .arg(&mi)
13184            .arg(&rb)
13185            .arg(&sc);
13186        unsafe {
13187            b.launch(cfg)?;
13188        }
13189        Ok(y)
13190    }
13191
13192    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
13193    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
13194    #[allow(clippy::too_many_arguments)]
13195    pub fn qmatvec_e4m3_blk_batched_raw(
13196        &self,
13197        bytes: &CudaSlice<u8>,
13198        x: &CudaSlice<f32>,
13199        scales: &CudaSlice<f32>,
13200        m: usize,
13201        in_f: usize,
13202        out_f: usize,
13203        row_bytes: usize,
13204        scale_cols: usize,
13205        mcols: usize,
13206    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13207        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13208        self.qmatvec_e4m3_blk_mmvq_batched(
13209            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
13210        )
13211    }
13212
13213    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
13214    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
13215    #[allow(clippy::too_many_arguments)]
13216    pub fn qmatvec_e4m3_blk_mmvq_raw(
13217        &self,
13218        bytes: &CudaSlice<u8>,
13219        x: &CudaSlice<f32>,
13220        scales: &CudaSlice<f32>,
13221        m: usize,
13222        in_f: usize,
13223        out_f: usize,
13224        row_bytes: usize,
13225        scale_cols: usize,
13226    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13227        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13228        self.qmatvec_e4m3_blk_mmvq(
13229            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
13230        )
13231    }
13232
13233    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
13234    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
13235    #[allow(clippy::too_many_arguments)]
13236    pub fn qmatvec_e4m3_fused2_raw(
13237        &self,
13238        b0: &CudaSlice<u8>,
13239        b1: &CudaSlice<u8>,
13240        x: &CudaSlice<f32>,
13241        in_f: usize,
13242        out0: usize,
13243        out1: usize,
13244        row_bytes: usize,
13245        ws0: f32,
13246        ws1: f32,
13247    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13248        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13249        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
13250    }
13251
13252    #[allow(clippy::too_many_arguments)]
13253    pub fn qmatvec_e4m3_fused3_raw(
13254        &self,
13255        b0: &CudaSlice<u8>,
13256        b1: &CudaSlice<u8>,
13257        b2: &CudaSlice<u8>,
13258        x: &CudaSlice<f32>,
13259        in_f: usize,
13260        out0: usize,
13261        out1: usize,
13262        out2: usize,
13263        row_bytes: usize,
13264        ws0: f32,
13265        ws1: f32,
13266        ws2: f32,
13267    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13268        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13269        self.e4m3_fused3_core(
13270            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13271        )
13272    }
13273
13274    #[allow(clippy::too_many_arguments)]
13275    pub fn qmatvec_e4m3_fused2_t_raw(
13276        &self,
13277        b0: &CudaSlice<u8>,
13278        b1: &CudaSlice<u8>,
13279        x: &CudaSlice<f32>,
13280        m: usize,
13281        in_f: usize,
13282        out0: usize,
13283        out1: usize,
13284        row_bytes: usize,
13285        ws0: f32,
13286        ws1: f32,
13287    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13288        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13289        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
13290    }
13291
13292    #[allow(clippy::too_many_arguments)]
13293    pub fn qmatvec_e4m3_fused3_t_raw(
13294        &self,
13295        b0: &CudaSlice<u8>,
13296        b1: &CudaSlice<u8>,
13297        b2: &CudaSlice<u8>,
13298        x: &CudaSlice<f32>,
13299        m: usize,
13300        in_f: usize,
13301        out0: usize,
13302        out1: usize,
13303        out2: usize,
13304        row_bytes: usize,
13305        ws0: f32,
13306        ws1: f32,
13307        ws2: f32,
13308    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13309        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13310        self.e4m3_fused3_t_core(
13311            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13312        )
13313    }
13314
13315    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
13316    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
13317    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
13318    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
13319    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
13320    ///
13321    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
13322    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
13323    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
13324    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
13325    fn try_e4m3_blk_pre(
13326        &self,
13327        w: &crate::model::GpuTensor,
13328        aq: &CudaSlice<i8>,
13329        ad: &CudaSlice<f32>,
13330        m: usize,
13331    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13332        use crate::model::GpuTensor;
13333        if let GpuTensor::Quant {
13334            bytes,
13335            qtype,
13336            row_bytes,
13337            blk: Some(g),
13338            ..
13339        } = w
13340        {
13341            if *qtype == QT_F8_E4M3_BLK {
13342                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
13343                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
13344                // below, so the decode-exactness contract is preserved at every width. Gated by
13345                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
13346                // one rollback door covers every dtype's batched tier.
13347                if (2..=16).contains(&m)
13348                    && std::env::var("MEMRA_NO_BATCHED").is_err()
13349                    && (m <= 4 || Self::b8_enabled())
13350                {
13351                    let mcols = Self::batched_mcols(m);
13352                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
13353                        bytes,
13354                        aq,
13355                        ad,
13356                        &g.scales,
13357                        m,
13358                        w.in_features(),
13359                        w.out_features(),
13360                        *row_bytes,
13361                        g.cols,
13362                        mcols,
13363                    )?));
13364                }
13365                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
13366                    bytes,
13367                    aq,
13368                    ad,
13369                    &g.scales,
13370                    m,
13371                    w.in_features(),
13372                    w.out_features(),
13373                    *row_bytes,
13374                    g.cols,
13375                )?));
13376            }
13377        }
13378        Ok(None)
13379    }
13380
13381    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
13382    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
13383    ///
13384    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
13385    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
13386    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
13387    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
13388    /// prefill keeps the floor's arithmetic and the floor's kernels.
13389    ///
13390    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
13391    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
13392    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
13393    /// (projection, prefill call) and frees immediately.
13394    ///
13395    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
13396    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
13397    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
13398    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
13399    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
13400    /// single-variable comparison instead of a two-variable one.
13401    ///
13402    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
13403    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
13404    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
13405    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
13406    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
13407    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
13408    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
13409    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
13410    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
13411    ///
13412    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
13413    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
13414    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
13415    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
13416    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
13417    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
13418    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
13419    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
13420    /// because v2's denominator had its slab already resident while this class's floor must build it
13421    /// every call; same tile, opposite sign, because the question changed.
13422    ///
13423    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
13424    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
13425    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
13426    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
13427    fn try_e4m3_blk_prefill(
13428        &self,
13429        w: &crate::model::GpuTensor,
13430        x: &CudaSlice<f32>,
13431        m: usize,
13432    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13433        use crate::model::GpuTensor;
13434        let GpuTensor::Quant {
13435            bytes,
13436            qtype,
13437            blk: Some(g),
13438            ..
13439        } = w
13440        else {
13441            return Ok(None);
13442        };
13443        if *qtype != QT_F8_E4M3_BLK {
13444            return Ok(None);
13445        }
13446        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
13447        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
13448        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
13449        // through to the dequant below when they do, never silently produce nothing.
13450        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
13451            return Ok(Some(y));
13452        }
13453        let (in_f, out_f) = (w.in_features(), w.out_features());
13454        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
13455        let tmp = GpuTensor::Quant {
13456            bytes: slab,
13457            qtype: QT_Q8_0,
13458            row_bytes: in_f / 32 * 34,
13459            ne: vec![in_f as u64, out_f as u64],
13460            scale: 1.0,
13461            rp: false,
13462            #[cfg(memra_cutlass)]
13463            cutlass: None,
13464            fp8: None,
13465            blk: None,
13466            f16: None,
13467            rp4: None,
13468        };
13469        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
13470        Ok(Some(self.matmul(&tmp, x, m)?))
13471    }
13472
13473    pub fn matmul_pre_noscale(
13474        &self,
13475        w: &crate::model::GpuTensor,
13476        aq: &CudaSlice<i8>,
13477        ad: &CudaSlice<f32>,
13478        m: usize,
13479    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
13480        use crate::model::GpuTensor;
13481        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
13482        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
13483        // rather than let the tail below refuse and cost the caller a re-dispatch.
13484        if m == 1 {
13485            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
13486                return Ok(Some((y, 1.0)));
13487            }
13488        }
13489        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
13490        if m != 1 || !self.uses_q8_1_fast(w) {
13491            return Ok(None);
13492        }
13493        let in_f = w.in_features();
13494        let out_f = w.out_features();
13495        let (bytes, qtype, row_bytes, scale, rp) = match w {
13496            GpuTensor::Quant {
13497                bytes,
13498                qtype,
13499                row_bytes,
13500                scale,
13501                rp,
13502                ..
13503            } => (bytes, *qtype, *row_bytes, *scale, *rp),
13504            _ => return Ok(None),
13505        };
13506        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
13507        if self.mmvq_supports(qtype) {
13508            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
13509            let (mbytes, mrp) = match w {
13510                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
13511                _ => (bytes, rp),
13512            };
13513            let y = self.qmatvec_mmvq(
13514                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
13515            )?;
13516            return Ok(Some((y, scale)));
13517        }
13518        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
13519        let name = match qtype {
13520            QT_Q8_0 => "qmatvec_q8_0_dp4a",
13521            QT_Q4_K => "qmatvec_q4_K_dp4a",
13522            QT_Q6_K => "qmatvec_q6_K_dp4a",
13523            QT_Q5_K => "qmatvec_q5_K_dp4a",
13524            QT_Q3_K => "qmatvec_q3_K_dp4a",
13525            QT_NVFP4 => {
13526                if rp {
13527                    "qmatvec_nvfp4_dp4a_rp"
13528                } else {
13529                    "qmatvec_nvfp4_dp4a"
13530                }
13531            }
13532            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
13533            _ => return Ok(None),
13534        };
13535        let f = self.func(name);
13536        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13537        let cfg = LaunchConfig {
13538            grid_dim: (out_f as u32, m as u32, 1),
13539            block_dim: (128, 1, 1),
13540            shared_mem_bytes: 0,
13541        };
13542        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13543        let __s_b = self.gpu.stream();
13544        let mut b = __s_b.launch_builder(&f);
13545        b.arg(bytes)
13546            .arg(aq)
13547            .arg(ad)
13548            .arg(&mut y)
13549            .arg(&inf)
13550            .arg(&outf)
13551            .arg(&mi)
13552            .arg(&rb);
13553        unsafe {
13554            b.launch(cfg)?;
13555        }
13556        Ok(Some((y, scale)))
13557    }
13558
13559    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
13560    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
13561    pub fn mmvq_supports(&self, qtype: i32) -> bool {
13562        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
13563        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
13564        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
13565        // is a pure function of the dtype — the decode-parity law holds under every env.
13566        if qtype == QT_F8_E4M3 {
13567            return true;
13568        }
13569        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
13570            return false;
13571        }
13572        matches!(
13573            qtype,
13574            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
13575        )
13576    }
13577
13578    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
13579    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
13580    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
13581    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
13582    pub fn qmatvec_mmvq(
13583        &self,
13584        bytes: &CudaSlice<u8>,
13585        aq: &CudaSlice<i8>,
13586        ad: &CudaSlice<f32>,
13587        m: usize,
13588        in_f: usize,
13589        out_f: usize,
13590        qtype: i32,
13591        row_bytes: usize,
13592        scale: f32,
13593        rp: bool,
13594    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13595        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
13596        self.qmatvec_mmvq_into(
13597            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
13598        )?;
13599        Ok(y)
13600    }
13601
13602    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
13603    #[allow(clippy::too_many_arguments)]
13604    pub fn qmatvec_mmvq_into(
13605        &self,
13606        bytes: &CudaSlice<u8>,
13607        aq: &CudaSlice<i8>,
13608        ad: &CudaSlice<f32>,
13609        m: usize,
13610        in_f: usize,
13611        out_f: usize,
13612        qtype: i32,
13613        row_bytes: usize,
13614        scale: f32,
13615        rp: bool,
13616        y: &mut CudaSlice<f32>,
13617    ) -> Result<(), Box<dyn std::error::Error>> {
13618        debug_assert!(y.len() >= m * out_f);
13619        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13620        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
13621        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
13622        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
13623        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
13624        if qtype == QT_Q8_0
13625            && rp
13626            && m == 1
13627            && out_f >= 64
13628            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
13629            && {
13630                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13631                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
13632            }
13633        {
13634            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
13635            let cfg = LaunchConfig {
13636                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
13637                block_dim: (32, 2, 1),
13638                shared_mem_bytes: 0,
13639            };
13640            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
13641            let __s_b = self.gpu.stream();
13642            let mut b = __s_b.launch_builder(&f);
13643            b.arg(bytes)
13644                .arg(aq)
13645                .arg(ad)
13646                .arg(&mut *y)
13647                .arg(&inf)
13648                .arg(&outf)
13649                .arg(&mi)
13650                .arg(&rb);
13651            unsafe {
13652                b.launch(cfg)?;
13653            }
13654            if scale != 1.0 {
13655                self.scale_inplace(y, scale, out_f)?;
13656            }
13657            return Ok(());
13658        }
13659        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
13660        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
13661        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
13662        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
13663        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
13664        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
13665        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
13666        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
13667        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
13668            2
13669        } else {
13670            1
13671        };
13672        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
13673        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
13674        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
13675        // valid-window interleaved, bit-identical per row — same dot program).
13676        if m == 1 && qtype == QT_Q4_0 {
13677            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13678            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
13679            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
13680            mr = *Q40MR.get_or_init(|| {
13681                std::env::var("MEMRA_Q40_MR")
13682                    .ok()
13683                    .and_then(|v| v.parse().ok())
13684                    .unwrap_or(1)
13685            });
13686        }
13687        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
13688        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
13689        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
13690        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
13691        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
13692        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
13693        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
13694        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
13695        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
13696        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
13697        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
13698        let q5_force = q5_mode.as_deref() == Some("2");
13699        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
13700        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
13701        let q5_il = qtype == QT_Q5_K
13702            && m == 1
13703            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
13704        if q5_il && !q5_force && out_f > 65536 {
13705            mr = 1;
13706        }
13707        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
13708        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
13709        if qtype == QT_Q4_0 && rp && mr != 1 {
13710            mr = 2;
13711        }
13712        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
13713        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
13714        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
13715        if qtype == QT_Q8_0 && rp {
13716            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13717            mr = *Q80MR.get_or_init(|| {
13718                std::env::var("MEMRA_Q80_MR")
13719                    .ok()
13720                    .and_then(|v| v.parse().ok())
13721                    .unwrap_or(1)
13722            });
13723        }
13724        let name = match (qtype, mr, rp) {
13725            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
13726            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
13727            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
13728            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
13729            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
13730            (QT_Q5_K, 2, _) => {
13731                if q5_il {
13732                    "qmatvec_q5_K_mmvq_mr2_il"
13733                } else {
13734                    "qmatvec_q5_K_mmvq_mr2"
13735                }
13736            }
13737            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
13738            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
13739            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
13740            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
13741            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
13742            (QT_Q8_0, _, true)
13743                if in_f % 1024 == 0 && {
13744                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13745                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
13746                } =>
13747            {
13748                "qmatvec_q8_0_mmvq_rpca"
13749            }
13750            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
13751            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
13752            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
13753            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
13754            // reach a GGUF-layout kernel or vice versa.
13755            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
13756            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
13757            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
13758            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
13759            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
13760            (QT_Q5_K, _, _) => {
13761                if q5_il {
13762                    "qmatvec_q5_K_mmvq_il"
13763                } else {
13764                    "qmatvec_q5_K_mmvq"
13765                }
13766            }
13767            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
13768            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
13769            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
13770            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
13771        };
13772        let f = self.func(name);
13773        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
13774        let rows_per_block = ROWS_PER_BLOCK * mr;
13775        let cfg = LaunchConfig {
13776            grid_dim: (
13777                (out_f as u32 + rows_per_block - 1) / rows_per_block,
13778                m as u32,
13779                1,
13780            ),
13781            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
13782            shared_mem_bytes: 0,                // warp-only reduce at m=1
13783        };
13784        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13785        let __s_b = self.gpu.stream();
13786        let mut b = __s_b.launch_builder(&f);
13787        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
13788        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
13789        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
13790        // weight_scale). Other mmvq kernels keep the 8-arg signature.
13791        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
13792            // PDL wave-B: the nvfp4 mr2_rp single (gemma wo / generic rp singles) joins
13793            // the wave-A launch class — 9-arg flavor (fused macro-scale epilogue).
13794            if Self::pdl_on()
13795                && Self::pdl_mmvq_on()
13796                && Self::pdl_nvfp4q8_on()
13797                && name == "qmatvec_nvfp4_mmvq_mr2_rp"
13798            {
13799                use cudarc::driver::{DevicePtr, DevicePtrMut};
13800                let s = &self.gpu.stream();
13801                let (pw, _g0) = bytes.device_ptr(s);
13802                let (paq, _g1) = aq.device_ptr(s);
13803                let (pad, _g2) = ad.device_ptr(s);
13804                let (py, _g3) = y.device_ptr_mut(s);
13805                let mut ps = [
13806                    &pw as *const _ as *mut std::ffi::c_void,
13807                    &paq as *const _ as *mut _,
13808                    &pad as *const _ as *mut _,
13809                    &py as *const _ as *mut _,
13810                    &inf as *const _ as *mut _,
13811                    &outf as *const _ as *mut _,
13812                    &mi as *const _ as *mut _,
13813                    &rb as *const _ as *mut _,
13814                    &scale as *const _ as *mut _,
13815                ];
13816                unsafe {
13817                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13818                }
13819                return Ok(());
13820            }
13821            b.arg(bytes)
13822                .arg(aq)
13823                .arg(ad)
13824                .arg(&mut *y)
13825                .arg(&inf)
13826                .arg(&outf)
13827                .arg(&mi)
13828                .arg(&rb)
13829                .arg(&scale);
13830            unsafe {
13831                b.launch(cfg)?;
13832            }
13833        } else if Self::pdl_on()
13834            && Self::pdl_mmvq_on()
13835            && (matches!(
13836                name,
13837                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
13838            ) || (Self::pdl_nvfp4q8_on()
13839                && matches!(name, "qmatvec_q8_0_mmvq_rp" | "qmatvec_q8_0_mmvq_mr2_rp")))
13840        {
13841            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
13842            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
13843            // names may take this launch (unmarked kernels would read unordered).
13844            {
13845                use cudarc::driver::{DevicePtr, DevicePtrMut};
13846                let s = &self.gpu.stream();
13847                let (pw, _g0) = bytes.device_ptr(s);
13848                let (paq, _g1) = aq.device_ptr(s);
13849                let (pad, _g2) = ad.device_ptr(s);
13850                let (py, _g3) = y.device_ptr_mut(s);
13851                let mut ps = [
13852                    &pw as *const _ as *mut std::ffi::c_void,
13853                    &paq as *const _ as *mut _,
13854                    &pad as *const _ as *mut _,
13855                    &py as *const _ as *mut _,
13856                    &inf as *const _ as *mut _,
13857                    &outf as *const _ as *mut _,
13858                    &mi as *const _ as *mut _,
13859                    &rb as *const _ as *mut _,
13860                ];
13861                unsafe {
13862                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13863                }
13864            }
13865            if scale != 1.0 {
13866                self.scale_inplace(y, scale, m * out_f)?;
13867            }
13868        } else {
13869            b.arg(bytes)
13870                .arg(aq)
13871                .arg(ad)
13872                .arg(&mut *y)
13873                .arg(&inf)
13874                .arg(&outf)
13875                .arg(&mi)
13876                .arg(&rb);
13877            unsafe {
13878                b.launch(cfg)?;
13879            }
13880            if scale != 1.0 {
13881                self.scale_inplace(y, scale, m * out_f)?;
13882            }
13883        }
13884        Ok(())
13885    }
13886
13887    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
13888    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
13889    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
13890    pub fn qmatvec_mmvq_raw(
13891        &self,
13892        bytes: &CudaSlice<u8>,
13893        x: &CudaSlice<f32>,
13894        m: usize,
13895        in_f: usize,
13896        out_f: usize,
13897        qtype: i32,
13898        row_bytes: usize,
13899        rp: bool,
13900    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13901        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13902        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
13903    }
13904
13905    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
13906    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
13907    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
13908    pub fn batched_supports(&self, qtype: i32) -> bool {
13909        matches!(
13910            qtype,
13911            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
13912        )
13913    }
13914
13915    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
13916    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
13917    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
13918    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
13919    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
13920    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
13921    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
13922    pub fn iq_fast_enabled() -> bool {
13923        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13924        *ON.get_or_init(|| {
13925            std::env::var("MEMRA_IQ_FAST")
13926                .map(|v| v != "0")
13927                .unwrap_or(true)
13928        })
13929    }
13930
13931    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
13932    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
13933    pub fn b8_enabled() -> bool {
13934        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13935        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
13936    }
13937
13938    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
13939    pub fn batched_mcols(m: usize) -> usize {
13940        if m == 2 {
13941            2
13942        } else if m <= 4 {
13943            4
13944        } else if m <= 8 {
13945            8
13946        } else {
13947            16
13948        }
13949    }
13950
13951    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
13952    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
13953    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
13954    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
13955    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
13956        Some(match (qtype, mcols) {
13957            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
13958            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
13959            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
13960            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
13961            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
13962            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
13963            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
13964            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
13965            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
13966            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
13967            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
13968            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
13969            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
13970            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
13971            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
13972            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
13973            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
13974            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
13975            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
13976            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
13977            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
13978            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
13979            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
13980            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
13981            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
13982            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
13983            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
13984            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
13985            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
13986            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
13987            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
13988            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
13989            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
13990            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
13991            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
13992            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
13993            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
13994            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
13995            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
13996            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
13997            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
13998            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
13999            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
14000            _ => return None,
14001        })
14002    }
14003
14004    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
14005    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
14006    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
14007    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
14008    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
14009    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
14010    ///
14011    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
14012    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
14013    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
14014    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
14015    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
14016    /// msweep on all six 27B shapes (2026-07-03):
14017    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
14018    ///          it applies for b4 (-3..-14%), never loses;
14019    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
14020    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
14021    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
14022    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
14023    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
14024    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
14025    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
14026    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
14027    /// b2: in_f>=6144 -> r2, else base.
14028    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
14029    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
14030    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
14031    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
14032    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
14033    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
14034    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
14035    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
14036    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
14037    /// Device SM count (cached) — grid-fill policy input.
14038    pub fn sm_count(&self) -> i32 {
14039        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
14040        *SMS.get_or_init(|| {
14041            use cudarc::driver::sys::CUdevice_attribute_enum as A;
14042            self.gpu
14043                .ctx
14044                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
14045                .unwrap_or(82)
14046        })
14047    }
14048
14049    pub fn batched_variant(
14050        &self,
14051        _m: usize,
14052        in_f: usize,
14053        out_f: usize,
14054        qtype: i32,
14055        row_bytes: usize,
14056        mcols: usize,
14057        rp: bool,
14058    ) -> &'static str {
14059        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
14060        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
14061        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
14062        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
14063        if qtype == QT_Q8_0 {
14064            return if rp { "rp" } else { "base" };
14065        }
14066        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14067        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
14068            Ok("base") => "base",
14069            Ok("pf") => "pf",
14070            Ok("r2") => "r2",
14071            Ok("r2w8") => "r2w8",
14072            Ok("pfr2") => "pfr2",
14073            Ok("ca") => "ca",
14074            Ok("car2") => "car2",
14075            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
14076            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
14077            Ok("rp") => "rp",
14078            Ok("rpr2") => "rpr2",
14079            Ok("rpr2w8") => "rpr2w8",
14080            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
14081            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
14082            Ok("rpca") => "rpca",
14083            Ok("rpcar2") => "rpcar2",
14084            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
14085            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
14086            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
14087            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
14088            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
14089            // bit-identical to the decode path — measurement corpus ONLY, never auto).
14090            Ok("rpsc") => "rpsc",
14091            Ok("rpms") => "rpms",
14092            Ok("rpmsc") => "rpmsc",
14093            Ok("rpks") => "rpks",
14094            Ok("rpksc") => "rpksc",
14095            _ => "auto",
14096        });
14097        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
14098        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
14099        // shapes qualify; anything else falls back to the register variants.
14100        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
14101        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
14102        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
14103        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
14104        // forced MEMRA_MMVQ_BV values still work).
14105        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14106        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
14107        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
14108        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
14109        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
14110        let sms = *SMS.get_or_init(|| {
14111            use cudarc::driver::sys::CUdevice_attribute_enum as A;
14112            self.gpu
14113                .ctx
14114                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
14115                .unwrap_or(82)
14116        });
14117        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
14118        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
14119        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
14120        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
14121        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
14122        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
14123        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
14124        // AUTO RULE = the measured winners table (differs from NVFP4's!):
14125        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
14126        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
14127        //     r2 1258us) — kernels kept behind the force seam for the corpus;
14128        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
14129        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
14130        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
14131        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
14132        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
14133        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
14134        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
14135        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
14136        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
14137        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
14138        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
14139        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14140        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
14141            Ok("base") => "base",
14142            Ok("r2") => "r2",
14143            Ok("r2w8") => "r2w8",
14144            _ => "auto",
14145        });
14146        let variant: &'static str = if qtype == QT_Q4_0 {
14147            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
14148            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
14149            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
14150            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14151            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
14152                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
14153                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
14154                // + syncs cost more than the stalls, bank-pad made no difference);
14155                // register load-ahead flat (nvcc already reorders). The b-tier limiter
14156                // is still unidentified — see the jsonl row.
14157                Ok("base") => "base",
14158                Ok("r2") => "r2",
14159                Ok("ms") => "ms",
14160                Ok("sm") => "sm",
14161                Ok("la") => "la",
14162                _ => "auto",
14163            });
14164            let v = if q40 != "auto" {
14165                q40
14166            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
14167                "r2"
14168            } else {
14169                "base"
14170            };
14171            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
14172            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
14173            // and the limiter is the per-column activation load chain (long_scoreboard
14174            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
14175            if rp {
14176                match v {
14177                    "ms" => "r2ms_rp",
14178                    "sm" => "r2sm_rp",
14179                    "la" => "r2la_rp",
14180                    "r2" => "r2_rp",
14181                    _ => "rp",
14182                }
14183            } else if matches!(v, "ms" | "sm" | "la") {
14184                "r2"
14185            } else {
14186                v
14187            }
14188        } else if qtype != QT_NVFP4 && !kq_r2 {
14189            "base"
14190        } else if kq_r2 && rp {
14191            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
14192            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
14193            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
14194            "rp"
14195        } else if kq_r2 {
14196            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
14197            // mcols != 4 forced r2w8 falls to unbounded r2.
14198            if kq_bv != "auto" {
14199                if kq_bv == "r2w8" && mcols != 4 {
14200                    "r2"
14201                } else {
14202                    kq_bv
14203                }
14204            } else if bv != "auto" {
14205                match bv {
14206                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
14207                    "r2w8" | "rpr2w8" => {
14208                        if mcols != 4 {
14209                            "r2"
14210                        } else {
14211                            "r2w8"
14212                        }
14213                    }
14214                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
14215                }
14216            } else {
14217                let blocks = (out_f + 7) / 8;
14218                let waves = blocks as f64 / (7 * sms as usize) as f64;
14219                let filled = blocks >= 4 * sms as usize;
14220                let use_r2 = if qtype == QT_Q4_K {
14221                    filled
14222                } else {
14223                    waves >= 2.0
14224                };
14225                if use_r2 { "r2" } else { "base" }
14226            }
14227        } else if bv != "auto" {
14228            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
14229            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
14230            // unsupported (shape, mcols) combos fall back to pf/r2.
14231            // On rp buffers, forced legacy names map to their rp twins (layout law).
14232            let v = if bv == "r2w8" && mcols == 2 {
14233                "r2"
14234            } else if bv == "ca" && (!ca_ok || mcols == 8) {
14235                "pf"
14236            } else if bv == "car2" && (!ca_ok || mcols == 8) {
14237                "r2"
14238            } else if bv == "pfr2" && mcols == 8 {
14239                "r2"
14240            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
14241                "rpr2"
14242            }
14243            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
14244            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
14245                if mcols == 8 { "rpr2w8" } else { "rpr2" }
14246            } else if bv == "rpcar2" && mcols == 2 {
14247                "rpca"
14248            }
14249            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
14250            // (rpms has no smem and no alignment need — always valid on rp buffers).
14251            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
14252                "rpr2"
14253            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
14254                "rpr2"
14255            } else {
14256                bv
14257            };
14258            if rp {
14259                match v {
14260                    "base" | "pf" | "ca" | "rp" => "rp",
14261                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
14262                    "r2w8" | "rpr2w8" => {
14263                        if mcols == 2 {
14264                            "rpr2"
14265                        } else {
14266                            "rpr2w8"
14267                        }
14268                    }
14269                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
14270                }
14271            } else {
14272                v
14273            }
14274        } else if mcols == 8 {
14275            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
14276            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
14277            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
14278            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
14279            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
14280            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
14281            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
14282            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
14283            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
14284            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
14285            if rp {
14286                if sc_ok { "rpsc" } else { "rpr2w8" }
14287            } else {
14288                "r2w8"
14289            }
14290        } else if mcols >= 4 {
14291            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
14292            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
14293            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
14294            let blocks = (out_f + 7) / 8;
14295            let r7 = 7 * sms as usize;
14296            let r8 = 8 * sms as usize;
14297            let waves = blocks as f64 / r7 as f64;
14298            let filled = blocks >= 4 * sms as usize;
14299            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
14300            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
14301            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
14302            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
14303                // the extra residency drops the INTEGER wave count -> the straggler wave a
14304                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
14305                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
14306                if rp { "rpr2w8" } else { "r2w8" }
14307            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
14308                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
14309                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
14310                if rp { "rpr2" } else { "r2" }
14311            } else {
14312                // fractional straggler-wave window with no crossing, or grid too small to fill
14313                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
14314                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
14315                if rp { "rp" } else { "pf" }
14316            }
14317        } else if in_f >= 6144 {
14318            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
14319            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
14320            // stays.
14321            if rp { "rpr2" } else { "r2" }
14322        } else if rp {
14323            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
14324            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
14325            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
14326            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
14327            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
14328            if sc_ok && waves >= 0.9 && waves <= 1.1 {
14329                "rpsc"
14330            } else {
14331                "rp"
14332            }
14333        } else {
14334            "base"
14335        };
14336        variant
14337    }
14338
14339    pub fn qmatvec_mmvq_batched(
14340        &self,
14341        bytes: &CudaSlice<u8>,
14342        aq: &CudaSlice<i8>,
14343        ad: &CudaSlice<f32>,
14344        m: usize,
14345        in_f: usize,
14346        out_f: usize,
14347        qtype: i32,
14348        row_bytes: usize,
14349        mcols: usize,
14350        scale: f32,
14351        rp: bool,
14352    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14353        const ROWS_PER_BLOCK: u32 = 4;
14354        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
14355        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
14356        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
14357        // weight keeps its rp-layout kernel family regardless of the override.
14358        let forced: Option<&'static str> = {
14359            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
14360            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
14361                .as_deref()
14362                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
14363        };
14364        let variant = match forced {
14365            Some(v) if !rp || v.contains("rp") => v,
14366            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
14367        };
14368        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
14369            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
14370        })?;
14371        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
14372        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
14373        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
14374        let variant = if mcols == 16 {
14375            if rp { "rp" } else { "base" }
14376        } else {
14377            variant
14378        };
14379        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
14380        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
14381        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
14382        // per-(token,row) chain (columns c >= m never execute in either form) ->
14383        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
14384        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
14385        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14386        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
14387        if b567
14388            && qtype == QT_NVFP4
14389            && rp
14390            && mcols == 8
14391            && (5..=7).contains(&m)
14392            && matches!(variant, "rpsc" | "rpr2w8")
14393        {
14394            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
14395            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
14396            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14397            let cfg = LaunchConfig {
14398                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14399                block_dim: (32, ROWS_PER_BLOCK, 1),
14400                shared_mem_bytes: 0,
14401            };
14402            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14403            let __s_b = self.gpu.stream();
14404            let mut b = __s_b.launch_builder(&f);
14405            b.arg(bytes)
14406                .arg(aq)
14407                .arg(ad)
14408                .arg(&mut y)
14409                .arg(&inf)
14410                .arg(&outf)
14411                .arg(&mi)
14412                .arg(&rb);
14413            unsafe {
14414                b.launch(cfg)?;
14415            }
14416            if scale != 1.0 {
14417                self.scale_inplace(&mut y, scale, m * out_f)?;
14418            }
14419            return Ok(y);
14420        }
14421        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
14422            "base" => (base_name.into(), ROWS_PER_BLOCK),
14423            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
14424            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
14425            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
14426            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
14427            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
14428            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
14429            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
14430            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
14431            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
14432            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
14433            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
14434            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
14435            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
14436            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
14437        };
14438        debug_assert!(
14439            !rp || name.contains("_rp"),
14440            "rp weight dispatched to a GGUF-layout kernel"
14441        );
14442        let f = self.func(&name);
14443        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14444        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
14445        let smem = if name.contains("_r2sm_rp") {
14446            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
14447        } else {
14448            0
14449        };
14450        let cfg = LaunchConfig {
14451            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14452            block_dim: (32, ROWS_PER_BLOCK, 1),
14453            shared_mem_bytes: smem,
14454        };
14455        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14456        let __s_b = self.gpu.stream();
14457        let mut b = __s_b.launch_builder(&f);
14458        b.arg(bytes)
14459            .arg(aq)
14460            .arg(ad)
14461            .arg(&mut y)
14462            .arg(&inf)
14463            .arg(&outf)
14464            .arg(&mi)
14465            .arg(&rb);
14466        unsafe {
14467            b.launch(cfg)?;
14468        }
14469        if scale != 1.0 {
14470            self.scale_inplace(&mut y, scale, m * out_f)?;
14471        }
14472        Ok(y)
14473    }
14474
14475    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
14476    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
14477    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
14478    pub fn qmatvec_batched_raw(
14479        &self,
14480        bytes: &CudaSlice<u8>,
14481        x: &CudaSlice<f32>,
14482        m: usize,
14483        in_f: usize,
14484        out_f: usize,
14485        qtype: i32,
14486        row_bytes: usize,
14487        mcols: usize,
14488        rp: bool,
14489    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14490        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14491        self.qmatvec_mmvq_batched(
14492            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
14493        )
14494    }
14495
14496    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
14497    pub fn qmatvec_nvfp4_batched_raw(
14498        &self,
14499        bytes: &CudaSlice<u8>,
14500        x: &CudaSlice<f32>,
14501        m: usize,
14502        in_f: usize,
14503        out_f: usize,
14504        row_bytes: usize,
14505        mcols: usize,
14506        rp: bool,
14507    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14508        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
14509    }
14510
14511    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
14512    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
14513    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
14514    fn try_fp4_gemm(
14515        &self,
14516        w: &crate::model::GpuTensor,
14517        x: &CudaSlice<f32>,
14518        m: usize,
14519        in_f: usize,
14520        out_f: usize,
14521    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14522        use crate::model::GpuTensor;
14523        if cfg!(memra_portable_cuda) {
14524            return Ok(None);
14525        }
14526        if std::env::var("MEMRA_FP4").is_err() {
14527            return Ok(None);
14528        }
14529        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
14530        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
14531        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
14532        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
14533        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
14534        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
14535        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
14536        // for the common no-macro-scale case.
14537        #[cfg(memra_cutlass)]
14538        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
14539            if let GpuTensor::Quant {
14540                bytes,
14541                qtype,
14542                scale,
14543                row_bytes,
14544                cutlass,
14545                ..
14546            } = w
14547            {
14548                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
14549                    if let Some(cw) = cutlass {
14550                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
14551                        let y = self.cutlass_fp4_gemm(
14552                            &cw.b_packed,
14553                            &cw.sfb_swizzled,
14554                            x,
14555                            *scale,
14556                            m,
14557                            out_f,
14558                            in_f,
14559                        )?;
14560                        return Ok(Some(y));
14561                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
14562                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
14563                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
14564                        // (the load-time repack ~doubles it) — needed for models that don't fit the
14565                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
14566                        let (b_packed, sfb_sw) =
14567                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
14568                        let y =
14569                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
14570                        return Ok(Some(y));
14571                    }
14572                }
14573            }
14574        }
14575        if let GpuTensor::Quant {
14576            bytes,
14577            qtype,
14578            row_bytes,
14579            scale,
14580            rp,
14581            ..
14582        } = w
14583        {
14584            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
14585            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
14586            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
14587                let y =
14588                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
14589                return Ok(Some(y));
14590            }
14591        }
14592        Ok(None)
14593    }
14594
14595    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
14596    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
14597    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
14598    pub fn rms_norm_f16out(
14599        &self,
14600        x: &CudaSlice<f32>,
14601        w: &CudaSlice<f32>,
14602        dst: &mut CudaSlice<f32>,
14603        dst16: &mut CudaSlice<u8>,
14604        ncols: usize,
14605        nrows: usize,
14606        eps: f32,
14607    ) -> Result<(), Box<dyn std::error::Error>> {
14608        let f = self.func("rms_norm_f16out_f32");
14609        let cfg = LaunchConfig {
14610            grid_dim: (nrows as u32, 1, 1),
14611            block_dim: (rms_block(), 1, 1),
14612            shared_mem_bytes: 0,
14613        };
14614        let (nc, e) = (ncols as i32, eps);
14615        let __s_b = self.gpu.stream();
14616        let mut b = __s_b.launch_builder(&f);
14617        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
14618        unsafe {
14619            b.launch(cfg)?;
14620        }
14621        Ok(())
14622    }
14623
14624    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
14625    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
14626    #[allow(clippy::too_many_arguments)]
14627    pub fn add_rms_norm_f16out(
14628        &self,
14629        a: &CudaSlice<f32>,
14630        b: &CudaSlice<f32>,
14631        w: &CudaSlice<f32>,
14632        res: &mut CudaSlice<f32>,
14633        dst: &mut CudaSlice<f32>,
14634        dst16: &mut CudaSlice<u8>,
14635        ncols: usize,
14636        nrows: usize,
14637        eps: f32,
14638    ) -> Result<(), Box<dyn std::error::Error>> {
14639        let f = self.func("add_rms_norm_f16out_f32");
14640        let cfg = LaunchConfig {
14641            grid_dim: (nrows as u32, 1, 1),
14642            block_dim: (rms_block(), 1, 1),
14643            shared_mem_bytes: 0,
14644        };
14645        let (nc, e) = (ncols as i32, eps);
14646        let __s_lb = self.gpu.stream();
14647        let mut lb = __s_lb.launch_builder(&f);
14648        lb.arg(a)
14649            .arg(b)
14650            .arg(w)
14651            .arg(res)
14652            .arg(dst)
14653            .arg(dst16)
14654            .arg(&nc)
14655            .arg(&e);
14656        unsafe {
14657            lb.launch(cfg)?;
14658        }
14659        Ok(())
14660    }
14661
14662    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
14663    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
14664    pub fn matmul_group_xh(
14665        &self,
14666        ws: &[&crate::model::GpuTensor],
14667        x: &CudaSlice<f32>,
14668        xh: &CudaSlice<u8>,
14669        m: usize,
14670    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14671        let mut out = Vec::with_capacity(ws.len());
14672        let in_f = ws[0].in_features();
14673        for w in ws {
14674            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
14675                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
14676                    out.push(y);
14677                    continue;
14678                }
14679            }
14680            out.push(self.matmul(w, x, m)?);
14681        }
14682        Ok(out)
14683    }
14684
14685    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
14686    /// GDN steps). Layouts [T, H].
14687    pub fn gdn_pad_mask(
14688        &self,
14689        beta: &mut CudaSlice<f32>,
14690        g_log: &mut CudaSlice<f32>,
14691        len_d: &CudaSlice<i32>,
14692        h: usize,
14693        t: usize,
14694    ) -> Result<(), Box<dyn std::error::Error>> {
14695        let f = self.func("gdn_pad_mask_f32");
14696        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
14697        let (hi, ti) = (h as i32, t as i32);
14698        let __s_b = self.gpu.stream();
14699        let mut b = __s_b.launch_builder(&f);
14700        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
14701        unsafe {
14702            b.launch(cfg)?;
14703        }
14704        Ok(())
14705    }
14706
14707    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
14708    /// gather for the padded prime graph's h_seed/hlast.
14709    pub fn row_gather_dev(
14710        &self,
14711        src: &CudaSlice<f32>,
14712        dst: &mut CudaSlice<f32>,
14713        len_d: &CudaSlice<i32>,
14714        ncols: usize,
14715    ) -> Result<(), Box<dyn std::error::Error>> {
14716        let f = self.func("row_gather_dev_f32");
14717        let cfg = LaunchConfig::for_num_elems(ncols as u32);
14718        let nc = ncols as i32;
14719        let __s_b = self.gpu.stream();
14720        let mut b = __s_b.launch_builder(&f);
14721        b.arg(src).arg(dst).arg(len_d).arg(&nc);
14722        unsafe {
14723            b.launch(cfg)?;
14724        }
14725        Ok(())
14726    }
14727
14728    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
14729    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
14730    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
14731    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
14732    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
14733    /// different in_f) falls back to its own `matmul` — behavior unchanged.
14734    pub fn matmul_group(
14735        &self,
14736        ws: &[&crate::model::GpuTensor],
14737        x: &CudaSlice<f32>,
14738        m: usize,
14739    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14740        use crate::model::GpuTensor;
14741        let mut out = Vec::with_capacity(ws.len());
14742        let any_mirror = ws
14743            .iter()
14744            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
14745        if m >= 16 && any_mirror && !self.verify_exact_on() {
14746            let in_f = ws[0].in_features();
14747            let xh = self.f16_act(x, m * in_f, in_f)?;
14748            for w in ws {
14749                if w.in_features() == in_f {
14750                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
14751                        out.push(y);
14752                        continue;
14753                    }
14754                }
14755                out.push(self.matmul(w, x, m)?);
14756            }
14757            return Ok(out);
14758        }
14759        for w in ws {
14760            out.push(self.matmul(w, x, m)?);
14761        }
14762        Ok(out)
14763    }
14764
14765    /// Cross-request grouped matmul (task #13): run ONE projection group over the
14766    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
14767    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
14768    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
14769    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
14770    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
14771    pub fn matmul_group_multi(
14772        &self,
14773        ws: &[&crate::model::GpuTensor],
14774        xs: &[&CudaSlice<f32>],
14775        ms: &[usize],
14776    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
14777        assert_eq!(xs.len(), ms.len());
14778        let in_f = ws[0].in_features();
14779        let total: usize = ms.iter().sum();
14780        let mut xcat = self.uninit(total * in_f)?;
14781        let mut off = 0usize;
14782        for (x, &m) in xs.iter().zip(ms) {
14783            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
14784            off += m;
14785        }
14786        let ys = self.matmul_group(ws, &xcat, total)?;
14787        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
14788        for (w, y) in ws.iter().zip(ys) {
14789            let out_f = w.out_features();
14790            let mut off = 0usize;
14791            for (s, &m) in ms.iter().enumerate() {
14792                let mut ys_s = self.uninit(m * out_f)?;
14793                let src = y.slice(off * out_f..(off + m) * out_f);
14794                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
14795                out[s].push(ys_s);
14796                off += m;
14797            }
14798        }
14799        Ok(out)
14800    }
14801
14802    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
14803    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
14804    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
14805    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
14806    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
14807    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
14808    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
14809    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
14810    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
14811    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
14812        use crate::model::GpuTensor;
14813        if !legacy_quant_gemm_allowed(
14814            cfg!(memra_portable_cuda),
14815            cfg!(memra_hopper_mma),
14816            std::env::var_os("MEMRA_NO_GEMM").is_some(),
14817        ) {
14818            return false;
14819        }
14820        match w {
14821            GpuTensor::Quant { qtype, .. } => {
14822                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
14823                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
14824            }
14825            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
14826        }
14827    }
14828
14829    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
14830    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
14831    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
14832    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
14833    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
14834    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
14835    pub fn qmatvec_gemm(
14836        &self,
14837        w: &crate::model::GpuTensor,
14838        aq: &CudaSlice<i8>,
14839        ad: &CudaSlice<f32>,
14840        m: usize,
14841    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14842        use crate::model::GpuTensor;
14843        let in_f = w.in_features();
14844        let out_f = w.out_features();
14845        let (bytes, qtype, row_bytes, scale, rp) = match w {
14846            GpuTensor::Quant {
14847                bytes,
14848                qtype,
14849                row_bytes,
14850                scale,
14851                rp,
14852                ..
14853            } => (bytes, *qtype, *row_bytes, *scale, *rp),
14854            _ => unreachable!("gemm_supports guaranteed Quant"),
14855        };
14856        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
14857        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
14858        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
14859        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
14860        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
14861        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
14862            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
14863                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
14864                if scale != 1.0 {
14865                    self.scale_inplace(&mut y, scale, m * out_f)?;
14866                }
14867                return Ok(y);
14868            }
14869        }
14870        let name = match qtype {
14871            QT_Q8_0 => "qmatvec_gemm_q8_0",
14872            QT_Q4_K => "qmatvec_gemm_q4_K",
14873            QT_Q4_0 => {
14874                if rp {
14875                    "qmatvec_gemm_q4_0_rp"
14876                } else {
14877                    "qmatvec_gemm_q4_0"
14878                }
14879            }
14880            QT_Q5_K => "qmatvec_gemm_q5_K",
14881            QT_Q6_K => "qmatvec_gemm_q6_K",
14882            QT_NVFP4 => {
14883                if rp {
14884                    "qmatvec_gemm_nvfp4_rp"
14885                } else {
14886                    "qmatvec_gemm_nvfp4"
14887                }
14888            }
14889            _ => unreachable!(),
14890        };
14891        let f = self.func(name);
14892        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14893        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
14894        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
14895        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
14896        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14897        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14898        let k1_tile = if is_k1 {
14899            k1_launch_override().unwrap_or((128, 128, 8))
14900        } else {
14901            (128, 128, 8)
14902        };
14903        let (bm, bn): (u32, u32) = if is_k1 {
14904            (k1_tile.0, k1_tile.1)
14905        } else {
14906            (64, 256)
14907        };
14908        let warps: u32 = if is_k1 {
14909            k1_tile.2
14910        } else {
14911            match qtype {
14912                QT_NVFP4 => 8,
14913                _ => 4,
14914            }
14915        };
14916        let cfg = LaunchConfig {
14917            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14918            block_dim: (32, warps, 1),
14919            shared_mem_bytes: 0,
14920        };
14921        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14922        let __s_b = self.gpu.stream();
14923        let mut b = __s_b.launch_builder(&f);
14924        b.arg(bytes)
14925            .arg(aq)
14926            .arg(ad)
14927            .arg(&mut y)
14928            .arg(&inf)
14929            .arg(&outf)
14930            .arg(&mi)
14931            .arg(&rb);
14932        unsafe {
14933            b.launch(cfg)?;
14934        }
14935        if scale != 1.0 {
14936            self.scale_inplace(&mut y, scale, m * out_f)?;
14937        }
14938        Ok(y)
14939    }
14940
14941    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
14942    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
14943    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
14944    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
14945    pub fn qmatvec_gemm_raw(
14946        &self,
14947        bytes: &CudaSlice<u8>,
14948        x: &CudaSlice<f32>,
14949        m: usize,
14950        in_f: usize,
14951        out_f: usize,
14952        qtype: i32,
14953        row_bytes: usize,
14954    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14955        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14956        let name = match qtype {
14957            QT_Q8_0 => "qmatvec_gemm_q8_0",
14958            QT_Q4_K => "qmatvec_gemm_q4_K",
14959            QT_Q4_0 => "qmatvec_gemm_q4_0",
14960            QT_Q5_K => "qmatvec_gemm_q5_K",
14961            QT_Q6_K => "qmatvec_gemm_q6_K",
14962            QT_NVFP4 => "qmatvec_gemm_nvfp4",
14963            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
14964            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
14965        };
14966        let f = self.func(name);
14967        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14968        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
14969        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
14970        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14971        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14972        let k1_tile = if is_k1 {
14973            k1_launch_override().unwrap_or((128, 128, 8))
14974        } else {
14975            (128, 128, 8)
14976        };
14977        let (bm, bn): (u32, u32) = if is_k1 {
14978            (k1_tile.0, k1_tile.1)
14979        } else {
14980            (64, 256)
14981        };
14982        let warps: u32 = if is_k1 {
14983            k1_tile.2
14984        } else {
14985            match qtype {
14986                QT_NVFP4 | QT_NVFP4_RP => 8,
14987                _ => 4,
14988            }
14989        };
14990        let cfg = LaunchConfig {
14991            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14992            block_dim: (32, warps, 1),
14993            shared_mem_bytes: 0,
14994        };
14995        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14996        let __s_b = self.gpu.stream();
14997        let mut b = __s_b.launch_builder(&f);
14998        b.arg(bytes)
14999            .arg(&aq)
15000            .arg(&ad)
15001            .arg(&mut y)
15002            .arg(&inf)
15003            .arg(&outf)
15004            .arg(&mi)
15005            .arg(&rb);
15006        unsafe {
15007            b.launch(cfg)?;
15008        }
15009        Ok(y)
15010    }
15011
15012    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
15013    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
15014    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
15015    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
15016    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
15017    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
15018    pub fn qmatvec_gemm_q8_0_wgmma_raw(
15019        &self,
15020        rp4: &CudaSlice<u8>,
15021        aq: &CudaSlice<i8>,
15022        ad: &CudaSlice<f32>,
15023        m: usize,
15024        in_f: usize,
15025        out_f: usize,
15026    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15027        assert!(
15028            out_f % 64 == 0 && in_f % 32 == 0,
15029            "wgmma GEMM needs out_f%64==0, in_f%32==0"
15030        );
15031        let f = self.func("qmatvec_gemm_q8_0_wgmma");
15032        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
15033        let cfg = LaunchConfig {
15034            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
15035            block_dim: (128, 1, 1),
15036            shared_mem_bytes: 0,
15037        };
15038        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
15039        let __s_b = self.gpu.stream();
15040        let mut b = __s_b.launch_builder(&f);
15041        b.arg(rp4)
15042            .arg(aq)
15043            .arg(ad)
15044            .arg(&mut y)
15045            .arg(&inf)
15046            .arg(&outf)
15047            .arg(&mi);
15048        unsafe {
15049            b.launch(cfg)?;
15050        }
15051        Ok(y)
15052    }
15053
15054    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
15055    pub fn scale_inplace(
15056        &self,
15057        y: &mut CudaSlice<f32>,
15058        s: f32,
15059        n: usize,
15060    ) -> Result<(), Box<dyn std::error::Error>> {
15061        let f = self.func("scale_f32");
15062        let cfg = LaunchConfig::for_num_elems(n as u32);
15063        let (sf, ni) = (s, n as i32);
15064        let __s_b = self.gpu.stream();
15065        let mut b = __s_b.launch_builder(&f);
15066        b.arg(y).arg(&sf).arg(&ni);
15067        unsafe {
15068            b.launch(cfg)?;
15069        }
15070        Ok(())
15071    }
15072
15073    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
15074    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
15075    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
15076    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
15077    pub fn bf16_to_f32(
15078        &self,
15079        data: &cudarc::driver::CudaView<'_, u8>,
15080        n: usize,
15081    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15082        let mut out = self.alloc_uninit::<f32>(n)?;
15083        let f = self.func("bf16_to_f32");
15084        let cfg = LaunchConfig::for_num_elems(n as u32);
15085        let ni = n as i32;
15086        let __s_b = self.gpu.stream();
15087        let mut b = __s_b.launch_builder(&f);
15088        b.arg(data).arg(&mut out).arg(&ni);
15089        unsafe {
15090            b.launch(cfg)?;
15091        }
15092        Ok(out)
15093    }
15094
15095    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
15096    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
15097    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
15098    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
15099    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
15100    /// calls, the spec-verify contract) vs plain linear.
15101    fn linear_bf16_chunked(
15102        &self,
15103        x: &CudaSlice<f32>,
15104        data: &CudaSlice<u8>,
15105        m: usize,
15106        in_f: usize,
15107        out_f: usize,
15108        exact: bool,
15109    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15110        const CHUNK_BYTES: usize = 256 << 20;
15111        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
15112        if chunk_rows >= out_f {
15113            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
15114            return if exact {
15115                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
15116            } else {
15117                self.linear(x, &wf32, m, in_f, out_f)
15118            };
15119        }
15120        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
15121        let mut r0 = 0usize;
15122        while r0 < out_f {
15123            let rows = chunk_rows.min(out_f - r0);
15124            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
15125            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
15126            let yc = if exact {
15127                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
15128            } else {
15129                self.linear(x, &wf32, m, in_f, rows)?
15130            };
15131            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
15132            for mi in 0..m {
15133                let src = yc.slice(mi * rows..(mi + 1) * rows);
15134                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
15135                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
15136            }
15137            r0 += rows;
15138        }
15139        Ok(y)
15140    }
15141
15142    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
15143    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
15144    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
15145    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
15146    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
15147    /// router/shexp sites and matmul_decode_exact's Float arm.
15148    pub fn linear_decode_exact(
15149        &self,
15150        x: &CudaSlice<f32>,
15151        w: &CudaSlice<f32>,
15152        m_tokens: usize,
15153        in_f: usize,
15154        out_f: usize,
15155    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15156        if m_tokens == 1 {
15157            return self.linear(x, w, 1, in_f, out_f);
15158        }
15159        let xv = self.view(x, m_tokens * in_f);
15160        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
15161        for t in 0..m_tokens {
15162            let row = xv.slice(t * in_f..(t + 1) * in_f);
15163            let mut xr = self.alloc_uninit::<f32>(in_f)?;
15164            self.copy_view_into(&mut xr, 0, &row, in_f)?;
15165            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
15166            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
15167        }
15168        Ok(y)
15169    }
15170
15171    pub fn linear(
15172        &self,
15173        x: &CudaSlice<f32>,
15174        w: &CudaSlice<f32>,
15175        m_tokens: usize,
15176        in_f: usize,
15177        out_f: usize,
15178    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15179        use cudarc::cublaslt::{Matmul, MatmulConfig};
15180        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
15181        let cfg = MatmulConfig {
15182            transa: true,
15183            transb: false,
15184            transc: false,
15185            m: out_f as u64,
15186            n: m_tokens as u64,
15187            k: in_f as u64,
15188            alpha: 1.0,
15189            lda: in_f as i64,
15190            ldb: in_f as i64,
15191            beta: 0.0,
15192            ldc: out_f as i64,
15193            stride_a: None,
15194            stride_b: None,
15195            stride_c: None,
15196            stride_bias: None,
15197            batch_size: None,
15198        };
15199        unsafe {
15200            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
15201        }
15202        Ok(c)
15203    }
15204
15205    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
15206    pub fn sdpa_naive(
15207        &self,
15208        q: &CudaSlice<f32>,
15209        k: &CudaSlice<f32>,
15210        v: &CudaSlice<f32>,
15211        o: &mut CudaSlice<f32>,
15212        head_dim: usize,
15213        n_head: usize,
15214        n_head_kv: usize,
15215        t: usize,
15216        t_kv: usize,
15217        scale: f32,
15218        causal: bool,
15219    ) -> Result<(), Box<dyn std::error::Error>> {
15220        let f = self.func("sdpa_naive_f32");
15221        let cfg = LaunchConfig {
15222            grid_dim: (n_head as u32, t as u32, 1),
15223            block_dim: (128, 1, 1),
15224            shared_mem_bytes: (t_kv * 4) as u32,
15225        };
15226        let (hd, nh, nhkv, ti, tkvi, cz) = (
15227            head_dim as i32,
15228            n_head as i32,
15229            n_head_kv as i32,
15230            t as i32,
15231            t_kv as i32,
15232            causal as i32,
15233        );
15234        let __s_b = self.gpu.stream();
15235        let mut b = __s_b.launch_builder(&f);
15236        b.arg(q)
15237            .arg(k)
15238            .arg(v)
15239            .arg(o)
15240            .arg(&hd)
15241            .arg(&nh)
15242            .arg(&nhkv)
15243            .arg(&ti)
15244            .arg(&tkvi)
15245            .arg(&scale)
15246            .arg(&cz);
15247        unsafe {
15248            b.launch(cfg)?;
15249        }
15250        Ok(())
15251    }
15252
15253    /// Island twin (lane/gemma-vision): causal + sliding-window attention with
15254    /// bidirectional image islands. `span_id` labels each absolute kv position
15255    /// (-1 text, >=0 island id); same-island keys are visible unconditionally,
15256    /// reproducing the reference's non-causal image batch. window 0 = no window.
15257    #[allow(clippy::too_many_arguments)]
15258    pub fn sdpa_naive_island(
15259        &self,
15260        q: &CudaSlice<f32>,
15261        k: &CudaSlice<f32>,
15262        v: &CudaSlice<f32>,
15263        o: &mut CudaSlice<f32>,
15264        span_id: &CudaSlice<i32>,
15265        head_dim: usize,
15266        n_head: usize,
15267        n_head_kv: usize,
15268        t: usize,
15269        t_kv: usize,
15270        scale: f32,
15271        window: usize,
15272    ) -> Result<(), Box<dyn std::error::Error>> {
15273        let f = self.func("sdpa_naive_island_f32");
15274        let cfg = LaunchConfig {
15275            grid_dim: (n_head as u32, t as u32, 1),
15276            block_dim: (128, 1, 1),
15277            shared_mem_bytes: (t_kv * 4) as u32,
15278        };
15279        let (hd, nh, nhkv, ti, tkvi, wi) = (
15280            head_dim as i32,
15281            n_head as i32,
15282            n_head_kv as i32,
15283            t as i32,
15284            t_kv as i32,
15285            window as i32,
15286        );
15287        let __s_b = self.gpu.stream();
15288        let mut b = __s_b.launch_builder(&f);
15289        b.arg(q)
15290            .arg(k)
15291            .arg(v)
15292            .arg(o)
15293            .arg(span_id)
15294            .arg(&hd)
15295            .arg(&nh)
15296            .arg(&nhkv)
15297            .arg(&ti)
15298            .arg(&tkvi)
15299            .arg(&scale)
15300            .arg(&wi);
15301        unsafe {
15302            b.launch(cfg)?;
15303        }
15304        Ok(())
15305    }
15306
15307    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
15308    #[allow(clippy::too_many_arguments)]
15309    pub fn sdpa_naive_w(
15310        &self,
15311        q: &CudaSlice<f32>,
15312        k: &CudaSlice<f32>,
15313        v: &CudaSlice<f32>,
15314        o: &mut CudaSlice<f32>,
15315        head_dim: usize,
15316        n_head: usize,
15317        n_head_kv: usize,
15318        t: usize,
15319        t_kv: usize,
15320        scale: f32,
15321        causal: bool,
15322        window: usize,
15323    ) -> Result<(), Box<dyn std::error::Error>> {
15324        let f = self.func("sdpa_naive_w_f32");
15325        let cfg = LaunchConfig {
15326            grid_dim: (n_head as u32, t as u32, 1),
15327            block_dim: (128, 1, 1),
15328            shared_mem_bytes: (t_kv * 4) as u32,
15329        };
15330        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15331            head_dim as i32,
15332            n_head as i32,
15333            n_head_kv as i32,
15334            t as i32,
15335            t_kv as i32,
15336            causal as i32,
15337            window as i32,
15338        );
15339        let __s_b = self.gpu.stream();
15340        let mut b = __s_b.launch_builder(&f);
15341        b.arg(q)
15342            .arg(k)
15343            .arg(v)
15344            .arg(o)
15345            .arg(&hd)
15346            .arg(&nh)
15347            .arg(&nhkv)
15348            .arg(&ti)
15349            .arg(&tkvi)
15350            .arg(&scale)
15351            .arg(&cz)
15352            .arg(&wi);
15353        unsafe {
15354            b.launch(cfg)?;
15355        }
15356        Ok(())
15357    }
15358
15359    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
15360    pub fn sdpa_naive_view(
15361        &self,
15362        q: &CudaSlice<f32>,
15363        k: &cudarc::driver::CudaView<f32>,
15364        v: &cudarc::driver::CudaView<f32>,
15365        o: &mut CudaSlice<f32>,
15366        head_dim: usize,
15367        n_head: usize,
15368        n_head_kv: usize,
15369        t: usize,
15370        t_kv: usize,
15371        scale: f32,
15372        causal: bool,
15373    ) -> Result<(), Box<dyn std::error::Error>> {
15374        let f = self.func("sdpa_naive_f32");
15375        let cfg = LaunchConfig {
15376            grid_dim: (n_head as u32, t as u32, 1),
15377            block_dim: (128, 1, 1),
15378            shared_mem_bytes: (t_kv * 4) as u32,
15379        };
15380        let (hd, nh, nhkv, ti, tkvi, cz) = (
15381            head_dim as i32,
15382            n_head as i32,
15383            n_head_kv as i32,
15384            t as i32,
15385            t_kv as i32,
15386            causal as i32,
15387        );
15388        let __s_b = self.gpu.stream();
15389        let mut b = __s_b.launch_builder(&f);
15390        b.arg(q)
15391            .arg(k)
15392            .arg(v)
15393            .arg(o)
15394            .arg(&hd)
15395            .arg(&nh)
15396            .arg(&nhkv)
15397            .arg(&ti)
15398            .arg(&tkvi)
15399            .arg(&scale)
15400            .arg(&cz);
15401        unsafe {
15402            b.launch(cfg)?;
15403        }
15404        Ok(())
15405    }
15406
15407    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
15408    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
15409    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
15410    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
15411    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
15412    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
15413    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
15414    #[allow(clippy::too_many_arguments)]
15415    pub fn fa_dequant_kv_view_f32(
15416        &self,
15417        k: &cudarc::driver::CudaView<u8>,
15418        v: &cudarc::driver::CudaView<u8>,
15419        kf: &mut CudaSlice<f32>,
15420        vf: &mut CudaSlice<f32>,
15421        kv_dim_k: usize,
15422        kv_dim_v: usize,
15423        t_kv: usize,
15424        k_tok_bytes: usize,
15425        v_tok_bytes: usize,
15426        g: bool,
15427    ) -> Result<(), Box<dyn std::error::Error>> {
15428        let f = if g {
15429            self.func_g("fa_dequant_kv_ws_f32")
15430        } else {
15431            self.func("fa_dequant_kv_ws_f32")
15432        };
15433        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
15434        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15435        let cfg = LaunchConfig {
15436            grid_dim: (nblk.max(1), 1, 1),
15437            block_dim: (256, 1, 1),
15438            shared_mem_bytes: 0,
15439        };
15440        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
15441        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15442        let __s_b = self.gpu.stream();
15443        let mut b = __s_b.launch_builder(&f);
15444        b.arg(k)
15445            .arg(v)
15446            .arg(&mut *kf)
15447            .arg(&mut *vf)
15448            .arg(&kdk)
15449            .arg(&kdv)
15450            .arg(&tkvi)
15451            .arg(&ktb)
15452            .arg(&vtb);
15453        unsafe {
15454            b.launch(cfg)?;
15455        }
15456        Ok(())
15457    }
15458
15459    #[allow(clippy::too_many_arguments)]
15460    pub fn sdpa_naive_quantized_view(
15461        &self,
15462        q: &CudaSlice<f32>,
15463        k: &cudarc::driver::CudaView<u8>,
15464        v: &cudarc::driver::CudaView<u8>,
15465        o: &mut CudaSlice<f32>,
15466        head_dim: usize,
15467        n_head: usize,
15468        n_head_kv: usize,
15469        t: usize,
15470        t_kv: usize,
15471        scale: f32,
15472        causal: bool,
15473        k_tok_bytes: usize,
15474        v_tok_bytes: usize,
15475    ) -> Result<(), Box<dyn std::error::Error>> {
15476        let kv_dim = n_head_kv * head_dim;
15477        let mut kf = self.uninit(t_kv * kv_dim)?;
15478        let mut vf = self.uninit(t_kv * kv_dim)?;
15479        let f = self.func("fa_dequant_kv_ws_f32");
15480        let total = (2 * t_kv * kv_dim) as u64;
15481        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15482        let cfg = LaunchConfig {
15483            grid_dim: (nblk.max(1), 1, 1),
15484            block_dim: (256, 1, 1),
15485            shared_mem_bytes: 0,
15486        };
15487        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15488        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15489        let __s_b = self.gpu.stream();
15490        let mut b = __s_b.launch_builder(&f);
15491        b.arg(k)
15492            .arg(v)
15493            .arg(&mut kf)
15494            .arg(&mut vf)
15495            .arg(&kv_dim_i)
15496            .arg(&kv_dim_i)
15497            .arg(&t_kv_i)
15498            .arg(&k_tok_bytes_i)
15499            .arg(&v_tok_bytes_i);
15500        unsafe { b.launch(cfg)? };
15501        self.sdpa_naive(
15502            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15503        )
15504    }
15505
15506    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
15507    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
15508    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
15509    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
15510    /// unwindowed function above and produces bit-identical output at window == 0.
15511    ///
15512    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
15513    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
15514    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
15515    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
15516    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
15517    #[allow(clippy::too_many_arguments)]
15518    pub fn sdpa_naive_w_quantized_view(
15519        &self,
15520        q: &CudaSlice<f32>,
15521        k: &cudarc::driver::CudaView<u8>,
15522        v: &cudarc::driver::CudaView<u8>,
15523        o: &mut CudaSlice<f32>,
15524        head_dim: usize,
15525        n_head: usize,
15526        n_head_kv: usize,
15527        t: usize,
15528        t_kv: usize,
15529        scale: f32,
15530        causal: bool,
15531        window: usize,
15532        k_tok_bytes: usize,
15533        v_tok_bytes: usize,
15534    ) -> Result<(), Box<dyn std::error::Error>> {
15535        let kv_dim = n_head_kv * head_dim;
15536        let mut kf = self.uninit(t_kv * kv_dim)?;
15537        let mut vf = self.uninit(t_kv * kv_dim)?;
15538        let f = self.func("fa_dequant_kv_ws_f32");
15539        let total = (2 * t_kv * kv_dim) as u64;
15540        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15541        let cfg = LaunchConfig {
15542            grid_dim: (nblk.max(1), 1, 1),
15543            block_dim: (256, 1, 1),
15544            shared_mem_bytes: 0,
15545        };
15546        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15547        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15548        let __s_b = self.gpu.stream();
15549        let mut b = __s_b.launch_builder(&f);
15550        b.arg(k)
15551            .arg(v)
15552            .arg(&mut kf)
15553            .arg(&mut vf)
15554            .arg(&kv_dim_i)
15555            .arg(&kv_dim_i)
15556            .arg(&t_kv_i)
15557            .arg(&k_tok_bytes_i)
15558            .arg(&v_tok_bytes_i);
15559        unsafe { b.launch(cfg)? };
15560        self.sdpa_naive_w(
15561            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15562        )
15563    }
15564
15565    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
15566    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
15567    /// Q/K/V/O [head_dim, n_head(_kv), T].
15568    pub fn fa_prefill(
15569        &self,
15570        q: &CudaSlice<f32>,
15571        k: &CudaSlice<f32>,
15572        v: &CudaSlice<f32>,
15573        o: &mut CudaSlice<f32>,
15574        head_dim: usize,
15575        n_head: usize,
15576        n_head_kv: usize,
15577        t: usize,
15578        t_kv: usize,
15579        scale: f32,
15580        causal: bool,
15581    ) -> Result<(), Box<dyn std::error::Error>> {
15582        if portable_mma_gated() {
15583            return self.sdpa_naive(
15584                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15585            );
15586        }
15587        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
15588        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
15589        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
15590        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
15591        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
15592        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
15593        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
15594        let fa3_on = head_dim == 256
15595            && causal
15596            && t == t_kv
15597            && match std::env::var("MEMRA_FA3").as_deref() {
15598                Ok("0") => false,
15599                Ok("1") => true,
15600                _ => cfg!(memra_hopper_mma),
15601            };
15602        if fa3_on {
15603            let n = t * n_head * head_dim;
15604            let nkv = t * n_head_kv * head_dim;
15605            let mut q16 = self.alloc_u8_uninit(n * 2)?;
15606            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
15607            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
15608            self.f32_to_bf16_into(q, &mut q16, n)?;
15609            self.f32_to_bf16_into(k, &mut k16, nkv)?;
15610            self.f32_to_bf16_into(v, &mut v16, nkv)?;
15611            let rc = {
15612                use cudarc::driver::{DevicePtr, DevicePtrMut};
15613                let stream = self.gpu.stream();
15614                let (qp, _g1) = q16.device_ptr(&stream);
15615                let (kp, _g2) = k16.device_ptr(&stream);
15616                let (vp, _g3) = v16.device_ptr(&stream);
15617                let (op, _g4) = o.device_ptr_mut(&stream);
15618                unsafe {
15619                    memra_fa3_prefill(
15620                        qp as *const core::ffi::c_void,
15621                        kp as *const core::ffi::c_void,
15622                        vp as *const core::ffi::c_void,
15623                        op as *mut f32,
15624                        t as i32,
15625                        n_head as i32,
15626                        n_head_kv as i32,
15627                        head_dim as i32,
15628                        scale,
15629                        stream.cu_stream() as *mut core::ffi::c_void,
15630                    )
15631                }
15632            };
15633            if rc != 0 {
15634                return Err(format!("memra_fa3_prefill rc={rc}").into());
15635            }
15636            return Ok(());
15637        }
15638        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
15639        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
15640        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
15641        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
15642        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15643        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
15644        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
15645            const BLOCK_Q: usize = 64;
15646            const BKX: usize = 32;
15647            let f = self.func("fa_prefill_bf16_p1");
15648            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
15649                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
15650            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15651            f.set_attribute(
15652                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15653                shmem as i32,
15654            )?;
15655            let cfg = LaunchConfig {
15656                grid_dim: (
15657                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15658                    n_head as u32,
15659                    1,
15660                ),
15661                block_dim: (32, 4, 1),
15662                shared_mem_bytes: shmem,
15663            };
15664            let (hd, nh, nhkv, ti, tkvi, cz) = (
15665                head_dim as i32,
15666                n_head as i32,
15667                n_head_kv as i32,
15668                t as i32,
15669                t_kv as i32,
15670                causal as i32,
15671            );
15672            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15673            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15674            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15675            let __s_b = self.gpu.stream();
15676            let mut b = __s_b.launch_builder(&f);
15677            b.arg(&qb)
15678                .arg(&kb)
15679                .arg(&vb)
15680                .arg(o)
15681                .arg(&hd)
15682                .arg(&nh)
15683                .arg(&nhkv)
15684                .arg(&ti)
15685                .arg(&tkvi)
15686                .arg(&scale)
15687                .arg(&cz);
15688            unsafe {
15689                b.launch(cfg)?;
15690            }
15691            return Ok(());
15692        }
15693        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
15694        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
15695        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
15696        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
15697        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
15698        const BK: usize = 32;
15699        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
15700        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
15701        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
15702        let (block_q, warps, w2_sfx): (usize, u32, &str) =
15703            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
15704        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
15705        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
15706        // other head_dims to sdpa_naive before reaching here.
15707        let hd_sfx = fa_hd_suffix(head_dim)?;
15708        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15709        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
15710        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
15711        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
15712        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
15713        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
15714        let (kb16, vb16) = if bf16kv {
15715            let n = t_kv * n_head_kv * head_dim;
15716            let mut kb = self.alloc_u8_uninit(n * 2)?;
15717            let mut vb = self.alloc_u8_uninit(n * 2)?;
15718            let fcv = self.func("f32_to_bf16_bulk");
15719            let ni = n as i64;
15720            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
15721            let __s_b = self.gpu.stream();
15722            let mut b = __s_b.launch_builder(&fcv);
15723            b.arg(k).arg(&mut kb).arg(&ni);
15724            unsafe {
15725                b.launch(cfgc)?;
15726            }
15727            let __s_b = self.gpu.stream();
15728            let mut b = __s_b.launch_builder(&fcv);
15729            b.arg(v).arg(&mut vb).arg(&ni);
15730            unsafe {
15731                b.launch(cfgc)?;
15732            }
15733            (Some(kb), Some(vb))
15734        } else {
15735            (None, None)
15736        };
15737        let f = self.func(&if bf16kv {
15738            format!("fa_prefill_bf16kv_pp{hd_sfx}")
15739        } else {
15740            format!(
15741                "fa_prefill_f32{}{}{hd_sfx}",
15742                if floor { "" } else { "_pp" },
15743                if floor { "" } else { w2_sfx }
15744            )
15745        });
15746        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
15747        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
15748        let kv_stages = if bf16kv { 2 } else { 1 };
15749        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
15750            + 4 * (block_q * BK + 2 * block_q)) as u32;
15751        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15752        f.set_attribute(
15753            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15754            shmem as i32,
15755        )?;
15756        let cfg = LaunchConfig {
15757            grid_dim: (
15758                (t as u32 + block_q as u32 - 1) / block_q as u32,
15759                n_head as u32,
15760                1,
15761            ),
15762            block_dim: (32, warps, 1),
15763            shared_mem_bytes: shmem,
15764        };
15765        let (hd, nh, nhkv, ti, tkvi, cz) = (
15766            head_dim as i32,
15767            n_head as i32,
15768            n_head_kv as i32,
15769            t as i32,
15770            t_kv as i32,
15771            causal as i32,
15772        );
15773        let __s_b = self.gpu.stream();
15774        let mut b = __s_b.launch_builder(&f);
15775        b.arg(q);
15776        match (&kb16, &vb16) {
15777            (Some(kb), Some(vb)) => {
15778                b.arg(kb).arg(vb);
15779            }
15780            _ => {
15781                b.arg(k).arg(v);
15782            }
15783        }
15784        b.arg(o)
15785            .arg(&hd)
15786            .arg(&nh)
15787            .arg(&nhkv)
15788            .arg(&ti)
15789            .arg(&tkvi)
15790            .arg(&scale)
15791            .arg(&cz);
15792        unsafe {
15793            b.launch(cfg)?;
15794        }
15795        Ok(())
15796    }
15797
15798    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
15799    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
15800    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
15801    #[allow(clippy::too_many_arguments)]
15802    pub fn fa_prefill_w(
15803        &self,
15804        q: &CudaSlice<f32>,
15805        k: &CudaSlice<f32>,
15806        v: &CudaSlice<f32>,
15807        o: &mut CudaSlice<f32>,
15808        head_dim: usize,
15809        n_head: usize,
15810        n_head_kv: usize,
15811        t: usize,
15812        t_kv: usize,
15813        scale: f32,
15814        causal: bool,
15815        window: usize,
15816    ) -> Result<(), Box<dyn std::error::Error>> {
15817        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
15818        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
15819        if portable_mma_gated() {
15820            return self.sdpa_naive_w(
15821                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15822            );
15823        }
15824        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
15825        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
15826        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
15827        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15828        let faw_f32 =
15829            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
15830        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15831        self.fa_prefill_w_arm(
15832            q,
15833            k,
15834            v,
15835            o,
15836            head_dim,
15837            n_head,
15838            n_head_kv,
15839            t,
15840            t_kv,
15841            scale,
15842            causal,
15843            window,
15844            floor || faw_f32,
15845            floor,
15846        )
15847    }
15848
15849    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
15850    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
15851    #[allow(clippy::too_many_arguments)]
15852    pub fn fa_prefill_w_pre(
15853        &self,
15854        qb: &CudaSlice<u8>,
15855        kb: &CudaSlice<u8>,
15856        vb: &CudaSlice<u8>,
15857        o: &mut CudaSlice<f32>,
15858        head_dim: usize,
15859        n_head: usize,
15860        n_head_kv: usize,
15861        t: usize,
15862        t_kv: usize,
15863        scale: f32,
15864        causal: bool,
15865        window: usize,
15866        v_f16: bool,
15867    ) -> Result<(), Box<dyn std::error::Error>> {
15868        const BLOCK_Q: usize = 64;
15869        const BK: usize = 32;
15870        debug_assert_eq!(head_dim, 256);
15871        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15872        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
15873        if hp {
15874            const BLOCK_QH: usize = 32;
15875            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
15876            // else re-encode through the pooled scratch (stream-ordered reuse).
15877            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15878            let vh: &CudaSlice<u8> = if v_f16 {
15879                vb
15880            } else {
15881                let n = t_kv * n_head_kv * head_dim;
15882                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
15883                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
15884                }
15885                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
15886                vguard.as_ref().unwrap()
15887            };
15888            let f = self.func("fa_prefill_w_bf16_p1h2");
15889            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15890            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15891            f.set_attribute(
15892                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15893                shmem as i32,
15894            )?;
15895            let cfg = LaunchConfig {
15896                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15897                block_dim: (32, 4, 1),
15898                shared_mem_bytes: shmem,
15899            };
15900            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15901                head_dim as i32,
15902                n_head as i32,
15903                n_head_kv as i32,
15904                t as i32,
15905                t_kv as i32,
15906                causal as i32,
15907                window as i32,
15908            );
15909            let __s_b = self.gpu.stream();
15910            let mut b = __s_b.launch_builder(&f);
15911            b.arg(qb)
15912                .arg(kb)
15913                .arg(vh)
15914                .arg(o)
15915                .arg(&hd)
15916                .arg(&nh)
15917                .arg(&nhkv)
15918                .arg(&ti)
15919                .arg(&tkvi)
15920                .arg(&scale)
15921                .arg(&cz)
15922                .arg(&wi);
15923            unsafe {
15924                b.launch(cfg)?;
15925            }
15926            return Ok(());
15927        }
15928        let f = self.func("fa_prefill_w_bf16_p1");
15929        let shmem =
15930            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15931        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15932        f.set_attribute(
15933            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15934            shmem as i32,
15935        )?;
15936        let cfg = LaunchConfig {
15937            grid_dim: (
15938                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15939                n_head as u32,
15940                1,
15941            ),
15942            block_dim: (32, 4, 1),
15943            shared_mem_bytes: shmem,
15944        };
15945        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15946            head_dim as i32,
15947            n_head as i32,
15948            n_head_kv as i32,
15949            t as i32,
15950            t_kv as i32,
15951            causal as i32,
15952            window as i32,
15953        );
15954        let __s_b = self.gpu.stream();
15955        let mut b = __s_b.launch_builder(&f);
15956        b.arg(qb)
15957            .arg(kb)
15958            .arg(vb)
15959            .arg(o)
15960            .arg(&hd)
15961            .arg(&nh)
15962            .arg(&nhkv)
15963            .arg(&ti)
15964            .arg(&tkvi)
15965            .arg(&scale)
15966            .arg(&cz)
15967            .arg(&wi);
15968        unsafe {
15969            b.launch(cfg)?;
15970        }
15971        Ok(())
15972    }
15973
15974    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
15975    #[allow(clippy::too_many_arguments)]
15976    pub fn fa_prefill_w_arm(
15977        &self,
15978        q: &CudaSlice<f32>,
15979        k: &CudaSlice<f32>,
15980        v: &CudaSlice<f32>,
15981        o: &mut CudaSlice<f32>,
15982        head_dim: usize,
15983        n_head: usize,
15984        n_head_kv: usize,
15985        t: usize,
15986        t_kv: usize,
15987        scale: f32,
15988        causal: bool,
15989        window: usize,
15990        f32_stage: bool,
15991        floor: bool,
15992    ) -> Result<(), Box<dyn std::error::Error>> {
15993        const BLOCK_Q: usize = 64;
15994        const BK: usize = 32;
15995        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
15996        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
15997        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
15998        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
15999        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16000        let p1 = !floor
16001            && !f32_stage
16002            && *P1_ON.get_or_init(|| {
16003                std::env::var("MEMRA_FAW_P1")
16004                    .map(|v| v != "0")
16005                    .unwrap_or(true)
16006            });
16007        let hp =
16008            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16009        if hp {
16010            const BLOCK_QH: usize = 32;
16011            let f = self.func("fa_prefill_w_bf16_p1h2");
16012            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
16013            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16014            f.set_attribute(
16015                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16016                shmem as i32,
16017            )?;
16018            let cfg = LaunchConfig {
16019                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
16020                block_dim: (32, 4, 1),
16021                shared_mem_bytes: shmem,
16022            };
16023            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16024                head_dim as i32,
16025                n_head as i32,
16026                n_head_kv as i32,
16027                t as i32,
16028                t_kv as i32,
16029                causal as i32,
16030                window as i32,
16031            );
16032            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16033            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16034            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
16035            let __s_b = self.gpu.stream();
16036            let mut b = __s_b.launch_builder(&f);
16037            b.arg(&qb)
16038                .arg(&kb)
16039                .arg(&vh)
16040                .arg(o)
16041                .arg(&hd)
16042                .arg(&nh)
16043                .arg(&nhkv)
16044                .arg(&ti)
16045                .arg(&tkvi)
16046                .arg(&scale)
16047                .arg(&cz)
16048                .arg(&wi);
16049            unsafe {
16050                b.launch(cfg)?;
16051            }
16052            return Ok(());
16053        }
16054        if p1 {
16055            let f = self.func("fa_prefill_w_bf16_p1");
16056            let shmem =
16057                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16058            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16059            f.set_attribute(
16060                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16061                shmem as i32,
16062            )?;
16063            let cfg = LaunchConfig {
16064                grid_dim: (
16065                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16066                    n_head as u32,
16067                    1,
16068                ),
16069                block_dim: (32, 4, 1),
16070                shared_mem_bytes: shmem,
16071            };
16072            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16073                head_dim as i32,
16074                n_head as i32,
16075                n_head_kv as i32,
16076                t as i32,
16077                t_kv as i32,
16078                causal as i32,
16079                window as i32,
16080            );
16081            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16082            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16083            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16084            let __s_b = self.gpu.stream();
16085            let mut b = __s_b.launch_builder(&f);
16086            b.arg(&qb)
16087                .arg(&kb)
16088                .arg(&vb)
16089                .arg(o)
16090                .arg(&hd)
16091                .arg(&nh)
16092                .arg(&nhkv)
16093                .arg(&ti)
16094                .arg(&tkvi)
16095                .arg(&scale)
16096                .arg(&cz)
16097                .arg(&wi);
16098            unsafe {
16099                b.launch(cfg)?;
16100            }
16101            return Ok(());
16102        }
16103        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
16104        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
16105        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16106        let g4 = !floor
16107            && !f32_stage
16108            && n_head_kv == 1
16109            && n_head % 4 == 0
16110            && *G4_ON.get_or_init(|| {
16111                std::env::var("MEMRA_FAW_G4")
16112                    .map(|v| v != "0")
16113                    .unwrap_or(true)
16114            });
16115        if g4 {
16116            const SP_M: usize = 16;
16117            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
16118            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
16119            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16120            let o2 = *O2_ON.get_or_init(|| {
16121                std::env::var("MEMRA_FAW_O2")
16122                    .map(|v| v != "0")
16123                    .unwrap_or(true)
16124            });
16125            let f = self.func(if o2 {
16126                "fa_prefill_w_bf16_g4o2"
16127            } else {
16128                "fa_prefill_w_bf16_g4"
16129            });
16130            let shmem = if o2 {
16131                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
16132            } else {
16133                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
16134                    as u32
16135            };
16136            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16137            f.set_attribute(
16138                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16139                shmem as i32,
16140            )?;
16141            let cfg = LaunchConfig {
16142                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
16143                block_dim: (32, 4, 1),
16144                shared_mem_bytes: shmem,
16145            };
16146            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16147                head_dim as i32,
16148                n_head as i32,
16149                n_head_kv as i32,
16150                t as i32,
16151                t_kv as i32,
16152                causal as i32,
16153                window as i32,
16154            );
16155            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16156            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16157            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16158            let __s_b = self.gpu.stream();
16159            let mut b = __s_b.launch_builder(&f);
16160            b.arg(&qb)
16161                .arg(&kb)
16162                .arg(&vb)
16163                .arg(o)
16164                .arg(&hd)
16165                .arg(&nh)
16166                .arg(&nhkv)
16167                .arg(&ti)
16168                .arg(&tkvi)
16169                .arg(&scale)
16170                .arg(&cz)
16171                .arg(&wi);
16172            unsafe {
16173                b.launch(cfg)?;
16174            }
16175            return Ok(());
16176        }
16177        let f = self.func(if floor {
16178            "fa_prefill_w_f32"
16179        } else if f32_stage {
16180            "fa_prefill_w_f32_pp"
16181        } else {
16182            "fa_prefill_w_bf16_pp"
16183        });
16184        let shmem =
16185            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16186        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16187        f.set_attribute(
16188            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16189            shmem as i32,
16190        )?;
16191        let cfg = LaunchConfig {
16192            grid_dim: (
16193                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16194                n_head as u32,
16195                1,
16196            ),
16197            block_dim: (32, 4, 1),
16198            shared_mem_bytes: shmem,
16199        };
16200        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16201            head_dim as i32,
16202            n_head as i32,
16203            n_head_kv as i32,
16204            t as i32,
16205            t_kv as i32,
16206            causal as i32,
16207            window as i32,
16208        );
16209        if f32_stage {
16210            let __s_b = self.gpu.stream();
16211            let mut b = __s_b.launch_builder(&f);
16212            b.arg(q)
16213                .arg(k)
16214                .arg(v)
16215                .arg(o)
16216                .arg(&hd)
16217                .arg(&nh)
16218                .arg(&nhkv)
16219                .arg(&ti)
16220                .arg(&tkvi)
16221                .arg(&scale)
16222                .arg(&cz)
16223                .arg(&wi);
16224            unsafe {
16225                b.launch(cfg)?;
16226            }
16227        } else {
16228            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16229            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16230            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16231            let __s_b = self.gpu.stream();
16232            let mut b = __s_b.launch_builder(&f);
16233            b.arg(&qb)
16234                .arg(&kb)
16235                .arg(&vb)
16236                .arg(o)
16237                .arg(&hd)
16238                .arg(&nh)
16239                .arg(&nhkv)
16240                .arg(&ti)
16241                .arg(&tkvi)
16242                .arg(&scale)
16243                .arg(&cz)
16244                .arg(&wi);
16245            unsafe {
16246                b.launch(cfg)?;
16247            }
16248        }
16249        Ok(())
16250    }
16251
16252    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
16253    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
16254    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
16255    #[allow(clippy::too_many_arguments)]
16256    pub fn fa_prefill_hd512(
16257        &self,
16258        q: &CudaSlice<f32>,
16259        k: &CudaSlice<f32>,
16260        v: &CudaSlice<f32>,
16261        o: &mut CudaSlice<f32>,
16262        head_dim: usize,
16263        n_head: usize,
16264        n_head_kv: usize,
16265        t: usize,
16266        t_kv: usize,
16267        scale: f32,
16268        causal: bool,
16269    ) -> Result<(), Box<dyn std::error::Error>> {
16270        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
16271        if portable_mma_gated() {
16272            return self.sdpa_naive(
16273                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
16274            );
16275        }
16276        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
16277        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
16278        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
16279        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
16280        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
16281        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16282        let f32_stage =
16283            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
16284        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
16285        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
16286        // Own numeric config (partial-sum order) — battery-gated.
16287        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16288        let sp = !f32_stage
16289            && *SP_ON.get_or_init(|| {
16290                std::env::var("MEMRA_FA512_SP")
16291                    .map(|v| v != "0")
16292                    .unwrap_or(true)
16293            });
16294        self.fa_prefill_hd512_arm(
16295            q,
16296            k,
16297            v,
16298            o,
16299            head_dim,
16300            n_head,
16301            n_head_kv,
16302            t,
16303            t_kv,
16304            scale,
16305            causal,
16306            f32_stage,
16307            sp,
16308            sp && fa_f16pv_on(),
16309        )
16310    }
16311
16312    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
16313    #[allow(clippy::too_many_arguments)]
16314    pub fn fa_prefill_hd512_pre(
16315        &self,
16316        qb: &CudaSlice<u8>,
16317        kb: &CudaSlice<u8>,
16318        vb: &CudaSlice<u8>,
16319        o: &mut CudaSlice<f32>,
16320        head_dim: usize,
16321        n_head: usize,
16322        n_head_kv: usize,
16323        t: usize,
16324        t_kv: usize,
16325        scale: f32,
16326        causal: bool,
16327        v_f16: bool,
16328    ) -> Result<(), Box<dyn std::error::Error>> {
16329        debug_assert_eq!(head_dim, 512);
16330        const SP_M: usize = 16;
16331        const BKS: usize = 32;
16332        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
16333        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
16334        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
16335        let f16pv = fa_f16pv_on();
16336        let nw = if f16pv { fa512_wide_warps() } else { 2 };
16337        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16338        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
16339        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
16340        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
16341            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
16342            let n = t_kv * n_head_kv * head_dim;
16343            let need = n * 2;
16344            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
16345                *vguard = Some(self.alloc_uninit::<u8>(need)?);
16346            }
16347            let dst = vguard.as_mut().unwrap();
16348            self.bf16_to_f16_into(vb, n, dst)?;
16349            vguard.as_ref().unwrap()
16350        } else {
16351            vb
16352        };
16353        let f = self.func(if hp {
16354            "fa_prefill_bf16_hd512_sp16h2"
16355        } else {
16356            match (f16pv, nw) {
16357                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16358                (true, _) => "fa_prefill_bf16_hd512_sp16",
16359                _ => "fa_prefill_bf16_hd512_sp",
16360            }
16361        });
16362        let (nwarp, npart) = if hp {
16363            (4usize, 4usize)
16364        } else if nw > 2 {
16365            (nw, nw)
16366        } else {
16367            (2, 1)
16368        };
16369        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
16370        let shmem = if hp {
16371            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
16372                as u32
16373        } else {
16374            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16375                + 4 * (npart * SP_M * BKS + SP_M)) as u32
16376        };
16377        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16378        f.set_attribute(
16379            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16380            shmem as i32,
16381        )?;
16382        let grid_y = if hp {
16383            (n_head / 2) as u32
16384        } else {
16385            n_head as u32
16386        };
16387        let cfg = LaunchConfig {
16388            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16389            block_dim: (32, nwarp as u32, 1),
16390            shared_mem_bytes: shmem,
16391        };
16392        let (hd, nh, nhkv, ti, tkvi, cz) = (
16393            head_dim as i32,
16394            n_head as i32,
16395            n_head_kv as i32,
16396            t as i32,
16397            t_kv as i32,
16398            causal as i32,
16399        );
16400        let __s_b = self.gpu.stream();
16401        let mut b = __s_b.launch_builder(&f);
16402        b.arg(qb)
16403            .arg(kb)
16404            .arg(vref)
16405            .arg(o)
16406            .arg(&hd)
16407            .arg(&nh)
16408            .arg(&nhkv)
16409            .arg(&ti)
16410            .arg(&tkvi)
16411            .arg(&scale)
16412            .arg(&cz);
16413        unsafe {
16414            b.launch(cfg)?;
16415        }
16416        Ok(())
16417    }
16418
16419    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
16420    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
16421    #[allow(clippy::too_many_arguments)]
16422    pub fn fa_prefill_hd512_arm(
16423        &self,
16424        q: &CudaSlice<f32>,
16425        k: &CudaSlice<f32>,
16426        v: &CudaSlice<f32>,
16427        o: &mut CudaSlice<f32>,
16428        head_dim: usize,
16429        n_head: usize,
16430        n_head_kv: usize,
16431        t: usize,
16432        t_kv: usize,
16433        scale: f32,
16434        causal: bool,
16435        f32_stage: bool,
16436        sp: bool,
16437        f16pv: bool,
16438    ) -> Result<(), Box<dyn std::error::Error>> {
16439        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
16440        if sp && !f32_stage {
16441            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
16442            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
16443            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
16444            const SP_M: usize = 16;
16445            const BKS: usize = 32;
16446            let nw = if f16pv { fa512_wide_warps() } else { 2 };
16447            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16448            let f = self.func(if hp {
16449                "fa_prefill_bf16_hd512_sp16h2"
16450            } else {
16451                match (f16pv, nw) {
16452                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16453                    (true, _) => "fa_prefill_bf16_hd512_sp16",
16454                    _ => "fa_prefill_bf16_hd512_sp",
16455                }
16456            });
16457            let (nwarp, npart) = if hp {
16458                (4usize, 4usize)
16459            } else if nw > 2 {
16460                (nw, nw)
16461            } else {
16462                (2, 1)
16463            };
16464            let shmem = if hp {
16465                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
16466                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
16467            } else {
16468                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16469                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
16470            };
16471            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16472            f.set_attribute(
16473                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16474                shmem as i32,
16475            )?;
16476            let grid_y = if hp {
16477                (n_head / 2) as u32
16478            } else {
16479                n_head as u32
16480            };
16481            let cfg = LaunchConfig {
16482                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16483                block_dim: (32, nwarp as u32, 1),
16484                shared_mem_bytes: shmem,
16485            };
16486            let (hd, nh, nhkv, ti, tkvi, cz) = (
16487                head_dim as i32,
16488                n_head as i32,
16489                n_head_kv as i32,
16490                t as i32,
16491                t_kv as i32,
16492                causal as i32,
16493            );
16494            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16495            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16496            let vb = if f16pv {
16497                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
16498            } else {
16499                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
16500            };
16501            let __s_b = self.gpu.stream();
16502            let mut b = __s_b.launch_builder(&f);
16503            b.arg(&qb)
16504                .arg(&kb)
16505                .arg(&vb)
16506                .arg(o)
16507                .arg(&hd)
16508                .arg(&nh)
16509                .arg(&nhkv)
16510                .arg(&ti)
16511                .arg(&tkvi)
16512                .arg(&scale)
16513                .arg(&cz);
16514            unsafe {
16515                b.launch(cfg)?;
16516            }
16517            return Ok(());
16518        }
16519        const BLOCK_Q: usize = 32;
16520        const BK: usize = 32;
16521        const HALF: usize = 256;
16522        let f = self.func(if f32_stage {
16523            "fa_prefill_f32_hd512"
16524        } else {
16525            "fa_prefill_bf16_hd512"
16526        });
16527        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
16528        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
16529            + 4 * BLOCK_Q) as u32;
16530        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16531        f.set_attribute(
16532            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16533            shmem as i32,
16534        )?;
16535        let cfg = LaunchConfig {
16536            grid_dim: (
16537                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16538                n_head as u32,
16539                2,
16540            ),
16541            block_dim: (32, 2, 1),
16542            shared_mem_bytes: shmem,
16543        };
16544        let (hd, nh, nhkv, ti, tkvi, cz) = (
16545            head_dim as i32,
16546            n_head as i32,
16547            n_head_kv as i32,
16548            t as i32,
16549            t_kv as i32,
16550            causal as i32,
16551        );
16552        if f32_stage {
16553            let __s_b = self.gpu.stream();
16554            let mut b = __s_b.launch_builder(&f);
16555            b.arg(q)
16556                .arg(k)
16557                .arg(v)
16558                .arg(o)
16559                .arg(&hd)
16560                .arg(&nh)
16561                .arg(&nhkv)
16562                .arg(&ti)
16563                .arg(&tkvi)
16564                .arg(&scale)
16565                .arg(&cz);
16566            unsafe {
16567                b.launch(cfg)?;
16568            }
16569        } else {
16570            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16571            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16572            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16573            let __s_b = self.gpu.stream();
16574            let mut b = __s_b.launch_builder(&f);
16575            b.arg(&qb)
16576                .arg(&kb)
16577                .arg(&vb)
16578                .arg(o)
16579                .arg(&hd)
16580                .arg(&nh)
16581                .arg(&nhkv)
16582                .arg(&ti)
16583                .arg(&tkvi)
16584                .arg(&scale)
16585                .arg(&cz);
16586            unsafe {
16587                b.launch(cfg)?;
16588            }
16589        }
16590        Ok(())
16591    }
16592
16593    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
16594    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
16595    /// separate f32_to_bf16 the FA entries would run).
16596    #[allow(clippy::too_many_arguments)]
16597    pub fn rope_neox2_bf16e(
16598        &self,
16599        q: &mut CudaSlice<f32>,
16600        k: &mut CudaSlice<f32>,
16601        qb: &mut CudaSlice<u8>,
16602        kb: &mut CudaSlice<u8>,
16603        pos: &CudaSlice<i32>,
16604        head_dim: usize,
16605        n_dims: usize,
16606        nh_q: usize,
16607        nh_k: usize,
16608        n_tokens: usize,
16609        base: f32,
16610        freq_scale: f32,
16611        ff: Option<&CudaSlice<f32>>,
16612    ) -> Result<(), Box<dyn std::error::Error>> {
16613        let f = self.func("rope_neox2_bf16e_f32");
16614        let rows = ((nh_q + nh_k) * n_tokens) as u32;
16615        let cfg = LaunchConfig {
16616            grid_dim: (rows, 1, 1),
16617            block_dim: ((head_dim / 2) as u32, 1, 1),
16618            shared_mem_bytes: 0,
16619        };
16620        let theta_scale = base.powf(-2.0 / n_dims as f32);
16621        let (hd, nd, nhq, nhk, nt) = (
16622            head_dim as i32,
16623            n_dims as i32,
16624            nh_q as i32,
16625            nh_k as i32,
16626            n_tokens as i32,
16627        );
16628        let __s_b = self.gpu.stream();
16629        let mut b = __s_b.launch_builder(&f);
16630        match ff {
16631            Some(t) => {
16632                b.arg(&mut *q)
16633                    .arg(&mut *k)
16634                    .arg(&mut *qb)
16635                    .arg(&mut *kb)
16636                    .arg(pos)
16637                    .arg(&hd)
16638                    .arg(&nd)
16639                    .arg(&nhq)
16640                    .arg(&nhk)
16641                    .arg(&nt)
16642                    .arg(&theta_scale)
16643                    .arg(&freq_scale)
16644                    .arg(t);
16645                unsafe {
16646                    b.launch(cfg)?;
16647                }
16648            }
16649            None => {
16650                let null: u64 = 0;
16651                b.arg(&mut *q)
16652                    .arg(&mut *k)
16653                    .arg(&mut *qb)
16654                    .arg(&mut *kb)
16655                    .arg(pos)
16656                    .arg(&hd)
16657                    .arg(&nd)
16658                    .arg(&nhq)
16659                    .arg(&nhk)
16660                    .arg(&nt)
16661                    .arg(&theta_scale)
16662                    .arg(&freq_scale)
16663                    .arg(&null);
16664                unsafe {
16665                    b.launch(cfg)?;
16666                }
16667            }
16668        }
16669        Ok(())
16670    }
16671
16672    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
16673    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
16674    pub fn f32_to_bf16(
16675        &self,
16676        x: &CudaSlice<f32>,
16677        n: usize,
16678    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16679        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
16680        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16681        let f = self.func("f32_to_bf16_flat");
16682        let n_i = n as i64;
16683        let cfg = LaunchConfig {
16684            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16685            block_dim: (256, 1, 1),
16686            shared_mem_bytes: 0,
16687        };
16688        let __s_b = self.gpu.stream();
16689        let mut b = __s_b.launch_builder(&f);
16690        b.arg(x).arg(&mut y).arg(&n_i);
16691        unsafe {
16692            b.launch(cfg)?;
16693        }
16694        Ok(y)
16695    }
16696
16697    pub fn f32_to_f16(
16698        &self,
16699        x: &CudaSlice<f32>,
16700        n: usize,
16701    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16702        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
16703        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16704        let f = self.func("f32_to_f16_flat");
16705        let n_i = n as i64;
16706        let cfg = LaunchConfig {
16707            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16708            block_dim: (256, 1, 1),
16709            shared_mem_bytes: 0,
16710        };
16711        let __s_b = self.gpu.stream();
16712        let mut b = __s_b.launch_builder(&f);
16713        b.arg(x).arg(&mut y).arg(&n_i);
16714        unsafe {
16715            b.launch(cfg)?;
16716        }
16717        Ok(y)
16718    }
16719
16720    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
16721    pub fn bf16_to_f16(
16722        &self,
16723        xb: &CudaSlice<u8>,
16724        n: usize,
16725    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16726        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16727        self.bf16_to_f16_into(xb, n, &mut y)?;
16728        Ok(y)
16729    }
16730
16731    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
16732    pub fn bf16_to_f16_into(
16733        &self,
16734        xb: &CudaSlice<u8>,
16735        n: usize,
16736        y: &mut CudaSlice<u8>,
16737    ) -> Result<(), Box<dyn std::error::Error>> {
16738        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
16739        assert!(y.len() >= n * 2);
16740        let f = self.func("bf16_to_f16_flat");
16741        let n2 = (n / 2) as i64;
16742        let cfg = LaunchConfig {
16743            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
16744            block_dim: (256, 1, 1),
16745            shared_mem_bytes: 0,
16746        };
16747        let __s_b = self.gpu.stream();
16748        let mut b = __s_b.launch_builder(&f);
16749        b.arg(xb).arg(y).arg(&n2);
16750        unsafe {
16751            b.launch(cfg)?;
16752        }
16753        Ok(())
16754    }
16755
16756    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
16757    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
16758    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
16759    /// head_dim in {256, 128}, bf16kv lane on.
16760    #[allow(clippy::too_many_arguments)]
16761    pub fn fa_prefill_vl8(
16762        &self,
16763        seqs: &[FaSeqVl],
16764        head_dim: usize,
16765        n_head: usize,
16766        n_head_kv: usize,
16767        scale: f32,
16768    ) -> Result<(), Box<dyn std::error::Error>> {
16769        const BK: usize = 32;
16770        let b = seqs.len();
16771        assert!(b >= 1 && b <= 8);
16772        let mut packed = [FaSeqVl::default(); 8];
16773        packed[..b].copy_from_slice(seqs);
16774        let v = FaVl8(packed);
16775        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16776        let ept = (n_head_kv * head_dim) as i32;
16777        {
16778            let f = self.func("fa_mirror_vl");
16779            let max_n = (max_t as i64) * ept as i64;
16780            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
16781            for which in 0..2i32 {
16782                let cfg = LaunchConfig {
16783                    grid_dim: (blocks, 1, b as u32),
16784                    block_dim: (256, 1, 1),
16785                    shared_mem_bytes: 0,
16786                };
16787                let __s_lb = self.gpu.stream();
16788                let mut lb = __s_lb.launch_builder(&f);
16789                lb.arg(&v).arg(&ept).arg(&which);
16790                unsafe {
16791                    lb.launch(cfg)?;
16792                }
16793            }
16794        }
16795        let hd_sfx = fa_hd_suffix(head_dim)?;
16796        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
16797        let block_q = 64usize;
16798        let kv_stages = 2usize;
16799        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
16800            + 4 * (block_q * BK + 2 * block_q)) as u32;
16801        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16802        f.set_attribute(
16803            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16804            shmem as i32,
16805        )?;
16806        let cfg = LaunchConfig {
16807            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
16808            block_dim: (32, 4, 1),
16809            shared_mem_bytes: shmem,
16810        };
16811        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16812        let __s_lb = self.gpu.stream();
16813        let mut lb = __s_lb.launch_builder(&f);
16814        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
16815        unsafe {
16816            lb.launch(cfg)?;
16817        }
16818        Ok(())
16819    }
16820
16821    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
16822    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
16823    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
16824    #[allow(clippy::too_many_arguments)]
16825    pub fn attn_pre_vl8(
16826        &self,
16827        seqs: &[AttnPreVl],
16828        wq: &CudaSlice<f32>,
16829        wk: &CudaSlice<f32>,
16830        head_dim: usize,
16831        rope_dims: usize,
16832        n_head: usize,
16833        n_head_kv: usize,
16834        eps: f32,
16835        freq_base: f32,
16836        freq_scale: f32,
16837        kv_dim_k: usize,
16838        kv_dim_v: usize,
16839        k_tok_bytes: usize,
16840        v_tok_bytes: usize,
16841    ) -> Result<(), Box<dyn std::error::Error>> {
16842        let b = seqs.len();
16843        assert!(b >= 1 && b <= 8);
16844        let mut packed = [AttnPreVl::default(); 8];
16845        packed[..b].copy_from_slice(seqs);
16846        let v = AttnPreVl8(packed);
16847        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16848        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16849        {
16850            let f = self.func("q_gate_split_vl");
16851            let n = max_t * (n_head * head_dim) as u32;
16852            let cfg = LaunchConfig {
16853                grid_dim: (n.div_ceil(256), 1, b as u32),
16854                block_dim: (256, 1, 1),
16855                shared_mem_bytes: 0,
16856            };
16857            let __s_lb = self.gpu.stream();
16858            let mut lb = __s_lb.launch_builder(&f);
16859            lb.arg(&v).arg(&hd).arg(&nh);
16860            unsafe {
16861                lb.launch(cfg)?;
16862            }
16863        }
16864        {
16865            let f = self.func("attn_rms_vl");
16866            let cfg = LaunchConfig {
16867                grid_dim: (max_t * n_head as u32, 2, b as u32),
16868                block_dim: (rms_block(), 1, 1),
16869                shared_mem_bytes: 0,
16870            };
16871            let __s_lb = self.gpu.stream();
16872            let mut lb = __s_lb.launch_builder(&f);
16873            lb.arg(&v)
16874                .arg(wq)
16875                .arg(wk)
16876                .arg(&hd)
16877                .arg(&nh)
16878                .arg(&nhkv)
16879                .arg(&eps);
16880            unsafe {
16881                lb.launch(cfg)?;
16882            }
16883        }
16884        {
16885            let f = self.func("attn_rope_vl");
16886            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
16887            let nd = rope_dims as i32;
16888            let cfg = LaunchConfig {
16889                grid_dim: (max_t * n_head as u32, 2, b as u32),
16890                block_dim: ((head_dim / 2) as u32, 1, 1),
16891                shared_mem_bytes: 0,
16892            };
16893            let __s_lb = self.gpu.stream();
16894            let mut lb = __s_lb.launch_builder(&f);
16895            lb.arg(&v)
16896                .arg(&hd)
16897                .arg(&nd)
16898                .arg(&nh)
16899                .arg(&nhkv)
16900                .arg(&theta_scale)
16901                .arg(&freq_scale);
16902            unsafe {
16903                lb.launch(cfg)?;
16904            }
16905        }
16906        {
16907            let f = self.func("append_kv_vl");
16908            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
16909            let cfg = LaunchConfig {
16910                grid_dim: (nblk, max_t, b as u32),
16911                block_dim: (32, 1, 1),
16912                shared_mem_bytes: 0,
16913            };
16914            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16915            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16916            let __s_lb = self.gpu.stream();
16917            let mut lb = __s_lb.launch_builder(&f);
16918            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
16919            unsafe {
16920                lb.launch(cfg)?;
16921            }
16922        }
16923        Ok(())
16924    }
16925
16926    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
16927    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
16928    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
16929    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
16930    pub fn fa_prefill_view(
16931        &self,
16932        q: &CudaSlice<f32>,
16933        k: &cudarc::driver::CudaView<u8>,
16934        v: &cudarc::driver::CudaView<u8>,
16935        o: &mut CudaSlice<f32>,
16936        head_dim: usize,
16937        n_head: usize,
16938        n_head_kv: usize,
16939        t: usize,
16940        t_kv: usize,
16941        scale: f32,
16942        causal: bool,
16943        k_tok_bytes: usize,
16944        v_tok_bytes: usize,
16945        g: bool,
16946    ) -> Result<(), Box<dyn std::error::Error>> {
16947        if portable_mma_gated() {
16948            return self.sdpa_naive_quantized_view(
16949                q,
16950                k,
16951                v,
16952                o,
16953                head_dim,
16954                n_head,
16955                n_head_kv,
16956                t,
16957                t_kv,
16958                scale,
16959                causal,
16960                k_tok_bytes,
16961                v_tok_bytes,
16962            );
16963        }
16964        const BLOCK_Q: usize = 64;
16965        const BK: usize = 32;
16966        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
16967        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
16968        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
16969        let f = if g {
16970            self.func_g(&name)
16971        } else {
16972            self.func(&name)
16973        };
16974        let shmem =
16975            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16976        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16977        f.set_attribute(
16978            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16979            shmem as i32,
16980        )?;
16981        let cfg = LaunchConfig {
16982            grid_dim: (
16983                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16984                n_head as u32,
16985                1,
16986            ),
16987            block_dim: (32, 4, 1),
16988            shared_mem_bytes: shmem,
16989        };
16990        let (hd, nh, nhkv, ti, tkvi, cz) = (
16991            head_dim as i32,
16992            n_head as i32,
16993            n_head_kv as i32,
16994            t as i32,
16995            t_kv as i32,
16996            causal as i32,
16997        );
16998        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16999        let __s_b = self.gpu.stream();
17000        let mut b = __s_b.launch_builder(&f);
17001        b.arg(q)
17002            .arg(k)
17003            .arg(v)
17004            .arg(o)
17005            .arg(&hd)
17006            .arg(&nh)
17007            .arg(&nhkv)
17008            .arg(&ti)
17009            .arg(&tkvi)
17010            .arg(&scale)
17011            .arg(&cz)
17012            .arg(&ktb)
17013            .arg(&vtb);
17014        unsafe {
17015            b.launch(cfg)?;
17016        }
17017        Ok(())
17018    }
17019
17020    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
17021    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
17022    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
17023    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
17024    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
17025    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
17026    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
17027    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
17028    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
17029    #[allow(clippy::too_many_arguments)]
17030    pub fn fa_prefill_view_ws(
17031        &self,
17032        q: &CudaSlice<f32>,
17033        k: &cudarc::driver::CudaView<u8>,
17034        v: &cudarc::driver::CudaView<u8>,
17035        o: &mut CudaSlice<f32>,
17036        head_dim: usize,
17037        n_head: usize,
17038        n_head_kv: usize,
17039        t: usize,
17040        t_kv: usize,
17041        scale: f32,
17042        causal: bool,
17043        k_tok_bytes: usize,
17044        v_tok_bytes: usize,
17045        g: bool,
17046    ) -> Result<(), Box<dyn std::error::Error>> {
17047        if portable_mma_gated() {
17048            return self.sdpa_naive_quantized_view(
17049                q,
17050                k,
17051                v,
17052                o,
17053                head_dim,
17054                n_head,
17055                n_head_kv,
17056                t,
17057                t_kv,
17058                scale,
17059                causal,
17060                k_tok_bytes,
17061                v_tok_bytes,
17062            );
17063        }
17064        const BLOCK_Q: usize = 64;
17065        const BK: usize = 32;
17066        let kv_dim_k = n_head_kv * head_dim;
17067        let kv_dim_v = n_head_kv * head_dim;
17068        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
17069        let v_ws_bytes = t_kv * kv_dim_v * 2;
17070        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
17071        let mut guard = self.prime_deqw_ws.lock().unwrap();
17072        let need_grow = match guard.as_ref() {
17073            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
17074            None => true,
17075        };
17076        if need_grow {
17077            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
17078            let (ck, cv) = guard
17079                .as_ref()
17080                .map(|(a, b)| (a.len(), b.len()))
17081                .unwrap_or((0, 0));
17082            *guard = Some((
17083                self.alloc_u8(grow(ck, k_ws_bytes))?,
17084                self.alloc_u8(grow(cv, v_ws_bytes))?,
17085            ));
17086        }
17087        let (kw, vw) = guard.as_mut().unwrap();
17088        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
17089        {
17090            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
17091            let f = if g {
17092                self.func_g("fa_dequant_kv_ws_bf16")
17093            } else {
17094                self.func("fa_dequant_kv_ws_bf16")
17095            };
17096            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17097            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17098            let cfg = LaunchConfig {
17099                grid_dim: (nblk.max(1), 1, 1),
17100                block_dim: (256, 1, 1),
17101                shared_mem_bytes: 0,
17102            };
17103            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17104            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17105            let __s_b = self.gpu.stream();
17106            let mut b = __s_b.launch_builder(&f);
17107            b.arg(k)
17108                .arg(v)
17109                .arg(&mut *kw)
17110                .arg(&mut *vw)
17111                .arg(&kdk)
17112                .arg(&kdv)
17113                .arg(&tkvi)
17114                .arg(&ktb)
17115                .arg(&vtb);
17116            unsafe {
17117                b.launch(cfg)?;
17118            }
17119        }
17120        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
17121        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
17122        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
17123        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
17124        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
17125        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
17126        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
17127        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17128            .map(|v| v != "0")
17129            .unwrap_or(true);
17130        {
17131            let hd_sfx = fa_hd_suffix(head_dim)?;
17132            let f = self.func(&format!(
17133                "fa_prefill_qw{}{hd_sfx}",
17134                if db { "_db" } else { "" }
17135            ));
17136            let shmem = if db {
17137                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
17138                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17139            } else {
17140                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17141            };
17142            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17143            f.set_attribute(
17144                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17145                shmem as i32,
17146            )?;
17147            let cfg = LaunchConfig {
17148                grid_dim: (
17149                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17150                    n_head as u32,
17151                    1,
17152                ),
17153                block_dim: (32, 4, 1),
17154                shared_mem_bytes: shmem,
17155            };
17156            let (hd, nh, nhkv, ti, tkvi, cz) = (
17157                head_dim as i32,
17158                n_head as i32,
17159                n_head_kv as i32,
17160                t as i32,
17161                t_kv as i32,
17162                causal as i32,
17163            );
17164            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17165            let __s_b = self.gpu.stream();
17166            let mut b = __s_b.launch_builder(&f);
17167            b.arg(q)
17168                .arg(&*kw)
17169                .arg(&*vw)
17170                .arg(o)
17171                .arg(&hd)
17172                .arg(&nh)
17173                .arg(&nhkv)
17174                .arg(&ti)
17175                .arg(&tkvi)
17176                .arg(&scale)
17177                .arg(&cz)
17178                .arg(&kdk)
17179                .arg(&kdv);
17180            unsafe {
17181                b.launch(cfg)?;
17182            }
17183        }
17184        Ok(())
17185    }
17186
17187    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
17188    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
17189    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
17190    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
17191    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
17192    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
17193    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
17194    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
17195    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
17196    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
17197    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
17198    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
17199    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
17200    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
17201    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
17202    #[allow(clippy::too_many_arguments)]
17203    pub fn fa_prefill_view_ws_w_hd128(
17204        &self,
17205        q: &CudaSlice<f32>,
17206        k: &cudarc::driver::CudaView<u8>,
17207        v: &cudarc::driver::CudaView<u8>,
17208        o: &mut CudaSlice<f32>,
17209        head_dim: usize,
17210        n_head: usize,
17211        n_head_kv: usize,
17212        t: usize,
17213        t_kv: usize,
17214        scale: f32,
17215        causal: bool,
17216        window: usize,
17217        k_tok_bytes: usize,
17218        v_tok_bytes: usize,
17219    ) -> Result<(), Box<dyn std::error::Error>> {
17220        assert_eq!(
17221            head_dim, 128,
17222            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
17223        );
17224        if portable_mma_gated() {
17225            return self.sdpa_naive_w_quantized_view(
17226                q,
17227                k,
17228                v,
17229                o,
17230                head_dim,
17231                n_head,
17232                n_head_kv,
17233                t,
17234                t_kv,
17235                scale,
17236                causal,
17237                window,
17238                k_tok_bytes,
17239                v_tok_bytes,
17240            );
17241        }
17242        const BLOCK_Q: usize = 64;
17243        const BK: usize = 32;
17244        let kv_dim_k = n_head_kv * head_dim;
17245        let kv_dim_v = n_head_kv * head_dim;
17246        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
17247        let v_ws_bytes = t_kv * kv_dim_v * 2;
17248        let mut guard = self.prime_deqw_ws.lock().unwrap();
17249        let need_grow = match guard.as_ref() {
17250            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
17251            None => true,
17252        };
17253        if need_grow {
17254            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
17255            let (ck, cv) = guard
17256                .as_ref()
17257                .map(|(a, b)| (a.len(), b.len()))
17258                .unwrap_or((0, 0));
17259            *guard = Some((
17260                self.alloc_u8(grow(ck, k_ws_bytes))?,
17261                self.alloc_u8(grow(cv, v_ws_bytes))?,
17262            ));
17263        }
17264        let (kw, vw) = guard.as_mut().unwrap();
17265        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
17266        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
17267        {
17268            let f = self.func("fa_dequant_kv_ws_bf16");
17269            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17270            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17271            let cfg = LaunchConfig {
17272                grid_dim: (nblk.max(1), 1, 1),
17273                block_dim: (256, 1, 1),
17274                shared_mem_bytes: 0,
17275            };
17276            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17277            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17278            let __s_b = self.gpu.stream();
17279            let mut b = __s_b.launch_builder(&f);
17280            b.arg(k)
17281                .arg(v)
17282                .arg(&mut *kw)
17283                .arg(&mut *vw)
17284                .arg(&kdk)
17285                .arg(&kdv)
17286                .arg(&tkvi)
17287                .arg(&ktb)
17288                .arg(&vtb);
17289            unsafe {
17290                b.launch(cfg)?;
17291            }
17292        }
17293        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
17294        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17295            .map(|v| v != "0")
17296            .unwrap_or(true);
17297        {
17298            let f = self.func(if db {
17299                "fa_prefill_qw_db_w_hd128"
17300            } else {
17301                "fa_prefill_qw_w_hd128"
17302            });
17303            let shmem = if db {
17304                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17305            } else {
17306                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17307            };
17308            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17309            f.set_attribute(
17310                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17311                shmem as i32,
17312            )?;
17313            let cfg = LaunchConfig {
17314                grid_dim: (
17315                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17316                    n_head as u32,
17317                    1,
17318                ),
17319                block_dim: (32, 4, 1),
17320                shared_mem_bytes: shmem,
17321            };
17322            let (hd, nh, nhkv, ti, tkvi, cz) = (
17323                head_dim as i32,
17324                n_head as i32,
17325                n_head_kv as i32,
17326                t as i32,
17327                t_kv as i32,
17328                causal as i32,
17329            );
17330            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
17331            let __s_b = self.gpu.stream();
17332            let mut b = __s_b.launch_builder(&f);
17333            b.arg(q)
17334                .arg(&*kw)
17335                .arg(&*vw)
17336                .arg(o)
17337                .arg(&hd)
17338                .arg(&nh)
17339                .arg(&nhkv)
17340                .arg(&ti)
17341                .arg(&tkvi)
17342                .arg(&scale)
17343                .arg(&cz)
17344                .arg(&kdk)
17345                .arg(&kdv)
17346                .arg(&wnd);
17347            unsafe {
17348                b.launch(cfg)?;
17349            }
17350        }
17351        Ok(())
17352    }
17353
17354    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
17355    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
17356    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
17357    pub fn fa_decode(
17358        &self,
17359        q: &CudaSlice<f32>,
17360        k: &cudarc::driver::CudaView<u8>,
17361        v: &cudarc::driver::CudaView<u8>,
17362        o: &mut CudaSlice<f32>,
17363        head_dim: usize,
17364        n_head: usize,
17365        n_head_kv: usize,
17366        t_kv: usize,
17367        scale: f32,
17368        k_tok_bytes: usize,
17369        v_tok_bytes: usize,
17370    ) -> Result<(), Box<dyn std::error::Error>> {
17371        self.fa_decode_kvmod(
17372            q,
17373            k,
17374            v,
17375            o,
17376            head_dim,
17377            n_head,
17378            n_head_kv,
17379            t_kv,
17380            scale,
17381            k_tok_bytes,
17382            v_tok_bytes,
17383            false,
17384        )
17385    }
17386
17387    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
17388    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
17389    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
17390    #[allow(clippy::too_many_arguments)]
17391    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
17392    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
17393    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
17394    #[allow(clippy::too_many_arguments)]
17395    #[allow(clippy::too_many_arguments)]
17396    fn fa_decode_scalar_unified(
17397        &self,
17398        q: &cudarc::driver::CudaView<f32>,
17399        k: &cudarc::driver::CudaView<u8>,
17400        v: &cudarc::driver::CudaView<u8>,
17401        o: &mut cudarc::driver::CudaViewMut<f32>,
17402        head_dim: usize,
17403        n_head: usize,
17404        n_head_kv: usize,
17405        t_kv_host: usize,
17406        t_kv_dev: Option<&CudaSlice<i32>>,
17407        scale: f32,
17408        n_splits: usize,
17409        split_keys: usize,
17410        k_tok_bytes: usize,
17411        v_tok_bytes: usize,
17412        g: bool,
17413        part_o: &mut CudaSlice<f32>,
17414        part_m: &mut CudaSlice<f32>,
17415        part_l: &mut CudaSlice<f32>,
17416        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17417    ) -> Result<(), Box<dyn std::error::Error>> {
17418        let f = if g {
17419            self.func_g("fa_decode_f32")
17420        } else {
17421            self.fa_func("fa_decode_f32", head_dim)
17422        };
17423        let cfg = LaunchConfig {
17424            grid_dim: (n_head as u32, n_splits as u32, 1),
17425            block_dim: (head_dim as u32, 1, 1),
17426            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
17427        };
17428        let (hd, nh, nhkv, nsp) = (
17429            head_dim as i32,
17430            n_head as i32,
17431            n_head_kv as i32,
17432            n_splits as i32,
17433        );
17434        let (ktb, vtb, tkvi, ski) = (
17435            k_tok_bytes as i64,
17436            v_tok_bytes as i64,
17437            t_kv_host as i32,
17438            split_keys as i32,
17439        );
17440        let __s_b = self.gpu.stream();
17441        let mut b = __s_b.launch_builder(&f);
17442        match t_kv_dev {
17443            Some(d) => {
17444                b.arg(q)
17445                    .arg(k)
17446                    .arg(v)
17447                    .arg(&mut *part_o)
17448                    .arg(&mut *part_m)
17449                    .arg(&mut *part_l)
17450                    .arg(&hd)
17451                    .arg(&nh)
17452                    .arg(&nhkv)
17453                    .arg(&tkvi)
17454                    .arg(d)
17455                    .arg(&scale)
17456                    .arg(&nsp)
17457                    .arg(&ski)
17458                    .arg(&ktb)
17459                    .arg(&vtb);
17460                unsafe {
17461                    b.launch(cfg)?;
17462                }
17463            }
17464            None => {
17465                let null: u64 = 0;
17466                b.arg(q)
17467                    .arg(k)
17468                    .arg(v)
17469                    .arg(&mut *part_o)
17470                    .arg(&mut *part_m)
17471                    .arg(&mut *part_l)
17472                    .arg(&hd)
17473                    .arg(&nh)
17474                    .arg(&nhkv)
17475                    .arg(&tkvi)
17476                    .arg(&null)
17477                    .arg(&scale)
17478                    .arg(&nsp)
17479                    .arg(&ski)
17480                    .arg(&ktb)
17481                    .arg(&vtb);
17482                unsafe {
17483                    b.launch(cfg)?;
17484                }
17485            }
17486        }
17487        let cfg2 = LaunchConfig {
17488            grid_dim: (n_head as u32, 1, 1),
17489            block_dim: (head_dim as u32, 1, 1),
17490            shared_mem_bytes: 0,
17491        };
17492        if let Some((oq, od)) = q8_out {
17493            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
17494            let fc = if g {
17495                self.func_g("fa_decode_combine_q8_1")
17496            } else {
17497                self.fa_func("fa_decode_combine_q8_1", head_dim)
17498            };
17499            let __s_b2 = self.gpu.stream();
17500            let mut b2 = __s_b2.launch_builder(&fc);
17501            b2.arg(&*part_o)
17502                .arg(&*part_m)
17503                .arg(&*part_l)
17504                .arg(oq)
17505                .arg(od)
17506                .arg(&hd)
17507                .arg(&nh)
17508                .arg(&nsp);
17509            unsafe {
17510                b2.launch(cfg2)?;
17511            }
17512            return Ok(());
17513        }
17514        let fc = if g {
17515            self.func_g("fa_decode_combine_f32")
17516        } else {
17517            self.fa_func("fa_decode_combine_f32", head_dim)
17518        };
17519        let __s_b2 = self.gpu.stream();
17520        let mut b2 = __s_b2.launch_builder(&fc);
17521        b2.arg(&*part_o)
17522            .arg(&*part_m)
17523            .arg(&*part_l)
17524            .arg(o)
17525            .arg(&hd)
17526            .arg(&nh)
17527            .arg(&nsp);
17528        unsafe {
17529            b2.launch(cfg2)?;
17530        }
17531        Ok(())
17532    }
17533
17534    pub fn fa_decode_kvmod(
17535        &self,
17536        q: &CudaSlice<f32>,
17537        k: &cudarc::driver::CudaView<u8>,
17538        v: &cudarc::driver::CudaView<u8>,
17539        o: &mut CudaSlice<f32>,
17540        head_dim: usize,
17541        n_head: usize,
17542        n_head_kv: usize,
17543        t_kv: usize,
17544        scale: f32,
17545        k_tok_bytes: usize,
17546        v_tok_bytes: usize,
17547        g: bool,
17548    ) -> Result<(), Box<dyn std::error::Error>> {
17549        let q_view = q.as_view();
17550        let mut o_view = o.as_view_mut();
17551        self.fa_decode_kvmod_view(
17552            &q_view,
17553            k,
17554            v,
17555            &mut o_view,
17556            head_dim,
17557            n_head,
17558            n_head_kv,
17559            t_kv,
17560            scale,
17561            k_tok_bytes,
17562            v_tok_bytes,
17563            g,
17564        )
17565    }
17566
17567    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
17568    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
17569    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
17570    /// per-session KV view and FA launch.
17571    #[allow(clippy::too_many_arguments)]
17572    pub fn fa_decode_kvmod_view(
17573        &self,
17574        q: &cudarc::driver::CudaView<f32>,
17575        k: &cudarc::driver::CudaView<u8>,
17576        v: &cudarc::driver::CudaView<u8>,
17577        o: &mut cudarc::driver::CudaViewMut<f32>,
17578        head_dim: usize,
17579        n_head: usize,
17580        n_head_kv: usize,
17581        t_kv: usize,
17582        scale: f32,
17583        k_tok_bytes: usize,
17584        v_tok_bytes: usize,
17585        g: bool,
17586    ) -> Result<(), Box<dyn std::error::Error>> {
17587        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
17588        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
17589        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
17590        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
17591        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
17592        //
17593        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
17594        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
17595        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
17596        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
17597        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
17598        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
17599        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
17600        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
17601        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
17602        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
17603        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
17604        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
17605        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
17606        // fall to the exact scalar there instead of the broken register arm.
17607        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
17608        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
17609        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
17610        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
17611        if g && head_dim == 256 && !fa_v4_at(t_kv) {
17612            fa_vec = false;
17613        }
17614        let sp = fa_split_keys(t_kv, n_head_kv);
17615        let n_splits = if fa_vec {
17616            ((t_kv + sp - 1) / sp).max(1)
17617        } else {
17618            ((t_kv + 255) / 256).max(1)
17619        };
17620        let o_len = n_head * n_splits * head_dim;
17621        let ml_len = n_head * n_splits;
17622        let mut part_guard = self.fa_part_pool.lock().unwrap();
17623        if part_guard
17624            .as_ref()
17625            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17626            .unwrap_or(true)
17627        {
17628            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17629            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17630            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17631            // later live allocations land at those addresses, and the next graph REPLAY writes
17632            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17633            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17634            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17635            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17636            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17637            // (total retired < final size).
17638            let old = part_guard.take();
17639            let (co, cm) = old
17640                .as_ref()
17641                .map(|pp| (pp.0.len(), pp.1.len()))
17642                .unwrap_or((0, 0));
17643            if let Some(old) = old {
17644                self.fa_part_retired.lock().unwrap().push(old);
17645            }
17646            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17647                eprintln!(
17648                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17649                    co, o_len, cm, ml_len
17650                );
17651            }
17652            *part_guard = Some((
17653                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17654                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17655                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17656            ));
17657        }
17658        let pg = part_guard.as_mut().unwrap();
17659        self.gpu
17660            .stream()
17661            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17662        self.gpu
17663            .stream()
17664            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17665        self.gpu
17666            .stream()
17667            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17668        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17669        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17670        let (hd, nh, nhkv, tkvi, nsp) = (
17671            head_dim as i32,
17672            n_head as i32,
17673            n_head_kv as i32,
17674            t_kv as i32,
17675            n_splits as i32,
17676        );
17677        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17678        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
17679        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
17680        // silently truncating the accumulator.
17681        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
17682        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
17683        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
17684        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
17685        // 178.4 -> 173.7 when 512 rode vec unconditionally).
17686        let fa512_min = fa512_min_tkv();
17687        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
17688        // g-module keeps the v4 pick (its class is not the depth-decay class).
17689        let deep = fa_vec
17690            && head_dim == 256
17691            && fa_v4_at(t_kv)
17692            && !g
17693            && fa_deep_at(t_kv)
17694            && !matches!(fa_v4_mode(), "noB3" | "stage");
17695        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
17696            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
17697            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
17698            let gqa = (n_head / n_head_kv).max(1) as u32;
17699            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
17700            (
17701                fv,
17702                LaunchConfig {
17703                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17704                    block_dim: (32, gqa, 1),
17705                    shared_mem_bytes: 0,
17706                },
17707            )
17708        } else if fa_vec && head_dim <= 256 {
17709            let gqa = (n_head / n_head_kv).max(1) as u32;
17710            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
17711            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
17712            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
17713            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
17714            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
17715            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
17716            // dequant each tile ONCE per block.
17717            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
17718            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
17719            // there by 12x — latency, not bandwidth, rules small KV).
17720            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17721            let smem_tkv = *SMEM_TKV.get_or_init(|| {
17722                std::env::var("MEMRA_FA_SMEM_TKV")
17723                    .ok()
17724                    .and_then(|v| v.parse().ok())
17725                    .unwrap_or_else(|| {
17726                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17727                    })
17728            });
17729            if fa_v4_at(t_kv) && head_dim == 256 {
17730                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
17731                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
17732                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
17733                let v4name = match fa_v4_mode() {
17734                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
17735                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
17736                    _ if deep => "fa_decode_vec_q_v4_deep",
17737                    _ => "fa_decode_vec_q_v4",
17738                };
17739                let fv = if g {
17740                    self.func_g(v4name)
17741                } else {
17742                    self.func(v4name)
17743                };
17744                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
17745                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
17746                let shmem = (if deep { 12160 } else { 11520 }
17747                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
17748                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17749                fv.set_attribute(
17750                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17751                    shmem as i32,
17752                )?;
17753                (
17754                    fv,
17755                    LaunchConfig {
17756                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17757                        block_dim: (32, gqa, 1),
17758                        shared_mem_bytes: shmem,
17759                    },
17760                )
17761            } else if fa_v3_active(head_dim) {
17762                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
17763                // smem = sV only (half of v2's).
17764                let fv = if g {
17765                    self.func_g("fa_decode_vec_q_v3")
17766                } else {
17767                    self.func("fa_decode_vec_q_v3")
17768                };
17769                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
17770                (
17771                    fv,
17772                    LaunchConfig {
17773                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17774                        block_dim: (32, gqa, 1),
17775                        shared_mem_bytes: shmem,
17776                    },
17777                )
17778            } else if fa_v2_on() {
17779                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
17780                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
17781                // partials; same 32KB sK+sV tile as the smem twin.
17782                let fv = if g {
17783                    self.func_g("fa_decode_vec_q_v2")
17784                } else {
17785                    self.func("fa_decode_vec_q_v2")
17786                };
17787                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17788                (
17789                    fv,
17790                    LaunchConfig {
17791                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17792                        block_dim: (32, gqa, 1),
17793                        shared_mem_bytes: shmem,
17794                    },
17795                )
17796            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
17797            {
17798                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
17799                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
17800                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
17801                let fv = if g {
17802                    self.func_g("fa_decode_vec_q_smem")
17803                } else {
17804                    self.func("fa_decode_vec_q_smem")
17805                };
17806                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17807                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17808                fv.set_attribute(
17809                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17810                    shmem as i32,
17811                )?;
17812                (
17813                    fv,
17814                    LaunchConfig {
17815                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17816                        block_dim: (32, gqa, 1),
17817                        shared_mem_bytes: shmem,
17818                    },
17819                )
17820            } else {
17821                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
17822                // dequant, zero dynamic shared memory.
17823                let fv = if g {
17824                    self.func_g("fa_decode_vec_q")
17825                } else {
17826                    self.func("fa_decode_vec_q")
17827                };
17828                (
17829                    fv,
17830                    LaunchConfig {
17831                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17832                        block_dim: (32, gqa, 1),
17833                        shared_mem_bytes: 0,
17834                    },
17835                )
17836            }
17837        } else {
17838            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
17839            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
17840            return self.fa_decode_scalar_unified(
17841                q,
17842                k,
17843                v,
17844                o,
17845                head_dim,
17846                n_head,
17847                n_head_kv,
17848                t_kv,
17849                None,
17850                scale,
17851                n_splits,
17852                if fa_vec { sp } else { 256 },
17853                k_tok_bytes,
17854                v_tok_bytes,
17855                g,
17856                part_o,
17857                part_m,
17858                part_l,
17859                None,
17860            );
17861        };
17862        let __s_b = self.gpu.stream();
17863        let mut b = __s_b.launch_builder(&f);
17864        b.arg(q)
17865            .arg(k)
17866            .arg(v)
17867            .arg(&mut *part_o)
17868            .arg(&mut *part_m)
17869            .arg(&mut *part_l)
17870            .arg(&hd)
17871            .arg(&nh)
17872            .arg(&nhkv)
17873            .arg(&tkvi)
17874            .arg(&scale)
17875            .arg(&nsp)
17876            .arg(&ktb)
17877            .arg(&vtb);
17878        unsafe {
17879            b.launch(cfg)?;
17880        }
17881        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
17882        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
17883        let (fc, cfg2) = (
17884            if g {
17885                self.func_g("fa_decode_combine_f32")
17886            } else {
17887                self.fa_func("fa_decode_combine_f32", head_dim)
17888            },
17889            LaunchConfig {
17890                grid_dim: (n_head as u32, 1, 1),
17891                block_dim: (head_dim as u32, 1, 1),
17892                shared_mem_bytes: 0,
17893            },
17894        );
17895        let __s_b2 = self.gpu.stream();
17896        let mut b2 = __s_b2.launch_builder(&fc);
17897        b2.arg(&*part_o)
17898            .arg(&*part_m)
17899            .arg(&*part_l)
17900            .arg(o)
17901            .arg(&hd)
17902            .arg(&nh)
17903            .arg(&nsp);
17904        unsafe {
17905            b2.launch(cfg2)?;
17906        }
17907        Ok(())
17908    }
17909
17910    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
17911    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
17912    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
17913    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
17914    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
17915    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
17916    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
17917    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
17918    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
17919    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
17920    #[allow(clippy::too_many_arguments)]
17921    pub fn fa_decode_batch_seqs_v4(
17922        &self,
17923        q: &CudaSlice<f32>,
17924        kv_ptrs: &cudarc::driver::CudaView<u64>,
17925        pos_seq: &CudaSlice<i32>,
17926        o: &mut CudaSlice<f32>,
17927        head_dim: usize,
17928        n_head: usize,
17929        n_head_kv: usize,
17930        b_n: usize,
17931        t_kv_max: usize,
17932        scale: f32,
17933        split_keys: usize,
17934        k_tok_bytes: usize,
17935        v_tok_bytes: usize,
17936    ) -> Result<(), Box<dyn std::error::Error>> {
17937        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
17938        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
17939        let o_len = b_n * n_head * n_splits_max * head_dim;
17940        let ml_len = b_n * n_head * n_splits_max;
17941        let mut part_guard = self.fa_part_pool.lock().unwrap();
17942        if part_guard
17943            .as_ref()
17944            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17945            .unwrap_or(true)
17946        {
17947            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17948            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17949            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17950            // later live allocations land at those addresses, and the next graph REPLAY writes
17951            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17952            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17953            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17954            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17955            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17956            // (total retired < final size).
17957            let old = part_guard.take();
17958            let (co, cm) = old
17959                .as_ref()
17960                .map(|pp| (pp.0.len(), pp.1.len()))
17961                .unwrap_or((0, 0));
17962            if let Some(old) = old {
17963                self.fa_part_retired.lock().unwrap().push(old);
17964            }
17965            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17966                eprintln!(
17967                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17968                    co, o_len, cm, ml_len
17969                );
17970            }
17971            *part_guard = Some((
17972                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17973                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17974                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17975            ));
17976        }
17977        let pg = part_guard.as_mut().unwrap();
17978        self.gpu
17979            .stream()
17980            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17981        self.gpu
17982            .stream()
17983            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17984        self.gpu
17985            .stream()
17986            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17987        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17988        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17989        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
17990        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17991        let gqa = (n_head / n_head_kv).max(1) as u32;
17992        let f = self.func("fa_decode_vec_q_seqs_v4");
17993        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
17994        let shmem = (11520 + 32 * head_dim * 2) as u32;
17995        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17996        f.set_attribute(
17997            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17998            shmem as i32,
17999        )?;
18000        let cfg = LaunchConfig {
18001            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
18002            block_dim: (32, gqa, 1),
18003            shared_mem_bytes: shmem,
18004        };
18005        {
18006            let __s_b = self.gpu.stream();
18007            let mut b = __s_b.launch_builder(&f);
18008            b.arg(q)
18009                .arg(kv_ptrs)
18010                .arg(pos_seq)
18011                .arg(&mut *part_o)
18012                .arg(&mut *part_m)
18013                .arg(&mut *part_l)
18014                .arg(&hd)
18015                .arg(&nh)
18016                .arg(&nhkv)
18017                .arg(&scale)
18018                .arg(&nspm)
18019                .arg(&spk)
18020                .arg(&ktb)
18021                .arg(&vtb);
18022            unsafe {
18023                b.launch(cfg)?;
18024            }
18025        }
18026        let fc = self.func("fa_decode_combine_seqs");
18027        let cfg2 = LaunchConfig {
18028            grid_dim: (n_head as u32, b_n as u32, 1),
18029            block_dim: (head_dim as u32, 1, 1),
18030            shared_mem_bytes: 0,
18031        };
18032        let __s_b2 = self.gpu.stream();
18033        let mut b2 = __s_b2.launch_builder(&fc);
18034        b2.arg(&*part_o)
18035            .arg(&*part_m)
18036            .arg(&*part_l)
18037            .arg(o)
18038            .arg(&hd)
18039            .arg(&nh)
18040            .arg(pos_seq)
18041            .arg(&nspm)
18042            .arg(&spk);
18043        unsafe {
18044            b2.launch(cfg2)?;
18045        }
18046        Ok(())
18047    }
18048
18049    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
18050    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
18051    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
18052    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
18053    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
18054    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
18055    #[allow(clippy::too_many_arguments)]
18056    pub fn append_kv_quantized_seqs(
18057        &self,
18058        k_rows: &CudaSlice<f32>,
18059        v_rows: &CudaSlice<f32>,
18060        kv_ptrs: &cudarc::driver::CudaView<u64>,
18061        pos_seq: &CudaSlice<i32>,
18062        b_n: usize,
18063        kv_dim_k: usize,
18064        kv_dim_v: usize,
18065        k_tok_bytes: usize,
18066        v_tok_bytes: usize,
18067    ) -> Result<(), Box<dyn std::error::Error>> {
18068        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
18069        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
18070        let cfg = LaunchConfig {
18071            grid_dim: (nblk, b_n as u32, 1),
18072            block_dim: (32, 1, 1),
18073            shared_mem_bytes: 0,
18074        };
18075        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
18076        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18077        let __s_b = self.gpu.stream();
18078        let mut b = __s_b.launch_builder(&f);
18079        b.arg(k_rows)
18080            .arg(v_rows)
18081            .arg(kv_ptrs)
18082            .arg(pos_seq)
18083            .arg(&kdk)
18084            .arg(&kdv)
18085            .arg(&ktb)
18086            .arg(&vtb);
18087        unsafe {
18088            b.launch(cfg)?;
18089        }
18090        Ok(())
18091    }
18092
18093    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
18094    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
18095    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
18096    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
18097    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
18098    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
18099        std::env::var("MEMRA_NO_FA_VEC").is_err()
18100            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
18101            && base_len + 1 >= fa_vec_min_tkv()
18102            && head_dim <= 256
18103            && head_dim % 32 == 0
18104    }
18105
18106    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
18107    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
18108    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
18109    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
18110    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
18111    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
18112    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
18113    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
18114    #[allow(clippy::too_many_arguments)]
18115    pub fn fa_decode_rows(
18116        &self,
18117        q: &CudaSlice<f32>,
18118        k: &cudarc::driver::CudaView<u8>,
18119        v: &cudarc::driver::CudaView<u8>,
18120        o: &mut CudaSlice<f32>,
18121        head_dim: usize,
18122        n_head: usize,
18123        n_head_kv: usize,
18124        base_len: usize,
18125        t: usize,
18126        scale: f32,
18127        k_tok_bytes: usize,
18128        v_tok_bytes: usize,
18129        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
18130        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
18131        // keep the host arg. None is a bug for hd512 (asserted below).
18132        base_dev: Option<(&CudaSlice<i32>, i32)>,
18133        // K and V planes hold the same values (gemma globals, wv:=wk): pick
18134        // the _kv twin — V plane never read, value rides the q8_0 key dq.
18135        kv_shared: bool,
18136        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
18137        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
18138        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
18139        g: bool,
18140        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
18141        // (hd512 path) — the standalone quantize launch folds away.
18142        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18143    ) -> Result<(), Box<dyn std::error::Error>> {
18144        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
18145        let t_kv_max = base_len + t; // LAST row's key bound
18146        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
18147        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
18148        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
18149        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
18150        // (parity law), so the partition is freely tunable — verify and decode move together.
18151        if head_dim == 512 {
18152            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18153            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
18154            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
18155            let v = *SP512.get_or_init(|| {
18156                std::env::var("MEMRA_FA_SP512")
18157                    .ok()
18158                    .and_then(|x| x.parse().ok())
18159                    .unwrap_or(0)
18160            });
18161            sp = if v >= 8 {
18162                v
18163            } else {
18164                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18165            };
18166        }
18167        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18168        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18169        let gqa = (n_head / n_head_kv).max(1) as u32;
18170        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
18171        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
18172        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
18173        // the different partition changes the combine's FP order (greedy tie flips at depth;
18174        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
18175        // consecutive rows by their OWN ladder value and launch once per group — each row then
18176        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
18177        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
18178        // sp override is t_kv-independent by construction).
18179        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
18180        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
18181            groups.push((0, t, sp));
18182        } else {
18183            let mut r0 = 0usize;
18184            while r0 < t {
18185                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
18186                let mut r1 = r0 + 1;
18187                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
18188                    r1 += 1;
18189                }
18190                groups.push((r0, r1 - r0, sp_g));
18191                r0 = r1;
18192            }
18193        }
18194        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
18195        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
18196        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
18197        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18198        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
18199            std::env::var("MEMRA_FA_SMEM_TKV")
18200                .ok()
18201                .and_then(|v| v.parse().ok())
18202                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18203        });
18204        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
18205        let v3 = fa_v3_active(head_dim);
18206        let smem_rows =
18207            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
18208        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
18209        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
18210        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
18211        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
18212        let _ = kv_shared;
18213        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
18214        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
18215        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
18216        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
18217        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
18218        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
18219        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
18220        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
18221        // (kv_head, split) stages its tile once and loops the rows over it — kills the
18222        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
18223        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
18224        // shared by every hd512 caller through this wrapper (decode+verify flip together;
18225        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
18226        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
18227        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
18228        // not unpack-bound; jsonl 2026-07-14.
18229        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18230        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
18231        let tb512 = head_dim == 512
18232            && sp <= 32
18233            && n_head / n_head_kv.max(1) <= 16
18234            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
18235        let fname = if tb512 {
18236            "fa_decode_vec_q_rows_v4_512_tb"
18237        } else if i2 {
18238            "fa_decode_vec_q_rows_dpl16_i2"
18239        } else if head_dim == 512 {
18240            "fa_decode_vec_q_rows_dpl16"
18241        }
18242        // gemma globals (parity law)
18243        else if v4 {
18244            "fa_decode_vec_q_rows_v4"
18245        } else if v3 {
18246            "fa_decode_vec_q_rows_v3"
18247        } else if fa_v2_on() {
18248            "fa_decode_vec_q_rows_v2"
18249        } else if smem_rows {
18250            "fa_decode_vec_q_rows_smem"
18251        } else {
18252            "fa_decode_vec_q_rows"
18253        };
18254        let f = if head_dim == 512 {
18255            self.fa_func(fname, head_dim)
18256        } else if g {
18257            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
18258            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
18259            // g-module rows against decode's g-module v4 — different programs, short-VG
18260            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
18261            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
18262            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
18263            // dq macros are format-aware.
18264            self.func_g(if smem_rows {
18265                "fa_decode_vec_q_rows"
18266            } else {
18267                fname
18268            })
18269        } else {
18270            self.func(fname)
18271        };
18272        let shmem = if tb512 {
18273            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
18274            let gk = Self::gkv_on();
18275            let sh =
18276                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
18277            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18278            f.set_attribute(
18279                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18280                sh as i32,
18281            )?;
18282            sh
18283        } else if v4 || v3 || smem_rows || fa_v2_on() {
18284            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
18285            let sh = (if v4 {
18286                11520 + 32 * head_dim * if g { 1 } else { 2 }
18287            } else if v3 {
18288                32 * head_dim * 2
18289            } else {
18290                2 * 32 * head_dim * 2
18291            }) as u32;
18292            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18293            f.set_attribute(
18294                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18295                sh as i32,
18296            )?;
18297            sh
18298        } else {
18299            0
18300        };
18301        // Per-GROUP launches (single group in the common case — identical to the pre-fix
18302        // single launch there): each group gets its own partials (the rows kernel indexes
18303        // partials by its LOCAL grid.z row) and q/o row-offset views.
18304        for &(r0, t_g, sp_g) in &groups {
18305            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
18306            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
18307            let base_i = (base_len + r0) as i32;
18308            let o_len = t_g * n_head * n_splits_g * head_dim;
18309            let ml_len = t_g * n_head * n_splits_g;
18310            let mut part_guard = self.fa_part_pool.lock().unwrap();
18311            if part_guard
18312                .as_ref()
18313                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18314                .unwrap_or(true)
18315            {
18316                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18317                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18318                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18319                // later live allocations land at those addresses, and the next graph REPLAY writes
18320                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18321                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18322                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18323                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18324                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18325                // (total retired < final size).
18326                let old = part_guard.take();
18327                let (co, cm) = old
18328                    .as_ref()
18329                    .map(|pp| (pp.0.len(), pp.1.len()))
18330                    .unwrap_or((0, 0));
18331                if let Some(old) = old {
18332                    self.fa_part_retired.lock().unwrap().push(old);
18333                }
18334                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18335                    eprintln!(
18336                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18337                        co, o_len, cm, ml_len
18338                    );
18339                }
18340                *part_guard = Some((
18341                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18342                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18343                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18344                ));
18345            }
18346            let pg = part_guard.as_mut().unwrap();
18347            self.gpu
18348                .stream()
18349                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18350            self.gpu
18351                .stream()
18352                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18353            self.gpu
18354                .stream()
18355                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18356            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18357            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
18358            let qv = self.view(q, t * n_head * head_dim);
18359            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18360            let cfg = LaunchConfig {
18361                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
18362                block_dim: (32, gqa, 1),
18363                shared_mem_bytes: shmem,
18364            };
18365            {
18366                let __s_b = self.gpu.stream();
18367                let mut b = __s_b.launch_builder(&f);
18368                if tb512 {
18369                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
18370                    let (bd, plus) =
18371                        base_dev.expect("hd512 rows twin requires a device base counter");
18372                    let plus_g = plus + r0 as i32;
18373                    let nr = t_g as i32;
18374                    if Self::pdl_on() && Self::pdl_wb_on() {
18375                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
18376                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18377                        let s = &self.gpu.stream();
18378                        let (pq, _b0) = q_g.device_ptr(s);
18379                        let (pk, _b1) = k.device_ptr(s);
18380                        let (pv, _b2) = v.device_ptr(s);
18381                        let (po, _b3) = part_o.device_ptr_mut(s);
18382                        let (pm, _b4) = part_m.device_ptr_mut(s);
18383                        let (pl, _b5) = part_l.device_ptr_mut(s);
18384                        let (pb, _b6) = bd.device_ptr(s);
18385                        let mut ps = [
18386                            &pq as *const _ as *mut std::ffi::c_void,
18387                            &pk as *const _ as *mut _,
18388                            &pv as *const _ as *mut _,
18389                            &po as *const _ as *mut _,
18390                            &pm as *const _ as *mut _,
18391                            &pl as *const _ as *mut _,
18392                            &hd as *const _ as *mut _,
18393                            &nh as *const _ as *mut _,
18394                            &nhkv as *const _ as *mut _,
18395                            &pb as *const _ as *mut _,
18396                            &plus_g as *const _ as *mut _,
18397                            &scale as *const _ as *mut _,
18398                            &nspm as *const _ as *mut _,
18399                            &spk as *const _ as *mut _,
18400                            &ktb as *const _ as *mut _,
18401                            &vtb as *const _ as *mut _,
18402                            &nr as *const _ as *mut _,
18403                        ];
18404                        unsafe {
18405                            self.launch_pdl_flash(
18406                                Self::gkv_on(),
18407                                "fa_decode_vec_q_rows_v4_512_tb",
18408                                (n_head_kv as u32, n_splits_g as u32, 1),
18409                                (32, gqa, 1),
18410                                shmem,
18411                                &mut ps,
18412                            )?;
18413                        }
18414                    } else {
18415                        let cfg_tb = LaunchConfig {
18416                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
18417                            block_dim: (32, gqa, 1),
18418                            shared_mem_bytes: shmem,
18419                        };
18420                        b.arg(&q_g)
18421                            .arg(k)
18422                            .arg(v)
18423                            .arg(&mut *part_o)
18424                            .arg(&mut *part_m)
18425                            .arg(&mut *part_l)
18426                            .arg(&hd)
18427                            .arg(&nh)
18428                            .arg(&nhkv)
18429                            .arg(bd)
18430                            .arg(&plus_g)
18431                            .arg(&scale)
18432                            .arg(&nspm)
18433                            .arg(&spk)
18434                            .arg(&ktb)
18435                            .arg(&vtb)
18436                            .arg(&nr);
18437                        unsafe {
18438                            b.launch(cfg_tb)?;
18439                        }
18440                    }
18441                } else if head_dim == 512 {
18442                    let (bd, plus) =
18443                        base_dev.expect("hd512 rows twin requires a device base counter");
18444                    let plus_g = plus + r0 as i32;
18445                    b.arg(&q_g)
18446                        .arg(k)
18447                        .arg(v)
18448                        .arg(&mut *part_o)
18449                        .arg(&mut *part_m)
18450                        .arg(&mut *part_l)
18451                        .arg(&hd)
18452                        .arg(&nh)
18453                        .arg(&nhkv)
18454                        .arg(bd)
18455                        .arg(&plus_g)
18456                        .arg(&scale)
18457                        .arg(&nspm)
18458                        .arg(&spk)
18459                        .arg(&ktb)
18460                        .arg(&vtb);
18461                    unsafe {
18462                        b.launch(cfg)?;
18463                    }
18464                } else {
18465                    b.arg(&q_g)
18466                        .arg(k)
18467                        .arg(v)
18468                        .arg(&mut *part_o)
18469                        .arg(&mut *part_m)
18470                        .arg(&mut *part_l)
18471                        .arg(&hd)
18472                        .arg(&nh)
18473                        .arg(&nhkv)
18474                        .arg(&base_i)
18475                        .arg(&scale)
18476                        .arg(&nspm)
18477                        .arg(&spk)
18478                        .arg(&ktb)
18479                        .arg(&vtb);
18480                    unsafe {
18481                        b.launch(cfg)?;
18482                    }
18483                }
18484            }
18485            let cfg2 = LaunchConfig {
18486                grid_dim: (n_head as u32, t_g as u32, 1),
18487                block_dim: (head_dim as u32, 1, 1),
18488                shared_mem_bytes: 0,
18489            };
18490            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18491            if head_dim == 512 {
18492                // device-len combine (shared by verify/eager/graph — parity by symbol): the
18493                // per-row n_splits derives from the SAME counter the rows kernel read.
18494                let (bd, plus) = base_dev.unwrap();
18495                let plus_g = plus + r0 as i32;
18496                if let Some((oq, od)) = q8_out.as_mut() {
18497                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
18498                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
18499                    if Self::pdl_on() && Self::pdl_wb_on() {
18500                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
18501                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18502                        let s = &self.gpu.stream();
18503                        let (po, _g0) = part_o.device_ptr(s);
18504                        let (pm, _g1) = part_m.device_ptr(s);
18505                        let (pl, _g2) = part_l.device_ptr(s);
18506                        let (pq, _g3) = oq.device_ptr_mut(s);
18507                        let (pd, _g4) = od.device_ptr_mut(s);
18508                        let (pb, _g5) = bd.device_ptr(s);
18509                        let mut ps = [
18510                            &po as *const _ as *mut std::ffi::c_void,
18511                            &pm as *const _ as *mut _,
18512                            &pl as *const _ as *mut _,
18513                            &pq as *const _ as *mut _,
18514                            &pd as *const _ as *mut _,
18515                            &hd as *const _ as *mut _,
18516                            &nh as *const _ as *mut _,
18517                            &pb as *const _ as *mut _,
18518                            &plus_g as *const _ as *mut _,
18519                            &nspm as *const _ as *mut _,
18520                            &spk as *const _ as *mut _,
18521                        ];
18522                        unsafe {
18523                            self.launch_pdl_flash(
18524                                Self::gkv_on(),
18525                                "fa_decode_combine_rows_dc_q8_1",
18526                                cfg2.grid_dim,
18527                                cfg2.block_dim,
18528                                0,
18529                                &mut ps,
18530                            )?;
18531                        }
18532                        continue;
18533                    }
18534                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
18535                    let __s_b2 = self.gpu.stream();
18536                    let mut b2 = __s_b2.launch_builder(&fc);
18537                    b2.arg(&*part_o)
18538                        .arg(&*part_m)
18539                        .arg(&*part_l)
18540                        .arg(&mut **oq)
18541                        .arg(&mut **od)
18542                        .arg(&hd)
18543                        .arg(&nh)
18544                        .arg(bd)
18545                        .arg(&plus_g)
18546                        .arg(&nspm)
18547                        .arg(&spk);
18548                    unsafe {
18549                        b2.launch(cfg2)?;
18550                    }
18551                    continue;
18552                }
18553                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
18554                let __s_b2 = self.gpu.stream();
18555                let mut b2 = __s_b2.launch_builder(&fc);
18556                b2.arg(&*part_o)
18557                    .arg(&*part_m)
18558                    .arg(&*part_l)
18559                    .arg(&mut o_g)
18560                    .arg(&hd)
18561                    .arg(&nh)
18562                    .arg(bd)
18563                    .arg(&plus_g)
18564                    .arg(&nspm)
18565                    .arg(&spk);
18566                unsafe {
18567                    b2.launch(cfg2)?;
18568                }
18569            } else {
18570                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
18571                // leave the caller's pair unwritten (consumer would read garbage).
18572                assert!(
18573                    q8_out.is_none(),
18574                    "rows q8 emit requires the hd512 dc combine"
18575                );
18576                let fc = self.func("fa_decode_combine_rows");
18577                let __s_b2 = self.gpu.stream();
18578                let mut b2 = __s_b2.launch_builder(&fc);
18579                b2.arg(&*part_o)
18580                    .arg(&*part_m)
18581                    .arg(&*part_l)
18582                    .arg(&mut o_g)
18583                    .arg(&hd)
18584                    .arg(&nh)
18585                    .arg(&base_i)
18586                    .arg(&nspm)
18587                    .arg(&spk);
18588                unsafe {
18589                    b2.launch(cfg2)?;
18590                }
18591            }
18592        }
18593        Ok(())
18594    }
18595
18596    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
18597    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
18598    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
18599    #[allow(clippy::too_many_arguments)]
18600    pub fn fa_decode_rows_w(
18601        &self,
18602        q: &CudaSlice<f32>,
18603        k: &cudarc::driver::CudaView<u8>,
18604        v: &cudarc::driver::CudaView<u8>,
18605        o: &mut CudaSlice<f32>,
18606        head_dim: usize,
18607        n_head: usize,
18608        n_head_kv: usize,
18609        base_dev: &CudaSlice<i32>,
18610        base_plus: i32,
18611        t: usize,
18612        scale: f32,
18613        window: usize,
18614        k_tok_bytes: usize,
18615        v_tok_bytes: usize,
18616        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18617    ) -> Result<(), Box<dyn std::error::Error>> {
18618        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
18619        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
18620        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
18621        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
18622        debug_assert!(head_dim == 256);
18623        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
18624        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
18625        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
18626        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
18627        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
18628        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
18629        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
18630        let sp = {
18631            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18632            let v = *SPW.get_or_init(|| {
18633                std::env::var("MEMRA_FA_SPW")
18634                    .ok()
18635                    .and_then(|x| x.parse().ok())
18636                    .unwrap_or(0)
18637            });
18638            if v >= 8 {
18639                v
18640            } else {
18641                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18642            }
18643        };
18644        let n_splits_max = (window + sp - 1) / sp;
18645        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18646        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
18647        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18648        let gqa = (n_head / n_head_kv).max(1) as u32;
18649        let o_len = t * n_head * n_splits_max * head_dim;
18650        let ml_len = t * n_head * n_splits_max;
18651        let mut part_guard = self.fa_part_pool.lock().unwrap();
18652        if part_guard
18653            .as_ref()
18654            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18655            .unwrap_or(true)
18656        {
18657            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18658            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18659            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18660            // later live allocations land at those addresses, and the next graph REPLAY writes
18661            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18662            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18663            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18664            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18665            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18666            // (total retired < final size).
18667            let old = part_guard.take();
18668            let (co, cm) = old
18669                .as_ref()
18670                .map(|pp| (pp.0.len(), pp.1.len()))
18671                .unwrap_or((0, 0));
18672            if let Some(old) = old {
18673                self.fa_part_retired.lock().unwrap().push(old);
18674            }
18675            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18676                eprintln!(
18677                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18678                    co, o_len, cm, ml_len
18679                );
18680            }
18681            *part_guard = Some((
18682                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18683                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18684                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18685            ));
18686        }
18687        let pg = part_guard.as_mut().unwrap();
18688        self.gpu
18689            .stream()
18690            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18691        self.gpu
18692            .stream()
18693            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18694        self.gpu
18695            .stream()
18696            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18697        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18698        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
18699        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
18700        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
18701        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
18702        // floor (deep-ctx broadcast win); register twin between.
18703        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18704        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
18705            std::env::var("MEMRA_FA_SMEM_TKV")
18706                .ok()
18707                .and_then(|v| v.parse().ok())
18708                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18709        });
18710        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
18711        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
18712        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
18713        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
18714        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
18715        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18716        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
18717        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
18718        // per (lane, format-module) keeps parity structural; the old register-i2 detour
18719        // (-33%) is retired.
18720        let wg = Self::wkv_on();
18721        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
18722        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
18723        let sp2 =
18724            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
18725        if sp2 {
18726            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18727            if Self::pdl_on() && Self::pdl_wb_on() {
18728                // wave-B2b: flavor mirrors wg.
18729                use cudarc::driver::{DevicePtr, DevicePtrMut};
18730                let s = &self.gpu.stream();
18731                let (pq, _b0) = q.device_ptr(s);
18732                let (pk, _b1) = k.device_ptr(s);
18733                let (pv, _b2) = v.device_ptr(s);
18734                let (po, _b3) = part_o.device_ptr_mut(s);
18735                let (pm, _b4) = part_m.device_ptr_mut(s);
18736                let (pl, _b5) = part_l.device_ptr_mut(s);
18737                let (pb, _b6) = base_dev.device_ptr(s);
18738                let mut ps = [
18739                    &pq as *const _ as *mut std::ffi::c_void,
18740                    &pk as *const _ as *mut _,
18741                    &pv as *const _ as *mut _,
18742                    &po as *const _ as *mut _,
18743                    &pm as *const _ as *mut _,
18744                    &pl as *const _ as *mut _,
18745                    &hd as *const _ as *mut _,
18746                    &nh as *const _ as *mut _,
18747                    &nhkv as *const _ as *mut _,
18748                    &pb as *const _ as *mut _,
18749                    &base_plus as *const _ as *mut _,
18750                    &scale as *const _ as *mut _,
18751                    &nspm as *const _ as *mut _,
18752                    &spk as *const _ as *mut _,
18753                    &ktb as *const _ as *mut _,
18754                    &vtb as *const _ as *mut _,
18755                    &wini as *const _ as *mut _,
18756                ];
18757                unsafe {
18758                    self.launch_pdl_flash(
18759                        wg,
18760                        "fa_decode_vec_q_rows_v4_w_sp",
18761                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18762                        (32, gqa + 1, 1),
18763                        sh,
18764                        &mut ps,
18765                    )?;
18766                }
18767            } else {
18768                let f = if wg {
18769                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
18770                } else {
18771                    self.func("fa_decode_vec_q_rows_v4_w_sp")
18772                };
18773                f.set_attribute(
18774                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18775                    sh as i32,
18776                )?;
18777                let cfg = LaunchConfig {
18778                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18779                    block_dim: (32, gqa + 1, 1),
18780                    shared_mem_bytes: sh,
18781                };
18782                let __s_b = self.gpu.stream();
18783                let mut b = __s_b.launch_builder(&f);
18784                b.arg(q)
18785                    .arg(k)
18786                    .arg(v)
18787                    .arg(&mut *part_o)
18788                    .arg(&mut *part_m)
18789                    .arg(&mut *part_l)
18790                    .arg(&hd)
18791                    .arg(&nh)
18792                    .arg(&nhkv)
18793                    .arg(base_dev)
18794                    .arg(&base_plus)
18795                    .arg(&scale)
18796                    .arg(&nspm)
18797                    .arg(&spk)
18798                    .arg(&ktb)
18799                    .arg(&vtb)
18800                    .arg(&wini);
18801                unsafe {
18802                    b.launch(cfg)?;
18803                }
18804            }
18805        } else {
18806            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
18807                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
18808                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18809                use cudarc::driver::{DevicePtr, DevicePtrMut};
18810                let s = &self.gpu.stream();
18811                let (pq, _b0) = q.device_ptr(s);
18812                let (pk, _b1) = k.device_ptr(s);
18813                let (pv, _b2) = v.device_ptr(s);
18814                let (po, _b3) = part_o.device_ptr_mut(s);
18815                let (pm, _b4) = part_m.device_ptr_mut(s);
18816                let (pl, _b5) = part_l.device_ptr_mut(s);
18817                let (pb, _b6) = base_dev.device_ptr(s);
18818                let mut ps = [
18819                    &pq as *const _ as *mut std::ffi::c_void,
18820                    &pk as *const _ as *mut _,
18821                    &pv as *const _ as *mut _,
18822                    &po as *const _ as *mut _,
18823                    &pm as *const _ as *mut _,
18824                    &pl as *const _ as *mut _,
18825                    &hd as *const _ as *mut _,
18826                    &nh as *const _ as *mut _,
18827                    &nhkv as *const _ as *mut _,
18828                    &pb as *const _ as *mut _,
18829                    &base_plus as *const _ as *mut _,
18830                    &scale as *const _ as *mut _,
18831                    &nspm as *const _ as *mut _,
18832                    &spk as *const _ as *mut _,
18833                    &ktb as *const _ as *mut _,
18834                    &vtb as *const _ as *mut _,
18835                    &wini as *const _ as *mut _,
18836                ];
18837                unsafe {
18838                    self.launch_pdl_flash(
18839                        wg,
18840                        "fa_decode_vec_q_rows_v4_w",
18841                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18842                        (32, gqa, 1),
18843                        sh,
18844                        &mut ps,
18845                    )?;
18846                }
18847            } else {
18848                let pick = |name: &str| {
18849                    if wg {
18850                        self.func_g(name)
18851                    } else {
18852                        self.func(name)
18853                    }
18854                };
18855                let (f, sh) = if fa_v4_at(window) {
18856                    let f = pick("fa_decode_vec_q_rows_v4_w");
18857                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
18858                } else if smem_tkv > 0 && window >= smem_tkv {
18859                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
18860                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
18861                    (
18862                        pick("fa_decode_vec_q_rows_smem_w"),
18863                        (2 * 32 * head_dim * 2) as u32,
18864                    )
18865                } else {
18866                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
18867                };
18868                f.set_attribute(
18869                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18870                    sh as i32,
18871                )?;
18872                let cfg = LaunchConfig {
18873                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18874                    block_dim: (32, gqa, 1),
18875                    shared_mem_bytes: sh,
18876                };
18877                let __s_b = self.gpu.stream();
18878                let mut b = __s_b.launch_builder(&f);
18879                b.arg(q)
18880                    .arg(k)
18881                    .arg(v)
18882                    .arg(&mut *part_o)
18883                    .arg(&mut *part_m)
18884                    .arg(&mut *part_l)
18885                    .arg(&hd)
18886                    .arg(&nh)
18887                    .arg(&nhkv)
18888                    .arg(base_dev)
18889                    .arg(&base_plus)
18890                    .arg(&scale)
18891                    .arg(&nspm)
18892                    .arg(&spk)
18893                    .arg(&ktb)
18894                    .arg(&vtb)
18895                    .arg(&wini);
18896                unsafe {
18897                    b.launch(cfg)?;
18898                }
18899            }
18900        }
18901        let cfg2 = LaunchConfig {
18902            grid_dim: (n_head as u32, t as u32, 1),
18903            block_dim: (head_dim as u32, 1, 1),
18904            shared_mem_bytes: 0,
18905        };
18906        if let Some((oq, od)) = q8_out {
18907            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
18908            // consumes the pair directly; the standalone quantize launch folds away.
18909            if Self::pdl_on() && Self::pdl_wb_on() {
18910                // wave-B2: flavor mirrors the builder's wg choice.
18911                use cudarc::driver::{DevicePtr, DevicePtrMut};
18912                let s = &self.gpu.stream();
18913                let (po, _g0) = part_o.device_ptr(s);
18914                let (pm, _g1) = part_m.device_ptr(s);
18915                let (pl, _g2) = part_l.device_ptr(s);
18916                let (pq, _g3) = oq.device_ptr_mut(s);
18917                let (pd, _g4) = od.device_ptr_mut(s);
18918                let mut ps = [
18919                    &po as *const _ as *mut std::ffi::c_void,
18920                    &pm as *const _ as *mut _,
18921                    &pl as *const _ as *mut _,
18922                    &pq as *const _ as *mut _,
18923                    &pd as *const _ as *mut _,
18924                    &hd as *const _ as *mut _,
18925                    &nh as *const _ as *mut _,
18926                    &nspm as *const _ as *mut _,
18927                    &spk as *const _ as *mut _,
18928                    &wini as *const _ as *mut _,
18929                ];
18930                unsafe {
18931                    self.launch_pdl_flash(
18932                        wg,
18933                        "fa_decode_combine_rows_w_q8_1",
18934                        cfg2.grid_dim,
18935                        cfg2.block_dim,
18936                        0,
18937                        &mut ps,
18938                    )?;
18939                }
18940                return Ok(());
18941            }
18942            let fc = if wg {
18943                self.func_g("fa_decode_combine_rows_w_q8_1")
18944            } else {
18945                self.func("fa_decode_combine_rows_w_q8_1")
18946            };
18947            let __s_b2 = self.gpu.stream();
18948            let mut b2 = __s_b2.launch_builder(&fc);
18949            b2.arg(&*part_o)
18950                .arg(&*part_m)
18951                .arg(&*part_l)
18952                .arg(oq)
18953                .arg(od)
18954                .arg(&hd)
18955                .arg(&nh)
18956                .arg(&nspm)
18957                .arg(&spk)
18958                .arg(&wini);
18959            unsafe {
18960                b2.launch(cfg2)?;
18961            }
18962            return Ok(());
18963        }
18964        let fc = if wg {
18965            self.func_g("fa_decode_combine_rows_w")
18966        } else {
18967            self.func("fa_decode_combine_rows_w")
18968        };
18969        let __s_b2 = self.gpu.stream();
18970        let mut b2 = __s_b2.launch_builder(&fc);
18971        b2.arg(&*part_o)
18972            .arg(&*part_m)
18973            .arg(&*part_l)
18974            .arg(o)
18975            .arg(&hd)
18976            .arg(&nh)
18977            .arg(&nspm)
18978            .arg(&spk)
18979            .arg(&wini);
18980        unsafe {
18981            b2.launch(cfg2)?;
18982        }
18983        Ok(())
18984    }
18985
18986    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
18987    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
18988    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
18989    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
18990    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
18991    #[allow(clippy::too_many_arguments)]
18992    pub fn fa_decode_rows_dc(
18993        &self,
18994        q: &CudaSlice<f32>,
18995        k: &cudarc::driver::CudaView<u8>,
18996        v: &cudarc::driver::CudaView<u8>,
18997        o: &mut CudaSlice<f32>,
18998        head_dim: usize,
18999        n_head: usize,
19000        n_head_kv: usize,
19001        base_dev: &CudaSlice<i32>,
19002        t_kv_upper: usize,
19003        t: usize,
19004        scale: f32,
19005        k_tok_bytes: usize,
19006        v_tok_bytes: usize,
19007        base_plus: i32,
19008        g: bool,
19009    ) -> Result<(), Box<dyn std::error::Error>> {
19010        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
19011        assert!(
19012            v4 || fa_v3_active(head_dim),
19013            "stream fa rows requires the v3 or v4 lane"
19014        );
19015        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
19016        if v4 {
19017            let sp = fa_split_keys(t_kv_upper, n_head_kv);
19018            let n_splits_max = (t_kv_upper + sp - 1) / sp;
19019            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
19020            let (nspm, spk) = (n_splits_max as i32, sp as i32);
19021            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19022            let gqa = (n_head / n_head_kv).max(1) as u32;
19023            let o_len = t * n_head * n_splits_max * head_dim;
19024            let ml_len = t * n_head * n_splits_max;
19025            let mut part_guard = self.fa_part_pool.lock().unwrap();
19026            if part_guard
19027                .as_ref()
19028                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19029                .unwrap_or(true)
19030            {
19031                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19032                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19033                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19034                // later live allocations land at those addresses, and the next graph REPLAY writes
19035                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19036                // output corruption began the burst after the trunk's t_kv growth first realloc'd
19037                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19038                // the baked addresses alive (single-stream: eager writes the new buffers, replays
19039                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19040                // (total retired < final size).
19041                let old = part_guard.take();
19042                let (co, cm) = old
19043                    .as_ref()
19044                    .map(|pp| (pp.0.len(), pp.1.len()))
19045                    .unwrap_or((0, 0));
19046                if let Some(old) = old {
19047                    self.fa_part_retired.lock().unwrap().push(old);
19048                }
19049                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19050                    eprintln!(
19051                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19052                        co, o_len, cm, ml_len
19053                    );
19054                }
19055                *part_guard = Some((
19056                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19057                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19058                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19059                ));
19060            }
19061            let pg = part_guard.as_mut().unwrap();
19062            self.gpu
19063                .stream()
19064                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19065            self.gpu
19066                .stream()
19067                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19068            self.gpu
19069                .stream()
19070                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19071            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19072            let f = if g {
19073                self.func_g("fa_decode_vec_q_rows_v4_dc")
19074            } else {
19075                self.func("fa_decode_vec_q_rows_v4_dc")
19076            };
19077            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
19078            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19079            f.set_attribute(
19080                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19081                sh as i32,
19082            )?;
19083            let cfg = LaunchConfig {
19084                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
19085                block_dim: (32, gqa, 1),
19086                shared_mem_bytes: sh,
19087            };
19088            let __s_b = self.gpu.stream();
19089            let mut b = __s_b.launch_builder(&f);
19090            b.arg(q)
19091                .arg(k)
19092                .arg(v)
19093                .arg(&mut *part_o)
19094                .arg(&mut *part_m)
19095                .arg(&mut *part_l)
19096                .arg(&hd)
19097                .arg(&nh)
19098                .arg(&nhkv)
19099                .arg(base_dev)
19100                .arg(&base_plus)
19101                .arg(&scale)
19102                .arg(&nspm)
19103                .arg(&spk)
19104                .arg(&ktb)
19105                .arg(&vtb);
19106            unsafe {
19107                b.launch(cfg)?;
19108            }
19109            let fc = self.func("fa_decode_combine_rows_dc");
19110            let cfg2 = LaunchConfig {
19111                grid_dim: (n_head as u32, t as u32, 1),
19112                block_dim: (head_dim as u32, 1, 1),
19113                shared_mem_bytes: 0,
19114            };
19115            let __s_b2 = self.gpu.stream();
19116            let mut b2 = __s_b2.launch_builder(&fc);
19117            b2.arg(&*part_o)
19118                .arg(&*part_m)
19119                .arg(&*part_l)
19120                .arg(o)
19121                .arg(&hd)
19122                .arg(&nh)
19123                .arg(base_dev)
19124                .arg(&base_plus)
19125                .arg(&nspm)
19126                .arg(&spk);
19127            unsafe {
19128                b2.launch(cfg2)?;
19129            }
19130            return Ok(());
19131        }
19132        let sp = fa_split_keys(t_kv_upper, n_head_kv);
19133        let n_splits_max = (t_kv_upper + sp - 1) / sp;
19134        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
19135        let (nspm, spk) = (n_splits_max as i32, sp as i32);
19136        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19137        let gqa = (n_head / n_head_kv).max(1) as u32;
19138        let o_len = t * n_head * n_splits_max * head_dim;
19139        let ml_len = t * n_head * n_splits_max;
19140        let mut part_guard = self.fa_part_pool.lock().unwrap();
19141        if part_guard
19142            .as_ref()
19143            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19144            .unwrap_or(true)
19145        {
19146            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19147            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19148            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19149            // later live allocations land at those addresses, and the next graph REPLAY writes
19150            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19151            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19152            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19153            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19154            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19155            // (total retired < final size).
19156            let old = part_guard.take();
19157            let (co, cm) = old
19158                .as_ref()
19159                .map(|pp| (pp.0.len(), pp.1.len()))
19160                .unwrap_or((0, 0));
19161            if let Some(old) = old {
19162                self.fa_part_retired.lock().unwrap().push(old);
19163            }
19164            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19165                eprintln!(
19166                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19167                    co, o_len, cm, ml_len
19168                );
19169            }
19170            *part_guard = Some((
19171                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19172                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19173                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19174            ));
19175        }
19176        let pg = part_guard.as_mut().unwrap();
19177        self.gpu
19178            .stream()
19179            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19180        self.gpu
19181            .stream()
19182            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19183        self.gpu
19184            .stream()
19185            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19186        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19187        let f = self.func("fa_decode_vec_q_rows_v3_dc");
19188        let sh = (32 * head_dim * 2) as u32;
19189        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19190        f.set_attribute(
19191            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19192            sh as i32,
19193        )?;
19194        let cfg = LaunchConfig {
19195            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
19196            block_dim: (32, gqa, 1),
19197            shared_mem_bytes: sh,
19198        };
19199        let __s_b = self.gpu.stream();
19200        let mut b = __s_b.launch_builder(&f);
19201        b.arg(q)
19202            .arg(k)
19203            .arg(v)
19204            .arg(&mut *part_o)
19205            .arg(&mut *part_m)
19206            .arg(&mut *part_l)
19207            .arg(&hd)
19208            .arg(&nh)
19209            .arg(&nhkv)
19210            .arg(base_dev)
19211            .arg(&scale)
19212            .arg(&nspm)
19213            .arg(&spk)
19214            .arg(&ktb)
19215            .arg(&vtb);
19216        unsafe {
19217            b.launch(cfg)?;
19218        }
19219        let fc = self.func("fa_decode_combine_rows_dc");
19220        let cfg2 = LaunchConfig {
19221            grid_dim: (n_head as u32, t as u32, 1),
19222            block_dim: (head_dim as u32, 1, 1),
19223            shared_mem_bytes: 0,
19224        };
19225        let plus0 = 0i32;
19226        let __s_b2 = self.gpu.stream();
19227        let mut b2 = __s_b2.launch_builder(&fc);
19228        b2.arg(&*part_o)
19229            .arg(&*part_m)
19230            .arg(&*part_l)
19231            .arg(o)
19232            .arg(&hd)
19233            .arg(&nh)
19234            .arg(base_dev)
19235            .arg(&plus0)
19236            .arg(&nspm)
19237            .arg(&spk);
19238        unsafe {
19239            b2.launch(cfg2)?;
19240        }
19241        Ok(())
19242    }
19243
19244    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
19245    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
19246    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
19247    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
19248    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
19249    ///
19250    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
19251    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
19252    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
19253    /// grouping (different but mathematically-equal log-sum-exp merge).
19254    pub fn fa_decode_dc(
19255        &self,
19256        q: &CudaSlice<f32>,
19257        k: &cudarc::driver::CudaView<u8>,
19258        v: &cudarc::driver::CudaView<u8>,
19259        o: &mut CudaSlice<f32>,
19260        head_dim: usize,
19261        n_head: usize,
19262        n_head_kv: usize,
19263        t_kv_dev: &CudaSlice<i32>,
19264        bucket_max: usize,
19265        scale: f32,
19266        k_tok_bytes: usize,
19267        v_tok_bytes: usize,
19268        g: bool,
19269    ) -> Result<(), Box<dyn std::error::Error>> {
19270        self.fa_decode_dc_q8(
19271            q,
19272            k,
19273            v,
19274            o,
19275            head_dim,
19276            n_head,
19277            n_head_kv,
19278            t_kv_dev,
19279            bucket_max,
19280            scale,
19281            k_tok_bytes,
19282            v_tok_bytes,
19283            g,
19284            None,
19285        )
19286    }
19287
19288    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
19289    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
19290    #[allow(clippy::too_many_arguments)]
19291    pub fn fa_decode_dc_q8(
19292        &self,
19293        q: &CudaSlice<f32>,
19294        k: &cudarc::driver::CudaView<u8>,
19295        v: &cudarc::driver::CudaView<u8>,
19296        o: &mut CudaSlice<f32>,
19297        head_dim: usize,
19298        n_head: usize,
19299        n_head_kv: usize,
19300        t_kv_dev: &CudaSlice<i32>,
19301        bucket_max: usize,
19302        scale: f32,
19303        k_tok_bytes: usize,
19304        v_tok_bytes: usize,
19305        g: bool,
19306        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
19307    ) -> Result<(), Box<dyn std::error::Error>> {
19308        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
19309        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
19310        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
19311        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
19312        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
19313        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
19314        // 2026-07-12).
19315        let mut fa_vec =
19316            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
19317        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
19318            fa_vec = false;
19319        } // mirror kvmod/geom
19320        let sp = fa_split_keys(bucket_max, n_head_kv);
19321        let n_splits = if fa_vec {
19322            ((bucket_max + sp - 1) / sp).max(1)
19323        } else {
19324            ((bucket_max + 255) / 256).max(1)
19325        };
19326        let o_len = n_head * n_splits * head_dim;
19327        let ml_len = n_head * n_splits;
19328        let mut part_guard = self.fa_part_pool.lock().unwrap();
19329        if part_guard
19330            .as_ref()
19331            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19332            .unwrap_or(true)
19333        {
19334            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19335            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19336            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19337            // later live allocations land at those addresses, and the next graph REPLAY writes
19338            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19339            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19340            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19341            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19342            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19343            // (total retired < final size).
19344            let old = part_guard.take();
19345            let (co, cm) = old
19346                .as_ref()
19347                .map(|pp| (pp.0.len(), pp.1.len()))
19348                .unwrap_or((0, 0));
19349            if let Some(old) = old {
19350                self.fa_part_retired.lock().unwrap().push(old);
19351            }
19352            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19353                eprintln!(
19354                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19355                    co, o_len, cm, ml_len
19356                );
19357            }
19358            *part_guard = Some((
19359                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19360                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19361                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19362            ));
19363        }
19364        let pg = part_guard.as_mut().unwrap();
19365        self.gpu
19366            .stream()
19367            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19368        self.gpu
19369            .stream()
19370            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19371        self.gpu
19372            .stream()
19373            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19374        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19375        let (hd, nh, nhkv, nsp) = (
19376            head_dim as i32,
19377            n_head as i32,
19378            n_head_kv as i32,
19379            n_splits as i32,
19380        );
19381        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19382        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
19383        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
19384        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
19385        let deep = fa_vec
19386            && head_dim == 256
19387            && fa_v4_at(bucket_max)
19388            && !g
19389            && fa_deep_at(bucket_max)
19390            && !matches!(fa_v4_mode(), "noB3" | "stage");
19391        let (f, cfg) = if fa_vec
19392            && head_dim == 512
19393            && bucket_max >= {
19394                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19395                *FA512_MIN_DC.get_or_init(|| {
19396                    std::env::var("MEMRA_FA512_MIN")
19397                        .ok()
19398                        .and_then(|v| v.parse().ok())
19399                        .unwrap_or(512)
19400                })
19401            } {
19402            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
19403            let gqa = (n_head / n_head_kv).max(1) as u32;
19404            (
19405                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
19406                LaunchConfig {
19407                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19408                    block_dim: (32, gqa, 1),
19409                    shared_mem_bytes: 0,
19410                },
19411            )
19412        } else if fa_vec && head_dim == 512 {
19413            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
19414            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
19415            let q_view = q.as_view();
19416            let mut o_view = o.as_view_mut();
19417            return self.fa_decode_scalar_unified(
19418                &q_view,
19419                k,
19420                v,
19421                &mut o_view,
19422                head_dim,
19423                n_head,
19424                n_head_kv,
19425                0,
19426                Some(t_kv_dev),
19427                scale,
19428                n_splits,
19429                sp,
19430                k_tok_bytes,
19431                v_tok_bytes,
19432                g,
19433                &mut *part_o,
19434                &mut *part_m,
19435                &mut *part_l,
19436                q8_out,
19437            );
19438        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
19439            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
19440            // incl the g-module route + raw-e4m3 sV sizing.
19441            let gqa = (n_head / n_head_kv).max(1) as u32;
19442            let fv = if g {
19443                self.func_g("fa_decode_vec_q_v4_dc")
19444            } else if deep {
19445                self.func("fa_decode_vec_q_v4_deep_dc")
19446            } else {
19447                self.func("fa_decode_vec_q_v4_dc")
19448            };
19449            let shmem =
19450                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
19451            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19452            fv.set_attribute(
19453                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19454                shmem as i32,
19455            )?;
19456            (
19457                fv,
19458                LaunchConfig {
19459                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19460                    block_dim: (32, gqa, 1),
19461                    shared_mem_bytes: shmem,
19462                },
19463            )
19464        } else if fa_vec && fa_v3_active(head_dim) {
19465            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
19466            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
19467            let gqa = (n_head / n_head_kv).max(1) as u32;
19468            let fv = if g {
19469                self.func_g("fa_decode_vec_q_v3_dc")
19470            } else {
19471                self.func("fa_decode_vec_q_v3_dc")
19472            };
19473            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
19474            (
19475                fv,
19476                LaunchConfig {
19477                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19478                    block_dim: (32, gqa, 1),
19479                    shared_mem_bytes: shmem,
19480                },
19481            )
19482        } else if fa_vec && fa_v2_on() {
19483            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
19484            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
19485            // a numeric config; eager, rows-verify and graph all switch together).
19486            let gqa = (n_head / n_head_kv).max(1) as u32;
19487            let fv = if g {
19488                self.func_g("fa_decode_vec_q_v2_dc")
19489            } else {
19490                self.func("fa_decode_vec_q_v2_dc")
19491            };
19492            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
19493            (
19494                fv,
19495                LaunchConfig {
19496                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19497                    block_dim: (32, gqa, 1),
19498                    shared_mem_bytes: shmem,
19499                },
19500            )
19501        } else if fa_vec {
19502            let gqa = (n_head / n_head_kv).max(1) as u32;
19503            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
19504            let fv = if g {
19505                self.func_g("fa_decode_vec_q_dc")
19506            } else {
19507                self.func("fa_decode_vec_q_dc")
19508            };
19509            (
19510                fv,
19511                LaunchConfig {
19512                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19513                    block_dim: (32, gqa, 1),
19514                    shared_mem_bytes: 0,
19515                },
19516            )
19517        } else {
19518            let q_view = q.as_view();
19519            let mut o_view = o.as_view_mut();
19520            return self.fa_decode_scalar_unified(
19521                &q_view,
19522                k,
19523                v,
19524                &mut o_view,
19525                head_dim,
19526                n_head,
19527                n_head_kv,
19528                0,
19529                Some(t_kv_dev),
19530                scale,
19531                n_splits,
19532                if fa_vec { sp } else { 256 },
19533                k_tok_bytes,
19534                v_tok_bytes,
19535                g,
19536                &mut *part_o,
19537                &mut *part_m,
19538                &mut *part_l,
19539                q8_out,
19540            );
19541        };
19542        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
19543        let __s_b = self.gpu.stream();
19544        let mut b = __s_b.launch_builder(&f);
19545        b.arg(q)
19546            .arg(k)
19547            .arg(v)
19548            .arg(&mut *part_o)
19549            .arg(&mut *part_m)
19550            .arg(&mut *part_l)
19551            .arg(&hd)
19552            .arg(&nh)
19553            .arg(&nhkv)
19554            .arg(t_kv_dev)
19555            .arg(&scale)
19556            .arg(&nsp)
19557            .arg(&ski)
19558            .arg(&ktb)
19559            .arg(&vtb);
19560        unsafe {
19561            b.launch(cfg)?;
19562        }
19563        let cfg2 = LaunchConfig {
19564            grid_dim: (n_head as u32, 1, 1),
19565            block_dim: (head_dim as u32, 1, 1),
19566            shared_mem_bytes: 0,
19567        };
19568        if let Some((oq, od)) = q8_out {
19569            let fc = if g {
19570                self.func_g("fa_decode_combine_q8_1")
19571            } else {
19572                self.fa_func("fa_decode_combine_q8_1", head_dim)
19573            };
19574            let __s_b2 = self.gpu.stream();
19575            let mut b2 = __s_b2.launch_builder(&fc);
19576            b2.arg(&*part_o)
19577                .arg(&*part_m)
19578                .arg(&*part_l)
19579                .arg(oq)
19580                .arg(od)
19581                .arg(&hd)
19582                .arg(&nh)
19583                .arg(&nsp);
19584            unsafe {
19585                b2.launch(cfg2)?;
19586            }
19587            return Ok(());
19588        }
19589        let fc = if g {
19590            self.func_g("fa_decode_combine_f32")
19591        } else {
19592            self.fa_func("fa_decode_combine_f32", head_dim)
19593        };
19594        let __s_b2 = self.gpu.stream();
19595        let mut b2 = __s_b2.launch_builder(&fc);
19596        b2.arg(&*part_o)
19597            .arg(&*part_m)
19598            .arg(&*part_l)
19599            .arg(o)
19600            .arg(&hd)
19601            .arg(&nh)
19602            .arg(&nsp);
19603        unsafe {
19604            b2.launch(cfg2)?;
19605        }
19606        Ok(())
19607    }
19608
19609    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
19610    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
19611    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
19612    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
19613    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
19614    pub fn fa_geom_eager(
19615        &self,
19616        t_kv: usize,
19617        head_dim: usize,
19618        n_head_kv: usize,
19619        g: bool,
19620    ) -> (bool, usize) {
19621        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
19622        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
19623        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
19624        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
19625        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
19626        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
19627        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
19628        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
19629        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
19630        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
19631        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
19632        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
19633        // family; everything else falls to the g-module scalar.
19634        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
19635        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
19636        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
19637        if g && head_dim == 256 && !fa_v4_at(t_kv) {
19638            fa_vec = false;
19639        }
19640        let sp = fa_split_keys(t_kv, n_head_kv);
19641        let n_splits = if fa_vec {
19642            ((t_kv + sp - 1) / sp).max(1)
19643        } else {
19644            ((t_kv + 255) / 256).max(1)
19645        };
19646        (fa_vec, n_splits)
19647    }
19648
19649    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
19650    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
19651    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
19652    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
19653    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
19654    pub fn fa_bucket_key(
19655        &self,
19656        t_kv: usize,
19657        head_dim: usize,
19658        n_head_kv: usize,
19659        g: bool,
19660    ) -> (bool, usize) {
19661        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
19662    }
19663
19664    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
19665    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
19666    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
19667    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
19668    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
19669    /// device data) — every per-step varying scalar must come from a device counter. Returns the
19670    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
19671    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
19672    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
19673    /// replays (transients returning to the pool get reused by unrelated work and corrupt
19674    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
19675    pub fn capture_graph_retained<F>(
19676        &self,
19677        step: F,
19678    ) -> Result<
19679        (
19680            cudarc::driver::CudaGraph,
19681            Vec<Box<dyn std::any::Any + Send>>,
19682        ),
19683        Box<dyn std::error::Error>,
19684    >
19685    where
19686        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19687    {
19688        use cudarc::driver::sys::CUgraphInstantiate_flags;
19689        self.capture_graph_retained_flags(
19690            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19691            step,
19692        )
19693    }
19694
19695    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
19696    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
19697    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
19698    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
19699    pub fn capture_graph_retained_flags<F>(
19700        &self,
19701        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
19702        mut step: F,
19703    ) -> Result<
19704        (
19705            cudarc::driver::CudaGraph,
19706            Vec<Box<dyn std::any::Any + Send>>,
19707        ),
19708        Box<dyn std::error::Error>,
19709    >
19710    where
19711        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19712    {
19713        use cudarc::driver::sys::CUstreamCaptureMode;
19714        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
19715        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
19716        // while the capture region is open become dead copy NODES replayed every launch
19717        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
19718        // warmup runs allocate the same transient sequence at the same pool addresses, so
19719        // retaining the warmup clones preserves the draft-graph fix without polluting the
19720        // captured graph.
19721        self.capture_keep.lock().unwrap().clear();
19722        let was_tracking = self.gpu.ctx.is_event_tracking();
19723        if was_tracking {
19724            unsafe {
19725                self.gpu.ctx.disable_event_tracking();
19726            }
19727        }
19728        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19729            self.capture_keep_on
19730                .store(true, std::sync::atomic::Ordering::Relaxed);
19731            let w = (|| {
19732                step(self)?;
19733                step(self)
19734            })();
19735            self.capture_keep_on
19736                .store(false, std::sync::atomic::Ordering::Relaxed);
19737            w?;
19738            self.gpu.stream().synchronize()?;
19739            self.gpu
19740                .stream()
19741                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19742            let r = step(self);
19743            let g = self.gpu.stream().end_capture(flags);
19744            r?;
19745            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19746            graph.upload()?;
19747            Ok(graph)
19748        };
19749        let result = run();
19750        self.capture_keep_on
19751            .store(false, std::sync::atomic::Ordering::Relaxed);
19752        if was_tracking {
19753            unsafe {
19754                self.gpu.ctx.enable_event_tracking();
19755            }
19756        }
19757        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
19758        Ok((result?, keeper))
19759    }
19760
19761    pub fn capture_graph<F>(
19762        &self,
19763        mut step: F,
19764    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
19765    where
19766        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19767    {
19768        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
19769        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
19770        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
19771        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
19772        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
19773        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
19774        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
19775        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
19776        let was_tracking = self.gpu.ctx.is_event_tracking();
19777        if was_tracking {
19778            unsafe {
19779                self.gpu.ctx.disable_event_tracking();
19780            }
19781        }
19782        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
19783        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
19784        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
19785        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
19786        // measure that scan's real cost on the generic path. Diagnostic door only; the
19787        // default stays AUTO_FREE until a measured A/B justifies moving it.
19788        let iflag = {
19789            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
19790            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
19791                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
19792                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
19793                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
19794                Ok("priority") => {
19795                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
19796                }
19797                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19798            })
19799        };
19800        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
19801        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
19802        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
19803        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
19804        // eager step executions and are node-count-invariant. Printing the split bounds the
19805        // refactor's ceiling instead of assuming it.
19806        let ct = {
19807            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19808            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
19809        };
19810        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
19811        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
19812        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
19813        // chased, and node-count-invariant, so no capture-body refactor could touch it.
19814        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
19815        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
19816        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
19817        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
19818        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
19819        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
19820        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
19821        // grow and never frees, resident counters/scratch, cache set in place), and the
19822        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
19823        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
19824        // settling and pool mapping. Arbitrated adversarially, not by taste:
19825        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
19826        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
19827        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
19828        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
19829        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
19830        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
19831        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
19832        let warmups = {
19833            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19834            *W.get_or_init(|| {
19835                std::env::var("MEMRA_GRAPH_WARMUPS")
19836                    .ok()
19837                    .and_then(|v| v.parse().ok())
19838                    .filter(|n| *n >= 1)
19839                    .unwrap_or(1)
19840            })
19841        };
19842        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19843            let t_w = std::time::Instant::now();
19844            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
19845            for _ in 0..warmups {
19846                step(self)?;
19847            }
19848            self.gpu.stream().synchronize()?;
19849            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
19850            // capture the third run.
19851            let t_c = std::time::Instant::now();
19852            self.gpu
19853                .stream()
19854                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19855            // If the body errors mid-capture, end the capture before propagating so the stream isn't
19856            // left in a capturing state.
19857            let r = step(self);
19858            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
19859            let t_i = std::time::Instant::now();
19860            let g = self.gpu.stream().end_capture(iflag);
19861            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
19862            r?;
19863            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19864            let t_u = std::time::Instant::now();
19865            graph.upload()?;
19866            if ct {
19867                println!(
19868                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
19869                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
19870                    t_u.elapsed().as_secs_f64() * 1e3
19871                );
19872            }
19873            Ok(graph)
19874        };
19875        let result = run();
19876        if was_tracking {
19877            unsafe {
19878                self.gpu.ctx.enable_event_tracking();
19879            }
19880        }
19881        result
19882    }
19883
19884    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
19885    pub fn gdn_scan_s128_view(
19886        &self,
19887        q: &CudaSlice<f32>,
19888        k: &CudaSlice<f32>,
19889        v: &CudaSlice<f32>,
19890        g: &CudaSlice<f32>,
19891        beta: &CudaSlice<f32>,
19892        state_in: &cudarc::driver::CudaView<f32>,
19893        state_out: &mut cudarc::driver::CudaViewMut<f32>,
19894        o: &mut CudaSlice<f32>,
19895        n_head: usize,
19896        t: usize,
19897        scale: f32,
19898    ) -> Result<(), Box<dyn std::error::Error>> {
19899        let f = self.func("gdn_scan_s128");
19900        const S_V: u32 = 128;
19901        const WARP: u32 = 32;
19902        const COLS: u32 = 4;
19903        let cfg = LaunchConfig {
19904            grid_dim: (n_head as u32, 1, S_V / COLS),
19905            block_dim: (WARP, COLS, 1),
19906            shared_mem_bytes: 0,
19907        };
19908        let (h, ti) = (n_head as i32, t as i32);
19909        let __s_b = self.gpu.stream();
19910        let mut b = __s_b.launch_builder(&f);
19911        b.arg(q)
19912            .arg(k)
19913            .arg(v)
19914            .arg(g)
19915            .arg(beta)
19916            .arg(state_in)
19917            .arg(state_out)
19918            .arg(o)
19919            .arg(&h)
19920            .arg(&ti)
19921            .arg(&scale);
19922        unsafe {
19923            b.launch(cfg)?;
19924        }
19925        Ok(())
19926    }
19927
19928    /// conv1d where the input is a CudaView (resident conv state assembled in place).
19929    pub fn ssm_conv1d_view(
19930        &self,
19931        x: &cudarc::driver::CudaView<f32>,
19932        w: &CudaSlice<f32>,
19933        y: &mut CudaSlice<f32>,
19934        conv_dim: usize,
19935        t: usize,
19936        d_conv: usize,
19937        silu: bool,
19938    ) -> Result<(), Box<dyn std::error::Error>> {
19939        let f = self.func("ssm_conv1d_silu_f32");
19940        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
19941        let cfg = LaunchConfig {
19942            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19943            block_dim: (256, 1, 1),
19944            shared_mem_bytes: 0,
19945        };
19946        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19947        let __s_b = self.gpu.stream();
19948        let mut b = __s_b.launch_builder(&f);
19949        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19950        unsafe {
19951            b.launch(cfg)?;
19952        }
19953        Ok(())
19954    }
19955
19956    /// Depthwise causal conv1d + optional SiLU.
19957    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
19958    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
19959    /// FUSED prefill conv (token-major input, zero left-state): replaces
19960    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
19961    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
19962    pub fn ssm_conv1d_tm(
19963        &self,
19964        qkv_tm: &CudaSlice<f32>,
19965        w: &CudaSlice<f32>,
19966        y: &mut CudaSlice<f32>,
19967        conv_dim: usize,
19968        t: usize,
19969        d_conv: usize,
19970    ) -> Result<(), Box<dyn std::error::Error>> {
19971        let f = self.func("ssm_conv1d_tm_f32");
19972        let cfg = LaunchConfig {
19973            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19974            block_dim: (256, 1, 1),
19975            shared_mem_bytes: 0,
19976        };
19977        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19978        let __s_b = self.gpu.stream();
19979        let mut b = __s_b.launch_builder(&f);
19980        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
19981        unsafe {
19982            b.launch(cfg)?;
19983        }
19984        Ok(())
19985    }
19986
19987    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
19988    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
19989    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
19990    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
19991    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
19992    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
19993    /// columns; the final ring == what T sequential decode ring rolls leave).
19994    pub fn ssm_conv1d_tm_state(
19995        &self,
19996        qkv_tm: &CudaSlice<f32>,
19997        conv_state: &mut CudaSlice<f32>,
19998        w: &CudaSlice<f32>,
19999        y: &mut CudaSlice<f32>,
20000        conv_dim: usize,
20001        t: usize,
20002        d_conv: usize,
20003    ) -> Result<(), Box<dyn std::error::Error>> {
20004        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
20005    }
20006
20007    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
20008    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
20009    #[allow(clippy::too_many_arguments)]
20010    pub fn ssm_conv1d_tm_state_pad(
20011        &self,
20012        qkv_tm: &CudaSlice<f32>,
20013        conv_state: &mut CudaSlice<f32>,
20014        w: &CudaSlice<f32>,
20015        y: &mut CudaSlice<f32>,
20016        conv_dim: usize,
20017        t: usize,
20018        d_conv: usize,
20019        pad_len: Option<&CudaSlice<i32>>,
20020    ) -> Result<(), Box<dyn std::error::Error>> {
20021        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
20022        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
20023        // the window kernel both read the pre-roll ring; the roll launches after both) — but
20024        // cloning first keeps the ordering trivially correct under any future stream split.
20025        let ring_old = if t < d_conv - 1 {
20026            Some(self.clone_dtod(conv_state)?)
20027        } else {
20028            None
20029        };
20030        {
20031            let f = self.func("ssm_conv1d_tm_state_f32");
20032            let cfg = LaunchConfig {
20033                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20034                block_dim: (256, 1, 1),
20035                shared_mem_bytes: 0,
20036            };
20037            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20038            let __s_b = self.gpu.stream();
20039            let mut b = __s_b.launch_builder(&f);
20040            b.arg(qkv_tm)
20041                .arg(&*conv_state)
20042                .arg(w)
20043                .arg(y)
20044                .arg(&cd)
20045                .arg(&ti)
20046                .arg(&dc);
20047            unsafe {
20048                b.launch(cfg)?;
20049            }
20050        }
20051        match (ring_old, pad_len) {
20052            (None, Some(len_d)) => {
20053                let f = self.func("ssm_conv_ring_update_dev_f32");
20054                let n = conv_dim * (d_conv - 1);
20055                let cfg = LaunchConfig::for_num_elems(n as u32);
20056                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20057                let __s_b = self.gpu.stream();
20058                let mut b = __s_b.launch_builder(&f);
20059                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20060                unsafe {
20061                    b.launch(cfg)?;
20062                }
20063            }
20064            (None, None) => {
20065                let f = self.func("ssm_conv_ring_update_f32");
20066                let n = conv_dim * (d_conv - 1);
20067                let cfg = LaunchConfig::for_num_elems(n as u32);
20068                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20069                let __s_b = self.gpu.stream();
20070                let mut b = __s_b.launch_builder(&f);
20071                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20072                unsafe {
20073                    b.launch(cfg)?;
20074                }
20075            }
20076            (Some(old), _) => {
20077                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
20078            }
20079        }
20080        Ok(())
20081    }
20082
20083    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
20084    pub fn ssm_conv1d_tm_state_pad_v(
20085        &self,
20086        qkv_tm: &cudarc::driver::CudaView<f32>,
20087        conv_state: &mut CudaSlice<f32>,
20088        w: &CudaSlice<f32>,
20089        y: &mut CudaSlice<f32>,
20090        conv_dim: usize,
20091        t: usize,
20092        d_conv: usize,
20093        pad_len: Option<&CudaSlice<i32>>,
20094    ) -> Result<(), Box<dyn std::error::Error>> {
20095        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
20096        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
20097        // the window kernel both read the pre-roll ring; the roll launches after both) — but
20098        // cloning first keeps the ordering trivially correct under any future stream split.
20099        let ring_old = if t < d_conv - 1 {
20100            Some(self.clone_dtod(conv_state)?)
20101        } else {
20102            None
20103        };
20104        {
20105            let f = self.func("ssm_conv1d_tm_state_f32");
20106            let cfg = LaunchConfig {
20107                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20108                block_dim: (256, 1, 1),
20109                shared_mem_bytes: 0,
20110            };
20111            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20112            let __s_b = self.gpu.stream();
20113            let mut b = __s_b.launch_builder(&f);
20114            b.arg(qkv_tm)
20115                .arg(&*conv_state)
20116                .arg(w)
20117                .arg(y)
20118                .arg(&cd)
20119                .arg(&ti)
20120                .arg(&dc);
20121            unsafe {
20122                b.launch(cfg)?;
20123            }
20124        }
20125        match (ring_old, pad_len) {
20126            (None, Some(len_d)) => {
20127                let f = self.func("ssm_conv_ring_update_dev_f32");
20128                let n = conv_dim * (d_conv - 1);
20129                let cfg = LaunchConfig::for_num_elems(n as u32);
20130                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20131                let __s_b = self.gpu.stream();
20132                let mut b = __s_b.launch_builder(&f);
20133                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20134                unsafe {
20135                    b.launch(cfg)?;
20136                }
20137            }
20138            (None, None) => {
20139                let f = self.func("ssm_conv_ring_update_f32");
20140                let n = conv_dim * (d_conv - 1);
20141                let cfg = LaunchConfig::for_num_elems(n as u32);
20142                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20143                let __s_b = self.gpu.stream();
20144                let mut b = __s_b.launch_builder(&f);
20145                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20146                unsafe {
20147                    b.launch(cfg)?;
20148                }
20149            }
20150            (Some(_), _) => unreachable!(
20151                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
20152            ),
20153        }
20154        Ok(())
20155    }
20156
20157    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
20158    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
20159    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
20160    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
20161    pub fn ssm_conv_ring_rebuild(
20162        &self,
20163        qkv_tm: &CudaSlice<f32>,
20164        ring_old: &CudaSlice<f32>,
20165        conv_state: &mut CudaSlice<f32>,
20166        conv_dim: usize,
20167        tc: usize,
20168        d_conv: usize,
20169    ) -> Result<(), Box<dyn std::error::Error>> {
20170        let f = self.func("ssm_conv_ring_rebuild_f32");
20171        let n = conv_dim * (d_conv - 1);
20172        let cfg = LaunchConfig::for_num_elems(n as u32);
20173        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
20174        let __s_b = self.gpu.stream();
20175        let mut b = __s_b.launch_builder(&f);
20176        b.arg(qkv_tm)
20177            .arg(ring_old)
20178            .arg(conv_state)
20179            .arg(&cd)
20180            .arg(&ti)
20181            .arg(&dc);
20182        unsafe {
20183            b.launch(cfg)?;
20184        }
20185        Ok(())
20186    }
20187
20188    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
20189    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
20190    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
20191    /// the argmax + run-spec gates are the authority.
20192    #[allow(clippy::too_many_arguments)]
20193    pub fn gdn_prep_decode(
20194        &self,
20195        conv_out: &CudaSlice<f32>,
20196        beta_raw: &CudaSlice<f32>,
20197        alpha: &CudaSlice<f32>,
20198        dt_bias: &CudaSlice<f32>,
20199        a: &CudaSlice<f32>,
20200        q_l2: &mut CudaSlice<f32>,
20201        k_l2: &mut CudaSlice<f32>,
20202        v_g: &mut CudaSlice<f32>,
20203        beta: &mut CudaSlice<f32>,
20204        g_log: &mut CudaSlice<f32>,
20205        d_state: usize,
20206        num_v: usize,
20207        num_k: usize,
20208        key_dim: usize,
20209        eps: f32,
20210    ) -> Result<(), Box<dyn std::error::Error>> {
20211        let f = self.func("gdn_prep_decode_f32");
20212        let cfg = LaunchConfig {
20213            grid_dim: (num_v as u32, 1, 1),
20214            block_dim: (32, 4, 1),
20215            shared_mem_bytes: 0,
20216        };
20217        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20218        let __s_b = self.gpu.stream();
20219        let mut b = __s_b.launch_builder(&f);
20220        b.arg(conv_out)
20221            .arg(beta_raw)
20222            .arg(alpha)
20223            .arg(dt_bias)
20224            .arg(a)
20225            .arg(q_l2)
20226            .arg(k_l2)
20227            .arg(v_g)
20228            .arg(beta)
20229            .arg(g_log)
20230            .arg(&ds)
20231            .arg(&nv)
20232            .arg(&nk)
20233            .arg(&kd)
20234            .arg(&eps);
20235        unsafe {
20236            b.launch(cfg)?;
20237        }
20238        Ok(())
20239    }
20240
20241    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
20242    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
20243    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
20244    #[allow(clippy::too_many_arguments)]
20245    pub fn ssm_conv1d_gdn(
20246        &self,
20247        qkv_tm: &CudaSlice<f32>,
20248        w: &CudaSlice<f32>,
20249        q_g: &mut CudaSlice<f32>,
20250        k_g: &mut CudaSlice<f32>,
20251        v_g: &mut CudaSlice<f32>,
20252        conv_dim: usize,
20253        t: usize,
20254        d_conv: usize,
20255        d_state: usize,
20256        num_v: usize,
20257        num_k: usize,
20258        key_dim: usize,
20259    ) -> Result<(), Box<dyn std::error::Error>> {
20260        let f = self.func("ssm_conv1d_gdn_f32");
20261        let cfg = LaunchConfig {
20262            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20263            block_dim: (256, 1, 1),
20264            shared_mem_bytes: 0,
20265        };
20266        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20267        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20268        let __s_b = self.gpu.stream();
20269        let mut b = __s_b.launch_builder(&f);
20270        b.arg(qkv_tm)
20271            .arg(w)
20272            .arg(q_g)
20273            .arg(k_g)
20274            .arg(v_g)
20275            .arg(&cd)
20276            .arg(&ti)
20277            .arg(&dc)
20278            .arg(&ds)
20279            .arg(&nv)
20280            .arg(&nk)
20281            .arg(&kd);
20282        unsafe {
20283            b.launch(cfg)?;
20284        }
20285        Ok(())
20286    }
20287
20288    pub fn ssm_conv1d(
20289        &self,
20290        x: &CudaSlice<f32>,
20291        w: &CudaSlice<f32>,
20292        y: &mut CudaSlice<f32>,
20293        conv_dim: usize,
20294        t: usize,
20295        d_conv: usize,
20296        silu: bool,
20297    ) -> Result<(), Box<dyn std::error::Error>> {
20298        let f = self.func("ssm_conv1d_silu_f32");
20299        let cfg = LaunchConfig {
20300            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
20301            block_dim: (256, 1, 1),
20302            shared_mem_bytes: 0,
20303        };
20304        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
20305        let __s_b = self.gpu.stream();
20306        let mut b = __s_b.launch_builder(&f);
20307        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
20308        unsafe {
20309            b.launch(cfg)?;
20310        }
20311        Ok(())
20312    }
20313
20314    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
20315    /// o:[128,H,T]. Single sequence.
20316    pub fn gdn_scan_s128(
20317        &self,
20318        q: &CudaSlice<f32>,
20319        k: &CudaSlice<f32>,
20320        v: &CudaSlice<f32>,
20321        g: &CudaSlice<f32>,
20322        beta: &CudaSlice<f32>,
20323        state_in: &CudaSlice<f32>,
20324        state_out: &mut CudaSlice<f32>,
20325        o: &mut CudaSlice<f32>,
20326        n_head: usize,
20327        t: usize,
20328        scale: f32,
20329    ) -> Result<(), Box<dyn std::error::Error>> {
20330        let f = self.func("gdn_scan_s128");
20331        const S_V: u32 = 128;
20332        const WARP: u32 = 32;
20333        const COLS_PER_BLOCK: u32 = 4;
20334        let cfg = LaunchConfig {
20335            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
20336            block_dim: (WARP, COLS_PER_BLOCK, 1),
20337            shared_mem_bytes: 0,
20338        };
20339        let (h, ti) = (n_head as i32, t as i32);
20340        let __s_b = self.gpu.stream();
20341        let mut b = __s_b.launch_builder(&f);
20342        b.arg(q)
20343            .arg(k)
20344            .arg(v)
20345            .arg(g)
20346            .arg(beta)
20347            .arg(state_in)
20348            .arg(state_out)
20349            .arg(o)
20350            .arg(&h)
20351            .arg(&ti)
20352            .arg(&scale);
20353        unsafe {
20354            b.launch(cfg)?;
20355        }
20356        Ok(())
20357    }
20358
20359    // ==== B2' batched decode state ops (decode_batch.rs) ====
20360    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
20361    // Bodies are the single-seq kernels per sequence — bit-identical per row.
20362
20363    #[allow(clippy::too_many_arguments)]
20364    pub fn ssm_conv1d_fused_decode_b(
20365        &self,
20366        qkv_cols: &CudaSlice<f32>,
20367        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20368        w: &CudaSlice<f32>,
20369        conv_outs: &mut CudaSlice<f32>,
20370        conv_dim: usize,
20371        d_conv: usize,
20372        b_n: usize,
20373    ) -> Result<(), Box<dyn std::error::Error>> {
20374        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20375        let cfg = LaunchConfig {
20376            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20377            block_dim: (256, 1, 1),
20378            shared_mem_bytes: 0,
20379        };
20380        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20381        let __s_b = self.gpu.stream();
20382        let mut b = __s_b.launch_builder(&f);
20383        b.arg(qkv_cols)
20384            .arg(conv_state_ptrs)
20385            .arg(w)
20386            .arg(conv_outs)
20387            .arg(&cd)
20388            .arg(&dc);
20389        unsafe {
20390            b.launch(cfg)?;
20391        }
20392        Ok(())
20393    }
20394
20395    #[allow(clippy::too_many_arguments)]
20396    pub fn gdn_prep_decode_b(
20397        &self,
20398        conv_outs: &CudaSlice<f32>,
20399        beta_raws: &CudaSlice<f32>,
20400        alphas: &CudaSlice<f32>,
20401        dt_bias: &CudaSlice<f32>,
20402        a: &CudaSlice<f32>,
20403        q_l2: &mut CudaSlice<f32>,
20404        k_l2: &mut CudaSlice<f32>,
20405        v_g: &mut CudaSlice<f32>,
20406        beta: &mut CudaSlice<f32>,
20407        g_log: &mut CudaSlice<f32>,
20408        d_state: usize,
20409        num_v: usize,
20410        num_k: usize,
20411        key_dim: usize,
20412        eps: f32,
20413        conv_dim: usize,
20414        b_n: usize,
20415    ) -> Result<(), Box<dyn std::error::Error>> {
20416        let f = self.func("gdn_prep_decode_b_f32");
20417        let cfg = LaunchConfig {
20418            grid_dim: (num_v as u32, 1, b_n as u32),
20419            block_dim: (32, 4, 1),
20420            shared_mem_bytes: 0,
20421        };
20422        let (ds, nv, nk, kd, cd) = (
20423            d_state as i32,
20424            num_v as i32,
20425            num_k as i32,
20426            key_dim as i32,
20427            conv_dim as i32,
20428        );
20429        let __s_b = self.gpu.stream();
20430        let mut b = __s_b.launch_builder(&f);
20431        b.arg(conv_outs)
20432            .arg(beta_raws)
20433            .arg(alphas)
20434            .arg(dt_bias)
20435            .arg(a)
20436            .arg(q_l2)
20437            .arg(k_l2)
20438            .arg(v_g)
20439            .arg(beta)
20440            .arg(g_log)
20441            .arg(&ds)
20442            .arg(&nv)
20443            .arg(&nk)
20444            .arg(&kd)
20445            .arg(&eps)
20446            .arg(&cd);
20447        unsafe {
20448            b.launch(cfg)?;
20449        }
20450        Ok(())
20451    }
20452
20453    #[allow(clippy::too_many_arguments)]
20454    pub fn gdn_scan_s128_batched(
20455        &self,
20456        q: &CudaSlice<f32>,
20457        k: &CudaSlice<f32>,
20458        v: &CudaSlice<f32>,
20459        g: &CudaSlice<f32>,
20460        beta: &CudaSlice<f32>,
20461        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20462        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20463        o: &mut CudaSlice<f32>,
20464        n_head: usize,
20465        b_n: usize,
20466        scale: f32,
20467    ) -> Result<(), Box<dyn std::error::Error>> {
20468        let f = self.func("gdn_scan_s128_b");
20469        const S_V: u32 = 128;
20470        const WARP: u32 = 32;
20471        const COLS_PER_BLOCK: u32 = 4;
20472        let cfg = LaunchConfig {
20473            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20474            block_dim: (WARP, COLS_PER_BLOCK, 1),
20475            shared_mem_bytes: 0,
20476        };
20477        let h = n_head as i32;
20478        let __s_b = self.gpu.stream();
20479        let mut b = __s_b.launch_builder(&f);
20480        b.arg(q)
20481            .arg(k)
20482            .arg(v)
20483            .arg(g)
20484            .arg(beta)
20485            .arg(state_in_ptrs)
20486            .arg(state_out_ptrs)
20487            .arg(o)
20488            .arg(&h)
20489            .arg(&scale);
20490        unsafe {
20491            b.launch(cfg)?;
20492        }
20493        Ok(())
20494    }
20495
20496    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
20497    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
20498    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
20499    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
20500    /// numeric class; only the pointer arithmetic moved host-side.
20501    #[allow(clippy::too_many_arguments)]
20502    pub fn ssm_conv1d_fused_decode_b_view(
20503        &self,
20504        qkv_cols: &cudarc::driver::CudaView<f32>,
20505        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20506        w: &CudaSlice<f32>,
20507        conv_outs: &mut CudaSlice<f32>,
20508        conv_dim: usize,
20509        d_conv: usize,
20510        b_n: usize,
20511    ) -> Result<(), Box<dyn std::error::Error>> {
20512        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20513        let cfg = LaunchConfig {
20514            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20515            block_dim: (256, 1, 1),
20516            shared_mem_bytes: 0,
20517        };
20518        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20519        let __s_b = self.gpu.stream();
20520        let mut b = __s_b.launch_builder(&f);
20521        b.arg(qkv_cols)
20522            .arg(conv_state_ptrs)
20523            .arg(w)
20524            .arg(conv_outs)
20525            .arg(&cd)
20526            .arg(&dc);
20527        unsafe {
20528            b.launch(cfg)?;
20529        }
20530        Ok(())
20531    }
20532
20533    #[allow(clippy::too_many_arguments)]
20534    pub fn gdn_prep_decode_b_view(
20535        &self,
20536        conv_outs: &CudaSlice<f32>,
20537        beta_raws: &cudarc::driver::CudaView<f32>,
20538        alphas: &cudarc::driver::CudaView<f32>,
20539        dt_bias: &CudaSlice<f32>,
20540        a: &CudaSlice<f32>,
20541        q_l2: &mut CudaSlice<f32>,
20542        k_l2: &mut CudaSlice<f32>,
20543        v_g: &mut CudaSlice<f32>,
20544        beta: &mut CudaSlice<f32>,
20545        g_log: &mut CudaSlice<f32>,
20546        d_state: usize,
20547        num_v: usize,
20548        num_k: usize,
20549        key_dim: usize,
20550        eps: f32,
20551        conv_dim: usize,
20552        b_n: usize,
20553    ) -> Result<(), Box<dyn std::error::Error>> {
20554        let f = self.func("gdn_prep_decode_b_f32");
20555        let cfg = LaunchConfig {
20556            grid_dim: (num_v as u32, 1, b_n as u32),
20557            block_dim: (32, 4, 1),
20558            shared_mem_bytes: 0,
20559        };
20560        let (ds, nv, nk, kd, cd) = (
20561            d_state as i32,
20562            num_v as i32,
20563            num_k as i32,
20564            key_dim as i32,
20565            conv_dim as i32,
20566        );
20567        let __s_b = self.gpu.stream();
20568        let mut b = __s_b.launch_builder(&f);
20569        b.arg(conv_outs)
20570            .arg(beta_raws)
20571            .arg(alphas)
20572            .arg(dt_bias)
20573            .arg(a)
20574            .arg(q_l2)
20575            .arg(k_l2)
20576            .arg(v_g)
20577            .arg(beta)
20578            .arg(g_log)
20579            .arg(&ds)
20580            .arg(&nv)
20581            .arg(&nk)
20582            .arg(&kd)
20583            .arg(&eps)
20584            .arg(&cd);
20585        unsafe {
20586            b.launch(cfg)?;
20587        }
20588        Ok(())
20589    }
20590
20591    #[allow(clippy::too_many_arguments)]
20592    pub fn gdn_scan_s128_batched_view(
20593        &self,
20594        q: &CudaSlice<f32>,
20595        k: &CudaSlice<f32>,
20596        v: &CudaSlice<f32>,
20597        g: &CudaSlice<f32>,
20598        beta: &CudaSlice<f32>,
20599        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20600        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20601        o: &mut cudarc::driver::CudaViewMut<f32>,
20602        n_head: usize,
20603        b_n: usize,
20604        scale: f32,
20605    ) -> Result<(), Box<dyn std::error::Error>> {
20606        let f = self.func("gdn_scan_s128_b");
20607        const S_V: u32 = 128;
20608        const WARP: u32 = 32;
20609        const COLS_PER_BLOCK: u32 = 4;
20610        let cfg = LaunchConfig {
20611            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20612            block_dim: (WARP, COLS_PER_BLOCK, 1),
20613            shared_mem_bytes: 0,
20614        };
20615        let h = n_head as i32;
20616        let __s_b = self.gpu.stream();
20617        let mut b = __s_b.launch_builder(&f);
20618        b.arg(q)
20619            .arg(k)
20620            .arg(v)
20621            .arg(g)
20622            .arg(beta)
20623            .arg(state_in_ptrs)
20624            .arg(state_out_ptrs)
20625            .arg(o)
20626            .arg(&h)
20627            .arg(&scale);
20628        unsafe {
20629            b.launch(cfg)?;
20630        }
20631        Ok(())
20632    }
20633
20634    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
20635    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
20636    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
20637    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
20638    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
20639    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
20640    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
20641    /// identity law); prime_cache/forward/forward_last are the only callers.
20642    pub fn gdn_chunked_enabled() -> bool {
20643        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
20644        *E.get_or_init(|| {
20645            std::env::var("MEMRA_GDN_CHUNKED")
20646                .map(|v| v != "0")
20647                .unwrap_or(true)
20648        })
20649    }
20650
20651    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
20652    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
20653    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
20654    /// of 32 in [32, 128] (kernel row mappings require it).
20655    pub fn gdn_chunk_size() -> usize {
20656        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
20657        *C.get_or_init(|| {
20658            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
20659                .ok()
20660                .and_then(|v| v.parse().ok())
20661                .unwrap_or(32);
20662            c.clamp(32, 128) / 32 * 32
20663        })
20664    }
20665
20666    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
20667    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
20668    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
20669    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
20670    #[allow(clippy::too_many_arguments)]
20671    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
20672    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
20673    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
20674    #[allow(clippy::too_many_arguments)]
20675    pub fn gdn_chunk_k123(
20676        &self,
20677        q: &CudaSlice<f32>,
20678        k: &CudaSlice<f32>,
20679        v: &CudaSlice<f32>,
20680        g: &CudaSlice<f32>,
20681        beta: &CudaSlice<f32>,
20682        wb16: Option<&mut CudaSlice<u8>>,
20683        n_head: usize,
20684        t: usize,
20685        c: usize,
20686        hk: usize,
20687        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
20688    ) -> Result<
20689        (
20690            CudaSlice<f32>,
20691            CudaSlice<f32>,
20692            CudaSlice<f32>,
20693            CudaSlice<f32>,
20694        ),
20695        Box<dyn std::error::Error>,
20696    > {
20697        const D: usize = 128;
20698        let h = n_head;
20699        let nc = (t + c - 1) / c;
20700        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20701        let mut gcum = self.uninit(t * h)?;
20702        let mut a = self.uninit(nc * h * c * c)?;
20703        let mut p = self.uninit(nc * h * c * c)?;
20704        let mut u = self.uninit(nc * h * c * D)?;
20705        let mut w = self.uninit(nc * h * c * D)?;
20706        {
20707            // K1
20708            let f = self.func("gdn_chunk_cumgate_f32");
20709            let cfg = LaunchConfig {
20710                grid_dim: (nc as u32, h as u32, 1),
20711                block_dim: (32, 1, 1),
20712                shared_mem_bytes: 0,
20713            };
20714            let __s_b = self.gpu.stream();
20715            let mut b = __s_b.launch_builder(&f);
20716            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
20717            unsafe {
20718                b.launch(cfg)?;
20719            }
20720        }
20721        if let Some((qb, kb, pb)) = k2w {
20722            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
20723            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
20724            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
20725            let f = self.func("gdn_k2_wgmma");
20726            let cfg = LaunchConfig {
20727                grid_dim: (nc as u32, h as u32, 1),
20728                block_dim: (128, 1, 1),
20729                shared_mem_bytes: 0,
20730            };
20731            let hki = hk as i32;
20732            let __s_b = self.gpu.stream();
20733            let mut b = __s_b.launch_builder(&f);
20734            b.arg(qb)
20735                .arg(kb)
20736                .arg(&gcum)
20737                .arg(beta)
20738                .arg(&mut a)
20739                .arg(&mut *pb)
20740                .arg(&hi)
20741                .arg(&ti)
20742                .arg(&ci)
20743                .arg(&hki);
20744            unsafe {
20745                b.launch(cfg)?;
20746            }
20747        } else if c <= 64 && !portable_mma_gated() {
20748            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
20749            let f = self.func("gdn_chunk_attn_f32");
20750            let jt = ((c + 31) / 32) as u32;
20751            let cfg = LaunchConfig {
20752                grid_dim: (nc as u32, h as u32, jt),
20753                block_dim: (256, 1, 1),
20754                shared_mem_bytes: 0,
20755            };
20756            let hki = hk as i32;
20757            let __s_b = self.gpu.stream();
20758            let mut b = __s_b.launch_builder(&f);
20759            b.arg(q)
20760                .arg(k)
20761                .arg(&gcum)
20762                .arg(beta)
20763                .arg(&mut a)
20764                .arg(&mut p)
20765                .arg(&hi)
20766                .arg(&ti)
20767                .arg(&ci)
20768                .arg(&hki);
20769            unsafe {
20770                b.launch(cfg)?;
20771            }
20772        } else {
20773            // K2 generic (C = 128, or the portable target's low-smem fallback)
20774            assert!(
20775                hk == h,
20776                "generic K2 is broadcast-only (de-broadcast rides C==32)"
20777            );
20778            let f = self.func("gdn_chunk_attn_g_f32");
20779            let cfg = LaunchConfig {
20780                grid_dim: (nc as u32, h as u32, 1),
20781                block_dim: (32, 8, 1),
20782                shared_mem_bytes: 0,
20783            };
20784            let __s_b = self.gpu.stream();
20785            let mut b = __s_b.launch_builder(&f);
20786            b.arg(q)
20787                .arg(k)
20788                .arg(&gcum)
20789                .arg(beta)
20790                .arg(&mut a)
20791                .arg(&mut p)
20792                .arg(&hi)
20793                .arg(&ti)
20794                .arg(&ci);
20795            unsafe {
20796                b.launch(cfg)?;
20797            }
20798        }
20799        {
20800            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
20801            let cfg = LaunchConfig {
20802                grid_dim: (nc as u32, h as u32, 1),
20803                block_dim: (256, 1, 1),
20804                shared_mem_bytes: 0,
20805            };
20806            match c {
20807                32 | 64 => {
20808                    let f = self.func(if c == 32 {
20809                        "gdn_chunk_solve32_f32"
20810                    } else {
20811                        "gdn_chunk_solve64_f32"
20812                    });
20813                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
20814                    let wb: u64 = match wb16 {
20815                        Some(d) => self.addr_u8(d),
20816                        None => 0,
20817                    };
20818                    let hki = hk as i32;
20819                    let __s_b = self.gpu.stream();
20820                    let mut b = __s_b.launch_builder(&f);
20821                    b.arg(v)
20822                        .arg(k)
20823                        .arg(&a)
20824                        .arg(&gcum)
20825                        .arg(&mut u)
20826                        .arg(&mut w)
20827                        .arg(&wb)
20828                        .arg(&hi)
20829                        .arg(&ti)
20830                        .arg(&hki);
20831                    unsafe {
20832                        b.launch(cfg)?;
20833                    }
20834                }
20835                _ => {
20836                    assert!(hk == h, "generic K3 is broadcast-only");
20837                    let f = self.func("gdn_chunk_solve_f32");
20838                    let __s_b = self.gpu.stream();
20839                    let mut b = __s_b.launch_builder(&f);
20840                    b.arg(v)
20841                        .arg(k)
20842                        .arg(&a)
20843                        .arg(&gcum)
20844                        .arg(&mut u)
20845                        .arg(&mut w)
20846                        .arg(&hi)
20847                        .arg(&ti)
20848                        .arg(&ci);
20849                    unsafe {
20850                        b.launch(cfg)?;
20851                    }
20852                }
20853            }
20854        }
20855        Ok((gcum, p, u, w))
20856    }
20857
20858    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
20859    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
20860    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
20861    pub fn gdn_db_on() -> bool {
20862        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
20863    }
20864
20865    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
20866    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
20867    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
20868        !portable_mma_gated()
20869            && c == 32
20870            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20871                Ok("1") => true,
20872                Ok("0") => false,
20873                _ => cfg!(memra_hopper_mma),
20874            }
20875    }
20876
20877    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
20878    /// mma config; same per-call env read discipline).
20879    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
20880        self.gdn_mma_enabled(c)
20881            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20882                Ok("0") => false,
20883                Ok("1") => true,
20884                _ => cfg!(memra_hopper_mma),
20885            }
20886    }
20887
20888    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
20889    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
20890    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
20891    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
20892    #[allow(clippy::too_many_arguments)]
20893    pub fn ssm_conv1d_gdn_state_pad(
20894        &self,
20895        qkv_tm: &cudarc::driver::CudaView<f32>,
20896        conv_state: &mut CudaSlice<f32>,
20897        w: &CudaSlice<f32>,
20898        q_g: &mut CudaSlice<f32>,
20899        k_g: &mut CudaSlice<f32>,
20900        v_g: &mut CudaSlice<f32>,
20901        conv_dim: usize,
20902        t: usize,
20903        d_conv: usize,
20904        d_state: usize,
20905        num_v: usize,
20906        num_k: usize,
20907        key_dim: usize,
20908        hk: usize,
20909        pad_len: Option<&CudaSlice<i32>>,
20910    ) -> Result<(), Box<dyn std::error::Error>> {
20911        assert!(
20912            t >= d_conv - 1,
20913            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
20914        );
20915        {
20916            let f = self.func("ssm_conv1d_gdn_state_f32");
20917            let cfg = LaunchConfig {
20918                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20919                block_dim: (256, 1, 1),
20920                shared_mem_bytes: 0,
20921            };
20922            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20923            let (ds, nv, nk, kd, hki) = (
20924                d_state as i32,
20925                num_v as i32,
20926                num_k as i32,
20927                key_dim as i32,
20928                hk as i32,
20929            );
20930            let __s_b = self.gpu.stream();
20931            let mut b = __s_b.launch_builder(&f);
20932            b.arg(qkv_tm)
20933                .arg(&*conv_state)
20934                .arg(w)
20935                .arg(q_g)
20936                .arg(k_g)
20937                .arg(v_g)
20938                .arg(&cd)
20939                .arg(&ti)
20940                .arg(&dc)
20941                .arg(&ds)
20942                .arg(&nv)
20943                .arg(&nk)
20944                .arg(&kd)
20945                .arg(&hki);
20946            unsafe {
20947                b.launch(cfg)?;
20948            }
20949        }
20950        match pad_len {
20951            Some(len_d) => {
20952                let f = self.func("ssm_conv_ring_update_dev_f32");
20953                let n = conv_dim * (d_conv - 1);
20954                let cfg = LaunchConfig::for_num_elems(n as u32);
20955                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20956                let __s_b = self.gpu.stream();
20957                let mut b = __s_b.launch_builder(&f);
20958                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20959                unsafe {
20960                    b.launch(cfg)?;
20961                }
20962            }
20963            None => {
20964                let f = self.func("ssm_conv_ring_update_f32");
20965                let n = conv_dim * (d_conv - 1);
20966                let cfg = LaunchConfig::for_num_elems(n as u32);
20967                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20968                let __s_b = self.gpu.stream();
20969                let mut b = __s_b.launch_builder(&f);
20970                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20971                unsafe {
20972                    b.launch(cfg)?;
20973                }
20974            }
20975        }
20976        Ok(())
20977    }
20978
20979    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
20980    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
20981    /// K2/K3 can write them.
20982    pub fn gdn_chunk_alloc(
20983        &self,
20984        n_head: usize,
20985        t: usize,
20986        c: usize,
20987        hk: usize,
20988    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
20989        const D: usize = 128;
20990        assert!(
20991            c == 32,
20992            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
20993        );
20994        let h = n_head;
20995        let nc = (t + c - 1) / c;
20996        Ok(GdnChunkBufs {
20997            gcum: self.uninit(t * h)?,
20998            a: self.uninit(nc * h * c * c)?,
20999            p: self.uninit(nc * h * c * c)?,
21000            u: self.uninit(nc * h * c * D)?,
21001            w: self.uninit(nc * h * c * D)?,
21002            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
21003            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
21004            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
21005            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
21006            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
21007            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
21008            o: self.uninit(D * h * t)?,
21009            t,
21010            nc,
21011        })
21012    }
21013
21014    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
21015    pub fn f32_to_bf16_v(
21016        &self,
21017        x: &cudarc::driver::CudaView<f32>,
21018        dst: &mut CudaSlice<u8>,
21019        n: usize,
21020    ) -> Result<(), Box<dyn std::error::Error>> {
21021        let f = self.func("f32_to_bf16_bulk");
21022        let ni = n as i64;
21023        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
21024        let __s_b = self.gpu.stream();
21025        let mut b = __s_b.launch_builder(&f);
21026        b.arg(x).arg(dst).arg(&ni);
21027        unsafe {
21028            b.launch(cfg)?;
21029        }
21030        Ok(())
21031    }
21032
21033    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
21034    pub fn f32_to_bf16_into(
21035        &self,
21036        x: &CudaSlice<f32>,
21037        dst: &mut CudaSlice<u8>,
21038        n: usize,
21039    ) -> Result<(), Box<dyn std::error::Error>> {
21040        let f = self.func("f32_to_bf16_bulk");
21041        let ni = n as i64;
21042        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
21043        let __s_b = self.gpu.stream();
21044        let mut b = __s_b.launch_builder(&f);
21045        b.arg(x).arg(dst).arg(&ni);
21046        unsafe {
21047            b.launch(cfg)?;
21048        }
21049        Ok(())
21050    }
21051
21052    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
21053    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
21054    pub fn gdn_chunk_k123_vl8(
21055        &self,
21056        seqs: &[GdnSeqVl],
21057        n_head: usize,
21058        hk: usize,
21059        wq: Option<&GdnWVl8>,
21060    ) -> Result<(), Box<dyn std::error::Error>> {
21061        let b = seqs.len();
21062        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
21063        let mut packed = [GdnSeqVl::default(); 8];
21064        packed[..b].copy_from_slice(seqs);
21065        let v = GdnVl8(packed);
21066        let (hi, ci) = (n_head as i32, 32i32);
21067        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
21068        {
21069            let f = self.func("gdn_chunk_cumgate_vl");
21070            let cfg = LaunchConfig {
21071                grid_dim: (max_nc, n_head as u32, b as u32),
21072                block_dim: (32, 1, 1),
21073                shared_mem_bytes: 0,
21074            };
21075            let __s_lb = self.gpu.stream();
21076            let mut lb = __s_lb.launch_builder(&f);
21077            lb.arg(&v).arg(&hi).arg(&ci);
21078            unsafe {
21079                lb.launch(cfg)?;
21080            }
21081        }
21082        let hki = hk as i32;
21083        if let Some(w) = wq {
21084            // K2-wgmma vl twin (writes A + pre-masked Pb16)
21085            let f = self.func("gdn_k2_wgmma_vl");
21086            let cfg = LaunchConfig {
21087                grid_dim: (max_nc, n_head as u32, b as u32),
21088                block_dim: (128, 1, 1),
21089                shared_mem_bytes: 0,
21090            };
21091            let __s_lb = self.gpu.stream();
21092            let mut lb = __s_lb.launch_builder(&f);
21093            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
21094            unsafe {
21095                lb.launch(cfg)?;
21096            }
21097        } else {
21098            let f = self.func("gdn_chunk_attn_vl");
21099            let cfg = LaunchConfig {
21100                grid_dim: (max_nc, n_head as u32, b as u32),
21101                block_dim: (256, 1, 1),
21102                shared_mem_bytes: 0,
21103            };
21104            let __s_lb = self.gpu.stream();
21105            let mut lb = __s_lb.launch_builder(&f);
21106            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21107            unsafe {
21108                lb.launch(cfg)?;
21109            }
21110        }
21111        {
21112            let f = self.func("gdn_chunk_solve32_vl");
21113            let cfg = LaunchConfig {
21114                grid_dim: (max_nc, n_head as u32, b as u32),
21115                block_dim: (256, 1, 1),
21116                shared_mem_bytes: 0,
21117            };
21118            let __s_lb = self.gpu.stream();
21119            let mut lb = __s_lb.launch_builder(&f);
21120            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21121            unsafe {
21122                lb.launch(cfg)?;
21123            }
21124        }
21125        Ok(())
21126    }
21127
21128    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
21129    /// fused gate-prep, 5 launches for every sequence (per-element math identical
21130    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
21131    #[allow(clippy::too_many_arguments)]
21132    pub fn gdn_prep_vl8(
21133        &self,
21134        seqs: &[GdnPrepVl],
21135        conv_w: &CudaSlice<f32>,
21136        dt_bias: &CudaSlice<f32>,
21137        a: &CudaSlice<f32>,
21138        conv_dim: usize,
21139        d_conv: usize,
21140        d_state: usize,
21141        num_v: usize,
21142        num_k: usize,
21143        key_dim: usize,
21144        hk: usize,
21145        eps: f32,
21146    ) -> Result<(), Box<dyn std::error::Error>> {
21147        let b = seqs.len();
21148        assert!(b >= 1 && b <= 8);
21149        let mut packed = [GdnPrepVl::default(); 8];
21150        packed[..b].copy_from_slice(seqs);
21151        let v = GdnPrepVl8(packed);
21152        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21153        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
21154        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
21155        assert!(
21156            conv_fuse || hk == num_v,
21157            "de-broadcast requires the fused conv"
21158        );
21159        if conv_fuse {
21160            let f = self.func("ssm_conv1d_gdn_state_vl");
21161            let cfg = LaunchConfig {
21162                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21163                block_dim: (256, 1, 1),
21164                shared_mem_bytes: 0,
21165            };
21166            let (dsi, nvi, nki, kdi, hki) = (
21167                d_state as i32,
21168                num_v as i32,
21169                num_k as i32,
21170                key_dim as i32,
21171                hk as i32,
21172            );
21173            let __s_lb = self.gpu.stream();
21174            let mut lb = __s_lb.launch_builder(&f);
21175            lb.arg(&v)
21176                .arg(conv_w)
21177                .arg(&cdi)
21178                .arg(&dci)
21179                .arg(&dsi)
21180                .arg(&nvi)
21181                .arg(&nki)
21182                .arg(&kdi)
21183                .arg(&hki);
21184            unsafe {
21185                lb.launch(cfg)?;
21186            }
21187        } else {
21188            let f = self.func("ssm_conv1d_tm_state_vl");
21189            let cfg = LaunchConfig {
21190                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21191                block_dim: (256, 1, 1),
21192                shared_mem_bytes: 0,
21193            };
21194            let __s_lb = self.gpu.stream();
21195            let mut lb = __s_lb.launch_builder(&f);
21196            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
21197            unsafe {
21198                lb.launch(cfg)?;
21199            }
21200        }
21201        {
21202            let f = self.func("ssm_conv_ring_update_vl");
21203            let n = (conv_dim * (d_conv - 1)) as u32;
21204            let cfg = LaunchConfig {
21205                grid_dim: (n.div_ceil(256), 1, b as u32),
21206                block_dim: (256, 1, 1),
21207                shared_mem_bytes: 0,
21208            };
21209            let __s_lb = self.gpu.stream();
21210            let mut lb = __s_lb.launch_builder(&f);
21211            lb.arg(&v).arg(&cdi).arg(&dci);
21212            unsafe {
21213                lb.launch(cfg)?;
21214            }
21215        }
21216        if !conv_fuse {
21217            let f = self.func("qkv_to_gdn_repack_vl");
21218            let n = max_t * (num_v * d_state) as u32;
21219            let cfg = LaunchConfig {
21220                grid_dim: (n.div_ceil(256), 1, b as u32),
21221                block_dim: (256, 1, 1),
21222                shared_mem_bytes: 0,
21223            };
21224            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
21225            let __s_lb = self.gpu.stream();
21226            let mut lb = __s_lb.launch_builder(&f);
21227            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
21228            unsafe {
21229                lb.launch(cfg)?;
21230            }
21231        }
21232        if Self::l2_v2_on(d_state) {
21233            let f = self.func("gdn_l2_v2_vl");
21234            let cfg = LaunchConfig {
21235                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
21236                block_dim: (256, 1, 1),
21237                shared_mem_bytes: 0,
21238            };
21239            let (dsi, nvi) = (d_state as i32, hk as i32);
21240            let __s_lb = self.gpu.stream();
21241            let mut lb = __s_lb.launch_builder(&f);
21242            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21243            unsafe {
21244                lb.launch(cfg)?;
21245            }
21246        } else {
21247            let f = self.func("gdn_l2_vl");
21248            let cfg = LaunchConfig {
21249                grid_dim: (max_t * hk as u32, 2, b as u32),
21250                block_dim: (256, 1, 1),
21251                shared_mem_bytes: 0,
21252            };
21253            let (dsi, nvi) = (d_state as i32, hk as i32);
21254            let __s_lb = self.gpu.stream();
21255            let mut lb = __s_lb.launch_builder(&f);
21256            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21257            unsafe {
21258                lb.launch(cfg)?;
21259            }
21260        }
21261        {
21262            let f = self.func("gdn_gate_prep_vl");
21263            let n = max_t * num_v as u32;
21264            let cfg = LaunchConfig {
21265                grid_dim: (n.div_ceil(256), 1, b as u32),
21266                block_dim: (256, 1, 1),
21267                shared_mem_bytes: 0,
21268            };
21269            let nvi = num_v as i32;
21270            let __s_lb = self.gpu.stream();
21271            let mut lb = __s_lb.launch_builder(&f);
21272            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
21273            unsafe {
21274                lb.launch(cfg)?;
21275            }
21276        }
21277        Ok(())
21278    }
21279
21280    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
21281    pub fn gdn_mirror_vl8(
21282        &self,
21283        seqs: &[GdnSeqVl],
21284        n_head: usize,
21285        which: i32,
21286        hk: usize,
21287    ) -> Result<(), Box<dyn std::error::Error>> {
21288        let b = seqs.len();
21289        assert!(b >= 1 && b <= 8);
21290        let mut packed = [GdnSeqVl::default(); 8];
21291        packed[..b].copy_from_slice(seqs);
21292        let v = GdnVl8(packed);
21293        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
21294        let max_n = seqs
21295            .iter()
21296            .map(|s| {
21297                if which == 0 {
21298                    s.t as i64 * ept as i64
21299                } else {
21300                    s.nc as i64 * ept as i64 * 32
21301                }
21302            })
21303            .max()
21304            .unwrap();
21305        let f = self.func("gdn_mirror_vl");
21306        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
21307        let cfg = LaunchConfig {
21308            grid_dim: (blocks, 1, b as u32),
21309            block_dim: (256, 1, 1),
21310            shared_mem_bytes: 0,
21311        };
21312        let __s_lb = self.gpu.stream();
21313        let mut lb = __s_lb.launch_builder(&f);
21314        lb.arg(&v).arg(&ept).arg(&which);
21315        unsafe {
21316            lb.launch(cfg)?;
21317        }
21318        Ok(())
21319    }
21320
21321    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
21322    pub fn gdn_tail_vl8(
21323        &self,
21324        seqs: &[GdnPrepVl],
21325        norm_w: &CudaSlice<f32>,
21326        d_state: usize,
21327        num_v: usize,
21328        eps: f32,
21329    ) -> Result<(), Box<dyn std::error::Error>> {
21330        let b = seqs.len();
21331        assert!(b >= 1 && b <= 8);
21332        let mut packed = [GdnPrepVl::default(); 8];
21333        packed[..b].copy_from_slice(seqs);
21334        let v = GdnPrepVl8(packed);
21335        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21336        let f = self.func("gated_rmsnorm_f16out_vl");
21337        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21338        let cfg = LaunchConfig {
21339            grid_dim: (max_t * num_v as u32, 1, b as u32),
21340            block_dim: (128, 1, 1),
21341            shared_mem_bytes: 0,
21342        };
21343        let (dsi, nvi) = (d_state as i32, num_v as i32);
21344        let __s_lb = self.gpu.stream();
21345        let mut lb = __s_lb.launch_builder(&f);
21346        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
21347        unsafe {
21348            lb.launch(cfg)?;
21349        }
21350        Ok(())
21351    }
21352
21353    /// Raw device address helpers for the varlen by-value arg struct (single-stream
21354    /// launches; every buffer outlives the call — the f16 FFI discipline).
21355    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
21356        use cudarc::driver::DevicePtr;
21357        let s = self.gpu.stream();
21358        let (p, _g) = x.device_ptr(&s);
21359        p as u64
21360    }
21361    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
21362        use cudarc::driver::DevicePtrMut;
21363        let s = self.gpu.stream();
21364        let (p, _g) = x.device_ptr_mut(&s);
21365        p as u64
21366    }
21367    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
21368        use cudarc::driver::DevicePtr;
21369        let s = self.gpu.stream();
21370        let (p, _g) = x.device_ptr(&s);
21371        p as u64
21372    }
21373    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
21374        use cudarc::driver::DevicePtr;
21375        let s = self.gpu.stream();
21376        let (p, _g) = x.device_ptr(&s);
21377        p as u64
21378    }
21379
21380    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
21381    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
21382    /// launches, so this is strictly bit-gateable against them).
21383    pub fn gdn_chunk_vl8(
21384        &self,
21385        seqs: &[GdnSeqVl],
21386        n_head: usize,
21387        scale: f32,
21388        hk: usize,
21389        wq: Option<&GdnWVl8>,
21390    ) -> Result<(), Box<dyn std::error::Error>> {
21391        const NSPLIT: u32 = 4;
21392        let b = seqs.len();
21393        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
21394        let mut packed = [GdnSeqVl::default(); 8];
21395        packed[..b].copy_from_slice(seqs);
21396        let v = GdnVl8(packed);
21397        let (hi, ci) = (n_head as i32, 32i32);
21398        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
21399        let hki = hk as i32;
21400        if let Some(w) = wq {
21401            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
21402            let f = self.func("gdn_k45_wgmma_vl");
21403            let cfg = LaunchConfig {
21404                grid_dim: (n_head as u32, NSPLIT, b as u32),
21405                block_dim: (256, 1, 1),
21406                shared_mem_bytes: 0,
21407            };
21408            let __s_lb = self.gpu.stream();
21409            let mut lb = __s_lb.launch_builder(&f);
21410            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
21411            unsafe {
21412                lb.launch(cfg)?;
21413            }
21414            let _ = max_nc;
21415            return Ok(());
21416        }
21417        {
21418            let f = self.func("gdn_chunk_state_mma_vl");
21419            let cfg = LaunchConfig {
21420                grid_dim: (n_head as u32, NSPLIT, b as u32),
21421                block_dim: (256, 1, 1),
21422                shared_mem_bytes: 0,
21423            };
21424            let __s_lb = self.gpu.stream();
21425            let mut lb = __s_lb.launch_builder(&f);
21426            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21427            unsafe {
21428                lb.launch(cfg)?;
21429            }
21430        }
21431        {
21432            let f = self.func("gdn_chunk_output_mma_vl");
21433            let cfg = LaunchConfig {
21434                grid_dim: (max_nc, n_head as u32, b as u32),
21435                block_dim: (256, 1, 1),
21436                shared_mem_bytes: 0,
21437            };
21438            let __s_lb = self.gpu.stream();
21439            let mut lb = __s_lb.launch_builder(&f);
21440            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
21441            unsafe {
21442                lb.launch(cfg)?;
21443            }
21444        }
21445        Ok(())
21446    }
21447    pub fn gdn_scan_chunked(
21448        &self,
21449        q: &CudaSlice<f32>,
21450        k: &CudaSlice<f32>,
21451        v: &CudaSlice<f32>,
21452        g: &CudaSlice<f32>,
21453        beta: &CudaSlice<f32>,
21454        kb16_pre: Option<&CudaSlice<u8>>,
21455        qb16_pre: Option<&CudaSlice<u8>>,
21456        state_in: &CudaSlice<f32>,
21457        state_out: &mut CudaSlice<f32>,
21458        o: &mut CudaSlice<f32>,
21459        n_head: usize,
21460        t: usize,
21461        scale: f32,
21462        c: usize,
21463        hk: usize,
21464    ) -> Result<(), Box<dyn std::error::Error>> {
21465        const D: usize = 128;
21466        const NSPLIT: u32 = 4;
21467        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
21468        let h = n_head;
21469        let nc = (t + c - 1) / c;
21470        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
21471        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
21472        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
21473        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
21474        let gdn_mma_pre = !portable_mma_gated()
21475            && c == 32
21476            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21477                Ok("1") => true,
21478                Ok("0") => false,
21479                _ => cfg!(memra_hopper_mma),
21480            };
21481        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
21482            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
21483        } else {
21484            None
21485        };
21486        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
21487        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
21488        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
21489        let gdn_wgmma_pre = gdn_mma_pre
21490            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
21491                Ok("0") => false,
21492                Ok("1") => true,
21493                _ => cfg!(memra_hopper_mma),
21494            };
21495        let nk = t * hk * D;
21496        let mut kb16_local: Option<CudaSlice<u8>> = None;
21497        if gdn_mma_pre && kb16_pre.is_none() {
21498            let mut kb = self.alloc_u8_uninit(nk * 2)?;
21499            let f = self.func("f32_to_bf16_bulk");
21500            let n2 = nk as i64;
21501            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21502            let __s_b = self.gpu.stream();
21503            let mut b = __s_b.launch_builder(&f);
21504            b.arg(k).arg(&mut kb).arg(&n2);
21505            unsafe {
21506                b.launch(cfg2)?;
21507            }
21508            kb16_local = Some(kb);
21509        }
21510        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
21511        if let Some(kb) = kb16_pre {
21512            assert!(kb.len() >= nk * 2, "kb16_pre too small");
21513        }
21514        let mut qb16: Option<CudaSlice<u8>> = None;
21515        let mut pb16: Option<CudaSlice<u8>> = None;
21516        if gdn_wgmma_pre {
21517            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
21518            // the standalone bulk cvt only serves callers without the prep mirror.
21519            if qb16_pre.is_none() {
21520                let mut qb = self.alloc_u8_uninit(nk * 2)?;
21521                let f = self.func("f32_to_bf16_bulk");
21522                let n2 = nk as i64;
21523                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21524                let __s_b = self.gpu.stream();
21525                let mut b = __s_b.launch_builder(&f);
21526                b.arg(q).arg(&mut qb).arg(&n2);
21527                unsafe {
21528                    b.launch(cfg2)?;
21529                }
21530                qb16 = Some(qb);
21531            } else if let Some(qb) = qb16_pre {
21532                assert!(qb.len() >= nk * 2, "qb16_pre too small");
21533            }
21534            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
21535        }
21536        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
21537        let k2w = if gdn_wgmma_pre {
21538            Some((
21539                *qb16_ref0.as_ref().unwrap(),
21540                *kb16_ref0.as_ref().unwrap(),
21541                pb16.as_mut().unwrap(),
21542            ))
21543        } else {
21544            None
21545        };
21546        let (gcum, p, u, w) =
21547            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
21548        let _ = &w;
21549        let mut y = self.uninit(nc * h * c * D)?;
21550        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
21551        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
21552        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
21553        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
21554        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
21555        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
21556        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
21557        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
21558        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
21559        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
21560        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
21561        let gdn_mma = !portable_mma_gated()
21562            && c == 32
21563            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21564                Ok("1") => true,
21565                Ok("0") => false,
21566                _ => cfg!(memra_hopper_mma),
21567            };
21568        if gdn_mma {
21569            let wb16 = wb16_pre
21570                .take()
21571                .expect("mma path pre-allocates wb16 (K3 store fold)");
21572            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
21573            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
21574            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
21575            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
21576            // pass runs inside the persistent-M kernel; Y and Ssnap are never
21577            // materialized. New numeric class (gk folds into k^T instead of ys) —
21578            // explicit opt-in until the state-carry battery promotes it. Env read per
21579            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
21580            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
21581            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
21582            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
21583            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
21584            if gdn_wgmma_pre {
21585                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
21586                let qb16 = qb16_ref0.unwrap();
21587                let pb16 = pb16.as_ref().unwrap();
21588                {
21589                    let f = self.func("gdn_k45_wgmma");
21590                    let cfg = LaunchConfig {
21591                        grid_dim: (h as u32, 4, 1),
21592                        block_dim: (256, 1, 1),
21593                        shared_mem_bytes: 0,
21594                    };
21595                    let hki = hk as i32;
21596                    let __s_b = self.gpu.stream();
21597                    let mut b = __s_b.launch_builder(&f);
21598                    b.arg(kb16_ref)
21599                        .arg(&gcum)
21600                        .arg(beta)
21601                        .arg(&u)
21602                        .arg(&wb16)
21603                        .arg(qb16)
21604                        .arg(pb16)
21605                        .arg(o)
21606                        .arg(&scale)
21607                        .arg(state_in)
21608                        .arg(&mut *state_out)
21609                        .arg(&hi)
21610                        .arg(&ti)
21611                        .arg(&ci)
21612                        .arg(&hki);
21613                    unsafe {
21614                        b.launch(cfg)?;
21615                    }
21616                }
21617                return Ok(());
21618            }
21619            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
21620            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
21621            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
21622            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
21623            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
21624            {
21625                let f = self.func("gdn_chunk_state_mma");
21626                let cfg = LaunchConfig {
21627                    grid_dim: (h as u32, NSPLIT, 1),
21628                    block_dim: (256, 1, 1),
21629                    shared_mem_bytes: 0,
21630                };
21631                let hki = hk as i32;
21632                let __s_b = self.gpu.stream();
21633                let mut b = __s_b.launch_builder(&f);
21634                b.arg(kb16_ref)
21635                    .arg(&gcum)
21636                    .arg(beta)
21637                    .arg(&u)
21638                    .arg(&wb16)
21639                    .arg(&mut y16)
21640                    .arg(&mut ssnap16)
21641                    .arg(state_in)
21642                    .arg(&mut *state_out)
21643                    .arg(&hi)
21644                    .arg(&ti)
21645                    .arg(&ci)
21646                    .arg(&hki);
21647                unsafe {
21648                    b.launch(cfg)?;
21649                }
21650            }
21651            {
21652                // K5-mma (bf16 St/Y consumers)
21653                let f = self.func("gdn_chunk_output_mma");
21654                let jt = ((c + 31) / 32) as u32;
21655                let cfg = LaunchConfig {
21656                    grid_dim: (nc as u32, h as u32, jt),
21657                    block_dim: (256, 1, 1),
21658                    shared_mem_bytes: 0,
21659                };
21660                let hki = hk as i32;
21661                let __s_b = self.gpu.stream();
21662                let mut b = __s_b.launch_builder(&f);
21663                b.arg(q)
21664                    .arg(&gcum)
21665                    .arg(&p)
21666                    .arg(&y16)
21667                    .arg(&ssnap16)
21668                    .arg(o)
21669                    .arg(&hi)
21670                    .arg(&ti)
21671                    .arg(&ci)
21672                    .arg(&scale)
21673                    .arg(&hki);
21674                unsafe {
21675                    b.launch(cfg)?;
21676                }
21677            }
21678            return Ok(());
21679        }
21680        {
21681            // K4 (sequential over chunks inside; blocks col-partition the state)
21682            let f = self.func("gdn_chunk_state_f32");
21683            let cfg = LaunchConfig {
21684                grid_dim: (h as u32, NSPLIT, 1),
21685                block_dim: (256, 1, 1),
21686                shared_mem_bytes: 0,
21687            };
21688            let __s_b = self.gpu.stream();
21689            let mut b = __s_b.launch_builder(&f);
21690            b.arg(k)
21691                .arg(&gcum)
21692                .arg(beta)
21693                .arg(&u)
21694                .arg(&w)
21695                .arg(&mut y)
21696                .arg(&mut ssnap)
21697                .arg(state_in)
21698                .arg(&mut *state_out)
21699                .arg(&hi)
21700                .arg(&ti)
21701                .arg(&ci);
21702            unsafe {
21703                b.launch(cfg)?;
21704            }
21705        }
21706        {
21707            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
21708            let f = self.func("gdn_chunk_output_f32");
21709            let jt = ((c + 31) / 32) as u32;
21710            let cfg = LaunchConfig {
21711                grid_dim: (nc as u32, h as u32, jt),
21712                block_dim: (256, 1, 1),
21713                shared_mem_bytes: 0,
21714            };
21715            let __s_b = self.gpu.stream();
21716            let mut b = __s_b.launch_builder(&f);
21717            b.arg(q)
21718                .arg(&gcum)
21719                .arg(&p)
21720                .arg(&y)
21721                .arg(&ssnap)
21722                .arg(o)
21723                .arg(&hi)
21724                .arg(&ti)
21725                .arg(&ci)
21726                .arg(&scale);
21727            unsafe {
21728                b.launch(cfg)?;
21729            }
21730        }
21731        Ok(())
21732    }
21733
21734    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
21735    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
21736    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
21737    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
21738    ///
21739    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
21740    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
21741    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
21742    #[allow(clippy::too_many_arguments)]
21743    #[allow(clippy::too_many_arguments)]
21744    pub fn gdn_scan_prefill(
21745        &self,
21746        q: &CudaSlice<f32>,
21747        k: &CudaSlice<f32>,
21748        v: &CudaSlice<f32>,
21749        g: &CudaSlice<f32>,
21750        beta: &CudaSlice<f32>,
21751        kb16_pre: Option<&CudaSlice<u8>>,
21752        qb16_pre: Option<&CudaSlice<u8>>,
21753        state_in: &CudaSlice<f32>,
21754        state_out: &mut CudaSlice<f32>,
21755        o: &mut CudaSlice<f32>,
21756        n_head: usize,
21757        t: usize,
21758        scale: f32,
21759        hk: usize,
21760    ) -> Result<(), Box<dyn std::error::Error>> {
21761        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
21762            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
21763            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
21764        }
21765        if Self::gdn_chunked_enabled() && t >= 16 {
21766            self.gdn_scan_chunked(
21767                q,
21768                k,
21769                v,
21770                g,
21771                beta,
21772                kb16_pre,
21773                qb16_pre,
21774                state_in,
21775                state_out,
21776                o,
21777                n_head,
21778                t,
21779                scale,
21780                Self::gdn_chunk_size(),
21781                hk,
21782            )
21783        } else {
21784            assert!(
21785                hk == n_head,
21786                "s128 scan is broadcast-only (prep guarantees by predicate)"
21787            );
21788            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
21789        }
21790    }
21791
21792    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
21793    #[allow(clippy::too_many_arguments)]
21794    fn gdn_scan_diff(
21795        &self,
21796        q: &CudaSlice<f32>,
21797        k: &CudaSlice<f32>,
21798        v: &CudaSlice<f32>,
21799        g: &CudaSlice<f32>,
21800        beta: &CudaSlice<f32>,
21801        state_in: &CudaSlice<f32>,
21802        state_out: &mut CudaSlice<f32>,
21803        o: &mut CudaSlice<f32>,
21804        n_head: usize,
21805        t: usize,
21806        scale: f32,
21807    ) -> Result<(), Box<dyn std::error::Error>> {
21808        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
21809        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21810        let mut o_c = self.uninit(o.len())?;
21811        let mut st_c = self.uninit(state_out.len())?;
21812        self.gdn_scan_chunked(
21813            q,
21814            k,
21815            v,
21816            g,
21817            beta,
21818            None,
21819            None,
21820            state_in,
21821            &mut st_c,
21822            &mut o_c,
21823            n_head,
21824            t,
21825            scale,
21826            Self::gdn_chunk_size(),
21827            n_head,
21828        )?;
21829        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
21830        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
21831        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
21832        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
21833            let mut max_abs = 0f32;
21834            let mut max_rel = 0f32;
21835            let mut sum_rel = 0f64;
21836            for (x, y) in a.iter().zip(b) {
21837                let ad = (x - y).abs();
21838                let rel = ad / x.abs().max(y.abs()).max(1e-3);
21839                if ad > max_abs {
21840                    max_abs = ad;
21841                }
21842                if rel > max_rel {
21843                    max_rel = rel;
21844                }
21845                sum_rel += rel as f64;
21846            }
21847            (max_abs, max_rel, sum_rel / a.len() as f64)
21848        };
21849        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
21850        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
21851        println!(
21852            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
21853                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
21854            Self::gdn_chunk_size()
21855        );
21856        Ok(())
21857    }
21858
21859    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
21860    pub fn gdn_glog(
21861        &self,
21862        alpha: &CudaSlice<f32>,
21863        dt_bias: &CudaSlice<f32>,
21864        a: &CudaSlice<f32>,
21865        g_log: &mut CudaSlice<f32>,
21866        n_head: usize,
21867        t: usize,
21868    ) -> Result<(), Box<dyn std::error::Error>> {
21869        let f = self.func("gdn_glog_f32");
21870        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21871        let (h, ti) = (n_head as i32, t as i32);
21872        let __s_b = self.gpu.stream();
21873        let mut b = __s_b.launch_builder(&f);
21874        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21875        unsafe {
21876            b.launch(cfg)?;
21877        }
21878        Ok(())
21879    }
21880
21881    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
21882    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
21883    pub fn sigmoid_v(
21884        &self,
21885        x: &cudarc::driver::CudaView<f32>,
21886        y: &mut CudaSlice<f32>,
21887        n: usize,
21888    ) -> Result<(), Box<dyn std::error::Error>> {
21889        let f = self.func("sigmoid_f32");
21890        let cfg = LaunchConfig::for_num_elems(n as u32);
21891        let ni = n as i32;
21892        let __s_b = self.gpu.stream();
21893        let mut b = __s_b.launch_builder(&f);
21894        b.arg(x).arg(y).arg(&ni);
21895        unsafe {
21896            b.launch(cfg)?;
21897        }
21898        Ok(())
21899    }
21900
21901    pub fn gdn_glog_v(
21902        &self,
21903        alpha: &cudarc::driver::CudaView<f32>,
21904        dt_bias: &CudaSlice<f32>,
21905        a: &CudaSlice<f32>,
21906        g_log: &mut CudaSlice<f32>,
21907        n_head: usize,
21908        t: usize,
21909    ) -> Result<(), Box<dyn std::error::Error>> {
21910        let f = self.func("gdn_glog_f32");
21911        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21912        let (h, ti) = (n_head as i32, t as i32);
21913        let __s_b = self.gpu.stream();
21914        let mut b = __s_b.launch_builder(&f);
21915        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21916        unsafe {
21917            b.launch(cfg)?;
21918        }
21919        Ok(())
21920    }
21921
21922    pub fn sigmoid(
21923        &self,
21924        x: &CudaSlice<f32>,
21925        y: &mut CudaSlice<f32>,
21926        n: usize,
21927    ) -> Result<(), Box<dyn std::error::Error>> {
21928        let f = self.func("sigmoid_f32");
21929        let cfg = LaunchConfig::for_num_elems(n as u32);
21930        let ni = n as i32;
21931        let __s_b = self.gpu.stream();
21932        let mut b = __s_b.launch_builder(&f);
21933        b.arg(x).arg(y).arg(&ni);
21934        unsafe {
21935            b.launch(cfg)?;
21936        }
21937        Ok(())
21938    }
21939
21940    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
21941    /// (replaces sigmoid + mul + convert). Bit-identical class.
21942    pub fn sig_mul_f16out(
21943        &self,
21944        a: &CudaSlice<f32>,
21945        g: &CudaSlice<f32>,
21946        dst: &mut CudaSlice<f32>,
21947        dst16: &mut CudaSlice<u8>,
21948        n: usize,
21949    ) -> Result<(), Box<dyn std::error::Error>> {
21950        let f = self.func("sig_mul_f16out_f32");
21951        let cfg = LaunchConfig::for_num_elems(n as u32);
21952        let ni = n as i32;
21953        let __s_b = self.gpu.stream();
21954        let mut b = __s_b.launch_builder(&f);
21955        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
21956        unsafe {
21957            b.launch(cfg)?;
21958        }
21959        Ok(())
21960    }
21961
21962    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
21963    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
21964    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
21965    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
21966    ///
21967    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
21968    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
21969    /// applies the wrong number of distinct gate values.
21970    #[allow(clippy::too_many_arguments)]
21971    pub fn attn_head_gate(
21972        &self,
21973        a: &CudaSlice<f32>,
21974        g: &CudaSlice<f32>,
21975        dst: &mut CudaSlice<f32>,
21976        dst16: Option<&mut CudaSlice<u8>>,
21977        head_dim: usize,
21978        n_head: usize,
21979        t: usize,
21980    ) -> Result<(), Box<dyn std::error::Error>> {
21981        let f = self.func("attn_head_gate_f32");
21982        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21983        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21984        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
21985        let d16: u64 = match dst16 {
21986            Some(d) => self.addr_u8(d),
21987            None => 0,
21988        };
21989        let __s_b = self.gpu.stream();
21990        let mut b = __s_b.launch_builder(&f);
21991        b.arg(a)
21992            .arg(g)
21993            .arg(dst)
21994            .arg(&d16)
21995            .arg(&hd)
21996            .arg(&nh)
21997            .arg(&ti);
21998        unsafe {
21999            b.launch(cfg)?;
22000        }
22001        Ok(())
22002    }
22003
22004    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
22005    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
22006    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
22007    ///
22008    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
22009    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
22010    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
22011    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
22012    #[allow(clippy::too_many_arguments)]
22013    pub fn swiglu_clamped_mul_scaled(
22014        &self,
22015        gate: &CudaSlice<f32>,
22016        up: &CudaSlice<f32>,
22017        gs: f32,
22018        us: f32,
22019        limit: f32,
22020        dst: &mut CudaSlice<f32>,
22021        n: usize,
22022    ) -> Result<(), Box<dyn std::error::Error>> {
22023        debug_assert!(
22024            limit > 1e-6,
22025            "swiglu_clamped needs a live limit; use silu_mul_scaled"
22026        );
22027        let f = self.func("swiglu_clamped_mul_scaled_f32");
22028        let cfg = LaunchConfig::for_num_elems(n as u32);
22029        let ni = n as i32;
22030        let __s_b = self.gpu.stream();
22031        let mut b = __s_b.launch_builder(&f);
22032        b.arg(gate)
22033            .arg(up)
22034            .arg(&gs)
22035            .arg(&us)
22036            .arg(&limit)
22037            .arg(dst)
22038            .arg(&ni);
22039        unsafe {
22040            b.launch(cfg)?;
22041        }
22042        Ok(())
22043    }
22044
22045    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
22046    pub fn gated_rmsnorm(
22047        &self,
22048        o: &CudaSlice<f32>,
22049        w: &CudaSlice<f32>,
22050        z: &CudaSlice<f32>,
22051        dst: &mut CudaSlice<f32>,
22052        ncols: usize,
22053        nrows: usize,
22054        eps: f32,
22055    ) -> Result<(), Box<dyn std::error::Error>> {
22056        let f = self.func("gated_rmsnorm_f32");
22057        let cfg = LaunchConfig {
22058            grid_dim: (nrows as u32, 1, 1),
22059            block_dim: (128, 1, 1),
22060            shared_mem_bytes: 0,
22061        };
22062        let (nc, e) = (ncols as i32, eps);
22063        let __s_b = self.gpu.stream();
22064        let mut b = __s_b.launch_builder(&f);
22065        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
22066        unsafe {
22067            b.launch(cfg)?;
22068        }
22069        Ok(())
22070    }
22071
22072    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
22073    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
22074    pub fn gated_rmsnorm_f16out(
22075        &self,
22076        o: &CudaSlice<f32>,
22077        w: &CudaSlice<f32>,
22078        z: &CudaSlice<f32>,
22079        dst: &mut CudaSlice<f32>,
22080        dst16: &mut CudaSlice<u8>,
22081        ncols: usize,
22082        nrows: usize,
22083        eps: f32,
22084    ) -> Result<(), Box<dyn std::error::Error>> {
22085        let f = self.func("gated_rmsnorm_f16out_f32");
22086        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
22087        let cfg = LaunchConfig {
22088            grid_dim: (nrows as u32, 1, 1),
22089            block_dim: (128, 1, 1),
22090            shared_mem_bytes: 0,
22091        };
22092        let (nc, e) = (ncols as i32, eps);
22093        let __s_b = self.gpu.stream();
22094        let mut b = __s_b.launch_builder(&f);
22095        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22096        unsafe {
22097            b.launch(cfg)?;
22098        }
22099        Ok(())
22100    }
22101
22102    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
22103    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
22104    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
22105    #[allow(clippy::too_many_arguments)]
22106    pub fn add_rms_norm_zq8(
22107        &self,
22108        a: &CudaSlice<f32>,
22109        b_in: &CudaSlice<f32>,
22110        w: &CudaSlice<f32>,
22111        res: &mut CudaSlice<f32>,
22112        z: &mut CudaSlice<f32>,
22113        ncols: usize,
22114        nrows: usize,
22115        eps: f32,
22116    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22117        assert!(ncols % 32 == 0);
22118        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
22119        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22120        let f = self.func("add_rms_norm_zq8");
22121        let cfg = LaunchConfig {
22122            grid_dim: (nrows as u32, 1, 1),
22123            block_dim: (1024, 1, 1),
22124            shared_mem_bytes: 0,
22125        };
22126        let (nc, ep) = (ncols as i32, eps);
22127        let __s_b = self.gpu.stream();
22128        let mut b = __s_b.launch_builder(&f);
22129        b.arg(a)
22130            .arg(b_in)
22131            .arg(w)
22132            .arg(res)
22133            .arg(z)
22134            .arg(&mut q)
22135            .arg(&mut d)
22136            .arg(&nc)
22137            .arg(&ep);
22138        unsafe {
22139            b.launch(cfg)?;
22140        }
22141        Ok((q, d))
22142    }
22143
22144    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
22145    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
22146    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
22147    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
22148    pub fn gated_rmsnorm_zv(
22149        &self,
22150        o: &CudaSlice<f32>,
22151        w: &CudaSlice<f32>,
22152        z: &cudarc::driver::CudaView<f32>,
22153        dst: &mut CudaSlice<f32>,
22154        ncols: usize,
22155        nrows: usize,
22156        eps: f32,
22157    ) -> Result<(), Box<dyn std::error::Error>> {
22158        let f = self.func("gated_rmsnorm_f32");
22159        let cfg = LaunchConfig {
22160            grid_dim: (nrows as u32, 1, 1),
22161            block_dim: (128, 1, 1),
22162            shared_mem_bytes: 0,
22163        };
22164        let (nc, e) = (ncols as i32, eps);
22165        let __s_b = self.gpu.stream();
22166        let mut b = __s_b.launch_builder(&f);
22167        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
22168        unsafe {
22169            b.launch(cfg)?;
22170        }
22171        Ok(())
22172    }
22173
22174    pub fn gated_rmsnorm_f16out_zv(
22175        &self,
22176        o: &CudaSlice<f32>,
22177        w: &CudaSlice<f32>,
22178        z: &cudarc::driver::CudaView<f32>,
22179        dst: &mut CudaSlice<f32>,
22180        dst16: &mut CudaSlice<u8>,
22181        ncols: usize,
22182        nrows: usize,
22183        eps: f32,
22184    ) -> Result<(), Box<dyn std::error::Error>> {
22185        let f = self.func("gated_rmsnorm_f16out_f32");
22186        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
22187        let cfg = LaunchConfig {
22188            grid_dim: (nrows as u32, 1, 1),
22189            block_dim: (128, 1, 1),
22190            shared_mem_bytes: 0,
22191        };
22192        let (nc, e) = (ncols as i32, eps);
22193        let __s_b = self.gpu.stream();
22194        let mut b = __s_b.launch_builder(&f);
22195        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22196        unsafe {
22197            b.launch(cfg)?;
22198        }
22199        Ok(())
22200    }
22201
22202    pub fn gated_rmsnorm_q8_1(
22203        &self,
22204        o: &CudaSlice<f32>,
22205        w: &CudaSlice<f32>,
22206        z: &CudaSlice<f32>,
22207        ncols: usize,
22208        nrows: usize,
22209        eps: f32,
22210    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22211        assert!(ncols % 32 == 0);
22212        let f = self.func("gated_rmsnorm_q8_1");
22213        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
22214        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22215        let cfg = LaunchConfig {
22216            grid_dim: (nrows as u32, 1, 1),
22217            block_dim: (128, 1, 1),
22218            shared_mem_bytes: 0,
22219        };
22220        let (nc, ep) = (ncols as i32, eps);
22221        let __s_b = self.gpu.stream();
22222        let mut b = __s_b.launch_builder(&f);
22223        b.arg(o)
22224            .arg(w)
22225            .arg(z)
22226            .arg(&mut out_q)
22227            .arg(&mut out_d)
22228            .arg(&nc)
22229            .arg(&ep);
22230        unsafe {
22231            b.launch(cfg)?;
22232        }
22233        Ok((out_q, out_d))
22234    }
22235
22236    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
22237    pub fn transpose(
22238        &self,
22239        inp: &CudaSlice<f32>,
22240        rows: usize,
22241        cols: usize,
22242    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22243        let f = self.func("transpose_f32");
22244        let mut out = self.zeros(rows * cols)?;
22245        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
22246        let (r, c) = (rows as i32, cols as i32);
22247        let __s_b = self.gpu.stream();
22248        let mut b = __s_b.launch_builder(&f);
22249        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
22250        unsafe {
22251            b.launch(cfg)?;
22252        }
22253        Ok(out)
22254    }
22255
22256    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
22257    pub fn repeat_heads(
22258        &self,
22259        inp: &CudaSlice<f32>,
22260        out: &mut CudaSlice<f32>,
22261        head_dim: usize,
22262        n_in: usize,
22263        n_out: usize,
22264        t: usize,
22265    ) -> Result<(), Box<dyn std::error::Error>> {
22266        let f = self.func("repeat_heads_f32");
22267        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
22268        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
22269        let __s_b = self.gpu.stream();
22270        let mut b = __s_b.launch_builder(&f);
22271        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
22272        unsafe {
22273            b.launch(cfg)?;
22274        }
22275        Ok(())
22276    }
22277
22278    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
22279    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
22280    ///
22281    /// Bounds-checked against `qf` before the launch: the kernel reads `2*head_dim*n_head*t`
22282    /// floats, and running it on a `wq` output that carries no fused gate reads 2x off the end
22283    /// (silently, on the device). A layout mismatch is a typed `FusedQGateExtent` here instead.
22284    pub fn q_gate_split(
22285        &self,
22286        qf: &CudaSlice<f32>,
22287        q_out: &mut CudaSlice<f32>,
22288        gate_out: &mut CudaSlice<f32>,
22289        head_dim: usize,
22290        n_head: usize,
22291        t: usize,
22292    ) -> Result<(), Box<dyn std::error::Error>> {
22293        memra_gguf::config::check_fused_q_gate_extent(qf.len(), head_dim, n_head, t)?;
22294        let out_need = head_dim * n_head * t;
22295        if q_out.len() < out_need || gate_out.len() < out_need {
22296            return Err(format!(
22297                "q_gate_split destinations too small: need {out_need} each, have q={} gate={}",
22298                q_out.len(),
22299                gate_out.len()
22300            )
22301            .into());
22302        }
22303        let f = self.func("q_gate_split_f32");
22304        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
22305        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
22306        let __s_b = self.gpu.stream();
22307        let mut b = __s_b.launch_builder(&f);
22308        b.arg(qf)
22309            .arg(q_out)
22310            .arg(gate_out)
22311            .arg(&hd)
22312            .arg(&nh)
22313            .arg(&ti);
22314        unsafe {
22315            b.launch(cfg)?;
22316        }
22317        Ok(())
22318    }
22319
22320    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
22321    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
22322    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
22323    pub fn qkv_to_gdn_repack(
22324        &self,
22325        conv_out: &CudaSlice<f32>,
22326        q_g: &mut CudaSlice<f32>,
22327        k_g: &mut CudaSlice<f32>,
22328        v_g: &mut CudaSlice<f32>,
22329        d_state: usize,
22330        num_v: usize,
22331        num_k: usize,
22332        key_dim: usize,
22333        t: usize,
22334    ) -> Result<(), Box<dyn std::error::Error>> {
22335        let f = self.func("qkv_to_gdn_repack_f32");
22336        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
22337        let (ds, nv, nk, kd, ti) = (
22338            d_state as i32,
22339            num_v as i32,
22340            num_k as i32,
22341            key_dim as i32,
22342            t as i32,
22343        );
22344        let __s_b = self.gpu.stream();
22345        let mut b = __s_b.launch_builder(&f);
22346        b.arg(conv_out)
22347            .arg(q_g)
22348            .arg(k_g)
22349            .arg(v_g)
22350            .arg(&ds)
22351            .arg(&nv)
22352            .arg(&nk)
22353            .arg(&kd)
22354            .arg(&ti);
22355        unsafe {
22356            b.launch(cfg)?;
22357        }
22358        Ok(())
22359    }
22360
22361    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
22362    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
22363    pub fn conv_left_pad(
22364        &self,
22365        src: &CudaSlice<f32>,
22366        dst: &mut CudaSlice<f32>,
22367        conv_dim: usize,
22368        t: usize,
22369        pad: usize,
22370    ) -> Result<(), Box<dyn std::error::Error>> {
22371        let f = self.func("conv_left_pad_f32");
22372        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
22373        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
22374        let __s_b = self.gpu.stream();
22375        let mut b = __s_b.launch_builder(&f);
22376        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
22377        unsafe {
22378            b.launch(cfg)?;
22379        }
22380        Ok(())
22381    }
22382
22383    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
22384    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
22385    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
22386    pub fn conv_assemble_and_roll(
22387        &self,
22388        qkv_col: &CudaSlice<f32>,
22389        conv_state: &mut CudaSlice<f32>,
22390        conv_in: &mut CudaSlice<f32>,
22391        conv_dim: usize,
22392        pad: usize,
22393    ) -> Result<(), Box<dyn std::error::Error>> {
22394        let f = self.func("conv_assemble_and_roll_f32");
22395        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22396        let (cd, p) = (conv_dim as i32, pad as i32);
22397        let __s_b = self.gpu.stream();
22398        let mut b = __s_b.launch_builder(&f);
22399        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
22400        unsafe {
22401            b.launch(cfg)?;
22402        }
22403        Ok(())
22404    }
22405
22406    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
22407    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
22408    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
22409    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
22410    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
22411    pub fn ssm_conv1d_fused_decode(
22412        &self,
22413        qkv_col: &CudaSlice<f32>,
22414        conv_state: &mut CudaSlice<f32>,
22415        w: &CudaSlice<f32>,
22416        conv_out: &mut CudaSlice<f32>,
22417        conv_dim: usize,
22418        d_conv: usize,
22419    ) -> Result<(), Box<dyn std::error::Error>> {
22420        let f = self.func("ssm_conv1d_fused_decode_f32");
22421        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22422        let (cd, dc) = (conv_dim as i32, d_conv as i32);
22423        let __s_b = self.gpu.stream();
22424        let mut b = __s_b.launch_builder(&f);
22425        b.arg(qkv_col)
22426            .arg(conv_state)
22427            .arg(w)
22428            .arg(conv_out)
22429            .arg(&cd)
22430            .arg(&dc);
22431        unsafe {
22432            b.launch(cfg)?;
22433        }
22434        Ok(())
22435    }
22436
22437    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
22438    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
22439    pub fn slice_range(
22440        &self,
22441        src: &CudaSlice<f32>,
22442        start: usize,
22443        len: usize,
22444    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22445        let host = self.gpu.stream().clone_dtoh(src)?;
22446        self.gpu.stream().synchronize()?;
22447        Ok(self.htod(&host[start..start + len])?)
22448    }
22449}
22450
22451#[cfg(test)]
22452mod target_dispatch_tests {
22453    use super::legacy_quant_gemm_allowed;
22454
22455    #[test]
22456    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
22457        // sm_120a native lane
22458        assert!(legacy_quant_gemm_allowed(false, false, false));
22459        assert!(!legacy_quant_gemm_allowed(false, false, true));
22460        // pure portable lane (sm_89): gated
22461        assert!(!legacy_quant_gemm_allowed(true, false, false));
22462        assert!(!legacy_quant_gemm_allowed(true, false, true));
22463        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
22464        assert!(legacy_quant_gemm_allowed(true, true, false));
22465        assert!(!legacy_quant_gemm_allowed(true, true, true));
22466    }
22467
22468    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
22469    #[test]
22470    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
22471        assert!(!legacy_quant_gemm_allowed(
22472            cfg!(memra_portable_cuda),
22473            cfg!(memra_hopper_mma),
22474            false
22475        ));
22476    }
22477
22478    #[cfg(memra_hopper_mma)]
22479    #[test]
22480    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
22481        assert!(legacy_quant_gemm_allowed(
22482            cfg!(memra_portable_cuda),
22483            cfg!(memra_hopper_mma),
22484            false
22485        ));
22486        assert!(super::portable_mma_gated() == false);
22487    }
22488}
22489
22490/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
22491/// inherent methods (inherent methods win name resolution, so no recursion).
22492impl memra_kv::KvDev for Engine {
22493    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22494        Engine::zeros(self, n)
22495    }
22496    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22497        Engine::uninit(self, n)
22498    }
22499    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
22500        Engine::alloc_u8(self, n)
22501    }
22502    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
22503        Engine::htod_i32(self, v)
22504    }
22505    fn clone_dtod(
22506        &self,
22507        src: &CudaSlice<f32>,
22508    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22509        Engine::clone_dtod(self, src)
22510    }
22511    fn copy_into(
22512        &self,
22513        dst: &mut CudaSlice<f32>,
22514        off: usize,
22515        src: &CudaSlice<f32>,
22516        len: usize,
22517    ) -> Result<(), Box<dyn std::error::Error>> {
22518        Engine::copy_into(self, dst, off, src, len)
22519    }
22520    fn set_i32_one(
22521        &self,
22522        d: &mut CudaSlice<i32>,
22523        v: i32,
22524    ) -> Result<(), Box<dyn std::error::Error>> {
22525        Engine::set_i32_one(self, d, v)
22526    }
22527}
22528
22529#[cfg(test)]
22530mod fused_gate_bounds_tests {
22531    use super::*;
22532
22533    /// The fused `[q|gate]` split's read-site guard, on the device.
22534    ///
22535    /// `q_gate_split_f32` reads `2*head_dim*n_head*T` floats out of `qf`. A checkpoint whose gate
22536    /// is a SEPARATE tensor produces a `wq` output of exactly half that, so before 2026-08-19 the
22537    /// kernel launched and read 2x past the end of the allocation — an out-of-bounds DEVICE read:
22538    /// no panic, no error, just whatever memory follows. The guard turns it into a typed
22539    /// `FusedQGateExtent` before the launch.
22540    ///
22541    /// Catch demonstration for this test (guard temporarily removed, then restored):
22542    /// `compute-sanitizer --tool memcheck` on the half-width case reported invalid `__global__`
22543    /// reads of size 4 in `q_gate_split_f32`; with the guard in place the same run is clean and
22544    /// the call returns `Err`. Receipt in the lane report.
22545    #[test]
22546    #[ignore = "requires a CUDA GPU"]
22547    fn q_gate_split_refuses_a_separate_gate_wq_instead_of_reading_past_it() {
22548        let e = Engine::new(0).unwrap();
22549        let (head_dim, n_head, t) = (8usize, 4usize, 2usize);
22550        let fused = 2 * head_dim * n_head * t;
22551        let out_n = head_dim * n_head * t;
22552
22553        // half-width `qf` = the separate-gate / ungated layout. MUST be refused.
22554        let narrow = e.htod(&vec![1.0f32; out_n]).unwrap();
22555        let mut q = e.uninit(out_n).unwrap();
22556        let mut gate = e.uninit(out_n).unwrap();
22557        let err = e
22558            .q_gate_split(&narrow, &mut q, &mut gate, head_dim, n_head, t)
22559            .expect_err("half-width wq must be refused, not read past")
22560            .to_string();
22561        assert!(err.contains("NO fused gate"), "{err}");
22562        assert!(err.contains(&format!("{fused}")), "{err}");
22563
22564        // full-width `qf` = a real qwen3.5 fused layout. MUST still run, and split correctly:
22565        // per head hh the block is [q(head_dim) | gate(head_dim)] at stride 2*head_dim.
22566        let host: Vec<f32> = (0..fused).map(|i| i as f32).collect();
22567        let wide = e.htod(&host).unwrap();
22568        e.q_gate_split(&wide, &mut q, &mut gate, head_dim, n_head, t)
22569            .expect("full-width wq splits");
22570        let (qh, gh) = (e.dtoh(&q).unwrap(), e.dtoh(&gate).unwrap());
22571        for tok in 0..t {
22572            for hh in 0..n_head {
22573                for d in 0..head_dim {
22574                    let base = tok * (n_head * 2 * head_dim) + hh * (2 * head_dim);
22575                    let idx = tok * (n_head * head_dim) + hh * head_dim + d;
22576                    assert_eq!(qh[idx], host[base + d], "q t{tok} h{hh} d{d}");
22577                    assert_eq!(gh[idx], host[base + head_dim + d], "gate t{tok} h{hh} d{d}");
22578                }
22579            }
22580        }
22581
22582        // undersized destinations are refused too (the other half of the extent contract)
22583        let mut small = e.uninit(out_n - 1).unwrap();
22584        assert!(
22585            e.q_gate_split(&wide, &mut small, &mut gate, head_dim, n_head, t)
22586                .is_err()
22587        );
22588    }
22589}
22590
22591/// FULL-WIDTH-ROPE CONTRACT on the fused rms_norm+qkv+rope kernels
22592/// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up). CPU-only: the guard runs before
22593/// any launch, so the refusal is testable without a device.
22594#[cfg(test)]
22595mod fused_rope_width_tests {
22596    use super::Engine;
22597
22598    /// gemma-4: rotary width == head width on both classes (GGUF 256/256 and 512/512, and the
22599    /// safetensors route derives the same), which is why the fusion is legal there today.
22600    #[test]
22601    fn full_width_is_accepted() {
22602        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 256).is_ok());
22603        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_cat", 512, 512).is_ok());
22604        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append", 128, 128).is_ok());
22605    }
22606
22607    /// The widths the gemma-4 31B OFFICIAL artifact declares, read from its own GGUF header
22608    /// (`gemma-4-31B-it-official-Q8_0-MTP.gguf`, box3, 2026-08-19):
22609    ///
22610    /// ```text
22611    /// attention.key_length     512   rope.dimension_count     512   (global class)
22612    /// attention.key_length_swa 256   rope.dimension_count_swa 256   (SWA class)
22613    /// ```
22614    ///
22615    /// Both classes satisfy `n_rot == head_dim`, which is why the fusion is legal for gemma and
22616    /// why `HybridModel::gemma4_rope_dims` can feed this guard without refusing what we serve.
22617    /// An artifact that ever declares otherwise gets a loud refusal at the first fused launch
22618    /// instead of a silently over-rotated head.
22619    #[test]
22620    fn gemma4_official_artifact_widths_pass() {
22621        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 512, 512).is_ok());
22622        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 256, 256).is_ok());
22623    }
22624
22625    /// The bug this guard exists to make impossible: a partial-rotary arch fused onto a kernel
22626    /// with no `n_dims`, silently rotating the pass-through band.
22627    #[test]
22628    fn partial_rotary_is_refused_with_the_geometry_named() {
22629        // qwen3.5: n_rot 64 of head_dim 256 (the shape probe O-10 pinned in the split path).
22630        let err = Engine::full_width_rope_only("rms_norm_qkv_rope", 64, 256)
22631            .expect_err("partial rotary must refuse");
22632        let msg = err.to_string();
22633        assert!(msg.contains("PARTIAL ROTARY REFUSED"), "{msg}");
22634        assert!(msg.contains("n_rot 64"), "{msg}");
22635        assert!(msg.contains("head_dim 256"), "{msg}");
22636        assert!(
22637            msg.contains("64..256"),
22638            "names the band it would corrupt: {msg}"
22639        );
22640        // step35 full-attn: 64 of 128 (upstream halves n_rot_full).
22641        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 64, 128).is_err());
22642        // and the reverse mismatch (a wider rope than the head) is not "close enough" either.
22643        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 128).is_err());
22644    }
22645}