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        let f = self.func("filter_stats_f32");
1421        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1422        let cfg = LaunchConfig {
1423            grid_dim: (nrow as u32, 1, 1),
1424            block_dim: (1024, 1, 1),
1425            shared_mem_bytes: 0,
1426        };
1427        let __s_b = self.gpu.stream();
1428        let mut b = __s_b.launch_builder(&f);
1429        b.arg(x)
1430            .arg(&rs)
1431            .arg(rows)
1432            .arg(&mut *out_th)
1433            .arg(&mut *out_z)
1434            .arg(&mut *out_max)
1435            .arg(&ni)
1436            .arg(&nr)
1437            .arg(&temp)
1438            .arg(&top_k)
1439            .arg(&top_p)
1440            .arg(&min_p);
1441        unsafe {
1442            b.launch(cfg)?;
1443        }
1444        Ok(())
1445    }
1446
1447    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1448    #[allow(clippy::too_many_arguments)]
1449    pub fn softmax_gather_filtered(
1450        &self,
1451        x: &CudaSlice<f32>,
1452        row_stride: usize,
1453        ids: &CudaSlice<u32>,
1454        rows: &CudaSlice<i32>,
1455        th: &CudaSlice<f32>,
1456        z: &CudaSlice<f32>,
1457        out: &mut CudaSlice<f32>,
1458        n: usize,
1459        npair: usize,
1460        temp: f32,
1461    ) -> Result<(), Box<dyn std::error::Error>> {
1462        let f = self.func("softmax_gather_filtered_f32");
1463        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1464        let cfg = LaunchConfig {
1465            grid_dim: (npair as u32, 1, 1),
1466            block_dim: (256, 1, 1),
1467            shared_mem_bytes: 0,
1468        };
1469        let __s_b = self.gpu.stream();
1470        let mut b = __s_b.launch_builder(&f);
1471        b.arg(x)
1472            .arg(&rs)
1473            .arg(ids)
1474            .arg(rows)
1475            .arg(th)
1476            .arg(z)
1477            .arg(&mut *out)
1478            .arg(&ni)
1479            .arg(&np)
1480            .arg(&temp);
1481        unsafe {
1482            b.launch(cfg)?;
1483        }
1484        Ok(())
1485    }
1486
1487    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1488    #[allow(clippy::too_many_arguments)]
1489    pub fn residual_sample_filtered(
1490        &self,
1491        p: &CudaSlice<f32>,
1492        q: Option<&CudaSlice<f32>>,
1493        n: usize,
1494        temp: f32,
1495        seed: u64,
1496        stream_pos: u32,
1497        p_stats: (f32, f32, f32),
1498        q_stats: (f32, f32, f32),
1499        out_tok: &mut CudaSlice<u32>,
1500    ) -> Result<(), Box<dyn std::error::Error>> {
1501        let f = self.func("residual_sample_filtered_f32");
1502        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1503        let has_q: i32 = q.is_some() as i32;
1504        let qbuf = q.unwrap_or(p);
1505        let (pm, pth, pz) = p_stats;
1506        let (qm, qth, qz) = q_stats;
1507        let cfg = LaunchConfig {
1508            grid_dim: (1, 1, 1),
1509            block_dim: (1024, 1, 1),
1510            shared_mem_bytes: 0,
1511        };
1512        let __s_b = self.gpu.stream();
1513        let mut b = __s_b.launch_builder(&f);
1514        b.arg(p)
1515            .arg(qbuf)
1516            .arg(&has_q)
1517            .arg(&ni)
1518            .arg(&temp)
1519            .arg(&slo)
1520            .arg(&shi)
1521            .arg(&stream_pos)
1522            .arg(&pm)
1523            .arg(&pth)
1524            .arg(&pz)
1525            .arg(&qm)
1526            .arg(&qth)
1527            .arg(&qz)
1528            .arg(&mut *out_tok);
1529        unsafe {
1530            b.launch(cfg)?;
1531        }
1532        Ok(())
1533    }
1534
1535    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1536    #[allow(clippy::too_many_arguments)]
1537    pub fn gumbel_perturb_filtered(
1538        &self,
1539        x: &CudaSlice<f32>,
1540        y: &mut CudaSlice<f32>,
1541        n: usize,
1542        seed: u64,
1543        stream_pos: u32,
1544        temp: f32,
1545        row_max: f32,
1546        th: f32,
1547    ) -> Result<(), Box<dyn std::error::Error>> {
1548        let f = self.func("gumbel_perturb_filtered_f32");
1549        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1550        let cfg = LaunchConfig {
1551            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1552            block_dim: (256, 1, 1),
1553            shared_mem_bytes: 0,
1554        };
1555        let __s_b = self.gpu.stream();
1556        let mut b = __s_b.launch_builder(&f);
1557        b.arg(x)
1558            .arg(&mut *y)
1559            .arg(&ni)
1560            .arg(&slo)
1561            .arg(&shi)
1562            .arg(&stream_pos)
1563            .arg(&temp)
1564            .arg(&row_max)
1565            .arg(&th);
1566        unsafe {
1567            b.launch(cfg)?;
1568        }
1569        Ok(())
1570    }
1571
1572    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1573    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1574    /// filtered rejection sampling exact for the penalized target.
1575    #[allow(clippy::too_many_arguments)]
1576    pub fn penalize_logits(
1577        &self,
1578        x: &mut CudaSlice<f32>,
1579        hist: &CudaSlice<u32>,
1580        n_hist: usize,
1581        rep: f32,
1582        freq: f32,
1583        present: f32,
1584        n: usize,
1585    ) -> Result<(), Box<dyn std::error::Error>> {
1586        if n_hist == 0 {
1587            return Ok(());
1588        }
1589        let f = self.func("penalize_logits_f32");
1590        let (nh, ni) = (n_hist as i32, n as i32);
1591        let cfg = LaunchConfig {
1592            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1593            block_dim: (128, 1, 1),
1594            shared_mem_bytes: 0,
1595        };
1596        let __s_b = self.gpu.stream();
1597        let mut b = __s_b.launch_builder(&f);
1598        b.arg(&mut *x)
1599            .arg(hist)
1600            .arg(&nh)
1601            .arg(&rep)
1602            .arg(&freq)
1603            .arg(&present)
1604            .arg(&ni);
1605        unsafe {
1606            b.launch(cfg)?;
1607        }
1608        Ok(())
1609    }
1610
1611    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1612    #[allow(clippy::too_many_arguments)]
1613    pub fn penalize_logits_rows(
1614        &self,
1615        x: &mut CudaSlice<f32>,
1616        hist: &CudaSlice<u32>,
1617        n_hist: usize,
1618        rep: f32,
1619        freq: f32,
1620        present: f32,
1621        n: usize,
1622        nrow: usize,
1623    ) -> Result<(), Box<dyn std::error::Error>> {
1624        if n_hist == 0 || nrow == 0 {
1625            return Ok(());
1626        }
1627        let f = self.func("penalize_logits_rows_f32");
1628        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1629        let cfg = LaunchConfig {
1630            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1631            block_dim: (128, 1, 1),
1632            shared_mem_bytes: 0,
1633        };
1634        let __s_b = self.gpu.stream();
1635        let mut b = __s_b.launch_builder(&f);
1636        b.arg(&mut *x)
1637            .arg(hist)
1638            .arg(&nh)
1639            .arg(&rep)
1640            .arg(&freq)
1641            .arg(&present)
1642            .arg(&ni)
1643            .arg(&nr);
1644        unsafe {
1645            b.launch(cfg)?;
1646        }
1647        Ok(())
1648    }
1649
1650    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1651    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1652    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1653    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1654    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1655    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1656    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1657    pub fn wpf_level() -> u32 {
1658        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1659        *ON.get_or_init(|| {
1660            std::env::var("MEMRA_WPF")
1661                .ok()
1662                .and_then(|v| v.parse().ok())
1663                .unwrap_or(1)
1664        })
1665    }
1666
1667    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1668    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1669    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1670    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1671    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1672    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1673    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1674    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1675    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1676    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1677    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1678    pub fn set_verify_exact(&self, on: bool) {
1679        self.verify_exact
1680            .store(on, std::sync::atomic::Ordering::Relaxed);
1681    }
1682    pub(crate) fn verify_exact_on(&self) -> bool {
1683        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1684    }
1685
1686    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1687    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1688    pub fn qkv_append_on() -> bool {
1689        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1690        *ON.get_or_init(|| {
1691            std::env::var("MEMRA_QKV_APPEND")
1692                .map(|v| v != "0")
1693                .unwrap_or(true)
1694        })
1695    }
1696
1697    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1698    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1699    pub fn pdl_wb_on() -> bool {
1700        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1701        *ON.get_or_init(|| {
1702            std::env::var("MEMRA_PDL_WB")
1703                .map(|v| v != "0")
1704                .unwrap_or(true)
1705        })
1706    }
1707
1708    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1709    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1710    /// per-model no-harm bisect knob.
1711    pub fn pdl_mmvq_on() -> bool {
1712        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1713        *ON.get_or_init(|| {
1714            std::env::var("MEMRA_PDL_MMVQ")
1715                .map(|v| v != "0")
1716                .unwrap_or(true)
1717        })
1718    }
1719
1720    pub fn pdl_on() -> bool {
1721        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1722        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1723    }
1724
1725    /// PDL wave-B seam (gap-diagnosis arc, GAP-DIAGNOSIS.md verdict 8): the gemma
1726    /// NVFP4mix decode chain's hot kernels — nvfp4 fused2/mr2 and the q8_0 `_rp`
1727    /// singles — join the wave-A launch class. Scheduling-only (the entry macro waits
1728    /// on the producer before any read), bit-identical by construction.
1729    /// MEMRA_PDL_NVFP4=0 reverts wave-B alone.
1730    pub fn pdl_nvfp4q8_on() -> bool {
1731        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1732        *ON.get_or_init(|| {
1733            std::env::var("MEMRA_PDL_NVFP4")
1734                .map(|v| v != "0")
1735                .unwrap_or(true)
1736        })
1737    }
1738
1739    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1740    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1741    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1742    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1743    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1744    fn q40_mr1_on() -> bool {
1745        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1746        match *Q40MR.get_or_init(|| {
1747            std::env::var("MEMRA_Q40_MR")
1748                .ok()
1749                .and_then(|v| v.parse().ok())
1750        }) {
1751            Some(v) => v == 1,
1752            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1753        }
1754    }
1755
1756    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1757    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1758    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1759    /// writes wrong bytes silently.
1760    fn pdl_func_flash(
1761        &self,
1762        g: bool,
1763        name: &'static str,
1764    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1765        use cudarc::driver::sys as cu;
1766        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1767        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1768        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1769        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1770        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1771        // this engine's CUcontext; single-context runs behave exactly as before.
1772        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1773            std::sync::Mutex::new(None);
1774        static FNS: std::sync::Mutex<
1775            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
1776        > = std::sync::Mutex::new(None);
1777        let ctx_key = self.ctx().cu_ctx() as usize;
1778        if let Some(&f) = FNS
1779            .lock()
1780            .unwrap()
1781            .get_or_insert_with(Default::default)
1782            .get(&(ctx_key, g, name))
1783        {
1784            return Ok(f as cu::CUfunction);
1785        }
1786        let module = {
1787            let mut mods = MODS.lock().unwrap();
1788            let map = mods.get_or_insert_with(Default::default);
1789            match map.get(&(ctx_key, g)) {
1790                Some(&m) => m,
1791                None => {
1792                    let m = self.pdl_load_module_in_ctx(if g {
1793                        FLASH_FATBIN_KF8VF8
1794                    } else {
1795                        FLASH_FATBIN
1796                    })?;
1797                    map.insert((ctx_key, g), m);
1798                    m
1799                }
1800            }
1801        };
1802        let cname = std::ffi::CString::new(name)?;
1803        let mut f: cu::CUfunction = std::ptr::null_mut();
1804        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1805        if r != cu::CUresult::CUDA_SUCCESS {
1806            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
1807        }
1808        FNS.lock()
1809            .unwrap()
1810            .get_or_insert_with(Default::default)
1811            .insert((ctx_key, g, name), f as usize);
1812        Ok(f)
1813    }
1814
1815    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1816    /// the module to the thread's CURRENT context — a remote-stage engine must not
1817    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1818    /// current context before returning.
1819    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1820        use cudarc::driver::sys as cu;
1821        let mut prev: cu::CUcontext = std::ptr::null_mut();
1822        unsafe {
1823            cu::cuCtxGetCurrent(&mut prev).result()?;
1824        }
1825        self.ctx().bind_to_thread()?;
1826        let mut m: cu::CUmodule = std::ptr::null_mut();
1827        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1828        let restore = if prev.is_null() {
1829            cu::CUresult::CUDA_SUCCESS
1830        } else {
1831            unsafe { cu::cuCtxSetCurrent(prev) }
1832        };
1833        if r != cu::CUresult::CUDA_SUCCESS {
1834            return Err(format!("pdl module load: {r:?}").into());
1835        }
1836        if restore != cu::CUresult::CUDA_SUCCESS {
1837            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1838        }
1839        Ok(m as usize)
1840    }
1841
1842    fn pdl_func(
1843        &self,
1844        name: &'static str,
1845    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1846        use cudarc::driver::sys as cu;
1847        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1848        // are context-scoped; key everything by this engine's CUcontext).
1849        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1850            std::sync::Mutex::new(None);
1851        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1852        // duplicate module, loaded lazily on the first kernels-module miss.
1853        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1854            std::sync::Mutex::new(None);
1855        static FNS: std::sync::Mutex<
1856            Option<std::collections::HashMap<(usize, &'static str), usize>>,
1857        > = std::sync::Mutex::new(None);
1858        let ctx_key = self.ctx().cu_ctx() as usize;
1859        if let Some(&f) = FNS
1860            .lock()
1861            .unwrap()
1862            .get_or_insert_with(Default::default)
1863            .get(&(ctx_key, name))
1864        {
1865            return Ok(f as cu::CUfunction);
1866        }
1867        let module = {
1868            let mut mods = MODULES.lock().unwrap();
1869            let map = mods.get_or_insert_with(Default::default);
1870            match map.get(&ctx_key) {
1871                Some(&m) => m,
1872                None => {
1873                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1874                    map.insert(ctx_key, m);
1875                    m
1876                }
1877            }
1878        };
1879        let cname = std::ffi::CString::new(name)?;
1880        let mut f: cu::CUfunction = std::ptr::null_mut();
1881        let mut r =
1882            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1883        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1884            let qmodule = {
1885                let mut mods = QMODULES.lock().unwrap();
1886                let map = mods.get_or_insert_with(Default::default);
1887                match map.get(&ctx_key) {
1888                    Some(&m) => m,
1889                    None => {
1890                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1891                        map.insert(ctx_key, m);
1892                        m
1893                    }
1894                }
1895            };
1896            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1897        }
1898        if r != cu::CUresult::CUDA_SUCCESS {
1899            return Err(format!("pdl_func {name}: {r:?}").into());
1900        }
1901        FNS.lock()
1902            .unwrap()
1903            .get_or_insert_with(Default::default)
1904            .insert((ctx_key, name), f as usize);
1905        Ok(f)
1906    }
1907
1908    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1909    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1910    ///
1911    /// # Safety
1912    /// `params` must match the kernel's exact parameter list (order, types, count) —
1913    /// a mismatch corrupts the launch silently.
1914    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1915    /// builder path's fa_func/func_g choice exactly).
1916    ///
1917    /// # Safety
1918    /// Same contract as `launch_pdl`.
1919    unsafe fn launch_pdl_flash(
1920        &self,
1921        g: bool,
1922        name: &'static str,
1923        grid: (u32, u32, u32),
1924        block: (u32, u32, u32),
1925        smem: u32,
1926        params: &mut [*mut std::ffi::c_void],
1927    ) -> Result<(), Box<dyn std::error::Error>> {
1928        use cudarc::driver::sys as cu;
1929        let f = self.pdl_func_flash(g, name)?;
1930        if smem > 0 {
1931            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1932            let r =
1933                unsafe {
1934                    cu::cuFuncSetAttribute(f,
1935                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1936                smem as i32)
1937                };
1938            if r != cu::CUresult::CUDA_SUCCESS {
1939                return Err(format!("pdl smem attr {name}: {r:?}").into());
1940            }
1941        }
1942        let mut attr = cu::CUlaunchAttribute {
1943            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1944            pad: [0; 4],
1945            value: cu::CUlaunchAttributeValue {
1946                programmaticStreamSerializationAllowed: 1,
1947            },
1948        };
1949        let cfg = cu::CUlaunchConfig {
1950            gridDimX: grid.0,
1951            gridDimY: grid.1,
1952            gridDimZ: grid.2,
1953            blockDimX: block.0,
1954            blockDimY: block.1,
1955            blockDimZ: block.2,
1956            sharedMemBytes: smem,
1957            hStream: self.gpu.stream().cu_stream(),
1958            attrs: &mut attr,
1959            numAttrs: 1,
1960        };
1961        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1962        if r != cu::CUresult::CUDA_SUCCESS {
1963            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
1964        }
1965        Ok(())
1966    }
1967
1968    unsafe fn launch_pdl(
1969        &self,
1970        name: &'static str,
1971        grid: (u32, u32, u32),
1972        block: (u32, u32, u32),
1973        params: &mut [*mut std::ffi::c_void],
1974    ) -> Result<(), Box<dyn std::error::Error>> {
1975        use cudarc::driver::sys as cu;
1976        let f = self.pdl_func(name)?;
1977        let mut attr = cu::CUlaunchAttribute {
1978            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1979            pad: [0; 4],
1980            value: cu::CUlaunchAttributeValue {
1981                programmaticStreamSerializationAllowed: 1,
1982            },
1983        };
1984        let cfg = cu::CUlaunchConfig {
1985            gridDimX: grid.0,
1986            gridDimY: grid.1,
1987            gridDimZ: grid.2,
1988            blockDimX: block.0,
1989            blockDimY: block.1,
1990            blockDimZ: block.2,
1991            sharedMemBytes: 0,
1992            hStream: self.gpu.stream().cu_stream(),
1993            attrs: &mut attr,
1994            numAttrs: 1,
1995        };
1996        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1997        if r != cu::CUresult::CUDA_SUCCESS {
1998            return Err(format!("launch_pdl {name}: {r:?}").into());
1999        }
2000        Ok(())
2001    }
2002
2003    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
2004    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
2005    pub fn prefetch_weight_l2(
2006        &self,
2007        w: &crate::model::GpuTensor,
2008    ) -> Result<(), Box<dyn std::error::Error>> {
2009        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
2010            let p = rp4.as_ref().unwrap_or(bytes);
2011            self.prefetch_l2(p, p.len())?;
2012        }
2013        Ok(())
2014    }
2015
2016    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
2017    /// by the DEVICE token id at tok[idx] into f32.
2018    pub fn gather_row_bf16(
2019        &self,
2020        table: &CudaSlice<u8>,
2021        tok: &CudaSlice<u32>,
2022        idx: usize,
2023        dst: &mut CudaSlice<f32>,
2024        ncols: usize,
2025    ) -> Result<(), Box<dyn std::error::Error>> {
2026        let f = self.func("gather_row_bf16_f32");
2027        let cfg = LaunchConfig {
2028            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2029            block_dim: (256, 1, 1),
2030            shared_mem_bytes: 0,
2031        };
2032        let (nc, ix) = (ncols as i32, idx as i32);
2033        let __s_b = self.gpu.stream();
2034        let mut b = __s_b.launch_builder(&f);
2035        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2036        unsafe {
2037            b.launch(cfg)?;
2038        }
2039        Ok(())
2040    }
2041
2042    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2043    pub fn add_row_inplace(
2044        &self,
2045        logits: &mut CudaSlice<f32>,
2046        bias: &CudaSlice<f32>,
2047        n: usize,
2048        row_off: usize,
2049    ) -> Result<(), Box<dyn std::error::Error>> {
2050        let f = self.func("add_row_inplace_f32");
2051        let cfg = LaunchConfig {
2052            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2053            block_dim: (256, 1, 1),
2054            shared_mem_bytes: 0,
2055        };
2056        let (ni, off) = (n as i32, row_off as i64);
2057        let __s_b = self.gpu.stream();
2058        let mut b = __s_b.launch_builder(&f);
2059        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2060        unsafe {
2061            b.launch(cfg)?;
2062        }
2063        Ok(())
2064    }
2065
2066    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2067    pub fn prefetch_l2(
2068        &self,
2069        p: &CudaSlice<u8>,
2070        n: usize,
2071    ) -> Result<(), Box<dyn std::error::Error>> {
2072        let f = self.func("prefetch_l2_bytes");
2073        let lines = n.div_ceil(128);
2074        let ni = n as i64;
2075        let cfg = LaunchConfig {
2076            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2077            block_dim: (256, 1, 1),
2078            shared_mem_bytes: 0,
2079        };
2080        let __s_b = self.gpu.stream();
2081        let mut b = __s_b.launch_builder(&f);
2082        b.arg(p).arg(&ni);
2083        unsafe {
2084            b.launch(cfg)?;
2085        }
2086        Ok(())
2087    }
2088
2089    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2090    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2091    pub fn router_gemv(
2092        &self,
2093        w: &CudaSlice<f32>,
2094        x: &CudaSlice<f32>,
2095        n_embd: usize,
2096        n_experts: usize,
2097        t: usize,
2098    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2099        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2100        // stream differs) — too small to justify a numeric config change; deleted.
2101        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2102        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2103        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2104        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2105            Ok("0") => false,
2106            Ok(_) => true,
2107            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2108        };
2109        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2110        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2111        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2112        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2113        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2114        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2115        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2116        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2117        // (perf-only, bits equal).
2118        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2119        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2120    }
2121
2122    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2123    /// force both forms; `batch` requires `w8`).
2124    pub fn router_gemv_form(
2125        &self,
2126        w: &CudaSlice<f32>,
2127        x: &CudaSlice<f32>,
2128        n_embd: usize,
2129        n_experts: usize,
2130        t: usize,
2131        w8: bool,
2132        batch: bool,
2133    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2134        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2135        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2136        let f = if batch {
2137            self.func("router_gemv_f32_w8_batch")
2138        } else if w8 {
2139            self.func("router_gemv_f32_w8")
2140        } else {
2141            self.func("router_gemv_f32")
2142        };
2143        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2144        let cfg = if batch {
2145            LaunchConfig {
2146                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2147                block_dim: (32, 8, 1),
2148                shared_mem_bytes: 0,
2149            }
2150        } else {
2151            LaunchConfig {
2152                grid_dim: (n_experts as u32, t as u32, 1),
2153                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2154                shared_mem_bytes: 0,
2155            }
2156        };
2157        let __s_b = self.gpu.stream();
2158        let mut b = __s_b.launch_builder(&f);
2159        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2160        unsafe {
2161            b.launch(cfg)?;
2162        }
2163        Ok(y)
2164    }
2165
2166    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2167    pub fn rows_permute(
2168        &self,
2169        src: &CudaSlice<f32>,
2170        idx: &CudaSlice<i32>,
2171        nrows: usize,
2172        ncols: usize,
2173    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2174        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2175        let f = self.func("rows_permute_f32");
2176        let (nc, nr) = (ncols as i32, nrows as i32);
2177        let cfg = LaunchConfig {
2178            grid_dim: (nrows as u32, 1, 1),
2179            block_dim: (256, 1, 1),
2180            shared_mem_bytes: 0,
2181        };
2182        let __s_b = self.gpu.stream();
2183        let mut b = __s_b.launch_builder(&f);
2184        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2185        unsafe {
2186            b.launch(cfg)?;
2187        }
2188        Ok(dst)
2189    }
2190
2191    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2192    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2193    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2194    /// decode chain and the small-t spec-verify chain match per row by construction.
2195    pub fn sigmoid_dot_rows(
2196        &self,
2197        x: &CudaSlice<f32>,
2198        w: &CudaSlice<f32>,
2199        n_embd: usize,
2200        t: usize,
2201    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2202        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2203        // config; same class as MEMRA_ROUTER_V2).
2204        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2205        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2206            let gs = self.linear(x, w, t, n_embd, 1)?;
2207            let mut g = self.uninit(t)?;
2208            self.sigmoid(&gs, &mut g, t)?;
2209            return Ok(g);
2210        }
2211        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2212        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2213        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2214        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2215        // flags doctrine; this per-token form serves every t.
2216        let mut g = self.alloc_uninit::<f32>(t)?;
2217        let f = self.func("sigmoid_dot_rows_f32");
2218        let (ne, ti) = (n_embd as i32, t as i32);
2219        let cfg = LaunchConfig {
2220            grid_dim: (t as u32, 1, 1),
2221            block_dim: (32, 8, 1),
2222            shared_mem_bytes: 0,
2223        };
2224        let __s_b = self.gpu.stream();
2225        let mut b = __s_b.launch_builder(&f);
2226        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2227        unsafe {
2228            b.launch(cfg)?;
2229        }
2230        Ok(g)
2231    }
2232
2233    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2234    pub fn spec_rollback_stream(
2235        &self,
2236        len_ptrs: &CudaSlice<u64>,
2237        pos_start: &CudaSlice<i32>,
2238        acc: &CudaSlice<u32>,
2239        base: usize,
2240        n_rows: usize,
2241    ) -> Result<(), Box<dyn std::error::Error>> {
2242        let f = self.func("spec_rollback_stream");
2243        let (b, nr) = (base as i32, n_rows as i32);
2244        let cfg = LaunchConfig {
2245            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2246            block_dim: (64, 1, 1),
2247            shared_mem_bytes: 0,
2248        };
2249        let __s_bl = self.gpu.stream();
2250        let mut bl = __s_bl.launch_builder(&f);
2251        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2252        unsafe {
2253            bl.launch(cfg)?;
2254        }
2255        Ok(())
2256    }
2257
2258    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2259    pub fn plain_tok_ring(
2260        &self,
2261        vam: &CudaSlice<u32>,
2262        pos_start: &CudaSlice<i32>,
2263        base: usize,
2264        ring: &mut CudaSlice<u32>,
2265    ) -> Result<(), Box<dyn std::error::Error>> {
2266        let f = self.func("plain_tok_ring");
2267        let (b, cap) = (base as i32, ring.len() as i32);
2268        let cfg = LaunchConfig {
2269            grid_dim: (1, 1, 1),
2270            block_dim: (32, 1, 1),
2271            shared_mem_bytes: 0,
2272        };
2273        let __s_bl = self.gpu.stream();
2274        let mut bl = __s_bl.launch_builder(&f);
2275        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2276        unsafe {
2277            bl.launch(cfg)?;
2278        }
2279        Ok(())
2280    }
2281
2282    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2283    pub fn spec_ring_commit(
2284        &self,
2285        vtok: &CudaSlice<u32>,
2286        acc: &CudaSlice<u32>,
2287        brk: &CudaSlice<u32>,
2288        ring: &mut CudaSlice<u32>,
2289        pend: &mut CudaSlice<u32>,
2290    ) -> Result<(), Box<dyn std::error::Error>> {
2291        let f = self.func("spec_ring_commit");
2292        let cfg = LaunchConfig {
2293            grid_dim: (1, 1, 1),
2294            block_dim: (32, 1, 1),
2295            shared_mem_bytes: 0,
2296        };
2297        let __s_b = self.gpu.stream();
2298        let mut b = __s_b.launch_builder(&f);
2299        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2300        unsafe {
2301            b.launch(cfg)?;
2302        }
2303        Ok(())
2304    }
2305    pub fn i32_copy_add(
2306        &self,
2307        src: &CudaSlice<i32>,
2308        dst: &mut CudaSlice<i32>,
2309        delta: i32,
2310    ) -> Result<(), Box<dyn std::error::Error>> {
2311        let f = self.func("i32_copy_add");
2312        let cfg = LaunchConfig {
2313            grid_dim: (1, 1, 1),
2314            block_dim: (32, 1, 1),
2315            shared_mem_bytes: 0,
2316        };
2317        let __s_b = self.gpu.stream();
2318        let mut b = __s_b.launch_builder(&f);
2319        b.arg(src).arg(dst).arg(&delta);
2320        unsafe {
2321            b.launch(cfg)?;
2322        }
2323        Ok(())
2324    }
2325    pub fn u32_copy(
2326        &self,
2327        src: &CudaSlice<u32>,
2328        dst: &mut CudaSlice<u32>,
2329    ) -> Result<(), Box<dyn std::error::Error>> {
2330        let f = self.func("u32_copy");
2331        let cfg = LaunchConfig {
2332            grid_dim: (1, 1, 1),
2333            block_dim: (32, 1, 1),
2334            shared_mem_bytes: 0,
2335        };
2336        let __s_b = self.gpu.stream();
2337        let mut b = __s_b.launch_builder(&f);
2338        b.arg(src).arg(dst);
2339        unsafe {
2340            b.launch(cfg)?;
2341        }
2342        Ok(())
2343    }
2344
2345    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2346    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2347    /// caps acceptance exactly like drafting fewer tokens).
2348    pub fn spec_adapt_k(
2349        &self,
2350        acc: &CudaSlice<u32>,
2351        brk: &mut CudaSlice<u32>,
2352        floor: usize,
2353        cap: usize,
2354    ) -> Result<(), Box<dyn std::error::Error>> {
2355        let f = self.func("spec_adapt_k");
2356        let (fl, cp) = (floor as i32, cap as i32);
2357        let cfg = LaunchConfig {
2358            grid_dim: (1, 1, 1),
2359            block_dim: (32, 1, 1),
2360            shared_mem_bytes: 0,
2361        };
2362        let __s_b = self.gpu.stream();
2363        let mut b = __s_b.launch_builder(&f);
2364        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2365        unsafe {
2366            b.launch(cfg)?;
2367        }
2368        Ok(())
2369    }
2370
2371    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2372    pub fn spec_accept_greedy_dc(
2373        &self,
2374        preds: &CudaSlice<u32>,
2375        vtok: &CudaSlice<u32>,
2376        last_pred: &CudaSlice<u32>,
2377        brk: &CudaSlice<u32>,
2378        out: &mut CudaSlice<u32>,
2379    ) -> Result<(), Box<dyn std::error::Error>> {
2380        let f = self.func("spec_accept_greedy_dc");
2381        let cfg = LaunchConfig {
2382            grid_dim: (1, 1, 1),
2383            block_dim: (32, 1, 1),
2384            shared_mem_bytes: 0,
2385        };
2386        let __s_b = self.gpu.stream();
2387        let mut b = __s_b.launch_builder(&f);
2388        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2389        unsafe {
2390            b.launch(cfg)?;
2391        }
2392        Ok(())
2393    }
2394
2395    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2396    pub fn pos_iota(
2397        &self,
2398        pos0: &CudaSlice<i32>,
2399        out: &mut CudaSlice<i32>,
2400        t: usize,
2401    ) -> Result<(), Box<dyn std::error::Error>> {
2402        let f = self.func("pos_iota_i32");
2403        let ti = t as i32;
2404        let cfg = LaunchConfig {
2405            grid_dim: (1, 1, 1),
2406            block_dim: (t.max(1) as u32, 1, 1),
2407            shared_mem_bytes: 0,
2408        };
2409        let __s_b = self.gpu.stream();
2410        let mut b = __s_b.launch_builder(&f);
2411        b.arg(pos0).arg(out).arg(&ti);
2412        unsafe {
2413            b.launch(cfg)?;
2414        }
2415        Ok(())
2416    }
2417    #[allow(clippy::too_many_arguments)]
2418    pub fn append_kv_quantized_rows_dc(
2419        &self,
2420        k_rows: &CudaSlice<f32>,
2421        v_rows: &CudaSlice<f32>,
2422        kc: &mut CudaSlice<u8>,
2423        vc: &mut CudaSlice<u8>,
2424        t0_dev: &CudaSlice<i32>,
2425        t: usize,
2426        kv_dim_k: usize,
2427        kv_dim_v: usize,
2428        k_tok_bytes: usize,
2429        v_tok_bytes: usize,
2430        g: bool,
2431    ) -> Result<(), Box<dyn std::error::Error>> {
2432        let f = if g {
2433            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2434        } else {
2435            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2436        };
2437        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2438        let cfg = LaunchConfig {
2439            grid_dim: (nblk, t as u32, 1),
2440            block_dim: (32, 1, 1),
2441            shared_mem_bytes: 0,
2442        };
2443        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2444        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2445        let __s_b = self.gpu.stream();
2446        let mut b = __s_b.launch_builder(&f);
2447        b.arg(k_rows)
2448            .arg(v_rows)
2449            .arg(kc)
2450            .arg(vc)
2451            .arg(t0_dev)
2452            .arg(&kdk)
2453            .arg(&kdv)
2454            .arg(&ktb)
2455            .arg(&vtb);
2456        unsafe {
2457            b.launch(cfg)?;
2458        }
2459        Ok(())
2460    }
2461
2462    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2463    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2464    #[allow(clippy::too_many_arguments)]
2465    pub fn append_kv_quantized_row_dc_inc(
2466        &self,
2467        k_row: &CudaSlice<f32>,
2468        v_row: &CudaSlice<f32>,
2469        kc: &mut CudaSlice<u8>,
2470        vc: &mut CudaSlice<u8>,
2471        t0_dev: &mut CudaSlice<i32>,
2472        kv_dim_k: usize,
2473        kv_dim_v: usize,
2474        k_tok_bytes: usize,
2475        v_tok_bytes: usize,
2476        g: bool,
2477    ) -> Result<(), Box<dyn std::error::Error>> {
2478        let f = if g {
2479            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2480        } else {
2481            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2482        };
2483        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2484        let cfg = LaunchConfig {
2485            grid_dim: (1, 1, 1),
2486            block_dim: (nthreads, 1, 1),
2487            shared_mem_bytes: 0,
2488        };
2489        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2490        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2491        let __s_b = self.gpu.stream();
2492        let mut b = __s_b.launch_builder(&f);
2493        b.arg(k_row)
2494            .arg(v_row)
2495            .arg(kc)
2496            .arg(vc)
2497            .arg(t0_dev)
2498            .arg(&kdk)
2499            .arg(&kdv)
2500            .arg(&ktb)
2501            .arg(&vtb);
2502        unsafe {
2503            b.launch(cfg)?;
2504        }
2505        Ok(())
2506    }
2507
2508    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
2509    pub fn pack_tok_p(
2510        &self,
2511        tok: &CudaSlice<u32>,
2512        p: &CudaSlice<f32>,
2513        out: &mut CudaSlice<u32>,
2514        slot: usize,
2515    ) -> Result<(), Box<dyn std::error::Error>> {
2516        let f = self.func("pack_tok_p");
2517        let sl = slot as i32;
2518        let cfg = LaunchConfig {
2519            grid_dim: (1, 1, 1),
2520            block_dim: (32, 1, 1),
2521            shared_mem_bytes: 0,
2522        };
2523        let __s_b = self.gpu.stream();
2524        let mut b = __s_b.launch_builder(&f);
2525        b.arg(tok).arg(p).arg(out).arg(&sl);
2526        unsafe {
2527            b.launch(cfg)?;
2528        }
2529        Ok(())
2530    }
2531    pub fn tok_map_u32(
2532        &self,
2533        tok: &mut CudaSlice<u32>,
2534        map: &CudaSlice<u32>,
2535    ) -> Result<(), Box<dyn std::error::Error>> {
2536        let f = self.func("tok_map_u32");
2537        let cfg = LaunchConfig {
2538            grid_dim: (1, 1, 1),
2539            block_dim: (32, 1, 1),
2540            shared_mem_bytes: 0,
2541        };
2542        let __s_b = self.gpu.stream();
2543        let mut b = __s_b.launch_builder(&f);
2544        b.arg(tok).arg(map);
2545        unsafe {
2546            b.launch(cfg)?;
2547        }
2548        Ok(())
2549    }
2550
2551    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
2552    #[allow(clippy::too_many_arguments)]
2553    pub fn spec_assemble_verify(
2554        &self,
2555        tokp: &CudaSlice<u32>,
2556        pend: &CudaSlice<u32>,
2557        d2t: Option<&CudaSlice<u32>>,
2558        vtok: &mut CudaSlice<u32>,
2559        brk: &mut CudaSlice<u32>,
2560        p_min: f32,
2561        k: usize,
2562        pmin0: bool,
2563    ) -> Result<(), Box<dyn std::error::Error>> {
2564        let f = self.func("spec_assemble_verify");
2565        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
2566        let cfg = LaunchConfig {
2567            grid_dim: (1, 1, 1),
2568            block_dim: (32, 1, 1),
2569            shared_mem_bytes: 0,
2570        };
2571        let __s_b = self.gpu.stream();
2572        let mut b = __s_b.launch_builder(&f);
2573        match d2t {
2574            Some(m) => {
2575                b.arg(tokp)
2576                    .arg(pend)
2577                    .arg(m)
2578                    .arg(vtok)
2579                    .arg(brk)
2580                    .arg(&p_min)
2581                    .arg(&ki)
2582                    .arg(&pm);
2583                unsafe {
2584                    b.launch(cfg)?;
2585                }
2586            }
2587            None => {
2588                let null: u64 = 0;
2589                b.arg(tokp)
2590                    .arg(pend)
2591                    .arg(&null)
2592                    .arg(vtok)
2593                    .arg(brk)
2594                    .arg(&p_min)
2595                    .arg(&ki)
2596                    .arg(&pm);
2597                unsafe {
2598                    b.launch(cfg)?;
2599                }
2600            }
2601        }
2602        Ok(())
2603    }
2604
2605    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
2606    #[allow(clippy::too_many_arguments)]
2607    pub fn ssm_conv_ring_rebuild_dc(
2608        &self,
2609        qkv_tm: &CudaSlice<f32>,
2610        ring_old: &CudaSlice<f32>,
2611        conv_state: &mut CudaSlice<f32>,
2612        conv_dim: usize,
2613        acc: &CudaSlice<u32>,
2614        base: usize,
2615        t_v: usize,
2616        d_conv: usize,
2617    ) -> Result<(), Box<dyn std::error::Error>> {
2618        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
2619        let n = conv_dim * (d_conv - 1);
2620        let cfg = LaunchConfig::for_num_elems(n as u32);
2621        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
2622        let __s_b = self.gpu.stream();
2623        let mut b = __s_b.launch_builder(&f);
2624        b.arg(qkv_tm)
2625            .arg(ring_old)
2626            .arg(conv_state)
2627            .arg(&cd)
2628            .arg(acc)
2629            .arg(&b0)
2630            .arg(&tv)
2631            .arg(&dc);
2632        unsafe {
2633            b.launch(cfg)?;
2634        }
2635        Ok(())
2636    }
2637    #[allow(clippy::too_many_arguments)]
2638    pub fn gdn_scan_s128_dc(
2639        &self,
2640        q: &CudaSlice<f32>,
2641        k: &CudaSlice<f32>,
2642        v: &CudaSlice<f32>,
2643        g: &CudaSlice<f32>,
2644        beta: &CudaSlice<f32>,
2645        state_in: &CudaSlice<f32>,
2646        state_out: &mut CudaSlice<f32>,
2647        o: &mut CudaSlice<f32>,
2648        n_head: usize,
2649        acc: &CudaSlice<u32>,
2650        base: usize,
2651        t_v: usize,
2652        scale: f32,
2653    ) -> Result<(), Box<dyn std::error::Error>> {
2654        let f = self.func("gdn_scan_s128_dc");
2655        const S_V: u32 = 128;
2656        const WARP: u32 = 32;
2657        const COLS_PER_BLOCK: u32 = 4;
2658        let cfg = LaunchConfig {
2659            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
2660            block_dim: (WARP, COLS_PER_BLOCK, 1),
2661            shared_mem_bytes: 0,
2662        };
2663        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
2664        let __s_b = self.gpu.stream();
2665        let mut b = __s_b.launch_builder(&f);
2666        b.arg(q)
2667            .arg(k)
2668            .arg(v)
2669            .arg(g)
2670            .arg(beta)
2671            .arg(state_in)
2672            .arg(state_out)
2673            .arg(o)
2674            .arg(&h)
2675            .arg(acc)
2676            .arg(&b0)
2677            .arg(&tv)
2678            .arg(&scale);
2679        unsafe {
2680            b.launch(cfg)?;
2681        }
2682        Ok(())
2683    }
2684
2685    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
2686    pub fn spec_rollback_kv(
2687        &self,
2688        len_ptrs: &CudaSlice<u64>,
2689        saved: &CudaSlice<i32>,
2690        acc: &CudaSlice<u32>,
2691        base: usize,
2692        n_layer: usize,
2693    ) -> Result<(), Box<dyn std::error::Error>> {
2694        let f = self.func("spec_rollback_kv");
2695        let (b, nl) = (base as i32, n_layer as i32);
2696        let cfg = LaunchConfig {
2697            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2698            block_dim: (64, 1, 1),
2699            shared_mem_bytes: 0,
2700        };
2701        let __s_bl = self.gpu.stream();
2702        let mut bl = __s_bl.launch_builder(&f);
2703        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
2704        unsafe {
2705            bl.launch(cfg)?;
2706        }
2707        Ok(())
2708    }
2709
2710    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
2711    pub fn spec_fork_valid(
2712        &self,
2713        acc: &CudaSlice<u32>,
2714        optimistic_pending: u32,
2715        valid: &mut CudaSlice<u32>,
2716    ) -> Result<(), Box<dyn std::error::Error>> {
2717        let f = self.func("spec_fork_valid");
2718        let cfg = LaunchConfig {
2719            grid_dim: (1, 1, 1),
2720            block_dim: (1, 1, 1),
2721            shared_mem_bytes: 0,
2722        };
2723        let __s_bl = self.gpu.stream();
2724        let mut bl = __s_bl.launch_builder(&f);
2725        bl.arg(acc).arg(&optimistic_pending).arg(valid);
2726        unsafe {
2727            bl.launch(cfg)?;
2728        }
2729        Ok(())
2730    }
2731
2732    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
2733    pub fn spec_fork_reconcile_kv(
2734        &self,
2735        len_ptrs: &CudaSlice<u64>,
2736        saved: &CudaSlice<i32>,
2737        acc: &CudaSlice<u32>,
2738        valid: &CudaSlice<u32>,
2739        base: usize,
2740        n_layer: usize,
2741    ) -> Result<(), Box<dyn std::error::Error>> {
2742        let f = self.func("spec_fork_reconcile_kv");
2743        let (b, nl) = (base as i32, n_layer as i32);
2744        let cfg = LaunchConfig {
2745            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2746            block_dim: (64, 1, 1),
2747            shared_mem_bytes: 0,
2748        };
2749        let __s_bl = self.gpu.stream();
2750        let mut bl = __s_bl.launch_builder(&f);
2751        bl.arg(len_ptrs)
2752            .arg(saved)
2753            .arg(acc)
2754            .arg(valid)
2755            .arg(&b)
2756            .arg(&nl);
2757        unsafe {
2758            bl.launch(cfg)?;
2759        }
2760        Ok(())
2761    }
2762
2763    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
2764    pub fn spec_fork_restore_f32(
2765        &self,
2766        snapshot: &CudaSlice<f32>,
2767        state: &mut CudaSlice<f32>,
2768        valid: &CudaSlice<u32>,
2769    ) -> Result<(), Box<dyn std::error::Error>> {
2770        assert_eq!(
2771            snapshot.len(),
2772            state.len(),
2773            "fork recurrent snapshot shape mismatch"
2774        );
2775        let f = self.func("spec_fork_restore_f32");
2776        let n = state.len() as i32;
2777        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
2778        let cfg = LaunchConfig {
2779            grid_dim: (blocks, 1, 1),
2780            block_dim: (256, 1, 1),
2781            shared_mem_bytes: 0,
2782        };
2783        let __s_bl = self.gpu.stream();
2784        let mut bl = __s_bl.launch_builder(&f);
2785        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
2786        unsafe {
2787            bl.launch(cfg)?;
2788        }
2789        Ok(())
2790    }
2791
2792    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
2793    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
2794    pub fn spec_seed_gather(
2795        &self,
2796        vx: &CudaSlice<f32>,
2797        fill_prev: &CudaSlice<f32>,
2798        acc: &CudaSlice<u32>,
2799        h_seed: &mut CudaSlice<f32>,
2800        base: usize,
2801        n_embd: usize,
2802    ) -> Result<(), Box<dyn std::error::Error>> {
2803        let f = self.func("spec_seed_gather");
2804        let (b, ne) = (base as i32, n_embd as i32);
2805        let cfg = LaunchConfig {
2806            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
2807            block_dim: (256, 1, 1),
2808            shared_mem_bytes: 0,
2809        };
2810        let __s_bl = self.gpu.stream();
2811        let mut bl = __s_bl.launch_builder(&f);
2812        bl.arg(vx)
2813            .arg(fill_prev)
2814            .arg(acc)
2815            .arg(h_seed)
2816            .arg(&b)
2817            .arg(&ne);
2818        unsafe {
2819            bl.launch(cfg)?;
2820        }
2821        Ok(())
2822    }
2823
2824    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2825    pub fn spec_accept_greedy(
2826        &self,
2827        preds: &CudaSlice<u32>,
2828        draft: &CudaSlice<u32>,
2829        last_pred: u32,
2830        base: usize,
2831        k_round: usize,
2832        out: &mut CudaSlice<u32>,
2833    ) -> Result<(), Box<dyn std::error::Error>> {
2834        let f = self.func("spec_accept_greedy");
2835        let (b, k) = (base as i32, k_round as i32);
2836        let cfg = LaunchConfig {
2837            grid_dim: (1, 1, 1),
2838            block_dim: (32, 1, 1),
2839            shared_mem_bytes: 0,
2840        };
2841        let __s_bl = self.gpu.stream();
2842        let mut bl = __s_bl.launch_builder(&f);
2843        bl.arg(preds)
2844            .arg(draft)
2845            .arg(&last_pred)
2846            .arg(&b)
2847            .arg(&k)
2848            .arg(out);
2849        unsafe {
2850            bl.launch(cfg)?;
2851        }
2852        Ok(())
2853    }
2854
2855    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2856    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2857    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2858
2859    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2860    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2861    pub fn gumbel_perturb(
2862        &self,
2863        x: &CudaSlice<f32>,
2864        y: &mut CudaSlice<f32>,
2865        n: usize,
2866        seed: u64,
2867        stream_pos: u32,
2868        temp: f32,
2869    ) -> Result<(), Box<dyn std::error::Error>> {
2870        let f = self.func("gumbel_perturb_f32");
2871        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2872        let cfg = LaunchConfig {
2873            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2874            block_dim: (256, 1, 1),
2875            shared_mem_bytes: 0,
2876        };
2877        let __s_b = self.gpu.stream();
2878        let mut b = __s_b.launch_builder(&f);
2879        b.arg(x)
2880            .arg(&mut *y)
2881            .arg(&ni)
2882            .arg(&slo)
2883            .arg(&shi)
2884            .arg(&stream_pos)
2885            .arg(&temp);
2886        unsafe {
2887            b.launch(cfg)?;
2888        }
2889        Ok(())
2890    }
2891
2892    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2893    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2894    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2895    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2896    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2897    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2898    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2899    pub fn mask_logits_col(
2900        &self,
2901        logits: &mut CudaSlice<f32>,
2902        mask: &CudaSlice<u32>,
2903        col: usize,
2904        n: usize,
2905        mask_words: usize,
2906    ) -> Result<(), Box<dyn std::error::Error>> {
2907        let f = self.func("mask_logits_f32");
2908        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2909        let cfg = LaunchConfig {
2910            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2911            block_dim: (256, 1, 1),
2912            shared_mem_bytes: 0,
2913        };
2914        let __s_b = self.gpu.stream();
2915        let mut b = __s_b.launch_builder(&f);
2916        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2917        unsafe {
2918            b.launch(cfg)?;
2919        }
2920        Ok(())
2921    }
2922
2923    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2924    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2925    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2926    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2927    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2928    /// pointer-invariance IS the serving isolation contract for sampled rows.
2929    pub fn gumbel_perturb_col(
2930        &self,
2931        x: &CudaSlice<f32>,
2932        col: usize,
2933        y: &mut CudaSlice<f32>,
2934        n: usize,
2935        seed: u64,
2936        stream_pos: u32,
2937        temp: f32,
2938    ) -> Result<(), Box<dyn std::error::Error>> {
2939        let f = self.func("gumbel_perturb_f32");
2940        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2941        let col_view = x.slice(col * n..(col + 1) * n);
2942        let cfg = LaunchConfig {
2943            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2944            block_dim: (256, 1, 1),
2945            shared_mem_bytes: 0,
2946        };
2947        let __s_b = self.gpu.stream();
2948        let mut b = __s_b.launch_builder(&f);
2949        b.arg(&col_view)
2950            .arg(&mut *y)
2951            .arg(&ni)
2952            .arg(&slo)
2953            .arg(&shi)
2954            .arg(&stream_pos)
2955            .arg(&temp);
2956        unsafe {
2957            b.launch(cfg)?;
2958        }
2959        Ok(())
2960    }
2961
2962    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
2963    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
2964    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
2965    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
2966    /// the serving isolation contract for sampled rows).
2967    #[allow(clippy::too_many_arguments)]
2968    pub fn gumbel_perturb_filtered_col(
2969        &self,
2970        x: &CudaSlice<f32>,
2971        col: usize,
2972        y: &mut CudaSlice<f32>,
2973        n: usize,
2974        seed: u64,
2975        stream_pos: u32,
2976        temp: f32,
2977        stat_max: &CudaSlice<f32>,
2978        stat_th: &CudaSlice<f32>,
2979        stat_idx: usize,
2980    ) -> Result<(), Box<dyn std::error::Error>> {
2981        let f = self.func("gumbel_perturb_filtered_col_f32");
2982        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2983        let (ci, si) = (col as i32, stat_idx as i32);
2984        let cfg = LaunchConfig {
2985            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2986            block_dim: (256, 1, 1),
2987            shared_mem_bytes: 0,
2988        };
2989        let __s_b = self.gpu.stream();
2990        let mut b = __s_b.launch_builder(&f);
2991        b.arg(x)
2992            .arg(&ci)
2993            .arg(&mut *y)
2994            .arg(&ni)
2995            .arg(&slo)
2996            .arg(&shi)
2997            .arg(&stream_pos)
2998            .arg(&temp)
2999            .arg(stat_max)
3000            .arg(stat_th)
3001            .arg(&si);
3002        unsafe {
3003            b.launch(cfg)?;
3004        }
3005        Ok(())
3006    }
3007
3008    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
3009    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
3010    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
3011    /// reads it (counter is data, not state — graph-replay-safe).
3012    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
3013        let f = self.func("memra_sctr_inc");
3014        let cfg = LaunchConfig {
3015            grid_dim: (1, 1, 1),
3016            block_dim: (1, 1, 1),
3017            shared_mem_bytes: 0,
3018        };
3019        let __s_b = self.gpu.stream();
3020        let mut b = __s_b.launch_builder(&f);
3021        b.arg(&mut *ctr);
3022        unsafe {
3023            b.launch(cfg)?;
3024        }
3025        Ok(())
3026    }
3027
3028    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3029    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3030    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3031    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3032    pub fn gumbel_perturb_ctr(
3033        &self,
3034        x: &CudaSlice<f32>,
3035        y: &mut CudaSlice<f32>,
3036        n: usize,
3037        seed: u64,
3038        ctr: &CudaSlice<u32>,
3039        temp: f32,
3040    ) -> Result<(), Box<dyn std::error::Error>> {
3041        let f = self.func("gumbel_perturb_ctr_f32");
3042        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3043        let cfg = LaunchConfig {
3044            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3045            block_dim: (256, 1, 1),
3046            shared_mem_bytes: 0,
3047        };
3048        let __s_b = self.gpu.stream();
3049        let mut b = __s_b.launch_builder(&f);
3050        b.arg(x)
3051            .arg(&mut *y)
3052            .arg(&ni)
3053            .arg(&slo)
3054            .arg(&shi)
3055            .arg(ctr)
3056            .arg(&temp);
3057        unsafe {
3058            b.launch(cfg)?;
3059        }
3060        Ok(())
3061    }
3062
3063    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3064    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3065    /// (smallest-index tie-break — matches the argmax-gate contract).
3066    pub fn softmax_gather(
3067        &self,
3068        x: &CudaSlice<f32>,
3069        row_stride: usize,
3070        ids: &CudaSlice<u32>,
3071        rows: &CudaSlice<i32>,
3072        out: &mut CudaSlice<f32>,
3073        n: usize,
3074        npair: usize,
3075        temp: f32,
3076    ) -> Result<(), Box<dyn std::error::Error>> {
3077        let f = self.func("softmax_gather_f32");
3078        let (ni, rs) = (n as i32, row_stride as i64);
3079        let np = npair as i32;
3080        let cfg = LaunchConfig {
3081            grid_dim: (npair as u32, 1, 1),
3082            block_dim: (256, 1, 1),
3083            shared_mem_bytes: 0,
3084        };
3085        let __s_b = self.gpu.stream();
3086        let mut b = __s_b.launch_builder(&f);
3087        b.arg(x)
3088            .arg(&rs)
3089            .arg(ids)
3090            .arg(rows)
3091            .arg(&mut *out)
3092            .arg(&ni)
3093            .arg(&np)
3094            .arg(&temp);
3095        unsafe {
3096            b.launch(cfg)?;
3097        }
3098        Ok(())
3099    }
3100
3101    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3102    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3103    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3104    pub fn residual_sample(
3105        &self,
3106        p: &CudaSlice<f32>,
3107        q: Option<&CudaSlice<f32>>,
3108        n: usize,
3109        temp: f32,
3110        seed: u64,
3111        stream_pos: u32,
3112        out_tok: &mut CudaSlice<u32>,
3113    ) -> Result<(), Box<dyn std::error::Error>> {
3114        let f = self.func("residual_sample_f32");
3115        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3116        let nth = 1024u32;
3117        let cfg = LaunchConfig {
3118            grid_dim: (1, 1, 1),
3119            block_dim: (nth, 1, 1),
3120            shared_mem_bytes: 0,
3121        };
3122        let has_q: i32 = q.is_some() as i32;
3123        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3124        let __s_b = self.gpu.stream();
3125        let mut b = __s_b.launch_builder(&f);
3126        b.arg(p)
3127            .arg(qbuf)
3128            .arg(&has_q)
3129            .arg(&ni)
3130            .arg(&temp)
3131            .arg(&slo)
3132            .arg(&shi)
3133            .arg(&stream_pos)
3134            .arg(&mut *out_tok);
3135        unsafe {
3136            b.launch(cfg)?;
3137        }
3138        Ok(())
3139    }
3140
3141    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3142    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3143    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3144    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3145    pub fn with_moe_cache<R>(
3146        &self,
3147        max_block_bytes: usize,
3148        f: impl FnOnce(
3149            &mut crate::moe_cache::MoeSlotCache,
3150            &Engine,
3151        ) -> Result<R, Box<dyn std::error::Error>>,
3152    ) -> Result<R, Box<dyn std::error::Error>> {
3153        let mut guard = self.moe_cache.lock().unwrap();
3154        if guard.is_none() {
3155            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3156        }
3157        let cache = guard.as_mut().unwrap();
3158        f(cache, self)
3159    }
3160
3161    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3162    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3163    pub fn freeze_moe_cache(&self) {
3164        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3165            cache.freeze();
3166        }
3167    }
3168
3169    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3170    /// Never constructs a cache.
3171    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3172        self.moe_cache
3173            .lock()
3174            .unwrap()
3175            .as_ref()
3176            .map(crate::moe_cache::MoeSlotCache::export_residency)
3177    }
3178
3179    pub(crate) fn moe_cache_frozen(&self) -> bool {
3180        self.moe_cache
3181            .lock()
3182            .unwrap()
3183            .as_ref()
3184            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3185    }
3186
3187    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3188    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3189    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3190    /// while leaving the profiling warmup's established batched behavior untouched.
3191    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3192    /// tokenwise arm anyway.)
3193    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3194        crate::cpu_experts::configured()
3195            && self.moe_cache_frozen()
3196            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3197    }
3198
3199    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3200    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3201        assert!(
3202            self.moe_cache.lock().unwrap().is_none(),
3203            "MoE cache layout configured after cache construction"
3204        );
3205        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3206    }
3207
3208    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3209        self.moe_cache_layout.lock().unwrap().clone()
3210    }
3211
3212    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3213    pub fn moe_cache_enabled() -> bool {
3214        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3215    }
3216
3217    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3218    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3219    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3220        let guard = self.moe_cache.lock().unwrap();
3221        guard
3222            .as_ref()
3223            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3224    }
3225
3226    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3227    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3228    /// callers compare a before/after snapshot around a decode window.
3229    pub fn cpu_expert_stats(
3230        &self,
3231    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3232        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3233    }
3234
3235    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3236    /// the backend tail that resident-GPU expert work did not hide.
3237    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3238        crate::cpu_experts::predictor_stats()
3239    }
3240
3241    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3242        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3243    }
3244
3245    /// CPU-routed expert selections grouped by how many of their three projections were already
3246    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3247    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3248        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3249    }
3250
3251    /// Positioned-read proof-backend counters:
3252    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3253    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3254        let guard = self.moe_cache.lock().unwrap();
3255        guard
3256            .as_ref()
3257            .and_then(|cache| cache.pread_stats())
3258            .map(|stats| {
3259                (
3260                    stats.reads,
3261                    stats.bytes,
3262                    stats.read_errors,
3263                    stats.short_reads,
3264                    stats.fallbacks,
3265                    stats.buffer_waits,
3266                    stats.ring_full,
3267                )
3268            })
3269    }
3270
3271    /// Spill configuration values that warned and substituted their documented defaults.
3272    pub fn spill_config_fallbacks(&self) -> u64 {
3273        crate::spill_pread::config_fallbacks()
3274    }
3275
3276    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3277    pub fn moe_cache_reset_counters(&self) {
3278        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3279            c.reset_counters();
3280        }
3281    }
3282
3283    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3284        Ok(self.gpu.stream().clone_htod(v)?)
3285    }
3286
3287    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3288    /// past the final q4_0 block through their aligned window — the bytes never reach a
3289    /// result (funnelshift discards them) but must be mapped memory.
3290    pub fn htod_bytes_padded(
3291        &self,
3292        v: &[u8],
3293        pad: usize,
3294    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3295        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3296        {
3297            let mut view = d.slice_mut(0..v.len());
3298            self.gpu.stream().memcpy_htod(v, &mut view)?;
3299        }
3300        Ok(d)
3301    }
3302
3303    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3304    pub fn copy_into(
3305        &self,
3306        dst: &mut CudaSlice<f32>,
3307        off: usize,
3308        src: &CudaSlice<f32>,
3309        len: usize,
3310    ) -> Result<(), Box<dyn std::error::Error>> {
3311        let mut view = dst.slice_mut(off..off + len);
3312        self.gpu
3313            .stream()
3314            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3315        Ok(())
3316    }
3317
3318    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3319    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3320    pub fn copy_u8_into(
3321        &self,
3322        dst: &mut CudaSlice<u8>,
3323        off: usize,
3324        src: &CudaSlice<u8>,
3325        len: usize,
3326    ) -> Result<(), Box<dyn std::error::Error>> {
3327        let mut view = dst.slice_mut(off..off + len);
3328        self.gpu
3329            .stream()
3330            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3331        Ok(())
3332    }
3333
3334    /// D2D byte-range copy with explicit source and destination offsets.
3335    pub fn copy_u8_range_into(
3336        &self,
3337        dst: &mut CudaSlice<u8>,
3338        dst_off: usize,
3339        src: &CudaSlice<u8>,
3340        src_off: usize,
3341        len: usize,
3342    ) -> Result<(), Box<dyn std::error::Error>> {
3343        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3344        self.gpu
3345            .stream()
3346            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3347        Ok(())
3348    }
3349
3350    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3351    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3352    /// keeping the audited attention range contiguous without changing its absolute start.
3353    pub fn prepare_kv_append(
3354        &self,
3355        kv: &mut crate::cache::KvLayer,
3356        retain_from: usize,
3357        append_rows: usize,
3358    ) -> Result<usize, Box<dyn std::error::Error>> {
3359        let Some(plan) = kv
3360            .ring
3361            .as_ref()
3362            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3363            .transpose()?
3364        else {
3365            return Ok(kv.len);
3366        };
3367        match plan {
3368            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3369            crate::cache::KvRingAppend::Rebase {
3370                src_row,
3371                keep_rows,
3372                new_base,
3373                write_row,
3374            } => {
3375                if keep_rows > 0 {
3376                    let k_len = keep_rows * kv.k_tok_bytes;
3377                    let v_len = keep_rows * kv.v_tok_bytes;
3378                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3379                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3380                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3381                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3382                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3383                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3384                }
3385                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3386                Ok(write_row)
3387            }
3388        }
3389    }
3390
3391    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3392    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3393    pub fn htod_u8_into(
3394        &self,
3395        dst: &mut CudaSlice<u8>,
3396        off: usize,
3397        src: &[u8],
3398    ) -> Result<(), Box<dyn std::error::Error>> {
3399        let mut view = dst.slice_mut(off..off + src.len());
3400        self.gpu.stream().memcpy_htod(src, &mut view)?;
3401        Ok(())
3402    }
3403
3404    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3405        b.slice(0..len)
3406    }
3407
3408    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3409    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3410    pub fn view_u8_range<'a>(
3411        &self,
3412        b: &'a CudaSlice<u8>,
3413        start: usize,
3414        end: usize,
3415    ) -> cudarc::driver::CudaView<'a, u8> {
3416        b.slice(start..end)
3417    }
3418    pub fn view_u8<'a>(
3419        &self,
3420        b: &'a CudaSlice<u8>,
3421        len: usize,
3422    ) -> cudarc::driver::CudaView<'a, u8> {
3423        b.slice(0..len)
3424    }
3425
3426    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3427    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3428    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3429    pub fn append_kv_quantized(
3430        &self,
3431        k_row: &CudaSlice<f32>,
3432        v_row: &CudaSlice<f32>,
3433        kc: &mut CudaSlice<u8>,
3434        vc: &mut CudaSlice<u8>,
3435        t: usize,
3436        kv_dim_k: usize,
3437        kv_dim_v: usize,
3438        k_tok_bytes: usize,
3439        v_tok_bytes: usize,
3440        g: bool,
3441    ) -> Result<(), Box<dyn std::error::Error>> {
3442        let f = if g {
3443            self.func_g("append_quantize_kv_q8_0_q5_1")
3444        } else {
3445            self.func("append_quantize_kv_q8_0_q5_1")
3446        };
3447        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3448        let cfg = LaunchConfig {
3449            grid_dim: (nblk, 1, 1),
3450            block_dim: (32, 1, 1),
3451            shared_mem_bytes: 0,
3452        };
3453        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3454        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3455        let __s_b = self.gpu.stream();
3456        let mut b = __s_b.launch_builder(&f);
3457        b.arg(k_row)
3458            .arg(v_row)
3459            .arg(kc)
3460            .arg(vc)
3461            .arg(&ti)
3462            .arg(&kdk)
3463            .arg(&kdv)
3464            .arg(&ktb)
3465            .arg(&vtb);
3466        unsafe {
3467            b.launch(cfg)?;
3468        }
3469        Ok(())
3470    }
3471
3472    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3473    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3474    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3475    pub fn append_kv_quantized_dc(
3476        &self,
3477        k_row: &CudaSlice<f32>,
3478        v_row: &CudaSlice<f32>,
3479        kc: &mut CudaSlice<u8>,
3480        vc: &mut CudaSlice<u8>,
3481        t_dev: &CudaSlice<i32>,
3482        kv_dim_k: usize,
3483        kv_dim_v: usize,
3484        k_tok_bytes: usize,
3485        v_tok_bytes: usize,
3486        g: bool,
3487    ) -> Result<(), Box<dyn std::error::Error>> {
3488        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3489        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3490        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3491        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3492        if Self::pdl_on() && Self::pdl_wb_on() {
3493            use cudarc::driver::{DevicePtr, DevicePtrMut};
3494            let s = &self.gpu.stream();
3495            let (pk, _g0) = k_row.device_ptr(s);
3496            let (pv, _g1) = v_row.device_ptr(s);
3497            let (pkc, _g2) = kc.device_ptr_mut(s);
3498            let (pvc, _g3) = vc.device_ptr_mut(s);
3499            let (pt, _g4) = t_dev.device_ptr(s);
3500            let mut ps = [
3501                &pk as *const _ as *mut std::ffi::c_void,
3502                &pv as *const _ as *mut _,
3503                &pkc as *const _ as *mut _,
3504                &pvc as *const _ as *mut _,
3505                &pt as *const _ as *mut _,
3506                &kdk as *const _ as *mut _,
3507                &kdv as *const _ as *mut _,
3508                &ktb as *const _ as *mut _,
3509                &vtb as *const _ as *mut _,
3510            ];
3511            unsafe {
3512                self.launch_pdl_flash(
3513                    g,
3514                    "append_quantize_kv_q8_0_q5_1_dc",
3515                    (nblk, 1, 1),
3516                    (32, 1, 1),
3517                    0,
3518                    &mut ps,
3519                )?;
3520            }
3521            return Ok(());
3522        }
3523        let f = if g {
3524            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3525        } else {
3526            self.func("append_quantize_kv_q8_0_q5_1_dc")
3527        };
3528        let cfg = LaunchConfig {
3529            grid_dim: (nblk, 1, 1),
3530            block_dim: (32, 1, 1),
3531            shared_mem_bytes: 0,
3532        };
3533        let __s_b = self.gpu.stream();
3534        let mut b = __s_b.launch_builder(&f);
3535        b.arg(k_row)
3536            .arg(v_row)
3537            .arg(kc)
3538            .arg(vc)
3539            .arg(t_dev)
3540            .arg(&kdk)
3541            .arg(&kdv)
3542            .arg(&ktb)
3543            .arg(&vtb);
3544        unsafe {
3545            b.launch(cfg)?;
3546        }
3547        Ok(())
3548    }
3549
3550    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3551    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3552    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3553    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3554    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3555    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3556    #[allow(clippy::too_many_arguments)]
3557    pub fn append_kv_quantized_rows(
3558        &self,
3559        k_rows: &CudaSlice<f32>,
3560        v_rows: &CudaSlice<f32>,
3561        kc: &mut CudaSlice<u8>,
3562        vc: &mut CudaSlice<u8>,
3563        t0: usize,
3564        t: usize,
3565        kv_dim_k: usize,
3566        kv_dim_v: usize,
3567        k_tok_bytes: usize,
3568        v_tok_bytes: usize,
3569        g: bool,
3570    ) -> Result<(), Box<dyn std::error::Error>> {
3571        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3572            for i in 0..t {
3573                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3574                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3575                self.append_kv_quantized_view(
3576                    &k_row,
3577                    &v_row,
3578                    kc,
3579                    vc,
3580                    t0 + i,
3581                    kv_dim_k,
3582                    kv_dim_v,
3583                    k_tok_bytes,
3584                    v_tok_bytes,
3585                    g,
3586                )?;
3587            }
3588            return Ok(());
3589        }
3590        let f = if g {
3591            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3592        } else {
3593            self.func("append_quantize_kv_q8_0_q5_1_rows")
3594        };
3595        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3596        let cfg = LaunchConfig {
3597            grid_dim: (nblk, t as u32, 1),
3598            block_dim: (32, 1, 1),
3599            shared_mem_bytes: 0,
3600        };
3601        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3602        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3603        let __s_b = self.gpu.stream();
3604        let mut b = __s_b.launch_builder(&f);
3605        b.arg(k_rows)
3606            .arg(v_rows)
3607            .arg(kc)
3608            .arg(vc)
3609            .arg(&t0i)
3610            .arg(&kdk)
3611            .arg(&kdv)
3612            .arg(&ktb)
3613            .arg(&vtb);
3614        unsafe {
3615            b.launch(cfg)?;
3616        }
3617        Ok(())
3618    }
3619
3620    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3621    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3622    /// later, inside a captured graph) without a host round-trip.
3623    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3624        let f = self.func("inc_i32");
3625        let cfg = LaunchConfig {
3626            grid_dim: (1, 1, 1),
3627            block_dim: (1, 1, 1),
3628            shared_mem_bytes: 0,
3629        };
3630        let __s_b = self.gpu.stream();
3631        let mut b = __s_b.launch_builder(&f);
3632        b.arg(p);
3633        unsafe {
3634            b.launch(cfg)?;
3635        }
3636        Ok(())
3637    }
3638
3639    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3640    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3641    pub fn append_kv_quantized_view(
3642        &self,
3643        k_row: &cudarc::driver::CudaView<f32>,
3644        v_row: &cudarc::driver::CudaView<f32>,
3645        kc: &mut CudaSlice<u8>,
3646        vc: &mut CudaSlice<u8>,
3647        t: usize,
3648        kv_dim_k: usize,
3649        kv_dim_v: usize,
3650        k_tok_bytes: usize,
3651        v_tok_bytes: usize,
3652        g: bool,
3653    ) -> Result<(), Box<dyn std::error::Error>> {
3654        let f = if g {
3655            self.func_g("append_quantize_kv_q8_0_q5_1")
3656        } else {
3657            self.func("append_quantize_kv_q8_0_q5_1")
3658        };
3659        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3660        let cfg = LaunchConfig {
3661            grid_dim: (nblk, 1, 1),
3662            block_dim: (32, 1, 1),
3663            shared_mem_bytes: 0,
3664        };
3665        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3666        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3667        let __s_b = self.gpu.stream();
3668        let mut b = __s_b.launch_builder(&f);
3669        b.arg(k_row)
3670            .arg(v_row)
3671            .arg(kc)
3672            .arg(vc)
3673            .arg(&ti)
3674            .arg(&kdk)
3675            .arg(&kdv)
3676            .arg(&ktb)
3677            .arg(&vtb);
3678        unsafe {
3679            b.launch(cfg)?;
3680        }
3681        Ok(())
3682    }
3683
3684    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3685    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3686    pub fn copy_view_into(
3687        &self,
3688        dst: &mut CudaSlice<f32>,
3689        off: usize,
3690        src: &cudarc::driver::CudaView<f32>,
3691        len: usize,
3692    ) -> Result<(), Box<dyn std::error::Error>> {
3693        let mut view = dst.slice_mut(off..off + len);
3694        self.gpu
3695            .stream()
3696            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3697        Ok(())
3698    }
3699
3700    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3701    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3702    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3703    pub fn clone_dtod(
3704        &self,
3705        src: &CudaSlice<f32>,
3706    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3707        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3708        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3709        Ok(dst)
3710    }
3711
3712    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3713    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3714    pub fn dtod_copy_view(
3715        &self,
3716        src: &cudarc::driver::CudaView<f32>,
3717        dst: &mut CudaSlice<f32>,
3718    ) -> Result<(), Box<dyn std::error::Error>> {
3719        self.gpu.stream().memcpy_dtod(src, dst)?;
3720        Ok(())
3721    }
3722
3723    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3724    pub fn dtod_copy_view_i8(
3725        &self,
3726        src: &cudarc::driver::CudaView<i8>,
3727        dst: &mut CudaSlice<i8>,
3728    ) -> Result<(), Box<dyn std::error::Error>> {
3729        self.gpu.stream().memcpy_dtod(src, dst)?;
3730        Ok(())
3731    }
3732
3733    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3734    pub fn dtod_copy_into(
3735        &self,
3736        src: &CudaSlice<f32>,
3737        dst: &mut CudaSlice<f32>,
3738        offset: usize,
3739    ) -> Result<(), Box<dyn std::error::Error>> {
3740        let n = src.len();
3741        let mut dv = dst.slice_mut(offset..offset + n);
3742        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3743        Ok(())
3744    }
3745
3746    /// Batched uniform-size D2D copy (engine-bundle slice 1, DSF-ROUNDCOST-20260820 §1.1):
3747    /// `n` disjoint regions of `words` f32 each; `table` = [src_0..src_{n-1}, dst_0..dst_{n-1}]
3748    /// raw device pointers. ONE kernel launch replaces `n` memcpy_dtod dispatches — the dspark
3749    /// round's snap/commit copy dribble (~0.9 ms/round of dispatch serialization measured).
3750    /// Bytes and stream order are identical to the memcpy sequence it replaces.
3751    pub fn copy_batch_uniform_f32(
3752        &self,
3753        table: &CudaSlice<u64>,
3754        n: usize,
3755        words: usize,
3756    ) -> Result<(), Box<dyn std::error::Error>> {
3757        if n == 0 || words == 0 {
3758            return Ok(());
3759        }
3760        debug_assert!(
3761            table.len() >= 2 * n,
3762            "pointer table must hold n srcs + n dsts"
3763        );
3764        let f = self.func("copy_batch_uniform_f32");
3765        // Enough blocks to stream a multi-MB region, few enough that (chunks x n) stays a
3766        // sane grid: 512K-word ssm regions get 48 grid-striding blocks each.
3767        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
3768        let (ni, wi) = (n as i32, words as i32);
3769        let cfg = LaunchConfig {
3770            grid_dim: (chunks, n as u32, 1),
3771            block_dim: (256, 1, 1),
3772            shared_mem_bytes: 0,
3773        };
3774        let __s = self.gpu.stream();
3775        let mut b = __s.launch_builder(&f);
3776        b.arg(table).arg(&ni).arg(&wi);
3777        unsafe {
3778            b.launch(cfg)?;
3779        }
3780        Ok(())
3781    }
3782
3783    /// H2D refresh of an EXISTING u64 pointer table IN PLACE (stable pointer — the batched
3784    /// state-copy tables are refreshed per round because the GDN ssm handles ping-pong).
3785    pub fn htod_u64_into(
3786        &self,
3787        v: &[u64],
3788        dst: &mut CudaSlice<u64>,
3789    ) -> Result<(), Box<dyn std::error::Error>> {
3790        let mut view = dst.slice_mut(0..v.len());
3791        self.gpu.stream().memcpy_htod(v, &mut view)?;
3792        Ok(())
3793    }
3794
3795    /// Indirect-source copy (engine-bundle slice 3): the src ADDRESS is loaded from a
3796    /// device pointer-table entry at run time, so a captured graph follows the gdn
3797    /// ping-pong through the same table its scan kernels read — a baked memcpy node
3798    /// would keep the capture-time physical buffer. `dst_off`/`words` in f32 elements.
3799    pub fn copy_indirect_src_f32(
3800        &self,
3801        src_entry: &cudarc::driver::CudaView<u64>,
3802        dst: &mut CudaSlice<f32>,
3803        dst_off: usize,
3804        words: usize,
3805    ) -> Result<(), Box<dyn std::error::Error>> {
3806        let f = self.func("copy_indirect_src_f32");
3807        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
3808        let wi = words as i32;
3809        let cfg = LaunchConfig {
3810            grid_dim: (chunks, 1, 1),
3811            block_dim: (256, 1, 1),
3812            shared_mem_bytes: 0,
3813        };
3814        let mut dv = dst.slice_mut(dst_off..dst_off + words);
3815        let __s = self.gpu.stream();
3816        let mut b = __s.launch_builder(&f);
3817        b.arg(src_entry).arg(&mut dv).arg(&wi);
3818        unsafe {
3819            b.launch(cfg)?;
3820        }
3821        Ok(())
3822    }
3823
3824    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3825    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3826        self.alloc_uninit::<i8>(n)
3827    }
3828
3829    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3830    pub fn qmatvec(
3831        &self,
3832        w: &CudaSlice<u8>,
3833        x: &CudaSlice<f32>,
3834        m: usize,
3835        in_f: usize,
3836        out_f: usize,
3837        qtype: i32,
3838        row_bytes: usize,
3839    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3840        let f = self.func("qmatvec_f32");
3841        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3842        let cfg = LaunchConfig {
3843            grid_dim: (out_f as u32, m as u32, 1),
3844            block_dim: (256, 1, 1),
3845            shared_mem_bytes: 0,
3846        };
3847        let (inf, outf, mi, qt, rb) =
3848            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3849        let __s_b = self.gpu.stream();
3850        let mut b = __s_b.launch_builder(&f);
3851        b.arg(w)
3852            .arg(x)
3853            .arg(&mut y)
3854            .arg(&inf)
3855            .arg(&outf)
3856            .arg(&mi)
3857            .arg(&qt)
3858            .arg(&rb);
3859        unsafe {
3860            b.launch(cfg)?;
3861        }
3862        Ok(y)
3863    }
3864
3865    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3866    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3867        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3868        self.keep_if_capturing(&s);
3869        Ok(s)
3870    }
3871
3872    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3873    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3874    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3875    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3876        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3877        self.keep_if_capturing(&s);
3878        Ok(s)
3879    }
3880
3881    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3882    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3883    pub fn memset_zeros_view(
3884        &self,
3885        dst: &mut cudarc::driver::CudaViewMut<f32>,
3886    ) -> Result<(), Box<dyn std::error::Error>> {
3887        self.gpu.stream().memset_zeros(dst)?;
3888        Ok(())
3889    }
3890
3891    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3892    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3893    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3894    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3895    /// stream would require an event).
3896    pub fn stage_expert(
3897        &self,
3898        host_bytes: &[u8],
3899        scratch: &mut CudaSlice<u8>,
3900        off: usize,
3901    ) -> Result<(), Box<dyn std::error::Error>> {
3902        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3903        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3904        Ok(())
3905    }
3906
3907    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3908    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3909    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3910    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3911    /// One CTA per token row, 256 threads (one per expert).
3912    pub fn moe_router_topk(
3913        &self,
3914        logits: &CudaSlice<f32>,
3915        t: usize,
3916        n_expert: usize,
3917        n_used: usize,
3918    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3919        let f = self.func("moe_router_topk_f32");
3920        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3921        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3922        let cfg = LaunchConfig {
3923            grid_dim: (t as u32, 1, 1),
3924            block_dim: (n_expert as u32, 1, 1),
3925            shared_mem_bytes: 0,
3926        };
3927        let (ne, nu) = (n_expert as i32, n_used as i32);
3928        let __s_b = self.gpu.stream();
3929        let mut b = __s_b.launch_builder(&f);
3930        b.arg(logits)
3931            .arg(&mut sel_idx)
3932            .arg(&mut sel_w)
3933            .arg(&ne)
3934            .arg(&nu);
3935        unsafe {
3936            b.launch(cfg)?;
3937        }
3938        Ok((sel_idx, sel_w))
3939    }
3940
3941    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3942    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3943    pub fn moe_router_topk_scaled(
3944        &self,
3945        logits: &CudaSlice<f32>,
3946        t: usize,
3947        n_expert: usize,
3948        n_used: usize,
3949        ex_scale: &CudaSlice<f32>,
3950    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3951        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3952        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3953        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3954        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3955        let f = self.func("moe_router_topk_scaled_f32");
3956        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3957        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3958        let cfg = LaunchConfig {
3959            grid_dim: (t as u32, 1, 1),
3960            block_dim: (n_expert as u32, 1, 1),
3961            shared_mem_bytes: 0,
3962        };
3963        let (ne, nu) = (n_expert as i32, n_used as i32);
3964        let __s_b = self.gpu.stream();
3965        let mut b = __s_b.launch_builder(&f);
3966        b.arg(logits)
3967            .arg(&mut sel_idx)
3968            .arg(&mut sel_w)
3969            .arg(&ne)
3970            .arg(&nu)
3971            .arg(ex_scale);
3972        unsafe {
3973            b.launch(cfg)?;
3974        }
3975        Ok((sel_idx, sel_w))
3976    }
3977
3978    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3979    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3980    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3981    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3982    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3983    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3984    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3985    pub fn moe_router_topk_host(
3986        &self,
3987        logits: &CudaSlice<f32>,
3988        t: usize,
3989        n_expert: usize,
3990        n_used: usize,
3991    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3992        let f = self.func("moe_router_topk_f32");
3993        let n = t * n_used;
3994        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
3995        let mut sel_w = self.alloc_uninit::<f32>(n)?;
3996        let cfg = LaunchConfig {
3997            grid_dim: (t as u32, 1, 1),
3998            block_dim: (n_expert as u32, 1, 1),
3999            shared_mem_bytes: 0,
4000        };
4001        let (ne, nu) = (n_expert as i32, n_used as i32);
4002        let __s_b = self.gpu.stream();
4003        let mut b = __s_b.launch_builder(&f);
4004        b.arg(logits)
4005            .arg(&mut sel_idx)
4006            .arg(&mut sel_w)
4007            .arg(&ne)
4008            .arg(&nu);
4009        unsafe {
4010            b.launch(cfg)?;
4011        }
4012        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
4013        let bytes = n * 8;
4014        let mut guard = self.router_stage.lock().unwrap();
4015        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4016            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4017        }
4018        let stage = guard.as_mut().unwrap();
4019        let (si, sw) = unsafe {
4020            (
4021                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4022                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4023            )
4024        };
4025        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
4026        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
4027        self.gpu.stream().synchronize()?; // ONE sync for both
4028        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4029    }
4030
4031    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
4032    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
4033    /// original expert ids before top-k. Exact key ties choose the smaller original id.
4034    #[allow(clippy::too_many_arguments)]
4035    pub fn moe_router_sigmoid_topk(
4036        &self,
4037        logits: &CudaSlice<f32>,
4038        t: usize,
4039        n_expert: usize,
4040        n_used: usize,
4041        active_count: usize,
4042        correction_bias: &CudaSlice<f32>,
4043        active: &CudaSlice<u8>,
4044        scaling_factor: f32,
4045        route_norm: bool,
4046    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4047        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
4048        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
4049            return Err(format!(
4050                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
4051            )
4052            .into());
4053        }
4054        if logits.len() < t * n_expert
4055            || correction_bias.len() != n_expert
4056            || active.len() != n_expert
4057        {
4058            return Err(format!(
4059                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
4060                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
4061            ).into());
4062        }
4063        let f = self.func("moe_router_sigmoid_topk_f32");
4064        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
4065        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
4066        let threads = n_expert.div_ceil(32) * 32;
4067        let cfg = LaunchConfig {
4068            grid_dim: (t as u32, 1, 1),
4069            block_dim: (threads as u32, 1, 1),
4070            shared_mem_bytes: 0,
4071        };
4072        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
4073        let __s_b = self.gpu.stream();
4074        let mut b = __s_b.launch_builder(&f);
4075        b.arg(logits)
4076            .arg(correction_bias)
4077            .arg(active)
4078            .arg(&mut sel_idx)
4079            .arg(&mut sel_w)
4080            .arg(&ne)
4081            .arg(&nu)
4082            .arg(&scaling_factor)
4083            .arg(&rn);
4084        unsafe {
4085            b.launch(cfg)?;
4086        }
4087        Ok((sel_idx, sel_w))
4088    }
4089
4090    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
4091    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
4092    #[allow(clippy::too_many_arguments)]
4093    pub fn moe_router_sigmoid_topk_host(
4094        &self,
4095        logits: &CudaSlice<f32>,
4096        t: usize,
4097        n_expert: usize,
4098        n_used: usize,
4099        active_count: usize,
4100        correction_bias: &CudaSlice<f32>,
4101        active: &CudaSlice<u8>,
4102        scaling_factor: f32,
4103        route_norm: bool,
4104    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4105        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4106            logits,
4107            t,
4108            n_expert,
4109            n_used,
4110            active_count,
4111            correction_bias,
4112            active,
4113            scaling_factor,
4114            route_norm,
4115        )?;
4116        let n = t * n_used;
4117        let bytes = n * 8;
4118        let mut guard = self.router_stage.lock().unwrap();
4119        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4120            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4121        }
4122        let stage = guard.as_mut().unwrap();
4123        let (si, sw) = unsafe {
4124            (
4125                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4126                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4127            )
4128        };
4129        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4130        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4131        self.gpu.stream().synchronize()?;
4132        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4133    }
4134
4135    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4136    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4137    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4138    pub fn stage_expert_async(
4139        &self,
4140        host_bytes: &[u8],
4141        scratch: &mut CudaSlice<u8>,
4142        off: usize,
4143    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4144        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4145        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4146        Ok(self.copy_stream.record_event(None)?)
4147    }
4148
4149    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4150    pub fn compute_wait(
4151        &self,
4152        ev: &cudarc::driver::CudaEvent,
4153    ) -> Result<(), Box<dyn std::error::Error>> {
4154        self.gpu.stream().wait(ev)?;
4155        Ok(())
4156    }
4157
4158    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4159    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4160    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4161    /// CudaView base+offset pointer is honored by the launch arg.
4162    pub fn qmatvec_view(
4163        &self,
4164        w: &CudaSlice<u8>,
4165        range: std::ops::Range<usize>,
4166        x: &cudarc::driver::CudaView<f32>,
4167        m: usize,
4168        in_f: usize,
4169        out_f: usize,
4170        qtype: i32,
4171        row_bytes: usize,
4172    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4173        let f = self.func("qmatvec_f32");
4174        let wv = w.slice(range); // CudaView<u8>, offset honored
4175        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4176        let cfg = LaunchConfig {
4177            grid_dim: (out_f as u32, m as u32, 1),
4178            block_dim: (256, 1, 1),
4179            shared_mem_bytes: 0,
4180        };
4181        let (inf, outf, mi, qt, rb) =
4182            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4183        let __s_b = self.gpu.stream();
4184        let mut b = __s_b.launch_builder(&f);
4185        b.arg(&wv)
4186            .arg(x)
4187            .arg(&mut y)
4188            .arg(&inf)
4189            .arg(&outf)
4190            .arg(&mi)
4191            .arg(&qt)
4192            .arg(&rb);
4193        unsafe {
4194            b.launch(cfg)?;
4195        }
4196        Ok(y)
4197    }
4198
4199    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4200    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4201    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4202    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4203    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4204    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4205    #[allow(clippy::too_many_arguments)]
4206    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4207    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4208    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4209    pub fn moe_gate_up_silu8_q8(
4210        &self,
4211        gp: WPtr8,
4212        up: WPtr8,
4213        aq: &CudaSlice<i8>,
4214        ad: &CudaSlice<f32>,
4215        in_f: usize,
4216        n_ff: usize,
4217        n_used: usize,
4218        qt_g: i32,
4219        qt_u: i32,
4220        rb_g: usize,
4221        rb_u: usize,
4222    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4223        let f = self.func("moe_gate_up_silu8_q8");
4224        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4225        let cfg = LaunchConfig {
4226            grid_dim: (n_ff as u32, n_used as u32, 1),
4227            block_dim: (32, 1, 1),
4228            shared_mem_bytes: 0,
4229        };
4230        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4231        let __s_b = self.gpu.stream();
4232        let mut b = __s_b.launch_builder(&f);
4233        b.arg(&gp)
4234            .arg(&up)
4235            .arg(aq)
4236            .arg(ad)
4237            .arg(&mut act)
4238            .arg(&inf)
4239            .arg(&nff)
4240            .arg(&qt_g)
4241            .arg(&qt_u)
4242            .arg(&rbg)
4243            .arg(&rbu);
4244        unsafe {
4245            b.launch(cfg)?;
4246        }
4247        Ok(act)
4248    }
4249
4250    #[allow(clippy::too_many_arguments)]
4251    pub fn moe_down8_fma_q8(
4252        &self,
4253        dp: WPtr8,
4254        w: F32x8,
4255        aq2: &CudaSlice<i8>,
4256        ad2: &CudaSlice<f32>,
4257        dst: &mut cudarc::driver::CudaViewMut<f32>,
4258        in_f: usize,
4259        out_f: usize,
4260        n_used: usize,
4261        qt: i32,
4262        rb: usize,
4263    ) -> Result<(), Box<dyn std::error::Error>> {
4264        let f = self.func("moe_down8_fma_q8");
4265        let cfg = LaunchConfig {
4266            grid_dim: (out_f as u32, 1, 1),
4267            block_dim: (32, 1, 1),
4268            shared_mem_bytes: 0,
4269        };
4270        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4271        let __s_b = self.gpu.stream();
4272        let mut b = __s_b.launch_builder(&f);
4273        b.arg(&dp)
4274            .arg(&w)
4275            .arg(aq2)
4276            .arg(ad2)
4277            .arg(dst)
4278            .arg(&inf)
4279            .arg(&outf)
4280            .arg(&nu)
4281            .arg(&qt)
4282            .arg(&rbi);
4283        unsafe {
4284            b.launch(cfg)?;
4285        }
4286        Ok(())
4287    }
4288
4289    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4290    pub fn qmatvec_expert_q8(
4291        &self,
4292        w: &CudaSlice<u8>,
4293        range: std::ops::Range<usize>,
4294        aq: &CudaSlice<i8>,
4295        ad: &CudaSlice<f32>,
4296        m: usize,
4297        in_f: usize,
4298        out_f: usize,
4299        qtype: i32,
4300        row_bytes: usize,
4301    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4302        let f = self.func("qmatvec_expert_q8");
4303        let wv = w.slice(range);
4304        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4305        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4306        let cfg = LaunchConfig {
4307            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4308            block_dim: (32, ROWS, 1),
4309            shared_mem_bytes: 0,
4310        };
4311        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4312        let __s_b = self.gpu.stream();
4313        let mut b = __s_b.launch_builder(&f);
4314        b.arg(&wv)
4315            .arg(aq)
4316            .arg(ad)
4317            .arg(&mut y)
4318            .arg(&inf)
4319            .arg(&outf)
4320            .arg(&mi)
4321            .arg(&qtype)
4322            .arg(&rbi);
4323        unsafe {
4324            b.launch(cfg)?;
4325        }
4326        Ok(y)
4327    }
4328
4329    pub fn moe_gate_up_silu8(
4330        &self,
4331        gp: WPtr8,
4332        up: WPtr8,
4333        x: &cudarc::driver::CudaView<f32>,
4334        in_f: usize,
4335        n_ff: usize,
4336        n_used: usize,
4337        qt_g: i32,
4338        qt_u: i32,
4339        rb_g: usize,
4340        rb_u: usize,
4341    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4342        let f = self.func("moe_gate_up_silu8_f32");
4343        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4344        let cfg = LaunchConfig {
4345            grid_dim: (n_ff as u32, n_used as u32, 1),
4346            block_dim: (256, 1, 1),
4347            shared_mem_bytes: 0,
4348        };
4349        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4350        let __s_b = self.gpu.stream();
4351        let mut b = __s_b.launch_builder(&f);
4352        b.arg(&gp)
4353            .arg(&up)
4354            .arg(x)
4355            .arg(&mut act)
4356            .arg(&inf)
4357            .arg(&nff)
4358            .arg(&qt_g)
4359            .arg(&qt_u)
4360            .arg(&rbg)
4361            .arg(&rbu);
4362        unsafe {
4363            b.launch(cfg)?;
4364        }
4365        Ok(act)
4366    }
4367
4368    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4369    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4370    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4371    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4372    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4373    #[allow(clippy::too_many_arguments)]
4374    pub fn moe_down8_fma_into(
4375        &self,
4376        dp: WPtr8,
4377        w: F32x8,
4378        act: &CudaSlice<f32>,
4379        dst: &mut cudarc::driver::CudaViewMut<f32>,
4380        in_f: usize,
4381        out_f: usize,
4382        n_used: usize,
4383        qt: i32,
4384        rb: usize,
4385    ) -> Result<(), Box<dyn std::error::Error>> {
4386        let f = self.func("moe_down8_fma_f32");
4387        let cfg = LaunchConfig {
4388            grid_dim: (out_f as u32, 1, 1),
4389            block_dim: (256, 1, 1),
4390            shared_mem_bytes: 0,
4391        };
4392        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4393        let __s_b = self.gpu.stream();
4394        let mut b = __s_b.launch_builder(&f);
4395        b.arg(&dp)
4396            .arg(&w)
4397            .arg(act)
4398            .arg(dst)
4399            .arg(&inf)
4400            .arg(&outf)
4401            .arg(&nu)
4402            .arg(&qt)
4403            .arg(&rbv);
4404        unsafe {
4405            b.launch(cfg)?;
4406        }
4407        Ok(())
4408    }
4409
4410    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4411    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4412    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4413    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4414    #[allow(clippy::too_many_arguments)]
4415    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4416    ///
4417    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4418    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4419    /// down's FMA chain stays slot-ordered serial). Seams:
4420    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4421    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4422    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4423    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4424    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4425    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4426    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4427    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4428    ///                       only) | w8h2 (h2 x slot-parallel)
4429    #[allow(clippy::too_many_arguments)]
4430    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4431    #[allow(clippy::too_many_arguments)]
4432    pub fn moe_pairs_matvec_q8(
4433        &self,
4434        table: &CudaSlice<u64>,
4435        proj: i32,
4436        pair_tok: &CudaSlice<i32>,
4437        pair_ex: &CudaSlice<i32>,
4438        aq: &CudaSlice<i8>,
4439        ad: &CudaSlice<f32>,
4440        in_f: usize,
4441        out_f: usize,
4442        n_expert: usize,
4443        n_pairs: usize,
4444        qtype: i32,
4445        row_bytes: usize,
4446    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4447        let f = self.func("moe_pairs_matvec_q8");
4448        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4449        const ROWS: u32 = 4;
4450        let cfg = LaunchConfig {
4451            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4452            block_dim: (32, ROWS, 1),
4453            shared_mem_bytes: 0,
4454        };
4455        let (inf, outf, ne, np, rbi) = (
4456            in_f as i32,
4457            out_f as i32,
4458            n_expert as i32,
4459            n_pairs as i32,
4460            row_bytes as i64,
4461        );
4462        let __s_b = self.gpu.stream();
4463        let mut b = __s_b.launch_builder(&f);
4464        b.arg(table)
4465            .arg(&proj)
4466            .arg(pair_tok)
4467            .arg(pair_ex)
4468            .arg(aq)
4469            .arg(ad)
4470            .arg(&mut y)
4471            .arg(&inf)
4472            .arg(&outf)
4473            .arg(&ne)
4474            .arg(&np)
4475            .arg(&qtype)
4476            .arg(&rbi);
4477        unsafe {
4478            b.launch(cfg)?;
4479        }
4480        Ok(y)
4481    }
4482
4483    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4484    #[allow(clippy::too_many_arguments)]
4485    pub fn moe_pairs_matvec_q8_em(
4486        &self,
4487        table: &CudaSlice<u64>,
4488        proj: i32,
4489        ex_ids: &CudaSlice<i32>,
4490        ex_off: &CudaSlice<i32>,
4491        ex_pairs: &CudaSlice<i32>,
4492        pair_tok: &CudaSlice<i32>,
4493        aq: &CudaSlice<i8>,
4494        ad: &CudaSlice<f32>,
4495        in_f: usize,
4496        out_f: usize,
4497        n_expert: usize,
4498        n_active: usize,
4499        n_pairs: usize,
4500        qtype: i32,
4501        row_bytes: usize,
4502    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4503        let f = self.func("moe_pairs_matvec_q8_em");
4504        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4505        const ROWS: u32 = 4;
4506        let cfg = LaunchConfig {
4507            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4508            block_dim: (32, ROWS, 1),
4509            shared_mem_bytes: 0,
4510        };
4511        let (inf, outf, ne, na, rbi) = (
4512            in_f as i32,
4513            out_f as i32,
4514            n_expert as i32,
4515            n_active as i32,
4516            row_bytes as i64,
4517        );
4518        let __s_b = self.gpu.stream();
4519        let mut b = __s_b.launch_builder(&f);
4520        b.arg(table)
4521            .arg(&proj)
4522            .arg(ex_ids)
4523            .arg(ex_off)
4524            .arg(ex_pairs)
4525            .arg(pair_tok)
4526            .arg(aq)
4527            .arg(ad)
4528            .arg(&mut y)
4529            .arg(&inf)
4530            .arg(&outf)
4531            .arg(&ne)
4532            .arg(&na)
4533            .arg(&qtype)
4534            .arg(&rbi);
4535        unsafe {
4536            b.launch(cfg)?;
4537        }
4538        Ok(y)
4539    }
4540
4541    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4542    // weight group once per (row,group) then dp4a's across the expert's token group.
4543    #[allow(clippy::too_many_arguments)]
4544    pub fn moe_pairs_matvec_q8_dec(
4545        &self,
4546        table: &CudaSlice<u64>,
4547        proj: i32,
4548        ex_ids: &CudaSlice<i32>,
4549        ex_off: &CudaSlice<i32>,
4550        ex_pairs: &CudaSlice<i32>,
4551        pair_tok: &CudaSlice<i32>,
4552        aq: &CudaSlice<i8>,
4553        ad: &CudaSlice<f32>,
4554        in_f: usize,
4555        out_f: usize,
4556        n_expert: usize,
4557        n_active: usize,
4558        n_pairs: usize,
4559        qtype: i32,
4560        row_bytes: usize,
4561    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4562        let f = self.func("moe_pairs_matvec_q8_dec");
4563        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4564        const ROWS: u32 = 4;
4565        let cfg = LaunchConfig {
4566            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4567            block_dim: (32, ROWS, 1),
4568            shared_mem_bytes: 0,
4569        };
4570        let (inf, outf, ne, na, rbi) = (
4571            in_f as i32,
4572            out_f as i32,
4573            n_expert as i32,
4574            n_active as i32,
4575            row_bytes as i64,
4576        );
4577        let __s_b = self.gpu.stream();
4578        let mut b = __s_b.launch_builder(&f);
4579        b.arg(table)
4580            .arg(&proj)
4581            .arg(ex_ids)
4582            .arg(ex_off)
4583            .arg(ex_pairs)
4584            .arg(pair_tok)
4585            .arg(aq)
4586            .arg(ad)
4587            .arg(&mut y)
4588            .arg(&inf)
4589            .arg(&outf)
4590            .arg(&ne)
4591            .arg(&na)
4592            .arg(&qtype)
4593            .arg(&rbi);
4594        unsafe {
4595            b.launch(cfg)?;
4596        }
4597        Ok(y)
4598    }
4599
4600    pub fn moe_pairs_gelu_mul(
4601        &self,
4602        gate: &CudaSlice<f32>,
4603        up: &CudaSlice<f32>,
4604        n: usize,
4605    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4606        let f = self.func("moe_pairs_gelu_mul");
4607        let mut act = self.alloc_uninit::<f32>(n)?;
4608        let cfg = LaunchConfig::for_num_elems(n as u32);
4609        let nl = n as i64;
4610        let __s_b = self.gpu.stream();
4611        let mut b = __s_b.launch_builder(&f);
4612        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4613        unsafe {
4614            b.launch(cfg)?;
4615        }
4616        Ok(act)
4617    }
4618
4619    pub fn moe_pairs_silu_mul(
4620        &self,
4621        gate: &CudaSlice<f32>,
4622        up: &CudaSlice<f32>,
4623        n: usize,
4624    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4625        let f = self.func("moe_pairs_silu_mul");
4626        let mut act = self.alloc_uninit::<f32>(n)?;
4627        let cfg = LaunchConfig::for_num_elems(n as u32);
4628        let nl = n as i64;
4629        let __s_b = self.gpu.stream();
4630        let mut b = __s_b.launch_builder(&f);
4631        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4632        unsafe {
4633            b.launch(cfg)?;
4634        }
4635        Ok(act)
4636    }
4637
4638    #[allow(clippy::too_many_arguments)]
4639    pub fn moe_pairs_scatter(
4640        &self,
4641        y_down: &CudaSlice<f32>,
4642        pair_w: &CudaSlice<f32>,
4643        tok_pair_off: &CudaSlice<i32>,
4644        tok_pair_ids: &CudaSlice<i32>,
4645        moe_out: &mut CudaSlice<f32>,
4646        t: usize,
4647        n_embd: usize,
4648    ) -> Result<(), Box<dyn std::error::Error>> {
4649        let f = self.func("moe_pairs_scatter");
4650        let cfg = LaunchConfig {
4651            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4652            block_dim: (256, 1, 1),
4653            shared_mem_bytes: 0,
4654        };
4655        let ne = n_embd as i32;
4656        let __s_b = self.gpu.stream();
4657        let mut b = __s_b.launch_builder(&f);
4658        b.arg(y_down)
4659            .arg(pair_w)
4660            .arg(tok_pair_off)
4661            .arg(tok_pair_ids)
4662            .arg(moe_out)
4663            .arg(&ne);
4664        unsafe {
4665            b.launch(cfg)?;
4666        }
4667        Ok(())
4668    }
4669
4670    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4671    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4672    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4673    #[allow(clippy::too_many_arguments)]
4674    pub fn moe_gate_up_gelu8_dev_q8(
4675        &self,
4676        table: &CudaSlice<u64>,
4677        sel: &cudarc::driver::CudaView<i32>,
4678        aq: &CudaSlice<i8>,
4679        ad: &CudaSlice<f32>,
4680        in_f: usize,
4681        n_ff: usize,
4682        n_used: usize,
4683        n_expert: usize,
4684        qt_g: i32,
4685        qt_u: i32,
4686        rb_g: usize,
4687        rb_u: usize,
4688    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4689        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4690        let (inf, nff, ne, rbg, rbu) = (
4691            in_f as i32,
4692            n_ff as i32,
4693            n_expert as i32,
4694            rb_g as i64,
4695            rb_u as i64,
4696        );
4697        let f = self.func("moe_gate_up_gelu8_dev_q8");
4698        let cfg = LaunchConfig {
4699            grid_dim: (n_ff as u32, n_used as u32, 1),
4700            block_dim: (32, 1, 1),
4701            shared_mem_bytes: 0,
4702        };
4703        let __s_b = self.gpu.stream();
4704        let mut b = __s_b.launch_builder(&f);
4705        b.arg(table)
4706            .arg(sel)
4707            .arg(aq)
4708            .arg(ad)
4709            .arg(&mut act)
4710            .arg(&inf)
4711            .arg(&nff)
4712            .arg(&ne)
4713            .arg(&qt_g)
4714            .arg(&qt_u)
4715            .arg(&rbg)
4716            .arg(&rbu);
4717        unsafe {
4718            b.launch(cfg)?;
4719        }
4720        Ok(act)
4721    }
4722
4723    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4724    #[allow(clippy::too_many_arguments)]
4725    pub fn moe_gate_up_gelu8_dev_q8_rows(
4726        &self,
4727        table: &CudaSlice<u64>,
4728        sel: &CudaSlice<i32>,
4729        aq: &CudaSlice<i8>,
4730        ad: &CudaSlice<f32>,
4731        t: usize,
4732        in_f: usize,
4733        n_ff: usize,
4734        n_used: usize,
4735        n_expert: usize,
4736        qt_g: i32,
4737        qt_u: i32,
4738        rb_g: usize,
4739        rb_u: usize,
4740    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4741        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4742        let (inf, nff, ne, rbg, rbu, nu) = (
4743            in_f as i32,
4744            n_ff as i32,
4745            n_expert as i32,
4746            rb_g as i64,
4747            rb_u as i64,
4748            n_used as i32,
4749        );
4750        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4751        let cfg = LaunchConfig {
4752            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4753            block_dim: (32, 1, 1),
4754            shared_mem_bytes: 0,
4755        };
4756        let __s_b = self.gpu.stream();
4757        let mut b = __s_b.launch_builder(&f);
4758        b.arg(table)
4759            .arg(sel)
4760            .arg(aq)
4761            .arg(ad)
4762            .arg(&mut act)
4763            .arg(&inf)
4764            .arg(&nff)
4765            .arg(&ne)
4766            .arg(&qt_g)
4767            .arg(&qt_u)
4768            .arg(&rbg)
4769            .arg(&rbu)
4770            .arg(&nu);
4771        unsafe {
4772            b.launch(cfg)?;
4773        }
4774        Ok(act)
4775    }
4776
4777    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4778    #[allow(clippy::too_many_arguments)]
4779    pub fn moe_gate_up_gelu8_dev_q8_csr(
4780        &self,
4781        table: &CudaSlice<u64>,
4782        sel: &CudaSlice<i32>,
4783        aq: &CudaSlice<i8>,
4784        ad: &CudaSlice<f32>,
4785        n_pairs: usize,
4786        in_f: usize,
4787        n_ff: usize,
4788        n_used: usize,
4789        n_expert: usize,
4790        qt_g: i32,
4791        qt_u: i32,
4792        rb_g: usize,
4793        rb_u: usize,
4794    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4795        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4796        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4797            in_f as i32,
4798            n_ff as i32,
4799            n_expert as i32,
4800            rb_g as i64,
4801            rb_u as i64,
4802            n_used as i32,
4803            n_pairs as i32,
4804        );
4805        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4806        let cfg = LaunchConfig {
4807            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4808            block_dim: (32, 1, 1),
4809            shared_mem_bytes: 0,
4810        };
4811        let __s_b = self.gpu.stream();
4812        let mut b = __s_b.launch_builder(&f);
4813        b.arg(table)
4814            .arg(sel)
4815            .arg(aq)
4816            .arg(ad)
4817            .arg(&mut act)
4818            .arg(&inf)
4819            .arg(&nff)
4820            .arg(&ne)
4821            .arg(&qt_g)
4822            .arg(&qt_u)
4823            .arg(&rbg)
4824            .arg(&rbu)
4825            .arg(&nu)
4826            .arg(&npi);
4827        unsafe {
4828            b.launch(cfg)?;
4829        }
4830        Ok(act)
4831    }
4832
4833    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4834    #[allow(clippy::too_many_arguments)]
4835    pub fn moe_down8_fma_dev_q8_rows_g(
4836        &self,
4837        table: &CudaSlice<u64>,
4838        sel: &CudaSlice<i32>,
4839        w: &CudaSlice<f32>,
4840        aq2: &CudaSlice<i8>,
4841        ad2: &CudaSlice<f32>,
4842        dst: &mut CudaSlice<f32>,
4843        t: usize,
4844        in_f: usize,
4845        out_f: usize,
4846        n_used: usize,
4847        n_expert: usize,
4848        qt: i32,
4849        rb: usize,
4850    ) -> Result<(), Box<dyn std::error::Error>> {
4851        let (inf, outf, nu, ne, rbi) = (
4852            in_f as i32,
4853            out_f as i32,
4854            n_used as i32,
4855            n_expert as i32,
4856            rb as i64,
4857        );
4858        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4859        // eight warps, then replay the original slot-ordered FMA chain. Every
4860        // other shape retains the generic one-warp rows kernel.
4861        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4862        let f = self.func(if step_b1_w8 {
4863            "moe_down8_fma_dev_q8_rows_w8"
4864        } else {
4865            "moe_down8_fma_dev_q8_rows_g"
4866        });
4867        let cfg = LaunchConfig {
4868            grid_dim: (out_f as u32, 1, t as u32),
4869            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4870            shared_mem_bytes: 0,
4871        };
4872        let __s_b = self.gpu.stream();
4873        let mut b = __s_b.launch_builder(&f);
4874        b.arg(table)
4875            .arg(sel)
4876            .arg(w)
4877            .arg(aq2)
4878            .arg(ad2)
4879            .arg(dst)
4880            .arg(&inf)
4881            .arg(&outf)
4882            .arg(&nu)
4883            .arg(&ne)
4884            .arg(&qt)
4885            .arg(&rbi);
4886        unsafe {
4887            b.launch(cfg)?;
4888        }
4889        Ok(())
4890    }
4891
4892    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4893    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4894    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4895    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4896        let (out_f, in_f) = (2048usize, 2816usize);
4897        let nblk = in_f / 32;
4898        let mut seed = 0x9E3779B97F4A7C15u64;
4899        let mut rng = move || {
4900            seed = seed
4901                .wrapping_mul(6364136223846793005)
4902                .wrapping_add(1442695040888963407);
4903            (seed >> 33) as u8
4904        };
4905        let mut w = vec![0u8; out_f * nblk * 18];
4906        for b in w.iter_mut() {
4907            *b = rng();
4908        }
4909        for r in 0..out_f {
4910            for g in 0..nblk {
4911                let off = (r * nblk + g) * 18;
4912                w[off] = 0x00;
4913                w[off + 1] = 0x2C; // sane half d
4914            }
4915        }
4916        let qplane = out_f * nblk * 16;
4917        let mut wrp = vec![0u8; w.len()];
4918        for r in 0..out_f {
4919            for g in 0..nblk {
4920                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4921                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4922                    .copy_from_slice(&src[0..2]);
4923                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4924            }
4925        }
4926        let w_d = self.htod_bytes(&w)?;
4927        let wrp_d = self.htod_bytes(&wrp)?;
4928        let mut aq = vec![0i8; m * in_f];
4929        for v in aq.iter_mut() {
4930            *v = rng() as i8;
4931        }
4932        let aq_d = self.htod_i8(&aq)?;
4933        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4934        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4935        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4936        const RPB: u32 = 4;
4937        let cfg = LaunchConfig {
4938            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4939            block_dim: (32, RPB, 1),
4940            shared_mem_bytes: 0,
4941        };
4942        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4943        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4944        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4945        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4946        {
4947            let __s_b = self.gpu.stream();
4948            let mut b = __s_b.launch_builder(&fb);
4949            b.arg(&w_d)
4950                .arg(&aq_d)
4951                .arg(&ad_d)
4952                .arg(&mut y0)
4953                .arg(&inf)
4954                .arg(&outf)
4955                .arg(&mi)
4956                .arg(&rb);
4957            unsafe {
4958                b.launch(cfg)?;
4959            }
4960            let __s_b = self.gpu.stream();
4961            let mut b = __s_b.launch_builder(&fr);
4962            b.arg(&wrp_d)
4963                .arg(&aq_d)
4964                .arg(&ad_d)
4965                .arg(&mut y1)
4966                .arg(&inf)
4967                .arg(&outf)
4968                .arg(&mi)
4969                .arg(&qp);
4970            unsafe {
4971                b.launch(cfg)?;
4972            }
4973        }
4974        self.gpu.stream().synchronize()?;
4975        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4976        let nd = h0
4977            .iter()
4978            .zip(&h1)
4979            .filter(|(a, b)| a.to_bits() != b.to_bits())
4980            .count();
4981        if nd != 0 {
4982            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4983        }
4984        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4985            self.gpu.stream().synchronize()?;
4986            let t0 = std::time::Instant::now();
4987            for _ in 0..500 {
4988                if rp {
4989                    let __s_b = self.gpu.stream();
4990                    let mut b = __s_b.launch_builder(&fr);
4991                    b.arg(&wrp_d)
4992                        .arg(&aq_d)
4993                        .arg(&ad_d)
4994                        .arg(&mut y1)
4995                        .arg(&inf)
4996                        .arg(&outf)
4997                        .arg(&mi)
4998                        .arg(&qp);
4999                    unsafe {
5000                        b.launch(cfg)?;
5001                    }
5002                } else {
5003                    let __s_b = self.gpu.stream();
5004                    let mut b = __s_b.launch_builder(&fb);
5005                    b.arg(&w_d)
5006                        .arg(&aq_d)
5007                        .arg(&ad_d)
5008                        .arg(&mut y0)
5009                        .arg(&inf)
5010                        .arg(&outf)
5011                        .arg(&mi)
5012                        .arg(&rb);
5013                    unsafe {
5014                        b.launch(cfg)?;
5015                    }
5016                }
5017            }
5018            self.gpu.stream().synchronize()?;
5019            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
5020        };
5021        let _ = time(false)?;
5022        let _ = time(true)?; // warm
5023        Ok((time(false)?, time(true)?))
5024    }
5025
5026    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
5027    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
5028    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
5029    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
5030    pub fn build_q4_rp4(
5031        &self,
5032        t: &mut crate::model::GpuTensor,
5033    ) -> Result<(), Box<dyn std::error::Error>> {
5034        use crate::model::GpuTensor;
5035        let GpuTensor::Quant {
5036            bytes,
5037            qtype,
5038            row_bytes,
5039            ne,
5040            rp4,
5041            ..
5042        } = t
5043        else {
5044            return Ok(());
5045        };
5046        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
5047            return Ok(());
5048        }
5049        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5050        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
5051            return Ok(());
5052        }
5053        let nblk = in_f / 32;
5054        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
5055        let f = self.func("q4_0_split_rp_build");
5056        let n = (out_f * nblk) as i32;
5057        let cfg = LaunchConfig {
5058            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5059            block_dim: (256, 1, 1),
5060            shared_mem_bytes: 0,
5061        };
5062        let (of, nb) = (out_f as i32, nblk as i32);
5063        let _ = n;
5064        let __s_b = self.gpu.stream();
5065        let mut b = __s_b.launch_builder(&f);
5066        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5067        unsafe {
5068            b.launch(cfg)?;
5069        }
5070        *rp4 = Some(dst);
5071        Ok(())
5072    }
5073
5074    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
5075    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
5076    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
5077    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5078    pub fn build_q8_rp4(
5079        &self,
5080        t: &mut crate::model::GpuTensor,
5081    ) -> Result<(), Box<dyn std::error::Error>> {
5082        use crate::model::GpuTensor;
5083        let GpuTensor::Quant {
5084            bytes,
5085            qtype,
5086            row_bytes,
5087            ne,
5088            rp4,
5089            ..
5090        } = t
5091        else {
5092            return Ok(());
5093        };
5094        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
5095            return Ok(());
5096        }
5097        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5098        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5099            return Ok(());
5100        }
5101        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5102        Ok(())
5103    }
5104
5105    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5106    /// mirror without a GpuTensor (same kernel the loader path above uses).
5107    pub fn build_q8_rp4_raw(
5108        &self,
5109        bytes: &CudaSlice<u8>,
5110        in_f: usize,
5111        out_f: usize,
5112    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5113        assert!(in_f % 32 == 0);
5114        let nblk = in_f / 32;
5115        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5116        let f = self.func("q8_0_split_rp_build");
5117        let cfg = LaunchConfig {
5118            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5119            block_dim: (256, 1, 1),
5120            shared_mem_bytes: 0,
5121        };
5122        let (of, nb) = (out_f as i32, nblk as i32);
5123        let __s_b = self.gpu.stream();
5124        let mut b = __s_b.launch_builder(&f);
5125        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5126        unsafe {
5127            b.launch(cfg)?;
5128        }
5129        Ok(dst)
5130    }
5131
5132    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5133    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5134    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5135    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5136    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5137    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5138    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5139    pub fn build_q4k_rp4(
5140        &self,
5141        t: &mut crate::model::GpuTensor,
5142    ) -> Result<(), Box<dyn std::error::Error>> {
5143        use crate::model::GpuTensor;
5144        let GpuTensor::Quant {
5145            bytes,
5146            qtype,
5147            row_bytes,
5148            ne,
5149            rp4,
5150            ..
5151        } = t
5152        else {
5153            return Ok(());
5154        };
5155        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5156            return Ok(());
5157        }
5158        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5159        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5160            return Ok(());
5161        }
5162        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5163        Ok(())
5164    }
5165
5166    pub fn build_q6k_rp4(
5167        &self,
5168        t: &mut crate::model::GpuTensor,
5169    ) -> Result<(), Box<dyn std::error::Error>> {
5170        use crate::model::GpuTensor;
5171        let GpuTensor::Quant {
5172            bytes,
5173            qtype,
5174            row_bytes,
5175            ne,
5176            rp4,
5177            ..
5178        } = t
5179        else {
5180            return Ok(());
5181        };
5182        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5183            return Ok(());
5184        }
5185        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5186        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5187            return Ok(());
5188        }
5189        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5190        Ok(())
5191    }
5192
5193    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5194    pub fn build_kq_rp4_raw(
5195        &self,
5196        bytes: &CudaSlice<u8>,
5197        in_f: usize,
5198        out_f: usize,
5199        qtype: i32,
5200    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5201        assert!(in_f % 256 == 0);
5202        let nsbk = in_f / 256;
5203        let (sb_bytes, kname) = match qtype {
5204            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5205            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5206            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5207        };
5208        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5209        let f = self.func(kname);
5210        let cfg = LaunchConfig {
5211            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5212            block_dim: (256, 1, 1),
5213            shared_mem_bytes: 0,
5214        };
5215        let (of, nb) = (out_f as i32, nsbk as i32);
5216        let __s_b = self.gpu.stream();
5217        let mut b = __s_b.launch_builder(&f);
5218        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5219        unsafe {
5220            b.launch(cfg)?;
5221        }
5222        Ok(dst)
5223    }
5224
5225    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5226    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5227    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5228    pub fn kqrp_enabled() -> bool {
5229        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5230        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5231            Ok("0") => false,
5232            Ok(_) => true,
5233            Err(_) => cfg!(memra_hopper_mma),
5234        })
5235    }
5236
5237    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5238    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5239    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5240    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5241    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5242    pub fn build_q4_rp_swap(
5243        &self,
5244        t: &mut crate::model::GpuTensor,
5245    ) -> Result<bool, Box<dyn std::error::Error>> {
5246        use crate::model::GpuTensor;
5247        // QTYPE GUARD IN THE SWAP ITSELF (gemma4 NVFP4mix prefill-NaN, 2026-08-17):
5248        // `rp4` is a SHARED field — the Q8RP walk parks Q8_0 MIRRORS there, and this
5249        // fn used to `take()` whatever it found. On a Q8_0-carrying gemma4-dense trunk
5250        // the swap hijacked those mirrors: `bytes` became split-plane in place, the
5251        // m<=16 `_rp` dispatch read them correctly (masking the corruption from every
5252        // decode pin), and every GGUF-layout prefill consumer (MMQ + GEMM) read the
5253        // fp16 d-plane as weights -> layer-0 NaN, <pad>-spam serving. Only a tensor
5254        // this fn's OWN builder serves may ever be swapped; everything else refuses
5255        // here, regardless of walk ordering.
5256        if !matches!(t, GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0) {
5257            return Ok(false);
5258        }
5259        self.build_q4_rp4(t)?;
5260        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5261        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5262            return Ok(false);
5263        };
5264        match rp4.take() {
5265            Some(split) => {
5266                *bytes = split; // the GGUF-layout buffer drops here
5267                *rp = true;
5268                Ok(true)
5269            }
5270            None => Ok(false),
5271        }
5272    }
5273
5274    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5275    pub fn q4rp_enabled() -> bool {
5276        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5277        *ON.get_or_init(|| {
5278            std::env::var("MEMRA_Q4RP")
5279                .map(|v| v != "0")
5280                .unwrap_or(true)
5281        })
5282    }
5283
5284    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5285    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5286    pub fn copy_rows_strided(
5287        &self,
5288        src: &CudaSlice<f32>,
5289        dst: &mut CudaSlice<f32>,
5290        row_elems: usize,
5291        n_rows: usize,
5292        src_stride: usize,
5293        src_off: usize,
5294    ) -> Result<(), Box<dyn std::error::Error>> {
5295        let f = self.func("copy_rows_strided_f32");
5296        let cfg = LaunchConfig {
5297            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5298            block_dim: (256, 1, 1),
5299            shared_mem_bytes: 0,
5300        };
5301        let (re, nr) = (row_elems as i32, n_rows as i32);
5302        let (st, off) = (src_stride as i64, src_off as i64);
5303        let __s_b = self.gpu.stream();
5304        let mut b = __s_b.launch_builder(&f);
5305        b.arg(src)
5306            .arg(&mut *dst)
5307            .arg(&re)
5308            .arg(&nr)
5309            .arg(&st)
5310            .arg(&off);
5311        unsafe {
5312            b.launch(cfg)?;
5313        }
5314        Ok(())
5315    }
5316
5317    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5318    pub fn u32_set_k(
5319        &self,
5320        dst: &mut CudaSlice<u32>,
5321        v: u32,
5322        idx: usize,
5323    ) -> Result<(), Box<dyn std::error::Error>> {
5324        let f = self.func("u32_set_k");
5325        let cfg = LaunchConfig {
5326            grid_dim: (1, 1, 1),
5327            block_dim: (1, 1, 1),
5328            shared_mem_bytes: 0,
5329        };
5330        let ii = idx as i32;
5331        let __s_b = self.gpu.stream();
5332        let mut b = __s_b.launch_builder(&f);
5333        b.arg(dst).arg(&v).arg(&ii);
5334        unsafe {
5335            b.launch(cfg)?;
5336        }
5337        Ok(())
5338    }
5339
5340    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5341    pub fn i32_add_k(
5342        &self,
5343        d: &mut CudaSlice<i32>,
5344        v: i32,
5345    ) -> Result<(), Box<dyn std::error::Error>> {
5346        let f = self.func("i32_add_k");
5347        let cfg = LaunchConfig {
5348            grid_dim: (1, 1, 1),
5349            block_dim: (32, 1, 1),
5350            shared_mem_bytes: 0,
5351        };
5352        let __s_b = self.gpu.stream();
5353        let mut b = __s_b.launch_builder(&f);
5354        b.arg(d).arg(&v);
5355        unsafe {
5356            b.launch(cfg)?;
5357        }
5358        Ok(())
5359    }
5360
5361    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5362    pub fn i32_iota_from(
5363        &self,
5364        ctr: &CudaSlice<i32>,
5365        dst: &mut CudaSlice<i32>,
5366        n: usize,
5367    ) -> Result<(), Box<dyn std::error::Error>> {
5368        let f = self.func("i32_iota_from");
5369        let cfg = LaunchConfig::for_num_elems(n as u32);
5370        let ni = n as i32;
5371        let __s_b = self.gpu.stream();
5372        let mut b = __s_b.launch_builder(&f);
5373        b.arg(ctr).arg(dst).arg(&ni);
5374        unsafe {
5375            b.launch(cfg)?;
5376        }
5377        Ok(())
5378    }
5379
5380    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5381    pub fn u32_map_k(
5382        &self,
5383        buf: &mut CudaSlice<u32>,
5384        map: &CudaSlice<u32>,
5385        idx: usize,
5386    ) -> Result<(), Box<dyn std::error::Error>> {
5387        let f = self.func("u32_map_k");
5388        let cfg = LaunchConfig {
5389            grid_dim: (1, 1, 1),
5390            block_dim: (1, 1, 1),
5391            shared_mem_bytes: 0,
5392        };
5393        let ii = idx as i32;
5394        let __s_b = self.gpu.stream();
5395        let mut b = __s_b.launch_builder(&f);
5396        b.arg(buf).arg(map).arg(&ii);
5397        unsafe {
5398            b.launch(cfg)?;
5399        }
5400        Ok(())
5401    }
5402
5403    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5404    #[allow(clippy::too_many_arguments)]
5405    pub fn u32_pack2(
5406        &self,
5407        a: &CudaSlice<u32>,
5408        off_a: usize,
5409        n1: usize,
5410        b_in: &CudaSlice<u32>,
5411        n2: usize,
5412        out: &mut CudaSlice<u32>,
5413    ) -> Result<(), Box<dyn std::error::Error>> {
5414        let f = self.func("u32_pack2");
5415        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5416        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5417        let __s_b = self.gpu.stream();
5418        let mut b = __s_b.launch_builder(&f);
5419        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5420        unsafe {
5421            b.launch(cfg)?;
5422        }
5423        Ok(())
5424    }
5425
5426    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5427    pub fn moe_w_exscale(
5428        &self,
5429        w: &mut CudaSlice<f32>,
5430        sel: &CudaSlice<i32>,
5431        s: &CudaSlice<f32>,
5432        n: usize,
5433    ) -> Result<(), Box<dyn std::error::Error>> {
5434        let f = self.func("moe_w_exscale");
5435        let cfg = LaunchConfig::for_num_elems(n as u32);
5436        let ni = n as i32;
5437        let __s_b = self.gpu.stream();
5438        let mut b = __s_b.launch_builder(&f);
5439        b.arg(w).arg(sel).arg(s).arg(&ni);
5440        unsafe {
5441            b.launch(cfg)?;
5442        }
5443        Ok(())
5444    }
5445
5446    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5447    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5448    pub fn moe_w_scale_by_expert(
5449        &self,
5450        w: &mut CudaSlice<f32>,
5451        sel: &CudaSlice<i32>,
5452        macros: &CudaSlice<f32>,
5453        n_expert: usize,
5454        n: usize,
5455    ) -> Result<(), Box<dyn std::error::Error>> {
5456        let f = self.func("moe_w_scale_by_expert");
5457        let cfg = LaunchConfig {
5458            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5459            block_dim: (64, 1, 1),
5460            shared_mem_bytes: 0,
5461        };
5462        let (ne, nn) = (n_expert as i32, n as i32);
5463        let __s_b = self.gpu.stream();
5464        let mut b = __s_b.launch_builder(&f);
5465        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5466        unsafe {
5467            b.launch(cfg)?;
5468        }
5469        Ok(())
5470    }
5471
5472    pub fn moe_gate_up_silu8_dev_q8(
5473        &self,
5474        table: &CudaSlice<u64>,
5475        sel: &cudarc::driver::CudaView<i32>,
5476        aq: &CudaSlice<i8>,
5477        ad: &CudaSlice<f32>,
5478        in_f: usize,
5479        n_ff: usize,
5480        n_used: usize,
5481        n_expert: usize,
5482        qt_g: i32,
5483        qt_u: i32,
5484        rb_g: usize,
5485        rb_u: usize,
5486        macros: &CudaSlice<f32>,
5487    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5488        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5489        let (mode, wpb) = GU.get_or_init(|| {
5490            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5491            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5492                .ok()
5493                .and_then(|v| v.parse().ok())
5494                .unwrap_or(4u32)
5495                .clamp(1, 16);
5496            (mode, wpb)
5497        });
5498        let (mode, wpb) = (mode.as_str(), *wpb);
5499        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5500        let (inf, nff, ne, rbg, rbu) = (
5501            in_f as i32,
5502            n_ff as i32,
5503            n_expert as i32,
5504            rb_g as i64,
5505            rb_u as i64,
5506        );
5507        let (f, cfg) = match mode {
5508            "1" | "2" | "4" => {
5509                let rpw: u32 = mode.parse().unwrap();
5510                let f = self.func(match rpw {
5511                    1 => "moe_gate_up_silu8_dev_q8_r1",
5512                    2 => "moe_gate_up_silu8_dev_q8_r2",
5513                    _ => "moe_gate_up_silu8_dev_q8_r4",
5514                });
5515                let rows_per_block = (rpw * wpb) as usize;
5516                let gx = n_ff.div_ceil(rows_per_block) as u32;
5517                (
5518                    f,
5519                    LaunchConfig {
5520                        grid_dim: (gx, n_used as u32, 1),
5521                        block_dim: (32, wpb, 1),
5522                        shared_mem_bytes: 0,
5523                    },
5524                )
5525            }
5526            "j8" if n_used <= 32 => (
5527                self.func("moe_gate_up_silu8_dev_q8_j8"),
5528                LaunchConfig {
5529                    grid_dim: (n_ff as u32, 1, 1),
5530                    block_dim: (32, n_used as u32, 1),
5531                    shared_mem_bytes: 0,
5532                },
5533            ),
5534            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5535            "vsm2" => {
5536                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5537                let sh = (rb_g + rb_u) as u32;
5538                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5539                f.set_attribute(
5540                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5541                    sh as i32,
5542                )?;
5543                (
5544                    f,
5545                    LaunchConfig {
5546                        grid_dim: (n_ff as u32, n_used as u32, 1),
5547                        block_dim: (32, 1, 1),
5548                        shared_mem_bytes: sh,
5549                    },
5550                )
5551            }
5552            "vsm" => {
5553                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5554                let sh = (rb_g + rb_u) as u32;
5555                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5556                f.set_attribute(
5557                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5558                    sh as i32,
5559                )?;
5560                (
5561                    f,
5562                    LaunchConfig {
5563                        grid_dim: (n_ff as u32, n_used as u32, 1),
5564                        block_dim: (32, 1, 1),
5565                        shared_mem_bytes: sh,
5566                    },
5567                )
5568            }
5569            "sg" => (
5570                self.func("moe_gate_up_silu8_dev_q8_sg"),
5571                LaunchConfig {
5572                    grid_dim: (n_ff as u32, n_used as u32, 1),
5573                    block_dim: (32, 1, 1),
5574                    shared_mem_bytes: 0,
5575                },
5576            ),
5577            "j8sg" if n_used <= 32 => (
5578                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5579                LaunchConfig {
5580                    grid_dim: (n_ff as u32, 1, 1),
5581                    block_dim: (32, n_used as u32, 1),
5582                    shared_mem_bytes: 0,
5583                },
5584            ),
5585            "u64" if in_f == 2048 => (
5586                self.func("moe_gate_up_silu8_dev_q8_u64"),
5587                LaunchConfig {
5588                    grid_dim: (n_ff as u32, n_used as u32, 1),
5589                    block_dim: (32, 1, 1),
5590                    shared_mem_bytes: 0,
5591                },
5592            ),
5593            "gs4" if in_f == 2048 => (
5594                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5595                LaunchConfig {
5596                    grid_dim: (n_ff as u32, n_used as u32, 1),
5597                    block_dim: (32, 4, 1),
5598                    shared_mem_bytes: 0,
5599                },
5600            ),
5601            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5602            "v" | "" => (
5603                self.func("moe_gate_up_silu8_dev_q8_v"),
5604                LaunchConfig {
5605                    grid_dim: (n_ff as u32, n_used as u32, 1),
5606                    block_dim: (32, 1, 1),
5607                    shared_mem_bytes: 0,
5608                },
5609            ),
5610            "s2" => (
5611                self.func("moe_gate_up_silu8_dev_q8_s2"),
5612                LaunchConfig {
5613                    grid_dim: (n_ff as u32, n_used as u32, 1),
5614                    block_dim: (32, 2, 1),
5615                    shared_mem_bytes: 0,
5616                },
5617            ),
5618            "s2z" => {
5619                let rz = wpb.min(16); // s2z smem tile is [16][2]
5620                (
5621                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5622                    LaunchConfig {
5623                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5624                        block_dim: (32, 2, rz),
5625                        shared_mem_bytes: 0,
5626                    },
5627                )
5628            }
5629            _ => (
5630                self.func("moe_gate_up_silu8_dev_q8"),
5631                LaunchConfig {
5632                    grid_dim: (n_ff as u32, n_used as u32, 1),
5633                    block_dim: (32, 1, 1),
5634                    shared_mem_bytes: 0,
5635                },
5636            ),
5637        };
5638        let __s_b = self.gpu.stream();
5639        let mut b = __s_b.launch_builder(&f);
5640        b.arg(table)
5641            .arg(sel)
5642            .arg(aq)
5643            .arg(ad)
5644            .arg(&mut act)
5645            .arg(&inf)
5646            .arg(&nff)
5647            .arg(&ne)
5648            .arg(&qt_g)
5649            .arg(&qt_u)
5650            .arg(&rbg)
5651            .arg(&rbu)
5652            .arg(macros);
5653        unsafe {
5654            b.launch(cfg)?;
5655        }
5656        Ok(act)
5657    }
5658
5659    #[allow(clippy::too_many_arguments)]
5660    pub fn moe_down8_fma_dev_q8(
5661        &self,
5662        table: &CudaSlice<u64>,
5663        sel: &cudarc::driver::CudaView<i32>,
5664        w: &cudarc::driver::CudaView<f32>,
5665        aq2: &CudaSlice<i8>,
5666        ad2: &CudaSlice<f32>,
5667        dst: &mut cudarc::driver::CudaViewMut<f32>,
5668        in_f: usize,
5669        out_f: usize,
5670        n_used: usize,
5671        n_expert: usize,
5672        qt: i32,
5673        rb: usize,
5674    ) -> Result<(), Box<dyn std::error::Error>> {
5675        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5676        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5677        let (inf, outf, nu, ne, rbi) = (
5678            in_f as i32,
5679            out_f as i32,
5680            n_used as i32,
5681            n_expert as i32,
5682            rb as i64,
5683        );
5684        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5685        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5686        let (f, cfg) = match mode.as_str() {
5687            m @ ("1" | "2" | "4") if n_used <= 8 => {
5688                let rpw: usize = m.parse().unwrap();
5689                let f = self.func(match rpw {
5690                    1 => "moe_down8_fma_dev_q8_w8r1",
5691                    2 => "moe_down8_fma_dev_q8_w8r2",
5692                    _ => "moe_down8_fma_dev_q8_w8r4",
5693                });
5694                (
5695                    f,
5696                    LaunchConfig {
5697                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5698                        block_dim: (32, n_used as u32, 1),
5699                        shared_mem_bytes: 0,
5700                    },
5701                )
5702            }
5703            "h2" if in_f == 512 => (
5704                self.func("moe_down8_fma_dev_q8_h2"),
5705                LaunchConfig {
5706                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5707                    block_dim: (32, 1, 1),
5708                    shared_mem_bytes: 0,
5709                },
5710            ),
5711            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5712            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5713            "" if in_f == 704 && n_used <= 8 => (
5714                self.func("moe_down8_fma_dev_q8_w8r2"),
5715                LaunchConfig {
5716                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5717                    block_dim: (32, n_used as u32, 1),
5718                    shared_mem_bytes: 0,
5719                },
5720            ),
5721            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5722            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5723            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5724            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5725                self.func("moe_down8_fma_dev_q8_w8h2v"),
5726                LaunchConfig {
5727                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5728                    block_dim: (32, n_used as u32, 1),
5729                    shared_mem_bytes: 0,
5730                },
5731            ),
5732            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5733                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5734                LaunchConfig {
5735                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5736                    block_dim: (32, n_used as u32, 1),
5737                    shared_mem_bytes: 0,
5738                },
5739            ),
5740            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5741                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5742                LaunchConfig {
5743                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5744                    block_dim: (32, n_used as u32, 1),
5745                    shared_mem_bytes: 0,
5746                },
5747            ),
5748            "w8h2" if in_f == 512 && n_used <= 8 => (
5749                self.func("moe_down8_fma_dev_q8_w8h2"),
5750                LaunchConfig {
5751                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5752                    block_dim: (32, n_used as u32, 1),
5753                    shared_mem_bytes: 0,
5754                },
5755            ),
5756            _ => (
5757                self.func("moe_down8_fma_dev_q8"),
5758                LaunchConfig {
5759                    grid_dim: (out_f as u32, 1, 1),
5760                    block_dim: (32, 1, 1),
5761                    shared_mem_bytes: 0,
5762                },
5763            ),
5764        };
5765        let __s_b = self.gpu.stream();
5766        let mut b = __s_b.launch_builder(&f);
5767        b.arg(table)
5768            .arg(sel)
5769            .arg(w)
5770            .arg(aq2)
5771            .arg(ad2)
5772            .arg(dst)
5773            .arg(&inf)
5774            .arg(&outf)
5775            .arg(&nu)
5776            .arg(&ne)
5777            .arg(&qt)
5778            .arg(&rbi);
5779        unsafe {
5780            b.launch(cfg)?;
5781        }
5782        Ok(())
5783    }
5784
5785    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5786    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5787    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5788    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5789    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5790    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5791    #[allow(clippy::too_many_arguments)]
5792    pub fn moe_gate_up_silu8_dev_q8_rows(
5793        &self,
5794        table: &CudaSlice<u64>,
5795        sel: &CudaSlice<i32>,
5796        aq: &CudaSlice<i8>,
5797        ad: &CudaSlice<f32>,
5798        t: usize,
5799        in_f: usize,
5800        n_ff: usize,
5801        n_used: usize,
5802        n_expert: usize,
5803        qt_g: i32,
5804        qt_u: i32,
5805        rb_g: usize,
5806        rb_u: usize,
5807        macros: &CudaSlice<f32>,
5808    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5809        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5810        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5811        let cfg = LaunchConfig {
5812            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5813            block_dim: (32, 1, 1),
5814            shared_mem_bytes: 0,
5815        };
5816        let (inf, nff, ne, nu, rbg, rbu) = (
5817            in_f as i32,
5818            n_ff as i32,
5819            n_expert as i32,
5820            n_used as i32,
5821            rb_g as i64,
5822            rb_u as i64,
5823        );
5824        let __s_b = self.gpu.stream();
5825        let mut b = __s_b.launch_builder(&f);
5826        b.arg(table)
5827            .arg(sel)
5828            .arg(aq)
5829            .arg(ad)
5830            .arg(&mut act)
5831            .arg(&inf)
5832            .arg(&nff)
5833            .arg(&ne)
5834            .arg(&qt_g)
5835            .arg(&qt_u)
5836            .arg(&rbg)
5837            .arg(&rbu)
5838            .arg(&nu)
5839            .arg(macros);
5840        unsafe {
5841            b.launch(cfg)?;
5842        }
5843        Ok(act)
5844    }
5845
5846    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5847    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5848    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5849    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5850    #[allow(clippy::too_many_arguments)]
5851    pub fn moe_down8_fma_dev_q8_rows(
5852        &self,
5853        table: &CudaSlice<u64>,
5854        sel: &CudaSlice<i32>,
5855        w: &CudaSlice<f32>,
5856        aq2: &CudaSlice<i8>,
5857        ad2: &CudaSlice<f32>,
5858        dst: &mut CudaSlice<f32>,
5859        t: usize,
5860        in_f: usize,
5861        out_f: usize,
5862        n_used: usize,
5863        n_expert: usize,
5864        qt: i32,
5865        rb: usize,
5866    ) -> Result<(), Box<dyn std::error::Error>> {
5867        assert!(
5868            in_f == 512 && n_used <= 8,
5869            "down rows twin is w8h2v shape-gated"
5870        );
5871        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5872        let cfg = LaunchConfig {
5873            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5874            block_dim: (32, n_used as u32, 1),
5875            shared_mem_bytes: 0,
5876        };
5877        let (inf, outf, nu, ne, rbi) = (
5878            in_f as i32,
5879            out_f as i32,
5880            n_used as i32,
5881            n_expert as i32,
5882            rb as i64,
5883        );
5884        let __s_b = self.gpu.stream();
5885        let mut b = __s_b.launch_builder(&f);
5886        b.arg(table)
5887            .arg(sel)
5888            .arg(w)
5889            .arg(aq2)
5890            .arg(ad2)
5891            .arg(dst)
5892            .arg(&inf)
5893            .arg(&outf)
5894            .arg(&nu)
5895            .arg(&ne)
5896            .arg(&qt)
5897            .arg(&rbi);
5898        unsafe {
5899            b.launch(cfg)?;
5900        }
5901        Ok(())
5902    }
5903
5904    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5905    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5906    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5907    #[allow(clippy::too_many_arguments)]
5908    pub fn moe_gate_up_silu8_dev_q8_csr(
5909        &self,
5910        table: &CudaSlice<u64>,
5911        sel: &CudaSlice<i32>,
5912        aq: &CudaSlice<i8>,
5913        ad: &CudaSlice<f32>,
5914        n_pairs: usize,
5915        in_f: usize,
5916        n_ff: usize,
5917        n_used: usize,
5918        n_expert: usize,
5919        qt_g: i32,
5920        qt_u: i32,
5921        rb_g: usize,
5922        rb_u: usize,
5923    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5924        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
5925        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5926        let cfg = LaunchConfig {
5927            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5928            block_dim: (32, 1, 1),
5929            shared_mem_bytes: 0,
5930        };
5931        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5932            in_f as i32,
5933            n_ff as i32,
5934            n_expert as i32,
5935            n_used as i32,
5936            n_pairs as i32,
5937            rb_g as i64,
5938            rb_u as i64,
5939        );
5940        let __s_b = self.gpu.stream();
5941        let mut b = __s_b.launch_builder(&f);
5942        b.arg(table)
5943            .arg(sel)
5944            .arg(aq)
5945            .arg(ad)
5946            .arg(&mut act)
5947            .arg(&inf)
5948            .arg(&nff)
5949            .arg(&ne)
5950            .arg(&qt_g)
5951            .arg(&qt_u)
5952            .arg(&rbg)
5953            .arg(&rbu)
5954            .arg(&nu)
5955            .arg(&npi);
5956        unsafe {
5957            b.launch(cfg)?;
5958        }
5959        Ok(act)
5960    }
5961
5962    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5963    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5964    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5965    #[allow(clippy::too_many_arguments)]
5966    pub fn moe_down8_fma_dev_q8_variant(
5967        &self,
5968        variant: &str,
5969        table: &CudaSlice<u64>,
5970        sel: &cudarc::driver::CudaView<i32>,
5971        w: &cudarc::driver::CudaView<f32>,
5972        aq2: &CudaSlice<i8>,
5973        ad2: &CudaSlice<f32>,
5974        dst: &mut cudarc::driver::CudaViewMut<f32>,
5975        in_f: usize,
5976        out_f: usize,
5977        n_used: usize,
5978        n_expert: usize,
5979        qt: i32,
5980        rb: usize,
5981    ) -> Result<(), Box<dyn std::error::Error>> {
5982        let (inf, outf, nu, ne, rbi) = (
5983            in_f as i32,
5984            out_f as i32,
5985            n_used as i32,
5986            n_expert as i32,
5987            rb as i64,
5988        );
5989        let (f, cfg) = match variant {
5990            "w8h2" | "w8h2v" => (
5991                self.func(if variant == "w8h2" {
5992                    "moe_down8_fma_dev_q8_w8h2"
5993                } else {
5994                    "moe_down8_fma_dev_q8_w8h2v"
5995                }),
5996                LaunchConfig {
5997                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5998                    block_dim: (32, n_used as u32, 1),
5999                    shared_mem_bytes: 0,
6000                },
6001            ),
6002            "w8h2r2" | "w8h2r2v" => (
6003                self.func(if variant == "w8h2r2" {
6004                    "moe_down8_fma_dev_q8_w8h2r2"
6005                } else {
6006                    "moe_down8_fma_dev_q8_w8h2r2v"
6007                }),
6008                LaunchConfig {
6009                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
6010                    block_dim: (32, n_used as u32, 1),
6011                    shared_mem_bytes: 0,
6012                },
6013            ),
6014            _ => (
6015                self.func("moe_down8_fma_dev_q8"),
6016                LaunchConfig {
6017                    grid_dim: (out_f as u32, 1, 1),
6018                    block_dim: (32, 1, 1),
6019                    shared_mem_bytes: 0,
6020                },
6021            ),
6022        };
6023        let __s_b = self.gpu.stream();
6024        let mut b = __s_b.launch_builder(&f);
6025        b.arg(table)
6026            .arg(sel)
6027            .arg(w)
6028            .arg(aq2)
6029            .arg(ad2)
6030            .arg(dst)
6031            .arg(&inf)
6032            .arg(&outf)
6033            .arg(&nu)
6034            .arg(&ne)
6035            .arg(&qt)
6036            .arg(&rbi);
6037        unsafe {
6038            b.launch(cfg)?;
6039        }
6040        Ok(())
6041    }
6042
6043    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
6044    #[allow(clippy::too_many_arguments)]
6045    pub fn moe_gate_up_silu8_dev_q8_variant(
6046        &self,
6047        variant: &str,
6048        table: &CudaSlice<u64>,
6049        sel: &cudarc::driver::CudaView<i32>,
6050        aq: &CudaSlice<i8>,
6051        ad: &CudaSlice<f32>,
6052        in_f: usize,
6053        n_ff: usize,
6054        n_used: usize,
6055        n_expert: usize,
6056        qt_g: i32,
6057        qt_u: i32,
6058        rb_g: usize,
6059        rb_u: usize,
6060    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6061        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
6062        let (inf, nff, ne, rbg, rbu) = (
6063            in_f as i32,
6064            n_ff as i32,
6065            n_expert as i32,
6066            rb_g as i64,
6067            rb_u as i64,
6068        );
6069        let f = self.func(if variant == "v" {
6070            "moe_gate_up_silu8_dev_q8_v"
6071        } else {
6072            "moe_gate_up_silu8_dev_q8"
6073        });
6074        let cfg = LaunchConfig {
6075            grid_dim: (n_ff as u32, n_used as u32, 1),
6076            block_dim: (32, 1, 1),
6077            shared_mem_bytes: 0,
6078        };
6079        let __s_b = self.gpu.stream();
6080        let mut b = __s_b.launch_builder(&f);
6081        b.arg(table)
6082            .arg(sel)
6083            .arg(aq)
6084            .arg(ad)
6085            .arg(&mut act)
6086            .arg(&inf)
6087            .arg(&nff)
6088            .arg(&ne)
6089            .arg(&qt_g)
6090            .arg(&qt_u)
6091            .arg(&rbg)
6092            .arg(&rbu);
6093        unsafe {
6094            b.launch(cfg)?;
6095        }
6096        Ok(act)
6097    }
6098
6099    pub fn moe_gate_up_silu8_dev(
6100        &self,
6101        table: &CudaSlice<u64>,
6102        sel: &cudarc::driver::CudaView<i32>,
6103        x: &cudarc::driver::CudaView<f32>,
6104        in_f: usize,
6105        n_ff: usize,
6106        n_used: usize,
6107        n_expert: usize,
6108        qt_g: i32,
6109        qt_u: i32,
6110        rb_g: usize,
6111        rb_u: usize,
6112        macros: &CudaSlice<f32>,
6113    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6114        let f = self.func("moe_gate_up_silu8_dev");
6115        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6116        let cfg = LaunchConfig {
6117            grid_dim: (n_ff as u32, n_used as u32, 1),
6118            block_dim: (256, 1, 1),
6119            shared_mem_bytes: 0,
6120        };
6121        let (inf, nff, ne, rbg, rbu) = (
6122            in_f as i32,
6123            n_ff as i32,
6124            n_expert as i32,
6125            rb_g as i64,
6126            rb_u as i64,
6127        );
6128        let __s_b = self.gpu.stream();
6129        let mut b = __s_b.launch_builder(&f);
6130        b.arg(table)
6131            .arg(sel)
6132            .arg(x)
6133            .arg(&mut act)
6134            .arg(&inf)
6135            .arg(&nff)
6136            .arg(&ne)
6137            .arg(&qt_g)
6138            .arg(&qt_u)
6139            .arg(&rbg)
6140            .arg(&rbu)
6141            .arg(macros);
6142        unsafe {
6143            b.launch(cfg)?;
6144        }
6145        Ok(act)
6146    }
6147
6148    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6149    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6150    #[allow(clippy::too_many_arguments)]
6151    pub fn moe_down8_fma_dev(
6152        &self,
6153        table: &CudaSlice<u64>,
6154        sel: &cudarc::driver::CudaView<i32>,
6155        w: &cudarc::driver::CudaView<f32>,
6156        act: &CudaSlice<f32>,
6157        dst: &mut cudarc::driver::CudaViewMut<f32>,
6158        in_f: usize,
6159        out_f: usize,
6160        n_used: usize,
6161        n_expert: usize,
6162        qt: i32,
6163        rb: usize,
6164    ) -> Result<(), Box<dyn std::error::Error>> {
6165        let f = self.func("moe_down8_fma_dev");
6166        let cfg = LaunchConfig {
6167            grid_dim: (out_f as u32, 1, 1),
6168            block_dim: (256, 1, 1),
6169            shared_mem_bytes: 0,
6170        };
6171        let (inf, outf, nu, ne, rbv) = (
6172            in_f as i32,
6173            out_f as i32,
6174            n_used as i32,
6175            n_expert as i32,
6176            rb as i64,
6177        );
6178        let __s_b = self.gpu.stream();
6179        let mut b = __s_b.launch_builder(&f);
6180        b.arg(table)
6181            .arg(sel)
6182            .arg(w)
6183            .arg(act)
6184            .arg(dst)
6185            .arg(&inf)
6186            .arg(&outf)
6187            .arg(&nu)
6188            .arg(&ne)
6189            .arg(&qt)
6190            .arg(&rbv);
6191        unsafe {
6192            b.launch(cfg)?;
6193        }
6194        Ok(())
6195    }
6196
6197    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6198    pub fn axpy_into(
6199        &self,
6200        src: &CudaSlice<f32>,
6201        alpha: f32,
6202        dst: &mut cudarc::driver::CudaViewMut<f32>,
6203        n: usize,
6204    ) -> Result<(), Box<dyn std::error::Error>> {
6205        let f = self.func("axpy_f32");
6206        let cfg = LaunchConfig::for_num_elems(n as u32);
6207        let (a, ni) = (alpha, n as i32);
6208        let __s_b = self.gpu.stream();
6209        let mut b = __s_b.launch_builder(&f);
6210        b.arg(src).arg(dst).arg(&a).arg(&ni);
6211        unsafe {
6212            b.launch(cfg)?;
6213        }
6214        Ok(())
6215    }
6216
6217    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6218    pub fn add_scaled_rows(
6219        &self,
6220        src: &CudaSlice<f32>,
6221        scale: &CudaSlice<f32>,
6222        dst: &mut CudaSlice<f32>,
6223        ncols: usize,
6224        nrows: usize,
6225    ) -> Result<(), Box<dyn std::error::Error>> {
6226        let f = self.func("add_scaled_rows_f32");
6227        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6228        let (nc, nr) = (ncols as i32, nrows as i32);
6229        let __s_b = self.gpu.stream();
6230        let mut b = __s_b.launch_builder(&f);
6231        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6232        unsafe {
6233            b.launch(cfg)?;
6234        }
6235        Ok(())
6236    }
6237
6238    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6239
6240    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6241    pub fn gather_rows(
6242        &self,
6243        src: &CudaSlice<f32>,
6244        idx: &CudaSlice<i32>,
6245        dst: &mut CudaSlice<f32>,
6246        ncols: usize,
6247        m_e: usize,
6248    ) -> Result<(), Box<dyn std::error::Error>> {
6249        let f = self.func("gather_rows_f32");
6250        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6251        let (nc, me) = (ncols as i32, m_e as i32);
6252        let __s_b = self.gpu.stream();
6253        let mut b = __s_b.launch_builder(&f);
6254        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6255        unsafe {
6256            b.launch(cfg)?;
6257        }
6258        Ok(())
6259    }
6260
6261    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6262    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6263    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6264    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6265    pub fn scatter_slot(
6266        &self,
6267        src: &CudaSlice<f32>,
6268        tok_idx: &CudaSlice<i32>,
6269        slot_idx: &CudaSlice<i32>,
6270        weight: &CudaSlice<f32>,
6271        dst: &mut CudaSlice<f32>,
6272        wbuf: &mut CudaSlice<f32>,
6273        ncols: usize,
6274        n_used: usize,
6275        m_e: usize,
6276    ) -> Result<(), Box<dyn std::error::Error>> {
6277        let f = self.func("scatter_add_slot_f32");
6278        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6279        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6280        let __s_b = self.gpu.stream();
6281        let mut b = __s_b.launch_builder(&f);
6282        b.arg(src)
6283            .arg(tok_idx)
6284            .arg(slot_idx)
6285            .arg(weight)
6286            .arg(dst)
6287            .arg(wbuf)
6288            .arg(&nc)
6289            .arg(&nu)
6290            .arg(&me);
6291        unsafe {
6292            b.launch(cfg)?;
6293        }
6294        Ok(())
6295    }
6296
6297    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6298    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6299    /// Uses FMA for bit-identity with the sequential axpy path.
6300    pub fn reduce_slots(
6301        &self,
6302        slots: &CudaSlice<f32>,
6303        wbuf: &CudaSlice<f32>,
6304        dst: &mut CudaSlice<f32>,
6305        ncols: usize,
6306        n_used: usize,
6307        t: usize,
6308    ) -> Result<(), Box<dyn std::error::Error>> {
6309        let f = self.func("reduce_slots_f32");
6310        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6311        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6312        let __s_b = self.gpu.stream();
6313        let mut b = __s_b.launch_builder(&f);
6314        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6315        unsafe {
6316            b.launch(cfg)?;
6317        }
6318        Ok(())
6319    }
6320
6321    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6322    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6323    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6324    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6325    /// GPU time, ~half of it redundant re-quantization of the same row.
6326    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6327    pub fn quantize_q8_1_view(
6328        &self,
6329        x: &cudarc::driver::CudaView<f32>,
6330        m: usize,
6331        in_f: usize,
6332    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6333        let f = self.func("quantize_q8_1");
6334        let nblk = in_f / 32;
6335        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6336        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6337        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6338        let (inf, mi) = (in_f as i32, m as i32);
6339        let __s_b = self.gpu.stream();
6340        let mut b = __s_b.launch_builder(&f);
6341        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6342        unsafe {
6343            b.launch(cfg)?;
6344        }
6345        Ok((q, d))
6346    }
6347
6348    pub fn quantize_q8_1(
6349        &self,
6350        x: &CudaSlice<f32>,
6351        m: usize,
6352        in_f: usize,
6353    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6354        let nblk = in_f / 32;
6355        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6356        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6357        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6358        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6359        let (inf, mi) = (in_f as i32, m as i32);
6360        if Self::pdl_on() && Self::pdl_wb_on() {
6361            {
6362                use cudarc::driver::{DevicePtr, DevicePtrMut};
6363                let s = &self.gpu.stream();
6364                let (px, _g0) = x.device_ptr(s);
6365                let (pq, _g1) = q.device_ptr_mut(s);
6366                let (pd, _g2) = d.device_ptr_mut(s);
6367                let mut ps = [
6368                    &px as *const _ as *mut std::ffi::c_void,
6369                    &pq as *const _ as *mut _,
6370                    &pd as *const _ as *mut _,
6371                    &inf as *const _ as *mut _,
6372                    &mi as *const _ as *mut _,
6373                ];
6374                unsafe {
6375                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6376                }
6377            }
6378            return Ok((q, d));
6379        }
6380        let f = self.func("quantize_q8_1");
6381        let __s_b = self.gpu.stream();
6382        let mut b = __s_b.launch_builder(&f);
6383        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6384        unsafe {
6385            b.launch(cfg)?;
6386        }
6387        Ok((q, d))
6388    }
6389
6390    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6391    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6392    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6393    pub fn quantize_fp4_act(
6394        &self,
6395        x: &CudaSlice<f32>,
6396        m: usize,
6397        in_f: usize,
6398    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6399        let f = self.func("quantize_fp4_act");
6400        let nb16 = in_f / 16;
6401        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6402        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6403        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6404        let (inf, mi) = (in_f as i32, m as i32);
6405        let __s_b = self.gpu.stream();
6406        let mut b = __s_b.launch_builder(&f);
6407        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6408        unsafe {
6409            b.launch(cfg)?;
6410        }
6411        Ok((aq4, ad4))
6412    }
6413
6414    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6415    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6416    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6417    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6418    pub fn qmatvec_gemm_nvfp4_fp4(
6419        &self,
6420        bytes: &CudaSlice<u8>,
6421        x: &CudaSlice<f32>,
6422        m: usize,
6423        in_f: usize,
6424        out_f: usize,
6425        row_bytes: usize,
6426        scale: f32,
6427    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6428        assert!(
6429            in_f % 64 == 0,
6430            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6431        );
6432        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6433        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6434        if scale != 1.0 {
6435            self.scale_inplace(&mut y, scale, m * out_f)?;
6436        }
6437        Ok(y)
6438    }
6439
6440    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6441    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6442    fn fp4_gemm_launch(
6443        &self,
6444        bytes: &CudaSlice<u8>,
6445        aq4: &CudaSlice<u32>,
6446        ad4: &CudaSlice<u8>,
6447        m: usize,
6448        in_f: usize,
6449        out_f: usize,
6450        row_bytes: usize,
6451    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6452        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6453        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6454        const BM: u32 = 64;
6455        const BN: u32 = 256;
6456        let cfg = LaunchConfig {
6457            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6458            block_dim: (32, 4, 1),
6459            shared_mem_bytes: 0,
6460        };
6461        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6462        let __s_b = self.gpu.stream();
6463        let mut b = __s_b.launch_builder(&f);
6464        b.arg(bytes)
6465            .arg(aq4)
6466            .arg(ad4)
6467            .arg(&mut y)
6468            .arg(&inf)
6469            .arg(&outf)
6470            .arg(&mi)
6471            .arg(&rb);
6472        unsafe {
6473            b.launch(cfg)?;
6474        }
6475        Ok(y)
6476    }
6477
6478    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6479    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6480        &self,
6481        bytes: &CudaSlice<u8>,
6482        x: &CudaSlice<f32>,
6483        m: usize,
6484        in_f: usize,
6485        out_f: usize,
6486        row_bytes: usize,
6487    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6488        assert!(
6489            in_f % 64 == 0,
6490            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6491        );
6492        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6493        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6494    }
6495
6496    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6497    pub fn qmatvec_q8_0_fast(
6498        &self,
6499        w: &CudaSlice<u8>,
6500        x: &CudaSlice<f32>,
6501        m: usize,
6502        in_f: usize,
6503        out_f: usize,
6504        row_bytes: usize,
6505    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6506        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6507        let f = self.func("qmatvec_q8_0_dp4a");
6508        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6509        let cfg = LaunchConfig {
6510            grid_dim: (out_f as u32, m as u32, 1),
6511            block_dim: (128, 1, 1),
6512            shared_mem_bytes: 0,
6513        };
6514        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6515        let __s_b = self.gpu.stream();
6516        let mut b = __s_b.launch_builder(&f);
6517        b.arg(w)
6518            .arg(&aq)
6519            .arg(&ad)
6520            .arg(&mut y)
6521            .arg(&inf)
6522            .arg(&outf)
6523            .arg(&mi)
6524            .arg(&rb);
6525        unsafe {
6526            b.launch(cfg)?;
6527        }
6528        Ok(y)
6529    }
6530
6531    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6532    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6533    pub fn qmatvec_q4_K_fast(
6534        &self,
6535        w: &CudaSlice<u8>,
6536        x: &CudaSlice<f32>,
6537        m: usize,
6538        in_f: usize,
6539        out_f: usize,
6540        row_bytes: usize,
6541    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6542        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6543        let f = self.func("qmatvec_q4_K_dp4a");
6544        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6545        let cfg = LaunchConfig {
6546            grid_dim: (out_f as u32, m as u32, 1),
6547            block_dim: (128, 1, 1),
6548            shared_mem_bytes: 0,
6549        };
6550        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6551        let __s_b = self.gpu.stream();
6552        let mut b = __s_b.launch_builder(&f);
6553        b.arg(w)
6554            .arg(&aq)
6555            .arg(&ad)
6556            .arg(&mut y)
6557            .arg(&inf)
6558            .arg(&outf)
6559            .arg(&mi)
6560            .arg(&rb);
6561        unsafe {
6562            b.launch(cfg)?;
6563        }
6564        Ok(y)
6565    }
6566
6567    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6568    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6569    pub fn qmatvec_q6_K_fast(
6570        &self,
6571        w: &CudaSlice<u8>,
6572        x: &CudaSlice<f32>,
6573        m: usize,
6574        in_f: usize,
6575        out_f: usize,
6576        row_bytes: usize,
6577    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6578        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6579        let f = self.func("qmatvec_q6_K_dp4a");
6580        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6581        let cfg = LaunchConfig {
6582            grid_dim: (out_f as u32, m as u32, 1),
6583            block_dim: (128, 1, 1),
6584            shared_mem_bytes: 0,
6585        };
6586        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6587        let __s_b = self.gpu.stream();
6588        let mut b = __s_b.launch_builder(&f);
6589        b.arg(w)
6590            .arg(&aq)
6591            .arg(&ad)
6592            .arg(&mut y)
6593            .arg(&inf)
6594            .arg(&outf)
6595            .arg(&mi)
6596            .arg(&rb);
6597        unsafe {
6598            b.launch(cfg)?;
6599        }
6600        Ok(y)
6601    }
6602
6603    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6604    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6605    pub fn qmatvec_q5_K_fast(
6606        &self,
6607        w: &CudaSlice<u8>,
6608        x: &CudaSlice<f32>,
6609        m: usize,
6610        in_f: usize,
6611        out_f: usize,
6612        row_bytes: usize,
6613    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6614        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6615    }
6616    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6617    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6618    pub fn qmatvec_q3_K_fast(
6619        &self,
6620        w: &CudaSlice<u8>,
6621        x: &CudaSlice<f32>,
6622        m: usize,
6623        in_f: usize,
6624        out_f: usize,
6625        row_bytes: usize,
6626    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6627        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6628    }
6629    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6630    pub fn qmatvec_nvfp4_fast_rp(
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        assert!(
6640            in_f % 64 == 0,
6641            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6642        );
6643        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6644    }
6645    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6646    pub fn qmatvec_nvfp4_fast(
6647        &self,
6648        w: &CudaSlice<u8>,
6649        x: &CudaSlice<f32>,
6650        m: usize,
6651        in_f: usize,
6652        out_f: usize,
6653        row_bytes: usize,
6654    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6655        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6656        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6657        assert!(
6658            in_f % 64 == 0,
6659            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6660        );
6661        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6662    }
6663    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6664    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6665    pub fn qmatvec_iq4_XS_fast(
6666        &self,
6667        w: &CudaSlice<u8>,
6668        x: &CudaSlice<f32>,
6669        m: usize,
6670        in_f: usize,
6671        out_f: usize,
6672        row_bytes: usize,
6673    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6674        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6675    }
6676
6677    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6678    fn qmatvec_dp4a_named(
6679        &self,
6680        name: &str,
6681        w: &CudaSlice<u8>,
6682        x: &CudaSlice<f32>,
6683        m: usize,
6684        in_f: usize,
6685        out_f: usize,
6686        row_bytes: usize,
6687    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6688        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6689        let f = self.func(name);
6690        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6691        let cfg = LaunchConfig {
6692            grid_dim: (out_f as u32, m as u32, 1),
6693            block_dim: (128, 1, 1),
6694            shared_mem_bytes: 0,
6695        };
6696        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6697        let __s_b = self.gpu.stream();
6698        let mut b = __s_b.launch_builder(&f);
6699        b.arg(w)
6700            .arg(&aq)
6701            .arg(&ad)
6702            .arg(&mut y)
6703            .arg(&inf)
6704            .arg(&outf)
6705            .arg(&mi)
6706            .arg(&rb);
6707        unsafe {
6708            b.launch(cfg)?;
6709        }
6710        Ok(y)
6711    }
6712
6713    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6714        Ok(self.gpu.stream().clone_htod(v)?)
6715    }
6716    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6717        Ok(self.gpu.stream().clone_htod(v)?)
6718    }
6719    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6720    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6721        Ok(self.gpu.stream().clone_htod(v)?)
6722    }
6723    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6724        Ok(self.gpu.stream().clone_htod(v)?)
6725    }
6726    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6727    pub fn dtoh_view(
6728        &self,
6729        d: &cudarc::driver::CudaView<f32>,
6730    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6731        let v = self.gpu.stream().clone_dtoh(d)?;
6732        self.gpu.stream().synchronize()?;
6733        Ok(v)
6734    }
6735    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6736        let v = self.gpu.stream().clone_dtoh(d)?;
6737        self.gpu.stream().synchronize()?;
6738        Ok(v)
6739    }
6740    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6741    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6742    /// issuing them together avoids a second stream synchronization in every trunk layer.
6743    pub fn dtoh_pair(
6744        &self,
6745        a: &CudaSlice<f32>,
6746        b: &CudaSlice<f32>,
6747    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6748        let av = self.gpu.stream().clone_dtoh(a)?;
6749        let bv = self.gpu.stream().clone_dtoh(b)?;
6750        self.gpu.stream().synchronize()?;
6751        Ok((av, bv))
6752    }
6753    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6754    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6755        let v = self.gpu.stream().clone_dtoh(d)?;
6756        self.gpu.stream().synchronize()?;
6757        Ok(v)
6758    }
6759    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6760    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6761        let v = self.gpu.stream().clone_dtoh(d)?;
6762        self.gpu.stream().synchronize()?;
6763        Ok(v)
6764    }
6765    pub fn dtoh_u8_view(
6766        &self,
6767        d: &cudarc::driver::CudaView<u8>,
6768    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6769        let v = self.gpu.stream().clone_dtoh(d)?;
6770        self.gpu.stream().synchronize()?;
6771        Ok(v)
6772    }
6773    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6774        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6775        self.keep_if_capturing(&s);
6776        Ok(s)
6777    }
6778
6779    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6780    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6781    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6782    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6783    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6784    /// back (or kept resident for graph replay). Returns the device token buffer.
6785    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6786    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6787    pub fn prob_of_token_device(
6788        &self,
6789        logits: &CudaSlice<f32>,
6790        tok: &CudaSlice<u32>,
6791        n_vocab: usize,
6792    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6793        let nb = ARGMAX_NB;
6794        let mut part = self.alloc_uninit::<f32>(nb)?;
6795        let mut p = self.alloc_uninit::<f32>(1)?;
6796        let f1 = self.func("prob_of_token_partial_f32");
6797        let cfg1 = LaunchConfig {
6798            grid_dim: (nb as u32, 1, 1),
6799            block_dim: (256, 1, 1),
6800            shared_mem_bytes: 0,
6801        };
6802        let nv = n_vocab as i32;
6803        let __s_b1 = self.gpu.stream();
6804        let mut b1 = __s_b1.launch_builder(&f1);
6805        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6806        unsafe {
6807            b1.launch(cfg1)?;
6808        }
6809        let f2 = self.func("prob_of_token_final_f32");
6810        let cfg2 = LaunchConfig {
6811            grid_dim: (1, 1, 1),
6812            block_dim: (256, 1, 1),
6813            shared_mem_bytes: 0,
6814        };
6815        let nbi = nb as i32;
6816        let __s_b2 = self.gpu.stream();
6817        let mut b2 = __s_b2.launch_builder(&f2);
6818        b2.arg(&part).arg(&mut p).arg(&nbi);
6819        unsafe {
6820            b2.launch(cfg2)?;
6821        }
6822        Ok(p)
6823    }
6824
6825    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6826    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6827    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6828    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6829    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6830    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6831    pub fn prob_of_token_device_col(
6832        &self,
6833        logits: &CudaSlice<f32>,
6834        tok_all: &CudaSlice<u32>,
6835        tok_idx: usize,
6836        p_out: &mut CudaSlice<f32>,
6837        p_idx: usize,
6838        n_vocab: usize,
6839    ) -> Result<(), Box<dyn std::error::Error>> {
6840        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6841        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6842        let nb = ARGMAX_NB;
6843        let mut part = self.alloc_uninit::<f32>(nb)?;
6844        let f1 = self.func("prob_of_token_partial_f32");
6845        let cfg1 = LaunchConfig {
6846            grid_dim: (nb as u32, 1, 1),
6847            block_dim: (256, 1, 1),
6848            shared_mem_bytes: 0,
6849        };
6850        let nv = n_vocab as i32;
6851        let __s_b1 = self.gpu.stream();
6852        let mut b1 = __s_b1.launch_builder(&f1);
6853        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6854        unsafe {
6855            b1.launch(cfg1)?;
6856        }
6857        let f2 = self.func("prob_of_token_final_f32");
6858        let cfg2 = LaunchConfig {
6859            grid_dim: (1, 1, 1),
6860            block_dim: (256, 1, 1),
6861            shared_mem_bytes: 0,
6862        };
6863        let nbi = nb as i32;
6864        let __s_b2 = self.gpu.stream();
6865        let mut b2 = __s_b2.launch_builder(&f2);
6866        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6867        unsafe {
6868            b2.launch(cfg2)?;
6869        }
6870        Ok(())
6871    }
6872
6873    pub fn prob_of_token_device_into(
6874        &self,
6875        logits: &CudaSlice<f32>,
6876        tok: &CudaSlice<u32>,
6877        p_out: &mut CudaSlice<f32>,
6878        n_vocab: usize,
6879    ) -> Result<(), Box<dyn std::error::Error>> {
6880        let nb = ARGMAX_NB;
6881        let mut part = self.alloc_uninit::<f32>(nb)?;
6882        let f1 = self.func("prob_of_token_partial_f32");
6883        let cfg1 = LaunchConfig {
6884            grid_dim: (nb as u32, 1, 1),
6885            block_dim: (256, 1, 1),
6886            shared_mem_bytes: 0,
6887        };
6888        let nv = n_vocab as i32;
6889        let __s_b1 = self.gpu.stream();
6890        let mut b1 = __s_b1.launch_builder(&f1);
6891        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6892        unsafe {
6893            b1.launch(cfg1)?;
6894        }
6895        let f2 = self.func("prob_of_token_final_f32");
6896        let cfg2 = LaunchConfig {
6897            grid_dim: (1, 1, 1),
6898            block_dim: (256, 1, 1),
6899            shared_mem_bytes: 0,
6900        };
6901        let nbi = nb as i32;
6902        let __s_b2 = self.gpu.stream();
6903        let mut b2 = __s_b2.launch_builder(&f2);
6904        b2.arg(&part).arg(p_out).arg(&nbi);
6905        unsafe {
6906            b2.launch(cfg2)?;
6907        }
6908        Ok(())
6909    }
6910
6911    pub fn argmax_token_device(
6912        &self,
6913        logits: &CudaSlice<f32>,
6914        n_vocab: usize,
6915    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6916        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6917        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6918        Ok(tok)
6919    }
6920    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6921    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6922    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6923    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6924    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6925    /// captured passes bake fixed addresses.
6926    pub fn argmax_token_device_into(
6927        &self,
6928        logits: &CudaSlice<f32>,
6929        tok: &mut CudaSlice<u32>,
6930        n_vocab: usize,
6931    ) -> Result<(), Box<dyn std::error::Error>> {
6932        let nb = ARGMAX_NB;
6933        let f1 = self.func("argmax_partial_f32");
6934        let f2 = self.func("argmax_final_f32");
6935        let mut guard = self.argmax_partials.lock().unwrap();
6936        if guard.is_none() {
6937            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6938            // buffers carry no cudarc events (illegal inside capture).
6939            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6940            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6941            *guard = Some((pv, pi));
6942        }
6943        let (part_v, part_i) = guard.as_mut().unwrap();
6944        let nv = n_vocab as i32;
6945        let nbi = nb as i32;
6946        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6947        let cfg1 = LaunchConfig {
6948            grid_dim: (nb as u32, 1, 1),
6949            block_dim: (256, 1, 1),
6950            shared_mem_bytes: 0,
6951        };
6952        let __s_b1 = self.gpu.stream();
6953        let mut b1 = __s_b1.launch_builder(&f1);
6954        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6955        unsafe {
6956            b1.launch(cfg1)?;
6957        }
6958        // pass 2: one block reduces NB partials -> token_out[0].
6959        let cfg2 = LaunchConfig {
6960            grid_dim: (1, 1, 1),
6961            block_dim: (256, 1, 1),
6962            shared_mem_bytes: 0,
6963        };
6964        let __s_b2 = self.gpu.stream();
6965        let mut b2 = __s_b2.launch_builder(&f2);
6966        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6967        unsafe {
6968            b2.launch(cfg2)?;
6969        }
6970        Ok(())
6971    }
6972    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6973    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6974    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6975    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6976    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6977    pub fn argmax_token_device_col(
6978        &self,
6979        logits: &CudaSlice<f32>,
6980        col: usize,
6981        n_vocab: usize,
6982        toks: &mut CudaSlice<u32>,
6983        out_idx: usize,
6984    ) -> Result<(), Box<dyn std::error::Error>> {
6985        let nb = ARGMAX_NB;
6986        let f1 = self.func("argmax_partial_f32");
6987        let f2 = self.func("argmax_final_f32");
6988        let mut guard = self.argmax_partials.lock().unwrap();
6989        if guard.is_none() {
6990            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6991            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6992            *guard = Some((pv, pi));
6993        }
6994        let (part_v, part_i) = guard.as_mut().unwrap();
6995        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
6996        let nv = n_vocab as i32;
6997        let nbi = nb as i32;
6998        let cfg1 = LaunchConfig {
6999            grid_dim: (nb as u32, 1, 1),
7000            block_dim: (256, 1, 1),
7001            shared_mem_bytes: 0,
7002        };
7003        let __s_b1 = self.gpu.stream();
7004        let mut b1 = __s_b1.launch_builder(&f1);
7005        b1.arg(&col_view)
7006            .arg(&mut *part_v)
7007            .arg(&mut *part_i)
7008            .arg(&nv);
7009        unsafe {
7010            b1.launch(cfg1)?;
7011        }
7012        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
7013        let cfg2 = LaunchConfig {
7014            grid_dim: (1, 1, 1),
7015            block_dim: (256, 1, 1),
7016            shared_mem_bytes: 0,
7017        };
7018        let __s_b2 = self.gpu.stream();
7019        let mut b2 = __s_b2.launch_builder(&f2);
7020        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
7021        unsafe {
7022            b2.launch(cfg2)?;
7023        }
7024        Ok(())
7025    }
7026    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
7027    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
7028        Ok(self.gpu.stream().clone_htod(v)?)
7029    }
7030    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
7031        let v = self.gpu.stream().clone_dtoh(d)?;
7032        self.gpu.stream().synchronize()?;
7033        Ok(v)
7034    }
7035    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
7036    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
7037    /// contents change every step, the address must not, so a captured graph can read it).
7038    pub fn htod_u32_into(
7039        &self,
7040        dst: &mut CudaSlice<u32>,
7041        src: &[u32],
7042    ) -> Result<(), Box<dyn std::error::Error>> {
7043        let mut view = dst.slice_mut(0..src.len());
7044        self.gpu.stream().memcpy_htod(src, &mut view)?;
7045        Ok(())
7046    }
7047
7048    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
7049    /// table without changing the device address its reconcile kernel consumes.
7050    pub fn htod_i32_into(
7051        &self,
7052        dst: &mut CudaSlice<i32>,
7053        src: &[i32],
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    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
7061        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
7062        self.keep_if_capturing(&s);
7063        Ok(s)
7064    }
7065    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
7066    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
7067    pub fn embed_gather_device_into(
7068        &self,
7069        embd: &CudaSlice<u8>,
7070        token_d: &CudaSlice<u32>,
7071        x_out: &mut CudaSlice<f32>,
7072        n_embd: usize,
7073        qtype: i32,
7074        row_bytes: usize,
7075    ) -> Result<(), Box<dyn std::error::Error>> {
7076        let f = self.func("embed_gather_u32");
7077        let cfg = LaunchConfig {
7078            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7079            block_dim: (256, 1, 1),
7080            shared_mem_bytes: 0,
7081        };
7082        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7083        let __s_b = self.gpu.stream();
7084        let mut b = __s_b.launch_builder(&f);
7085        b.arg(embd)
7086            .arg(token_d)
7087            .arg(x_out)
7088            .arg(&ne)
7089            .arg(&qt)
7090            .arg(&rb);
7091        unsafe {
7092            b.launch(cfg)?;
7093        }
7094        Ok(())
7095    }
7096    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
7097    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
7098        let v = self.gpu.stream().clone_dtoh(d)?;
7099        self.gpu.stream().synchronize()?;
7100        Ok(v[0])
7101    }
7102    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
7103    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
7104    /// the counter value after the throwaway capture warmups corrupt it.
7105    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
7106    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
7107    /// copy (fine at stream-idle boundaries, poison mid-round).
7108    pub fn i32_set_k(
7109        &self,
7110        dst: &mut CudaSlice<i32>,
7111        v: i32,
7112    ) -> Result<(), Box<dyn std::error::Error>> {
7113        let f = self.func("i32_set_k");
7114        let cfg = LaunchConfig {
7115            grid_dim: (1, 1, 1),
7116            block_dim: (1, 1, 1),
7117            shared_mem_bytes: 0,
7118        };
7119        let idx = 0i32;
7120        let __s_b = self.gpu.stream();
7121        let mut b = __s_b.launch_builder(&f);
7122        b.arg(dst).arg(&v).arg(&idx);
7123        unsafe {
7124            b.launch(cfg)?;
7125        }
7126        Ok(())
7127    }
7128
7129    pub fn set_i32_one(
7130        &self,
7131        d: &mut CudaSlice<i32>,
7132        v: i32,
7133    ) -> Result<(), Box<dyn std::error::Error>> {
7134        self.gpu.stream().memcpy_htod(&[v], d)?;
7135        Ok(())
7136    }
7137    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
7138    /// during priming / capture-state restore.
7139    pub fn set_u32_one(
7140        &self,
7141        d: &mut CudaSlice<u32>,
7142        v: u32,
7143    ) -> Result<(), Box<dyn std::error::Error>> {
7144        self.gpu.stream().memcpy_htod(&[v], d)?;
7145        Ok(())
7146    }
7147    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
7148    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
7149        let v = self.gpu.stream().clone_dtoh(d)?;
7150        self.gpu.stream().synchronize()?;
7151        Ok(v[0])
7152    }
7153    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
7154    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
7155        Ok(self.gpu.stream().clone_htod(bytes)?)
7156    }
7157    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7158    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7159    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7160    pub fn embed_gather_device(
7161        &self,
7162        embd: &CudaSlice<u8>,
7163        token_d: &CudaSlice<u32>,
7164        n_embd: usize,
7165        qtype: i32,
7166        row_bytes: usize,
7167    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7168        let f = self.func("embed_gather_u32");
7169        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7170        let cfg = LaunchConfig {
7171            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7172            block_dim: (256, 1, 1),
7173            shared_mem_bytes: 0,
7174        };
7175        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7176        let __s_b = self.gpu.stream();
7177        let mut b = __s_b.launch_builder(&f);
7178        b.arg(embd)
7179            .arg(token_d)
7180            .arg(&mut x)
7181            .arg(&ne)
7182            .arg(&qt)
7183            .arg(&rb);
7184        unsafe {
7185            b.launch(cfg)?;
7186        }
7187        Ok(x)
7188    }
7189
7190    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7191    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7192    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7193    pub fn embed_gather_device_t(
7194        &self,
7195        embd: &CudaSlice<u8>,
7196        tokens: &[u32],
7197        n_embd: usize,
7198        qtype: i32,
7199        row_bytes: usize,
7200    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7201        let t = tokens.len();
7202        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7203        let f = self.func("embed_gather_u32_t");
7204        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7205        let cfg = LaunchConfig {
7206            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7207            block_dim: (256, 1, 1),
7208            shared_mem_bytes: 0,
7209        };
7210        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7211        let __s_b = self.gpu.stream();
7212        let mut b = __s_b.launch_builder(&f);
7213        b.arg(embd)
7214            .arg(&tok_d)
7215            .arg(&mut x)
7216            .arg(&ne)
7217            .arg(&qt)
7218            .arg(&rb)
7219            .arg(&ti);
7220        unsafe {
7221            b.launch(cfg)?;
7222        }
7223        Ok(x)
7224    }
7225
7226    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7227    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7228    /// as embed_gather_device_t — bit-identical rows.
7229    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7230    pub fn embed_gather_device_tv(
7231        &self,
7232        embd: &CudaSlice<u8>,
7233        tok_v: &cudarc::driver::CudaView<u32>,
7234        t: usize,
7235        n_embd: usize,
7236        qtype: i32,
7237        row_bytes: usize,
7238    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7239        let f = self.func("embed_gather_u32_t");
7240        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7241        let cfg = LaunchConfig {
7242            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7243            block_dim: (256, 1, 1),
7244            shared_mem_bytes: 0,
7245        };
7246        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7247        let __s_b = self.gpu.stream();
7248        let mut b = __s_b.launch_builder(&f);
7249        b.arg(embd)
7250            .arg(tok_v)
7251            .arg(&mut x)
7252            .arg(&ne)
7253            .arg(&qt)
7254            .arg(&rb)
7255            .arg(&ti);
7256        unsafe {
7257            b.launch(cfg)?;
7258        }
7259        Ok(x)
7260    }
7261
7262    pub fn embed_gather_device_td(
7263        &self,
7264        embd: &CudaSlice<u8>,
7265        tok_d: &CudaSlice<u32>,
7266        t: usize,
7267        n_embd: usize,
7268        qtype: i32,
7269        row_bytes: usize,
7270    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7271        let f = self.func("embed_gather_u32_t");
7272        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7273        let cfg = LaunchConfig {
7274            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7275            block_dim: (256, 1, 1),
7276            shared_mem_bytes: 0,
7277        };
7278        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7279        let __s_b = self.gpu.stream();
7280        let mut b = __s_b.launch_builder(&f);
7281        b.arg(embd)
7282            .arg(tok_d)
7283            .arg(&mut x)
7284            .arg(&ne)
7285            .arg(&qt)
7286            .arg(&rb)
7287            .arg(&ti);
7288        unsafe {
7289            b.launch(cfg)?;
7290        }
7291        Ok(x)
7292    }
7293
7294    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7295    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7296    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7297    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7298    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7299    #[inline]
7300    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7301    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7302        if self
7303            .capture_keep_on
7304            .load(std::sync::atomic::Ordering::Relaxed)
7305        {
7306            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7307        }
7308    }
7309
7310    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7311        &self,
7312        n: usize,
7313    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7314        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7315        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7316        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7317        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7318        {
7319            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7320            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7321                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7322                use cudarc::driver::DevicePtrMut;
7323                let n_bytes = s.len() * std::mem::size_of::<T>();
7324                let stream = self.gpu.stream();
7325                let (p_, _g) = s.device_ptr_mut(&stream);
7326                unsafe {
7327                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7328                        .result()?;
7329                }
7330            }
7331        }
7332        self.keep_if_capturing(&s);
7333        Ok(s)
7334    }
7335
7336    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7337    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7338    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7339    /// consumers alloc through this (m=1 decode arms).
7340    pub fn uninit_q8_pair(
7341        &self,
7342        n: usize,
7343    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7344        Ok((
7345            self.alloc_uninit::<i8>(n)?,
7346            self.alloc_uninit::<f32>(n / 32)?,
7347        ))
7348    }
7349
7350    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7351        self.alloc_uninit::<f32>(n)
7352    }
7353
7354    /// i8 uninitialized scratch (same contract as `uninit`).
7355    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7356        self.alloc_uninit::<i8>(n)
7357    }
7358
7359    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7360    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7361    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7362    #[allow(clippy::too_many_arguments)]
7363    pub fn rms_norm3(
7364        &self,
7365        x: &CudaSlice<f32>,
7366        w0: &CudaSlice<f32>,
7367        w1: &CudaSlice<f32>,
7368        w2: &CudaSlice<f32>,
7369        d0: &mut CudaSlice<f32>,
7370        d1: &mut CudaSlice<f32>,
7371        d2: &mut CudaSlice<f32>,
7372        ncols: usize,
7373        nrows: usize,
7374        eps: f32,
7375    ) -> Result<(), Box<dyn std::error::Error>> {
7376        let f = self.func("rms_norm3_f32");
7377        let cfg = LaunchConfig {
7378            grid_dim: (nrows as u32, 1, 1),
7379            block_dim: (rms_block(), 1, 1),
7380            shared_mem_bytes: 0,
7381        };
7382        let (nc, e) = (ncols as i32, eps);
7383        let __s_b = self.gpu.stream();
7384        let mut b = __s_b.launch_builder(&f);
7385        b.arg(x)
7386            .arg(w0)
7387            .arg(w1)
7388            .arg(w2)
7389            .arg(d0)
7390            .arg(d1)
7391            .arg(d2)
7392            .arg(&nc)
7393            .arg(&e);
7394        unsafe {
7395            b.launch(cfg)?;
7396        }
7397        Ok(())
7398    }
7399
7400    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7401    #[allow(clippy::too_many_arguments)]
7402    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7403    /// piggybacks on the same conditions.
7404    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7405        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7406        *WARP_ON.get_or_init(|| {
7407            std::env::var("MEMRA_QKVNORM_W")
7408                .map(|v| v != "0")
7409                .unwrap_or(true)
7410        }) && ncols % 4 == 0
7411            && rows >= 64
7412    }
7413
7414    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7415    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7416    #[allow(clippy::too_many_arguments)]
7417    pub fn rms_norm_qkv_w4b(
7418        &self,
7419        q: &CudaSlice<f32>,
7420        k: &CudaSlice<f32>,
7421        v: &CudaSlice<f32>,
7422        wq: &CudaSlice<f32>,
7423        wk: &CudaSlice<f32>,
7424        wv: &CudaSlice<f32>,
7425        dq: &mut CudaSlice<f32>,
7426        dk: &mut CudaSlice<f32>,
7427        dv: &mut CudaSlice<f32>,
7428        dvb: &mut CudaSlice<u8>,
7429        ncols: usize,
7430        rq: usize,
7431        rk: usize,
7432        eps: f32,
7433        vf16: bool,
7434    ) -> Result<(), Box<dyn std::error::Error>> {
7435        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7436        let f = self.func("rms_norm_qkv_w4b_f32");
7437        let rows = (rq + 2 * rk) as u32;
7438        let cfg = LaunchConfig {
7439            grid_dim: (rows.div_ceil(8), 1, 1),
7440            block_dim: (256, 1, 1),
7441            shared_mem_bytes: 0,
7442        };
7443        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7444        let vf = vf16 as i32;
7445        let __s_b = self.gpu.stream();
7446        let mut b = __s_b.launch_builder(&f);
7447        b.arg(q)
7448            .arg(k)
7449            .arg(v)
7450            .arg(wq)
7451            .arg(wk)
7452            .arg(wv)
7453            .arg(dq)
7454            .arg(dk)
7455            .arg(dv)
7456            .arg(&mut *dvb)
7457            .arg(&nc)
7458            .arg(&rqi)
7459            .arg(&rki)
7460            .arg(&rvi)
7461            .arg(&e)
7462            .arg(&vf);
7463        unsafe {
7464            b.launch(cfg)?;
7465        }
7466        Ok(())
7467    }
7468
7469    pub fn rms_norm_qkv(
7470        &self,
7471        q: &CudaSlice<f32>,
7472        k: &CudaSlice<f32>,
7473        v: &CudaSlice<f32>,
7474        wq: &CudaSlice<f32>,
7475        wk: &CudaSlice<f32>,
7476        wv: &CudaSlice<f32>,
7477        dq: &mut CudaSlice<f32>,
7478        dk: &mut CudaSlice<f32>,
7479        dv: &mut CudaSlice<f32>,
7480        ncols: usize,
7481        rq: usize,
7482        rk: usize,
7483        eps: f32,
7484    ) -> Result<(), Box<dyn std::error::Error>> {
7485        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7486        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7487        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7488        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7489        let warp_on = *WARP_ON.get_or_init(|| {
7490            std::env::var("MEMRA_QKVNORM_W")
7491                .map(|v| v != "0")
7492                .unwrap_or(true)
7493        });
7494        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7495        // replay numerics are untouched on every model; only prefill depth takes the new config.
7496        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7497            let f = self.func("rms_norm_qkv_w4_f32");
7498            let rows = (rq + 2 * rk) as u32;
7499            let cfg = LaunchConfig {
7500                grid_dim: (rows.div_ceil(8), 1, 1),
7501                block_dim: (256, 1, 1),
7502                shared_mem_bytes: 0,
7503            };
7504            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7505            let __s_b = self.gpu.stream();
7506            let mut b = __s_b.launch_builder(&f);
7507            b.arg(q)
7508                .arg(k)
7509                .arg(v)
7510                .arg(wq)
7511                .arg(wk)
7512                .arg(wv)
7513                .arg(dq)
7514                .arg(dk)
7515                .arg(dv)
7516                .arg(&nc)
7517                .arg(&rqi)
7518                .arg(&rki)
7519                .arg(&rvi)
7520                .arg(&e);
7521            unsafe {
7522                b.launch(cfg)?;
7523            }
7524            return Ok(());
7525        }
7526        let f = self.func("rms_norm_qkv_f32");
7527        let grid = (rq + 2 * rk) as u32;
7528        let cfg = LaunchConfig {
7529            grid_dim: (grid, 1, 1),
7530            block_dim: (rms_block(), 1, 1),
7531            shared_mem_bytes: 0,
7532        };
7533        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7534        let __s_b = self.gpu.stream();
7535        let mut b = __s_b.launch_builder(&f);
7536        b.arg(q)
7537            .arg(k)
7538            .arg(v)
7539            .arg(wq)
7540            .arg(wk)
7541            .arg(wv)
7542            .arg(dq)
7543            .arg(dk)
7544            .arg(dv)
7545            .arg(&nc)
7546            .arg(&rqi)
7547            .arg(&rki)
7548            .arg(&e);
7549        unsafe {
7550            b.launch(cfg)?;
7551        }
7552        Ok(())
7553    }
7554
7555    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7556    #[allow(clippy::too_many_arguments)]
7557    pub fn rms_norm2x(
7558        &self,
7559        a: &CudaSlice<f32>,
7560        bb: &CudaSlice<f32>,
7561        wa: &CudaSlice<f32>,
7562        wb: &CudaSlice<f32>,
7563        da: &mut CudaSlice<f32>,
7564        db: &mut CudaSlice<f32>,
7565        ncols: usize,
7566        nrows: usize,
7567        eps: f32,
7568    ) -> Result<(), Box<dyn std::error::Error>> {
7569        let f = self.func("rms_norm2x_f32");
7570        let cfg = LaunchConfig {
7571            grid_dim: (2 * nrows as u32, 1, 1),
7572            block_dim: (rms_block(), 1, 1),
7573            shared_mem_bytes: 0,
7574        };
7575        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7576        let __s_b = self.gpu.stream();
7577        let mut b = __s_b.launch_builder(&f);
7578        b.arg(a)
7579            .arg(bb)
7580            .arg(wa)
7581            .arg(wb)
7582            .arg(da)
7583            .arg(db)
7584            .arg(&nc)
7585            .arg(&nr)
7586            .arg(&e);
7587        unsafe {
7588            b.launch(cfg)?;
7589        }
7590        Ok(())
7591    }
7592
7593    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7594    pub fn softcap(
7595        &self,
7596        y: &mut CudaSlice<f32>,
7597        cap: f32,
7598        n: usize,
7599    ) -> Result<(), Box<dyn std::error::Error>> {
7600        let f = self.func("softcap_f32");
7601        let cfg = LaunchConfig::for_num_elems(n as u32);
7602        let ni = n as i32;
7603        let __s_b = self.gpu.stream();
7604        let mut b = __s_b.launch_builder(&f);
7605        b.arg(y).arg(&cap).arg(&ni);
7606        unsafe {
7607            b.launch(cfg)?;
7608        }
7609        Ok(())
7610    }
7611
7612    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7613    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7614    pub fn mask_ids_rows(
7615        &self,
7616        y: &mut CudaSlice<f32>,
7617        ids: &CudaSlice<i32>,
7618        n_ids: usize,
7619        n_vocab: usize,
7620        t: usize,
7621    ) -> Result<(), Box<dyn std::error::Error>> {
7622        let f = self.func("mask_ids_rows_f32");
7623        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7624        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7625        let __s_b = self.gpu.stream();
7626        let mut b = __s_b.launch_builder(&f);
7627        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7628        unsafe {
7629            b.launch(cfg)?;
7630        }
7631        Ok(())
7632    }
7633
7634    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7635    #[allow(clippy::too_many_arguments)]
7636    pub fn add_scale_rms_norm(
7637        &self,
7638        a: &CudaSlice<f32>,
7639        b_in: &CudaSlice<f32>,
7640        c: f32,
7641        w: &CudaSlice<f32>,
7642        res: &mut CudaSlice<f32>,
7643        dst: &mut CudaSlice<f32>,
7644        ncols: usize,
7645        nrows: usize,
7646        eps: f32,
7647    ) -> Result<(), Box<dyn std::error::Error>> {
7648        let f = self.func("add_scale_rms_norm_f32");
7649        let cfg = LaunchConfig {
7650            grid_dim: (nrows as u32, 1, 1),
7651            block_dim: (rms_block(), 1, 1),
7652            shared_mem_bytes: 0,
7653        };
7654        let (nc, e2) = (ncols as i32, eps);
7655        let __s_b = self.gpu.stream();
7656        let mut b = __s_b.launch_builder(&f);
7657        b.arg(a)
7658            .arg(b_in)
7659            .arg(&c)
7660            .arg(w)
7661            .arg(res)
7662            .arg(dst)
7663            .arg(&nc)
7664            .arg(&e2);
7665        unsafe {
7666            b.launch(cfg)?;
7667        }
7668        Ok(())
7669    }
7670
7671    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7672    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7673    #[allow(clippy::too_many_arguments)]
7674    pub fn add_scale_rms_norm_q8_1(
7675        &self,
7676        a: &CudaSlice<f32>,
7677        b_in: &CudaSlice<f32>,
7678        c: f32,
7679        w: &CudaSlice<f32>,
7680        res: &mut CudaSlice<f32>,
7681        ncols: usize,
7682        nrows: usize,
7683        eps: f32,
7684    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7685        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7686        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7687        let (nc, e2) = (ncols as i32, eps);
7688        if Self::pdl_on() && Self::pdl_wb_on() {
7689            {
7690                use cudarc::driver::{DevicePtr, DevicePtrMut};
7691                let s = &self.gpu.stream();
7692                let (pa, _g0) = a.device_ptr(s);
7693                let (pb, _g1) = b_in.device_ptr(s);
7694                let (pw, _g2) = w.device_ptr(s);
7695                let (pr, _g3) = res.device_ptr_mut(s);
7696                let (pq, _g4) = out_q.device_ptr_mut(s);
7697                let (pd, _g5) = out_d.device_ptr_mut(s);
7698                let mut ps = [
7699                    &pa as *const _ as *mut std::ffi::c_void,
7700                    &pb as *const _ as *mut _,
7701                    &c as *const _ as *mut _,
7702                    &pw as *const _ as *mut _,
7703                    &pr as *const _ as *mut _,
7704                    &pq as *const _ as *mut _,
7705                    &pd as *const _ as *mut _,
7706                    &nc as *const _ as *mut _,
7707                    &e2 as *const _ as *mut _,
7708                ];
7709                unsafe {
7710                    self.launch_pdl(
7711                        "add_scale_rms_norm_q8_1",
7712                        (nrows as u32, 1, 1),
7713                        (rms_block(), 1, 1),
7714                        &mut ps,
7715                    )?;
7716                }
7717            }
7718            return Ok((out_q, out_d));
7719        }
7720        let f = self.func("add_scale_rms_norm_q8_1");
7721        let cfg = LaunchConfig {
7722            grid_dim: (nrows as u32, 1, 1),
7723            block_dim: (rms_block(), 1, 1),
7724            shared_mem_bytes: 0,
7725        };
7726        let __s_b = self.gpu.stream();
7727        let mut b = __s_b.launch_builder(&f);
7728        b.arg(a)
7729            .arg(b_in)
7730            .arg(&c)
7731            .arg(w)
7732            .arg(res)
7733            .arg(&mut out_q)
7734            .arg(&mut out_d)
7735            .arg(&nc)
7736            .arg(&e2);
7737        unsafe {
7738            b.launch(cfg)?;
7739        }
7740        Ok((out_q, out_d))
7741    }
7742
7743    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7744    #[allow(clippy::too_many_arguments)]
7745    pub fn add_scale_rms_norm_q8_1_into(
7746        &self,
7747        a: &CudaSlice<f32>,
7748        b_in: &CudaSlice<f32>,
7749        c: f32,
7750        w: &CudaSlice<f32>,
7751        res: &mut CudaSlice<f32>,
7752        ncols: usize,
7753        nrows: usize,
7754        eps: f32,
7755        out_q: &mut CudaSlice<i8>,
7756        out_d: &mut CudaSlice<f32>,
7757    ) -> Result<(), Box<dyn std::error::Error>> {
7758        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7759        let (nc, e2) = (ncols as i32, eps);
7760        if Self::pdl_on() && Self::pdl_wb_on() {
7761            use cudarc::driver::{DevicePtr, DevicePtrMut};
7762            let s = &self.gpu.stream();
7763            let (pa, _g0) = a.device_ptr(s);
7764            let (pb, _g1) = b_in.device_ptr(s);
7765            let (pw, _g2) = w.device_ptr(s);
7766            let (pr, _g3) = res.device_ptr_mut(s);
7767            let (pq, _g4) = out_q.device_ptr_mut(s);
7768            let (pd, _g5) = out_d.device_ptr_mut(s);
7769            let mut ps = [
7770                &pa as *const _ as *mut std::ffi::c_void,
7771                &pb as *const _ as *mut _,
7772                &c as *const _ as *mut _,
7773                &pw as *const _ as *mut _,
7774                &pr as *const _ as *mut _,
7775                &pq as *const _ as *mut _,
7776                &pd as *const _ as *mut _,
7777                &nc as *const _ as *mut _,
7778                &e2 as *const _ as *mut _,
7779            ];
7780            unsafe {
7781                self.launch_pdl(
7782                    "add_scale_rms_norm_q8_1",
7783                    (nrows as u32, 1, 1),
7784                    (rms_block(), 1, 1),
7785                    &mut ps,
7786                )?;
7787            }
7788            return Ok(());
7789        }
7790        let f = self.func("add_scale_rms_norm_q8_1");
7791        let cfg = LaunchConfig {
7792            grid_dim: (nrows as u32, 1, 1),
7793            block_dim: (rms_block(), 1, 1),
7794            shared_mem_bytes: 0,
7795        };
7796        let __s_b = self.gpu.stream();
7797        let mut b = __s_b.launch_builder(&f);
7798        b.arg(a)
7799            .arg(b_in)
7800            .arg(&c)
7801            .arg(w)
7802            .arg(res)
7803            .arg(&mut *out_q)
7804            .arg(&mut *out_d)
7805            .arg(&nc)
7806            .arg(&e2);
7807        unsafe {
7808            b.launch(cfg)?;
7809        }
7810        Ok(())
7811    }
7812
7813    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7814    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7815    #[allow(clippy::too_many_arguments)]
7816    pub fn rms_pre_add_scale_rms_norm_q8_1(
7817        &self,
7818        a: &CudaSlice<f32>,
7819        wa: &CudaSlice<f32>,
7820        b_in: &CudaSlice<f32>,
7821        c: f32,
7822        w: &CudaSlice<f32>,
7823        res: &mut CudaSlice<f32>,
7824        ncols: usize,
7825        nrows: usize,
7826        eps: f32,
7827    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7828        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7829        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7830        let (nc, e2) = (ncols as i32, eps);
7831        if Self::pdl_on() {
7832            {
7833                use cudarc::driver::{DevicePtr, DevicePtrMut};
7834                let s = &self.gpu.stream();
7835                let (pa, _g0) = a.device_ptr(s);
7836                let (pwa, _g1) = wa.device_ptr(s);
7837                let (pb, _g2) = b_in.device_ptr(s);
7838                let (pw, _g3) = w.device_ptr(s);
7839                let (pr, _g4) = res.device_ptr_mut(s);
7840                let (pq, _g5) = out_q.device_ptr_mut(s);
7841                let (pd, _g6) = out_d.device_ptr_mut(s);
7842                let mut ps = [
7843                    &pa as *const _ as *mut std::ffi::c_void,
7844                    &pwa as *const _ as *mut _,
7845                    &pb as *const _ as *mut _,
7846                    &c as *const _ as *mut _,
7847                    &pw as *const _ as *mut _,
7848                    &pr as *const _ as *mut _,
7849                    &pq as *const _ as *mut _,
7850                    &pd as *const _ as *mut _,
7851                    &nc as *const _ as *mut _,
7852                    &e2 as *const _ as *mut _,
7853                ];
7854                unsafe {
7855                    self.launch_pdl(
7856                        "rms_pre_add_scale_rms_norm_q8_1",
7857                        (nrows as u32, 1, 1),
7858                        (rms_block(), 1, 1),
7859                        &mut ps,
7860                    )?;
7861                }
7862            }
7863            return Ok((out_q, out_d));
7864        }
7865        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7866        let cfg = LaunchConfig {
7867            grid_dim: (nrows as u32, 1, 1),
7868            block_dim: (rms_block(), 1, 1),
7869            shared_mem_bytes: 0,
7870        };
7871        let __s_b = self.gpu.stream();
7872        let mut b = __s_b.launch_builder(&f);
7873        b.arg(a)
7874            .arg(wa)
7875            .arg(b_in)
7876            .arg(&c)
7877            .arg(w)
7878            .arg(res)
7879            .arg(&mut out_q)
7880            .arg(&mut out_d)
7881            .arg(&nc)
7882            .arg(&e2);
7883        unsafe {
7884            b.launch(cfg)?;
7885        }
7886        Ok((out_q, out_d))
7887    }
7888
7889    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7890    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7891    pub fn gelu_tanh_mul_q8_1(
7892        &self,
7893        gate: &CudaSlice<f32>,
7894        up: &cudarc::driver::CudaView<f32>,
7895        act: &mut CudaSlice<f32>,
7896        ncols: usize,
7897        nrows: usize,
7898    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7899        debug_assert!(ncols % 128 == 0);
7900        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7901        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7902        let nc = ncols as i32;
7903        if Self::pdl_on() {
7904            {
7905                use cudarc::driver::{DevicePtr, DevicePtrMut};
7906                let s = &self.gpu.stream();
7907                let (pg, _g0) = gate.device_ptr(s);
7908                let (pu, _g1) = up.device_ptr(s);
7909                let (pact, _g2) = act.device_ptr_mut(s);
7910                let (pq, _g3) = out_q.device_ptr_mut(s);
7911                let (pd, _g4) = out_d.device_ptr_mut(s);
7912                let mut ps = [
7913                    &pg as *const _ as *mut std::ffi::c_void,
7914                    &pu as *const _ as *mut _,
7915                    &pact as *const _ as *mut _,
7916                    &pq as *const _ as *mut _,
7917                    &pd as *const _ as *mut _,
7918                    &nc as *const _ as *mut _,
7919                ];
7920                unsafe {
7921                    self.launch_pdl(
7922                        "gelu_tanh_mul_q8_1",
7923                        (nrows as u32, 1, 1),
7924                        (rms_block(), 1, 1),
7925                        &mut ps,
7926                    )?;
7927                }
7928            }
7929            return Ok((out_q, out_d));
7930        }
7931        let f = self.func("gelu_tanh_mul_q8_1");
7932        let cfg = LaunchConfig {
7933            grid_dim: (nrows as u32, 1, 1),
7934            block_dim: (rms_block(), 1, 1),
7935            shared_mem_bytes: 0,
7936        };
7937        let __s_b = self.gpu.stream();
7938        let mut b = __s_b.launch_builder(&f);
7939        b.arg(gate)
7940            .arg(up)
7941            .arg(act)
7942            .arg(&mut out_q)
7943            .arg(&mut out_d)
7944            .arg(&nc);
7945        unsafe {
7946            b.launch(cfg)?;
7947        }
7948        Ok((out_q, out_d))
7949    }
7950
7951    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7952    #[allow(clippy::too_many_arguments)]
7953    pub fn gelu_tanh_mul_q8_1_into(
7954        &self,
7955        gate: &CudaSlice<f32>,
7956        up: &cudarc::driver::CudaView<f32>,
7957        act: &mut CudaSlice<f32>,
7958        ncols: usize,
7959        nrows: usize,
7960        out_q: &mut CudaSlice<i8>,
7961        out_d: &mut CudaSlice<f32>,
7962    ) -> Result<(), Box<dyn std::error::Error>> {
7963        debug_assert!(ncols % 128 == 0);
7964        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7965        let nc = ncols as i32;
7966        if Self::pdl_on() {
7967            use cudarc::driver::{DevicePtr, DevicePtrMut};
7968            let s = &self.gpu.stream();
7969            let (pg, _g0) = gate.device_ptr(s);
7970            let (pu, _g1) = up.device_ptr(s);
7971            let (pact, _g2) = act.device_ptr_mut(s);
7972            let (pq, _g3) = out_q.device_ptr_mut(s);
7973            let (pd, _g4) = out_d.device_ptr_mut(s);
7974            let mut ps = [
7975                &pg as *const _ as *mut std::ffi::c_void,
7976                &pu as *const _ as *mut _,
7977                &pact as *const _ as *mut _,
7978                &pq as *const _ as *mut _,
7979                &pd as *const _ as *mut _,
7980                &nc as *const _ as *mut _,
7981            ];
7982            unsafe {
7983                self.launch_pdl(
7984                    "gelu_tanh_mul_q8_1",
7985                    (nrows as u32, 1, 1),
7986                    (rms_block(), 1, 1),
7987                    &mut ps,
7988                )?;
7989            }
7990            return Ok(());
7991        }
7992        let f = self.func("gelu_tanh_mul_q8_1");
7993        let cfg = LaunchConfig {
7994            grid_dim: (nrows as u32, 1, 1),
7995            block_dim: (rms_block(), 1, 1),
7996            shared_mem_bytes: 0,
7997        };
7998        let __s_b = self.gpu.stream();
7999        let mut b = __s_b.launch_builder(&f);
8000        b.arg(gate)
8001            .arg(up)
8002            .arg(&mut *act)
8003            .arg(&mut *out_q)
8004            .arg(&mut *out_d)
8005            .arg(&nc);
8006        unsafe {
8007            b.launch(cfg)?;
8008        }
8009        Ok(())
8010    }
8011
8012    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
8013    #[allow(clippy::too_many_arguments)]
8014    pub fn add_rms_norm3_q8z(
8015        &self,
8016        a: &CudaSlice<f32>,
8017        b_in: &CudaSlice<f32>,
8018        w0: &CudaSlice<f32>,
8019        w1: &CudaSlice<f32>,
8020        w2: &CudaSlice<f32>,
8021        res: &mut CudaSlice<f32>,
8022        out1: &mut CudaSlice<f32>,
8023        ncols: usize,
8024        nrows: usize,
8025        eps: f32,
8026    ) -> Result<
8027        (
8028            (CudaSlice<i8>, CudaSlice<f32>),
8029            (CudaSlice<i8>, CudaSlice<f32>),
8030        ),
8031        Box<dyn std::error::Error>,
8032    > {
8033        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
8034        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8035        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
8036        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8037        let f = self.func("add_rms_norm3_q8z_f32");
8038        let cfg = LaunchConfig {
8039            grid_dim: (nrows as u32, 1, 1),
8040            block_dim: (rms_block(), 1, 1),
8041            shared_mem_bytes: 0,
8042        };
8043        let (nc, e2) = (ncols as i32, eps);
8044        let __s_b = self.gpu.stream();
8045        let mut b = __s_b.launch_builder(&f);
8046        b.arg(a)
8047            .arg(b_in)
8048            .arg(w0)
8049            .arg(w1)
8050            .arg(w2)
8051            .arg(res)
8052            .arg(&mut q0)
8053            .arg(&mut d0)
8054            .arg(out1)
8055            .arg(&mut q2)
8056            .arg(&mut d2)
8057            .arg(&nc)
8058            .arg(&e2);
8059        unsafe {
8060            b.launch(cfg)?;
8061        }
8062        Ok(((q0, d0), (q2, d2)))
8063    }
8064
8065    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
8066    #[allow(clippy::too_many_arguments)]
8067    pub fn add_rms_norm3(
8068        &self,
8069        a: &CudaSlice<f32>,
8070        b_in: &CudaSlice<f32>,
8071        w0: &CudaSlice<f32>,
8072        w1: &CudaSlice<f32>,
8073        w2: &CudaSlice<f32>,
8074        res: &mut CudaSlice<f32>,
8075        d0: &mut CudaSlice<f32>,
8076        d1: &mut CudaSlice<f32>,
8077        d2: &mut CudaSlice<f32>,
8078        ncols: usize,
8079        nrows: usize,
8080        eps: f32,
8081    ) -> Result<(), Box<dyn std::error::Error>> {
8082        let f = self.func("add_rms_norm3_f32");
8083        let cfg = LaunchConfig {
8084            grid_dim: (nrows as u32, 1, 1),
8085            block_dim: (rms_block(), 1, 1),
8086            shared_mem_bytes: 0,
8087        };
8088        let (nc, e2) = (ncols as i32, eps);
8089        let __s_b = self.gpu.stream();
8090        let mut b = __s_b.launch_builder(&f);
8091        b.arg(a)
8092            .arg(b_in)
8093            .arg(w0)
8094            .arg(w1)
8095            .arg(w2)
8096            .arg(res)
8097            .arg(d0)
8098            .arg(d1)
8099            .arg(d2)
8100            .arg(&nc)
8101            .arg(&e2);
8102        unsafe {
8103            b.launch(cfg)?;
8104        }
8105        Ok(())
8106    }
8107
8108    /// dst = (a + b) * c (residual add + layer scale, one launch).
8109    pub fn add_scale(
8110        &self,
8111        a: &CudaSlice<f32>,
8112        b_in: &CudaSlice<f32>,
8113        c: f32,
8114        dst: &mut CudaSlice<f32>,
8115        n: usize,
8116    ) -> Result<(), Box<dyn std::error::Error>> {
8117        let f = self.func("add_scale_f32");
8118        let cfg = LaunchConfig::for_num_elems(n as u32);
8119        let ni = n as i32;
8120        let __s_b = self.gpu.stream();
8121        let mut b = __s_b.launch_builder(&f);
8122        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
8123        unsafe {
8124            b.launch(cfg)?;
8125        }
8126        Ok(())
8127    }
8128
8129    /// Vision-tower LayerNorm (with bias) over [nrows, ncols] — lane/vision.
8130    pub fn layer_norm_bias(
8131        &self,
8132        x: &CudaSlice<f32>,
8133        w: &CudaSlice<f32>,
8134        b: &CudaSlice<f32>,
8135        dst: &mut CudaSlice<f32>,
8136        ncols: usize,
8137        nrows: usize,
8138        eps: f32,
8139    ) -> Result<(), Box<dyn std::error::Error>> {
8140        let f = self.func("layer_norm_bias_f32");
8141        let (nc, e) = (ncols as i32, eps);
8142        let cfg = LaunchConfig {
8143            grid_dim: (nrows as u32, 1, 1),
8144            block_dim: (256, 1, 1),
8145            shared_mem_bytes: 0,
8146        };
8147        let __s_b = self.gpu.stream();
8148        let mut lb = __s_b.launch_builder(&f);
8149        lb.arg(x).arg(w).arg(b).arg(&mut *dst).arg(&nc).arg(&e);
8150        unsafe {
8151            lb.launch(cfg)?;
8152        }
8153        Ok(())
8154    }
8155
8156    /// gelu_pytorch_tanh elementwise (vision tower MLP activation).
8157    pub fn gelu_tanh(
8158        &self,
8159        x: &CudaSlice<f32>,
8160        dst: &mut CudaSlice<f32>,
8161        n: usize,
8162    ) -> Result<(), Box<dyn std::error::Error>> {
8163        let f = self.func("gelu_tanh_f32");
8164        let ni = n as i64;
8165        let cfg = LaunchConfig {
8166            grid_dim: (n.div_ceil(256) as u32, 1, 1),
8167            block_dim: (256, 1, 1),
8168            shared_mem_bytes: 0,
8169        };
8170        let __s_b = self.gpu.stream();
8171        let mut lb = __s_b.launch_builder(&f);
8172        lb.arg(x).arg(&mut *dst).arg(&ni);
8173        unsafe {
8174            lb.launch(cfg)?;
8175        }
8176        Ok(())
8177    }
8178
8179    /// In-place row softmax over [nrows, ncols] (bidirectional vision attention).
8180    pub fn row_softmax(
8181        &self,
8182        x: &mut CudaSlice<f32>,
8183        ncols: usize,
8184        nrows: usize,
8185    ) -> Result<(), Box<dyn std::error::Error>> {
8186        let f = self.func("row_softmax_f32");
8187        let nc = ncols as i32;
8188        let cfg = LaunchConfig {
8189            grid_dim: (nrows as u32, 1, 1),
8190            block_dim: (256, 1, 1),
8191            shared_mem_bytes: 0,
8192        };
8193        let __s_b = self.gpu.stream();
8194        let mut lb = __s_b.launch_builder(&f);
8195        lb.arg(&mut *x).arg(&nc);
8196        unsafe {
8197            lb.launch(cfg)?;
8198        }
8199        Ok(())
8200    }
8201
8202    pub fn rms_norm(
8203        &self,
8204        x: &CudaSlice<f32>,
8205        w: &CudaSlice<f32>,
8206        dst: &mut CudaSlice<f32>,
8207        ncols: usize,
8208        nrows: usize,
8209        eps: f32,
8210    ) -> Result<(), Box<dyn std::error::Error>> {
8211        let (nc, e) = (ncols as i32, eps);
8212        if Self::pdl_on() && Self::pdl_wb_on() {
8213            use cudarc::driver::{DevicePtr, DevicePtrMut};
8214            let s = &self.gpu.stream();
8215            let (px, _g0) = x.device_ptr(s);
8216            let (pw, _g1) = w.device_ptr(s);
8217            let (pd, _g2) = dst.device_ptr_mut(s);
8218            let mut ps = [
8219                &px as *const _ as *mut std::ffi::c_void,
8220                &pw as *const _ as *mut _,
8221                &pd as *const _ as *mut _,
8222                &nc as *const _ as *mut _,
8223                &e as *const _ as *mut _,
8224            ];
8225            unsafe {
8226                self.launch_pdl(
8227                    "rms_norm_f32",
8228                    (nrows as u32, 1, 1),
8229                    (rms_block(), 1, 1),
8230                    &mut ps,
8231                )?;
8232            }
8233            return Ok(());
8234        }
8235        let f = self.func("rms_norm_f32");
8236        let cfg = LaunchConfig {
8237            grid_dim: (nrows as u32, 1, 1),
8238            block_dim: (rms_block(), 1, 1),
8239            shared_mem_bytes: 0,
8240        };
8241        let __s_b = self.gpu.stream();
8242        let mut b = __s_b.launch_builder(&f);
8243        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8244        unsafe {
8245            b.launch(cfg)?;
8246        }
8247        Ok(())
8248    }
8249
8250    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8251    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8252    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8253    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8254    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8255    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8256    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8257    pub fn rms_norm_decode(
8258        &self,
8259        x: &CudaSlice<f32>,
8260        w: &CudaSlice<f32>,
8261        dst: &mut CudaSlice<f32>,
8262        ncols: usize,
8263        nrows: usize,
8264        eps: f32,
8265    ) -> Result<(), Box<dyn std::error::Error>> {
8266        let f = self.func("rms_norm_f32");
8267        let cfg = LaunchConfig {
8268            grid_dim: (nrows as u32, 1, 1),
8269            block_dim: (1024, 1, 1),
8270            shared_mem_bytes: 0,
8271        };
8272        let (nc, e) = (ncols as i32, eps);
8273        let __s_b = self.gpu.stream();
8274        let mut b = __s_b.launch_builder(&f);
8275        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8276        unsafe {
8277            b.launch(cfg)?;
8278        }
8279        Ok(())
8280    }
8281
8282    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8283    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8284    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8285    pub fn rms_norm_q8_1(
8286        &self,
8287        x: &CudaSlice<f32>,
8288        w: &CudaSlice<f32>,
8289        ncols: usize,
8290        nrows: usize,
8291        eps: f32,
8292    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8293        let nblk = ncols / 32;
8294        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8295        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8296        let (nc, e) = (ncols as i32, eps);
8297        if Self::pdl_on() {
8298            {
8299                use cudarc::driver::{DevicePtr, DevicePtrMut};
8300                let s = &self.gpu.stream();
8301                let (px, _g0) = x.device_ptr(s);
8302                let (pw, _g1) = w.device_ptr(s);
8303                let (pq, _g2) = q.device_ptr_mut(s);
8304                let (pd, _g3) = d.device_ptr_mut(s);
8305                let mut ps = [
8306                    &px as *const _ as *mut std::ffi::c_void,
8307                    &pw as *const _ as *mut _,
8308                    &pq as *const _ as *mut _,
8309                    &pd as *const _ as *mut _,
8310                    &nc as *const _ as *mut _,
8311                    &e as *const _ as *mut _,
8312                ];
8313                unsafe {
8314                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8315                }
8316            }
8317            return Ok((q, d));
8318        }
8319        let f = self.func("rms_norm_q8_1");
8320        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8321        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8322        let cfg = LaunchConfig {
8323            grid_dim: (nrows as u32, 1, 1),
8324            block_dim: (1024, 1, 1),
8325            shared_mem_bytes: 0,
8326        };
8327        let __s_b = self.gpu.stream();
8328        let mut b = __s_b.launch_builder(&f);
8329        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8330        unsafe {
8331            b.launch(cfg)?;
8332        }
8333        Ok((q, d))
8334    }
8335
8336    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8337    /// PDL arm), caller-owned outputs.
8338    pub fn rms_norm_q8_1_into(
8339        &self,
8340        x: &CudaSlice<f32>,
8341        w: &CudaSlice<f32>,
8342        ncols: usize,
8343        nrows: usize,
8344        eps: f32,
8345        q: &mut CudaSlice<i8>,
8346        d: &mut CudaSlice<f32>,
8347    ) -> Result<(), Box<dyn std::error::Error>> {
8348        let nblk = ncols / 32;
8349        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8350        let (nc, e) = (ncols as i32, eps);
8351        if Self::pdl_on() {
8352            use cudarc::driver::{DevicePtr, DevicePtrMut};
8353            let s = &self.gpu.stream();
8354            let (px, _g0) = x.device_ptr(s);
8355            let (pw, _g1) = w.device_ptr(s);
8356            let (pq, _g2) = q.device_ptr_mut(s);
8357            let (pd, _g3) = d.device_ptr_mut(s);
8358            let mut ps = [
8359                &px as *const _ as *mut std::ffi::c_void,
8360                &pw as *const _ as *mut _,
8361                &pq as *const _ as *mut _,
8362                &pd as *const _ as *mut _,
8363                &nc as *const _ as *mut _,
8364                &e as *const _ as *mut _,
8365            ];
8366            unsafe {
8367                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8368            }
8369            return Ok(());
8370        }
8371        let f = self.func("rms_norm_q8_1");
8372        let cfg = LaunchConfig {
8373            grid_dim: (nrows as u32, 1, 1),
8374            block_dim: (1024, 1, 1),
8375            shared_mem_bytes: 0,
8376        };
8377        let __s_b = self.gpu.stream();
8378        let mut b = __s_b.launch_builder(&f);
8379        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8380        unsafe {
8381            b.launch(cfg)?;
8382        }
8383        Ok(())
8384    }
8385
8386    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8387    pub fn quantize_q8_1_into(
8388        &self,
8389        x: &CudaSlice<f32>,
8390        m: usize,
8391        in_f: usize,
8392        q: &mut CudaSlice<i8>,
8393        d: &mut CudaSlice<f32>,
8394    ) -> Result<(), Box<dyn std::error::Error>> {
8395        let nblk = in_f / 32;
8396        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8397        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8398        let (inf, mi) = (in_f as i32, m as i32);
8399        if Self::pdl_on() && Self::pdl_wb_on() {
8400            use cudarc::driver::{DevicePtr, DevicePtrMut};
8401            let s = &self.gpu.stream();
8402            let (px, _g0) = x.device_ptr(s);
8403            let (pq, _g1) = q.device_ptr_mut(s);
8404            let (pd, _g2) = d.device_ptr_mut(s);
8405            let mut ps = [
8406                &px as *const _ as *mut std::ffi::c_void,
8407                &pq as *const _ as *mut _,
8408                &pd as *const _ as *mut _,
8409                &inf as *const _ as *mut _,
8410                &mi as *const _ as *mut _,
8411            ];
8412            unsafe {
8413                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8414            }
8415            return Ok(());
8416        }
8417        let f = self.func("quantize_q8_1");
8418        let __s_b = self.gpu.stream();
8419        let mut b = __s_b.launch_builder(&f);
8420        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8421        unsafe {
8422            b.launch(cfg)?;
8423        }
8424        Ok(())
8425    }
8426
8427    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8428    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8429    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8430    pub fn add_rms_norm_q8_1(
8431        &self,
8432        a: &CudaSlice<f32>,
8433        b_in: &CudaSlice<f32>,
8434        w: &CudaSlice<f32>,
8435        res: &mut CudaSlice<f32>,
8436        ncols: usize,
8437        nrows: usize,
8438        eps: f32,
8439    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8440        let nblk = ncols / 32;
8441        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8442        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8443        let f = self.func("add_rms_norm_q8_1");
8444        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8445        let cfg = LaunchConfig {
8446            grid_dim: (nrows as u32, 1, 1),
8447            block_dim: (1024, 1, 1),
8448            shared_mem_bytes: 0,
8449        };
8450        let (nc, e) = (ncols as i32, eps);
8451        let __s_bld = self.gpu.stream();
8452        let mut bld = __s_bld.launch_builder(&f);
8453        bld.arg(a)
8454            .arg(b_in)
8455            .arg(w)
8456            .arg(res)
8457            .arg(&mut q)
8458            .arg(&mut d)
8459            .arg(&nc)
8460            .arg(&e);
8461        unsafe {
8462            bld.launch(cfg)?;
8463        }
8464        Ok((q, d))
8465    }
8466
8467    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8468    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8469    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8470    pub fn add_rms_norm(
8471        &self,
8472        a: &CudaSlice<f32>,
8473        b: &CudaSlice<f32>,
8474        w: &CudaSlice<f32>,
8475        res: &mut CudaSlice<f32>,
8476        dst: &mut CudaSlice<f32>,
8477        ncols: usize,
8478        nrows: usize,
8479        eps: f32,
8480    ) -> Result<(), Box<dyn std::error::Error>> {
8481        let (nc, e) = (ncols as i32, eps);
8482        if Self::pdl_on() && Self::pdl_wb_on() {
8483            use cudarc::driver::{DevicePtr, DevicePtrMut};
8484            let s = &self.gpu.stream();
8485            let (pa, _g0) = a.device_ptr(s);
8486            let (pb, _g1) = b.device_ptr(s);
8487            let (pw, _g2) = w.device_ptr(s);
8488            let (pr, _g3) = res.device_ptr_mut(s);
8489            let (pd, _g4) = dst.device_ptr_mut(s);
8490            let mut ps = [
8491                &pa as *const _ as *mut std::ffi::c_void,
8492                &pb as *const _ as *mut _,
8493                &pw as *const _ as *mut _,
8494                &pr as *const _ as *mut _,
8495                &pd as *const _ as *mut _,
8496                &nc as *const _ as *mut _,
8497                &e as *const _ as *mut _,
8498            ];
8499            unsafe {
8500                self.launch_pdl(
8501                    "add_rms_norm_f32",
8502                    (nrows as u32, 1, 1),
8503                    (rms_block(), 1, 1),
8504                    &mut ps,
8505                )?;
8506            }
8507            return Ok(());
8508        }
8509        let f = self.func("add_rms_norm_f32");
8510        let cfg = LaunchConfig {
8511            grid_dim: (nrows as u32, 1, 1),
8512            block_dim: (rms_block(), 1, 1),
8513            shared_mem_bytes: 0,
8514        };
8515        let __s_b2 = self.gpu.stream();
8516        let mut b2 = __s_b2.launch_builder(&f);
8517        b2.arg(a)
8518            .arg(b)
8519            .arg(w)
8520            .arg(&mut *res)
8521            .arg(&mut *dst)
8522            .arg(&nc)
8523            .arg(&e);
8524        unsafe {
8525            b2.launch(cfg)?;
8526        }
8527        Ok(())
8528    }
8529
8530    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8531    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8532    #[allow(clippy::too_many_arguments)]
8533    pub fn rms_pre_add_rms_norm(
8534        &self,
8535        a: &CudaSlice<f32>,
8536        wa: &CudaSlice<f32>,
8537        b: &CudaSlice<f32>,
8538        w: &CudaSlice<f32>,
8539        res: &mut CudaSlice<f32>,
8540        dst: &mut CudaSlice<f32>,
8541        ncols: usize,
8542        nrows: usize,
8543        eps: f32,
8544    ) -> Result<(), Box<dyn std::error::Error>> {
8545        let f = self.func("rms_pre_add_rms_norm_f32");
8546        let cfg = LaunchConfig {
8547            grid_dim: (nrows as u32, 1, 1),
8548            block_dim: (rms_block(), 1, 1),
8549            shared_mem_bytes: 0,
8550        };
8551        let (nc, e) = (ncols as i32, eps);
8552        let __s_b2 = self.gpu.stream();
8553        let mut b2 = __s_b2.launch_builder(&f);
8554        b2.arg(a)
8555            .arg(wa)
8556            .arg(b)
8557            .arg(w)
8558            .arg(&mut *res)
8559            .arg(&mut *dst)
8560            .arg(&nc)
8561            .arg(&e);
8562        unsafe {
8563            b2.launch(cfg)?;
8564        }
8565        Ok(())
8566    }
8567
8568    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8569    #[allow(clippy::too_many_arguments)]
8570    pub fn rms_pre_add_rms_norm_q8z(
8571        &self,
8572        a: &CudaSlice<f32>,
8573        wa: &CudaSlice<f32>,
8574        b: &CudaSlice<f32>,
8575        w: &CudaSlice<f32>,
8576        res: &mut CudaSlice<f32>,
8577        dst: &mut CudaSlice<f32>,
8578        ncols: usize,
8579        nrows: usize,
8580        eps: f32,
8581    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8582        debug_assert!(ncols % 128 == 0);
8583        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8584        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8585        let (nc, e) = (ncols as i32, eps);
8586        if Self::pdl_on() {
8587            {
8588                use cudarc::driver::{DevicePtr, DevicePtrMut};
8589                let s = &self.gpu.stream();
8590                let (pa, _g0) = a.device_ptr(s);
8591                let (pwa, _g1) = wa.device_ptr(s);
8592                let (pb, _g2) = b.device_ptr(s);
8593                let (pw, _g3) = w.device_ptr(s);
8594                let (pr, _g4) = res.device_ptr_mut(s);
8595                let (pdst, _g5) = dst.device_ptr_mut(s);
8596                let (pq, _g6) = out_q.device_ptr_mut(s);
8597                let (pd, _g7) = out_d.device_ptr_mut(s);
8598                let mut ps = [
8599                    &pa as *const _ as *mut std::ffi::c_void,
8600                    &pwa as *const _ as *mut _,
8601                    &pb as *const _ as *mut _,
8602                    &pw as *const _ as *mut _,
8603                    &pr as *const _ as *mut _,
8604                    &pdst as *const _ as *mut _,
8605                    &pq as *const _ as *mut _,
8606                    &pd as *const _ as *mut _,
8607                    &nc as *const _ as *mut _,
8608                    &e as *const _ as *mut _,
8609                ];
8610                unsafe {
8611                    self.launch_pdl(
8612                        "rms_pre_add_rms_norm_q8z_f32",
8613                        (nrows as u32, 1, 1),
8614                        (rms_block(), 1, 1),
8615                        &mut ps,
8616                    )?;
8617                }
8618            }
8619            return Ok((out_q, out_d));
8620        }
8621        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8622        let cfg = LaunchConfig {
8623            grid_dim: (nrows as u32, 1, 1),
8624            block_dim: (rms_block(), 1, 1),
8625            shared_mem_bytes: 0,
8626        };
8627        let __s_b2 = self.gpu.stream();
8628        let mut b2 = __s_b2.launch_builder(&f);
8629        b2.arg(a)
8630            .arg(wa)
8631            .arg(b)
8632            .arg(w)
8633            .arg(&mut *res)
8634            .arg(&mut *dst)
8635            .arg(&mut out_q)
8636            .arg(&mut out_d)
8637            .arg(&nc)
8638            .arg(&e);
8639        unsafe {
8640            b2.launch(cfg)?;
8641        }
8642        Ok((out_q, out_d))
8643    }
8644
8645    /// Slot-fed twin of `rms_pre_add_rms_norm_q8z` (gemma4 pn-fold, slotted/graph arm):
8646    /// identical kernel, caller-owned outputs, PLAIN launch only — the dc_slotted capture
8647    /// body must stay attribute-free (the fused2_into precedent).
8648    #[allow(clippy::too_many_arguments)]
8649    pub fn rms_pre_add_rms_norm_q8z_into(
8650        &self,
8651        a: &CudaSlice<f32>,
8652        wa: &CudaSlice<f32>,
8653        b: &CudaSlice<f32>,
8654        w: &CudaSlice<f32>,
8655        res: &mut CudaSlice<f32>,
8656        dst: &mut CudaSlice<f32>,
8657        ncols: usize,
8658        nrows: usize,
8659        eps: f32,
8660        out_q: &mut CudaSlice<i8>,
8661        out_d: &mut CudaSlice<f32>,
8662    ) -> Result<(), Box<dyn std::error::Error>> {
8663        debug_assert!(ncols % 128 == 0);
8664        let (nc, e) = (ncols as i32, eps);
8665        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8666        let cfg = LaunchConfig {
8667            grid_dim: (nrows as u32, 1, 1),
8668            block_dim: (rms_block(), 1, 1),
8669            shared_mem_bytes: 0,
8670        };
8671        let __s_b = self.gpu.stream();
8672        let mut b2 = __s_b.launch_builder(&f);
8673        b2.arg(a)
8674            .arg(wa)
8675            .arg(b)
8676            .arg(w)
8677            .arg(&mut *res)
8678            .arg(&mut *dst)
8679            .arg(&mut *out_q)
8680            .arg(&mut *out_d)
8681            .arg(&nc)
8682            .arg(&e);
8683        unsafe {
8684            b2.launch(cfg)?;
8685        }
8686        Ok(())
8687    }
8688
8689    /// Slot-fed twin of `rms_pre_add_scale_rms_norm_q8_1` (gemma4 pn-fold exit, slotted
8690    /// arm): identical kernel, caller-owned outputs, PLAIN launch (capture-safe).
8691    #[allow(clippy::too_many_arguments)]
8692    pub fn rms_pre_add_scale_rms_norm_q8_1_into(
8693        &self,
8694        a: &CudaSlice<f32>,
8695        wa: &CudaSlice<f32>,
8696        b_in: &CudaSlice<f32>,
8697        c: f32,
8698        w: &CudaSlice<f32>,
8699        res: &mut CudaSlice<f32>,
8700        ncols: usize,
8701        nrows: usize,
8702        eps: f32,
8703        out_q: &mut CudaSlice<i8>,
8704        out_d: &mut CudaSlice<f32>,
8705    ) -> Result<(), Box<dyn std::error::Error>> {
8706        debug_assert!(ncols % 128 == 0);
8707        let (nc, e2) = (ncols as i32, eps);
8708        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
8709        let cfg = LaunchConfig {
8710            grid_dim: (nrows as u32, 1, 1),
8711            block_dim: (rms_block(), 1, 1),
8712            shared_mem_bytes: 0,
8713        };
8714        let __s_b = self.gpu.stream();
8715        let mut b2 = __s_b.launch_builder(&f);
8716        b2.arg(a)
8717            .arg(wa)
8718            .arg(b_in)
8719            .arg(&c)
8720            .arg(w)
8721            .arg(&mut *res)
8722            .arg(&mut *out_q)
8723            .arg(&mut *out_d)
8724            .arg(&nc)
8725            .arg(&e2);
8726        unsafe {
8727            b2.launch(cfg)?;
8728        }
8729        Ok(())
8730    }
8731
8732    /// gemma4 pn-fold seam (GAP-DIAGNOSIS verdict 7, the E4B glue backport): the dense
8733    /// decode/verify/slotted trio folds post_attn_norm into the tail entry
8734    /// (rms_pre_add_rms_norm[_q8z]) and post_ffw_norm into the residual exit
8735    /// (rms_pre_add_scale_rms_norm_q8_1). BITS-CHANGING vs the two-launch chain (the
8736    /// single-phase reduction's expansion rounding — E4B receipts); every arm moves
8737    /// together so decode == verify == graph parity holds BY CONSTRUCTION within either
8738    /// seam value. MEMRA_G4_PNFOLD=0 restores the unfused chain everywhere.
8739    pub fn g4_pnfold_on() -> bool {
8740        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8741        *ON.get_or_init(|| {
8742            std::env::var("MEMRA_G4_PNFOLD")
8743                .map(|v| v != "0")
8744                .unwrap_or(true)
8745        })
8746    }
8747
8748    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8749    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8750    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8751    pub fn build_q4_out_concat3(
8752        &self,
8753        w0: &crate::model::GpuTensor,
8754        w1: &crate::model::GpuTensor,
8755        w2: &crate::model::GpuTensor,
8756    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8757        use crate::model::GpuTensor;
8758        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8759            match w {
8760                GpuTensor::Quant {
8761                    qtype,
8762                    row_bytes,
8763                    rp,
8764                    ..
8765                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8766                _ => None,
8767            }
8768        };
8769        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8770        else {
8771            return Ok(None);
8772        };
8773        if rb0 != rb1
8774            || rb0 != rb2
8775            || w0.in_features() != w1.in_features()
8776            || w0.in_features() != w2.in_features()
8777        {
8778            return Ok(None);
8779        }
8780        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8781            match w {
8782                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8783                _ => unreachable!(),
8784            }
8785        }
8786        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8787        let total = rb0 * (o0 + o1 + o2);
8788        let mut cat = self.alloc_u8(total)?;
8789        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8790        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8791        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8792        Ok(Some(GpuTensor::Quant {
8793            bytes: cat,
8794            qtype: QT_Q4_0,
8795            row_bytes: rb0,
8796            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8797            scale: 1.0,
8798            rp: false,
8799            #[cfg(memra_cutlass)]
8800            cutlass: None,
8801            fp8: None,
8802            blk: None,
8803            rp4: None,
8804            f16: None,
8805        }))
8806    }
8807
8808    /// FULL-WIDTH-ROPE CONTRACT for the fused rms_norm+qkv+rope kernels
8809    /// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up).
8810    ///
8811    /// `rms_norm_qkv_rope_f32` / `_cat_f32` (`cu/kernels.cu`) and
8812    /// `rms_norm_qkv_rope_append_body` (`cu/flash_attn.cu`) take NO `n_dims`/`n_rot` argument.
8813    /// They compute `int half = ncols / 2` and rotate the FULL head width by construction — the
8814    /// standalone `rope_neox*` kernels take `n_dims` and early-return above it, these do not.
8815    ///
8816    /// Every call site today is a gemma-4 arm, where `n_rot == head_dim`, so nothing is wrong
8817    /// now. But this is the n_rot bug ONE FUSION away: fuse a partial-rotary arch onto these
8818    /// kernels (qwen3.5 = 64 rotary dims of a 256-wide head; step35 full-attn = 64 of 128) and
8819    /// 192 dims that must pass through unrotated get rotated silently — no error, no NaN, just a
8820    /// wrong model. The n_rot lane already paid for that class once, in the config derivation.
8821    ///
8822    /// So the fusions now take the layer's DERIVED rope width and refuse anything but full
8823    /// width. A future partial-rotary caller fails at its first launch with the geometry named
8824    /// instead of serving quietly wrong logits.
8825    fn full_width_rope_only(
8826        kernel: &str,
8827        n_rot: usize,
8828        head_dim: usize,
8829    ) -> Result<(), Box<dyn std::error::Error>> {
8830        if n_rot == head_dim {
8831            return Ok(());
8832        }
8833        Err(format!(
8834            "{kernel}: PARTIAL ROTARY REFUSED — n_rot {n_rot} != head_dim {head_dim}. This fused \
8835             rms_norm+qkv+rope kernel carries no n_dims parameter and rotates the full head \
8836             width (half = ncols/2), so it would rotate dims {n_rot}..{head_dim} that must pass \
8837             through unrotated. Use the split path (rms_norm_qkv + rope_neox/rope_neox2 with \
8838             n_dims={n_rot}), or add an n_dims early-return to the kernel and widen this guard."
8839        )
8840        .into())
8841    }
8842
8843    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8844    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8845    /// ([`Engine::full_width_rope_only`]).
8846    #[allow(clippy::too_many_arguments)]
8847    pub fn rms_norm_qkv_rope_cat(
8848        &self,
8849        qkv: &CudaSlice<f32>,
8850        wq: &CudaSlice<f32>,
8851        wk: &CudaSlice<f32>,
8852        wv: &CudaSlice<f32>,
8853        q: &mut CudaSlice<f32>,
8854        k: &mut CudaSlice<f32>,
8855        v: &mut CudaSlice<f32>,
8856        head_dim: usize,
8857        n_rot: usize,
8858        rq: usize,
8859        rk: usize,
8860        pos: &CudaSlice<i32>,
8861        nh_q: usize,
8862        nh_k: usize,
8863        base: f32,
8864        freq_scale: f32,
8865        ff: Option<&CudaSlice<f32>>,
8866        eps: f32,
8867    ) -> Result<(), Box<dyn std::error::Error>> {
8868        Self::full_width_rope_only("rms_norm_qkv_rope_cat", n_rot, head_dim)?;
8869        let rows = rq + rk + rk;
8870        let theta_scale = base.powf(-2.0 / head_dim as f32);
8871        let (nc, rqi, rki, nhq, nhk) = (
8872            head_dim as i32,
8873            rq as i32,
8874            rk as i32,
8875            nh_q as i32,
8876            nh_k as i32,
8877        );
8878        if Self::pdl_on() {
8879            use cudarc::driver::{DevicePtr, DevicePtrMut};
8880            let s = &self.gpu.stream();
8881            let (pqkv, _g0) = qkv.device_ptr(s);
8882            let (pwq, _g1) = wq.device_ptr(s);
8883            let (pwk, _g2) = wk.device_ptr(s);
8884            let (pwv, _g3) = wv.device_ptr(s);
8885            let (pq, _g4) = q.device_ptr_mut(s);
8886            let (pk, _g5) = k.device_ptr_mut(s);
8887            let (pv, _g6) = v.device_ptr_mut(s);
8888            let (ppos, _g7) = pos.device_ptr(s);
8889            let (pff, _g8) = match ff {
8890                Some(t) => {
8891                    let (p, g) = t.device_ptr(s);
8892                    (p, Some(g))
8893                }
8894                None => (0, None),
8895            };
8896            let mut ps = [
8897                &pqkv as *const _ as *mut std::ffi::c_void,
8898                &pwq as *const _ as *mut _,
8899                &pwk as *const _ as *mut _,
8900                &pwv as *const _ as *mut _,
8901                &pq as *const _ as *mut _,
8902                &pk as *const _ as *mut _,
8903                &pv as *const _ as *mut _,
8904                &nc as *const _ as *mut _,
8905                &rqi as *const _ as *mut _,
8906                &rki as *const _ as *mut _,
8907                &ppos as *const _ as *mut _,
8908                &nhq as *const _ as *mut _,
8909                &nhk as *const _ as *mut _,
8910                &theta_scale as *const _ as *mut _,
8911                &freq_scale as *const _ as *mut _,
8912                &pff as *const _ as *mut _,
8913                &eps as *const _ as *mut _,
8914            ];
8915            unsafe {
8916                self.launch_pdl(
8917                    "rms_norm_qkv_rope_cat_f32",
8918                    (rows as u32, 1, 1),
8919                    (rms_block(), 1, 1),
8920                    &mut ps,
8921                )?;
8922            }
8923            return Ok(());
8924        }
8925        let f = self.func("rms_norm_qkv_rope_cat_f32");
8926        let cfg = LaunchConfig {
8927            grid_dim: (rows as u32, 1, 1),
8928            block_dim: (rms_block(), 1, 1),
8929            shared_mem_bytes: 0,
8930        };
8931        let __s_b = self.gpu.stream();
8932        let mut b = __s_b.launch_builder(&f);
8933        match ff {
8934            Some(t) => {
8935                b.arg(qkv)
8936                    .arg(wq)
8937                    .arg(wk)
8938                    .arg(wv)
8939                    .arg(&mut *q)
8940                    .arg(&mut *k)
8941                    .arg(&mut *v)
8942                    .arg(&nc)
8943                    .arg(&rqi)
8944                    .arg(&rki)
8945                    .arg(pos)
8946                    .arg(&nhq)
8947                    .arg(&nhk)
8948                    .arg(&theta_scale)
8949                    .arg(&freq_scale)
8950                    .arg(t)
8951                    .arg(&eps);
8952                unsafe {
8953                    b.launch(cfg)?;
8954                }
8955            }
8956            None => {
8957                let null: u64 = 0;
8958                b.arg(qkv)
8959                    .arg(wq)
8960                    .arg(wk)
8961                    .arg(wv)
8962                    .arg(&mut *q)
8963                    .arg(&mut *k)
8964                    .arg(&mut *v)
8965                    .arg(&nc)
8966                    .arg(&rqi)
8967                    .arg(&rki)
8968                    .arg(pos)
8969                    .arg(&nhq)
8970                    .arg(&nhk)
8971                    .arg(&theta_scale)
8972                    .arg(&freq_scale)
8973                    .arg(&null)
8974                    .arg(&eps);
8975                unsafe {
8976                    b.launch(cfg)?;
8977                }
8978            }
8979        }
8980        Ok(())
8981    }
8982
8983    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8984    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8985    /// ([`Engine::full_width_rope_only`]).
8986    #[allow(clippy::too_many_arguments)]
8987    pub fn rms_norm_qkv_rope(
8988        &self,
8989        q0: &CudaSlice<f32>,
8990        k0: &CudaSlice<f32>,
8991        v0: &CudaSlice<f32>,
8992        wq: &CudaSlice<f32>,
8993        wk: &CudaSlice<f32>,
8994        wv: &CudaSlice<f32>,
8995        q: &mut CudaSlice<f32>,
8996        k: &mut CudaSlice<f32>,
8997        v: &mut CudaSlice<f32>,
8998        head_dim: usize,
8999        n_rot: usize,
9000        rq: usize,
9001        rk: usize,
9002        pos: &CudaSlice<i32>,
9003        nh_q: usize,
9004        nh_k: usize,
9005        base: f32,
9006        freq_scale: f32,
9007        ff: Option<&CudaSlice<f32>>,
9008        eps: f32,
9009    ) -> Result<(), Box<dyn std::error::Error>> {
9010        Self::full_width_rope_only("rms_norm_qkv_rope", n_rot, head_dim)?;
9011        let f = self.func("rms_norm_qkv_rope_f32");
9012        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
9013        let cfg = LaunchConfig {
9014            grid_dim: (rows as u32, 1, 1),
9015            block_dim: (rms_block(), 1, 1),
9016            shared_mem_bytes: 0,
9017        };
9018        let theta_scale = base.powf(-2.0 / head_dim as f32);
9019        let (nc, rqi, rki, nhq, nhk) = (
9020            head_dim as i32,
9021            rq as i32,
9022            rk as i32,
9023            nh_q as i32,
9024            nh_k as i32,
9025        );
9026        let __s_b = self.gpu.stream();
9027        let mut b = __s_b.launch_builder(&f);
9028        match ff {
9029            Some(t) => {
9030                b.arg(q0)
9031                    .arg(k0)
9032                    .arg(v0)
9033                    .arg(wq)
9034                    .arg(wk)
9035                    .arg(wv)
9036                    .arg(&mut *q)
9037                    .arg(&mut *k)
9038                    .arg(&mut *v)
9039                    .arg(&nc)
9040                    .arg(&rqi)
9041                    .arg(&rki)
9042                    .arg(pos)
9043                    .arg(&nhq)
9044                    .arg(&nhk)
9045                    .arg(&theta_scale)
9046                    .arg(&freq_scale)
9047                    .arg(t)
9048                    .arg(&eps);
9049                unsafe {
9050                    b.launch(cfg)?;
9051                }
9052            }
9053            None => {
9054                let null: u64 = 0;
9055                b.arg(q0)
9056                    .arg(k0)
9057                    .arg(v0)
9058                    .arg(wq)
9059                    .arg(wk)
9060                    .arg(wv)
9061                    .arg(&mut *q)
9062                    .arg(&mut *k)
9063                    .arg(&mut *v)
9064                    .arg(&nc)
9065                    .arg(&rqi)
9066                    .arg(&rki)
9067                    .arg(pos)
9068                    .arg(&nhq)
9069                    .arg(&nhk)
9070                    .arg(&theta_scale)
9071                    .arg(&freq_scale)
9072                    .arg(&null)
9073                    .arg(&eps);
9074                unsafe {
9075                    b.launch(cfg)?;
9076                }
9077            }
9078        }
9079        Ok(())
9080    }
9081
9082    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
9083    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
9084    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
9085    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
9086    /// ([`Engine::full_width_rope_only`]).
9087    #[allow(clippy::too_many_arguments)]
9088    pub fn rms_norm_qkv_rope_append_dc(
9089        &self,
9090        q0: &CudaSlice<f32>,
9091        k0: &CudaSlice<f32>,
9092        v0: &CudaSlice<f32>,
9093        wq: &CudaSlice<f32>,
9094        wk: &CudaSlice<f32>,
9095        wv: &CudaSlice<f32>,
9096        q: &mut CudaSlice<f32>,
9097        k: &mut CudaSlice<f32>,
9098        v: &mut CudaSlice<f32>,
9099        head_dim: usize,
9100        n_rot: usize,
9101        rq: usize,
9102        rk: usize,
9103        pos: &CudaSlice<i32>,
9104        nh_q: usize,
9105        nh_k: usize,
9106        base: f32,
9107        freq_scale: f32,
9108        ff: Option<&CudaSlice<f32>>,
9109        eps: f32,
9110        kc: &mut CudaSlice<u8>,
9111        vc: &mut CudaSlice<u8>,
9112        t_dev: &CudaSlice<i32>,
9113        k_tok_bytes: usize,
9114        v_tok_bytes: usize,
9115        g: bool,
9116    ) -> Result<(), Box<dyn std::error::Error>> {
9117        Self::full_width_rope_only("rms_norm_qkv_rope_append_dc", n_rot, head_dim)?;
9118        let rows = rq + rk + rk;
9119        let theta_scale = base.powf(-2.0 / head_dim as f32);
9120        let (nc, rqi, rki, nhq, nhk) = (
9121            head_dim as i32,
9122            rq as i32,
9123            rk as i32,
9124            nh_q as i32,
9125            nh_k as i32,
9126        );
9127        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9128        if Self::pdl_on() && Self::pdl_wb_on() {
9129            use cudarc::driver::{DevicePtr, DevicePtrMut};
9130            let s = &self.gpu.stream();
9131            let (p0, _a0) = q0.device_ptr(s);
9132            let (p1, _a1) = k0.device_ptr(s);
9133            let (p2, _a2) = v0.device_ptr(s);
9134            let (pwq, _a3) = wq.device_ptr(s);
9135            let (pwk, _a4) = wk.device_ptr(s);
9136            let (pwv, _a5) = wv.device_ptr(s);
9137            let (pq, _a6) = q.device_ptr_mut(s);
9138            let (pk, _a7) = k.device_ptr_mut(s);
9139            let (pv, _a8) = v.device_ptr_mut(s);
9140            let (pp, _a9) = pos.device_ptr(s);
9141            let pff: u64 = match ff {
9142                Some(t) => {
9143                    let (p, _gg) = t.device_ptr(s);
9144                    p as u64
9145                }
9146                None => 0,
9147            };
9148            let (pkc, _a10) = kc.device_ptr_mut(s);
9149            let (pvc, _a11) = vc.device_ptr_mut(s);
9150            let (pt, _a12) = t_dev.device_ptr(s);
9151            let mut ps = [
9152                &p0 as *const _ as *mut std::ffi::c_void,
9153                &p1 as *const _ as *mut _,
9154                &p2 as *const _ as *mut _,
9155                &pwq as *const _ as *mut _,
9156                &pwk as *const _ as *mut _,
9157                &pwv as *const _ as *mut _,
9158                &pq as *const _ as *mut _,
9159                &pk as *const _ as *mut _,
9160                &pv as *const _ as *mut _,
9161                &nc as *const _ as *mut _,
9162                &rqi as *const _ as *mut _,
9163                &rki as *const _ as *mut _,
9164                &pp as *const _ as *mut _,
9165                &nhq as *const _ as *mut _,
9166                &nhk as *const _ as *mut _,
9167                &theta_scale as *const _ as *mut _,
9168                &freq_scale as *const _ as *mut _,
9169                &pff as *const _ as *mut _,
9170                &eps as *const _ as *mut _,
9171                &pkc as *const _ as *mut _,
9172                &pvc as *const _ as *mut _,
9173                &pt as *const _ as *mut _,
9174                &ktb as *const _ as *mut _,
9175                &vtb as *const _ as *mut _,
9176            ];
9177            unsafe {
9178                self.launch_pdl_flash(
9179                    g,
9180                    "rms_norm_qkv_rope_append_dc_f32",
9181                    (rows as u32, 1, 1),
9182                    (rms_block(), 1, 1),
9183                    0,
9184                    &mut ps,
9185                )?;
9186            }
9187            return Ok(());
9188        }
9189        let f = if g {
9190            self.func_g("rms_norm_qkv_rope_append_dc_f32")
9191        } else {
9192            self.func("rms_norm_qkv_rope_append_dc_f32")
9193        };
9194        let cfg = LaunchConfig {
9195            grid_dim: (rows as u32, 1, 1),
9196            block_dim: (rms_block(), 1, 1),
9197            shared_mem_bytes: 0,
9198        };
9199        let __s_b = self.gpu.stream();
9200        let mut b = __s_b.launch_builder(&f);
9201        match ff {
9202            Some(t) => {
9203                b.arg(q0)
9204                    .arg(k0)
9205                    .arg(v0)
9206                    .arg(wq)
9207                    .arg(wk)
9208                    .arg(wv)
9209                    .arg(&mut *q)
9210                    .arg(&mut *k)
9211                    .arg(&mut *v)
9212                    .arg(&nc)
9213                    .arg(&rqi)
9214                    .arg(&rki)
9215                    .arg(pos)
9216                    .arg(&nhq)
9217                    .arg(&nhk)
9218                    .arg(&theta_scale)
9219                    .arg(&freq_scale)
9220                    .arg(t)
9221                    .arg(&eps)
9222                    .arg(&mut *kc)
9223                    .arg(&mut *vc)
9224                    .arg(t_dev)
9225                    .arg(&ktb)
9226                    .arg(&vtb);
9227                unsafe {
9228                    b.launch(cfg)?;
9229                }
9230            }
9231            None => {
9232                let null: u64 = 0;
9233                b.arg(q0)
9234                    .arg(k0)
9235                    .arg(v0)
9236                    .arg(wq)
9237                    .arg(wk)
9238                    .arg(wv)
9239                    .arg(&mut *q)
9240                    .arg(&mut *k)
9241                    .arg(&mut *v)
9242                    .arg(&nc)
9243                    .arg(&rqi)
9244                    .arg(&rki)
9245                    .arg(pos)
9246                    .arg(&nhq)
9247                    .arg(&nhk)
9248                    .arg(&theta_scale)
9249                    .arg(&freq_scale)
9250                    .arg(&null)
9251                    .arg(&eps)
9252                    .arg(&mut *kc)
9253                    .arg(&mut *vc)
9254                    .arg(t_dev)
9255                    .arg(&ktb)
9256                    .arg(&vtb);
9257                unsafe {
9258                    b.launch(cfg)?;
9259                }
9260            }
9261        }
9262        Ok(())
9263    }
9264
9265    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
9266    /// Host-len twin of `rms_norm_qkv_rope_append_dc` (zoo-fusion arc): the eager decode
9267    /// arm tracks kv length on host (`kvl.len`), so the append slot rides the launch arg
9268    /// instead of the device counter. Kernel body is SHARED with the _dc entry (one
9269    /// inlined body — bit-identical to the rms_norm_qkv_rope + append pair it replaces,
9270    /// same law as the dc fold). `n_rot` is the layer's derived rotary width and MUST equal
9271    /// `head_dim` ([`Engine::full_width_rope_only`]).
9272    #[allow(clippy::too_many_arguments)]
9273    pub fn rms_norm_qkv_rope_append(
9274        &self,
9275        q0: &CudaSlice<f32>,
9276        k0: &CudaSlice<f32>,
9277        v0: &CudaSlice<f32>,
9278        wq: &CudaSlice<f32>,
9279        wk: &CudaSlice<f32>,
9280        wv: &CudaSlice<f32>,
9281        q: &mut CudaSlice<f32>,
9282        k: &mut CudaSlice<f32>,
9283        v: &mut CudaSlice<f32>,
9284        head_dim: usize,
9285        n_rot: usize,
9286        rq: usize,
9287        rk: usize,
9288        pos: &CudaSlice<i32>,
9289        nh_q: usize,
9290        nh_k: usize,
9291        base: f32,
9292        freq_scale: f32,
9293        ff: Option<&CudaSlice<f32>>,
9294        eps: f32,
9295        kc: &mut CudaSlice<u8>,
9296        vc: &mut CudaSlice<u8>,
9297        t: usize,
9298        k_tok_bytes: usize,
9299        v_tok_bytes: usize,
9300        g: bool,
9301    ) -> Result<(), Box<dyn std::error::Error>> {
9302        Self::full_width_rope_only("rms_norm_qkv_rope_append", n_rot, head_dim)?;
9303        let rows = rq + rk + rk;
9304        let theta_scale = base.powf(-2.0 / head_dim as f32);
9305        let (nc, rqi, rki, nhq, nhk) = (
9306            head_dim as i32,
9307            rq as i32,
9308            rk as i32,
9309            nh_q as i32,
9310            nh_k as i32,
9311        );
9312        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9313        let ti = t as i32;
9314        if Self::pdl_on() && Self::pdl_wb_on() {
9315            use cudarc::driver::{DevicePtr, DevicePtrMut};
9316            let s = &self.gpu.stream();
9317            let (p0, _a0) = q0.device_ptr(s);
9318            let (p1, _a1) = k0.device_ptr(s);
9319            let (p2, _a2) = v0.device_ptr(s);
9320            let (pwq, _a3) = wq.device_ptr(s);
9321            let (pwk, _a4) = wk.device_ptr(s);
9322            let (pwv, _a5) = wv.device_ptr(s);
9323            let (pq, _a6) = q.device_ptr_mut(s);
9324            let (pk, _a7) = k.device_ptr_mut(s);
9325            let (pv, _a8) = v.device_ptr_mut(s);
9326            let (pp, _a9) = pos.device_ptr(s);
9327            let pff: u64 = match ff {
9328                Some(t) => {
9329                    let (p, _gg) = t.device_ptr(s);
9330                    p as u64
9331                }
9332                None => 0,
9333            };
9334            let (pkc, _a10) = kc.device_ptr_mut(s);
9335            let (pvc, _a11) = vc.device_ptr_mut(s);
9336            let mut ps = [
9337                &p0 as *const _ as *mut std::ffi::c_void,
9338                &p1 as *const _ as *mut _,
9339                &p2 as *const _ as *mut _,
9340                &pwq as *const _ as *mut _,
9341                &pwk as *const _ as *mut _,
9342                &pwv as *const _ as *mut _,
9343                &pq as *const _ as *mut _,
9344                &pk as *const _ as *mut _,
9345                &pv as *const _ as *mut _,
9346                &nc as *const _ as *mut _,
9347                &rqi as *const _ as *mut _,
9348                &rki as *const _ as *mut _,
9349                &pp as *const _ as *mut _,
9350                &nhq as *const _ as *mut _,
9351                &nhk as *const _ as *mut _,
9352                &theta_scale as *const _ as *mut _,
9353                &freq_scale as *const _ as *mut _,
9354                &pff as *const _ as *mut _,
9355                &eps as *const _ as *mut _,
9356                &pkc as *const _ as *mut _,
9357                &pvc as *const _ as *mut _,
9358                &ti as *const _ as *mut _,
9359                &ktb as *const _ as *mut _,
9360                &vtb as *const _ as *mut _,
9361            ];
9362            unsafe {
9363                self.launch_pdl_flash(
9364                    g,
9365                    "rms_norm_qkv_rope_append_f32",
9366                    (rows as u32, 1, 1),
9367                    (rms_block(), 1, 1),
9368                    0,
9369                    &mut ps,
9370                )?;
9371            }
9372            return Ok(());
9373        }
9374        let f = if g {
9375            self.func_g("rms_norm_qkv_rope_append_f32")
9376        } else {
9377            self.func("rms_norm_qkv_rope_append_f32")
9378        };
9379        let cfg = LaunchConfig {
9380            grid_dim: (rows as u32, 1, 1),
9381            block_dim: (rms_block(), 1, 1),
9382            shared_mem_bytes: 0,
9383        };
9384        let __s_b = self.gpu.stream();
9385        let mut b = __s_b.launch_builder(&f);
9386        let null: u64 = 0;
9387        b.arg(q0)
9388            .arg(k0)
9389            .arg(v0)
9390            .arg(wq)
9391            .arg(wk)
9392            .arg(wv)
9393            .arg(&mut *q)
9394            .arg(&mut *k)
9395            .arg(&mut *v)
9396            .arg(&nc)
9397            .arg(&rqi)
9398            .arg(&rki)
9399            .arg(pos)
9400            .arg(&nhq)
9401            .arg(&nhk)
9402            .arg(&theta_scale)
9403            .arg(&freq_scale);
9404        match ff {
9405            Some(t) => {
9406                b.arg(t);
9407            }
9408            None => {
9409                b.arg(&null);
9410            }
9411        }
9412        b.arg(&eps)
9413            .arg(&mut *kc)
9414            .arg(&mut *vc)
9415            .arg(&ti)
9416            .arg(&ktb)
9417            .arg(&vtb);
9418        unsafe {
9419            b.launch(cfg)?;
9420        }
9421        Ok(())
9422    }
9423
9424    pub fn add_q8_1(
9425        &self,
9426        a: &CudaSlice<f32>,
9427        b: &CudaSlice<f32>,
9428        res: &mut CudaSlice<f32>,
9429        ncols: usize,
9430        nrows: usize,
9431    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9432        debug_assert!(ncols % 128 == 0);
9433        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9434        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9435        let f = self.func("add_q8_1_f32");
9436        let cfg = LaunchConfig {
9437            grid_dim: (nrows as u32, 1, 1),
9438            block_dim: (rms_block(), 1, 1),
9439            shared_mem_bytes: 0,
9440        };
9441        let nc = ncols as i32;
9442        let __s_b2 = self.gpu.stream();
9443        let mut b2 = __s_b2.launch_builder(&f);
9444        b2.arg(a)
9445            .arg(b)
9446            .arg(&mut *res)
9447            .arg(&mut out_q)
9448            .arg(&mut out_d)
9449            .arg(&nc);
9450        unsafe {
9451            b2.launch(cfg)?;
9452        }
9453        Ok((out_q, out_d))
9454    }
9455
9456    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
9457    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
9458    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
9459    pub fn rms_pre_add_q8_1(
9460        &self,
9461        a: &CudaSlice<f32>,
9462        wa: &CudaSlice<f32>,
9463        b: &CudaSlice<f32>,
9464        res: &mut CudaSlice<f32>,
9465        ncols: usize,
9466        nrows: usize,
9467        eps: f32,
9468    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9469        debug_assert!(ncols % 128 == 0);
9470        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9471        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9472        let f = self.func("rms_pre_add_q8_1_f32");
9473        let cfg = LaunchConfig {
9474            grid_dim: (nrows as u32, 1, 1),
9475            block_dim: (rms_block(), 1, 1),
9476            shared_mem_bytes: 0,
9477        };
9478        let (nc, ep) = (ncols as i32, eps);
9479        let __s_b2 = self.gpu.stream();
9480        let mut b2 = __s_b2.launch_builder(&f);
9481        b2.arg(a)
9482            .arg(wa)
9483            .arg(b)
9484            .arg(&mut *res)
9485            .arg(&mut out_q)
9486            .arg(&mut out_d)
9487            .arg(&nc)
9488            .arg(&ep);
9489        unsafe {
9490            b2.launch(cfg)?;
9491        }
9492        Ok((out_q, out_d))
9493    }
9494
9495    /// L2 norm per row (head_dim), no weight.
9496    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
9497    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
9498    pub fn l2_v2_on(ncols: usize) -> bool {
9499        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
9500    }
9501
9502    pub fn l2_norm_pp(
9503        &self,
9504        x: &CudaSlice<f32>,
9505        dst: &mut CudaSlice<f32>,
9506        dst16: Option<&mut CudaSlice<u8>>,
9507        ncols: usize,
9508        nrows: usize,
9509        eps: f32,
9510    ) -> Result<(), Box<dyn std::error::Error>> {
9511        if Self::l2_v2_on(ncols) {
9512            let f = self.func("l2_norm_pp_v2_f32");
9513            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
9514            let cfg = LaunchConfig {
9515                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
9516                block_dim: (256, 1, 1),
9517                shared_mem_bytes: 0,
9518            };
9519            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9520            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
9521            let d16: u64 = match dst16 {
9522                Some(d) => self.addr_u8(d),
9523                None => 0,
9524            };
9525            let __s_b = self.gpu.stream();
9526            let mut b = __s_b.launch_builder(&f);
9527            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
9528            unsafe {
9529                b.launch(cfg)?;
9530            }
9531            return Ok(());
9532        }
9533        self.l2_norm(x, dst, ncols, nrows, eps)
9534    }
9535
9536    pub fn l2_norm(
9537        &self,
9538        x: &CudaSlice<f32>,
9539        dst: &mut CudaSlice<f32>,
9540        ncols: usize,
9541        nrows: usize,
9542        eps: f32,
9543    ) -> Result<(), Box<dyn std::error::Error>> {
9544        let f = self.func("l2_norm_f32");
9545        let cfg = LaunchConfig {
9546            grid_dim: (nrows as u32, 1, 1),
9547            block_dim: (256, 1, 1),
9548            shared_mem_bytes: 0,
9549        };
9550        let (nc, e) = (ncols as i32, eps);
9551        let __s_b = self.gpu.stream();
9552        let mut b = __s_b.launch_builder(&f);
9553        b.arg(x).arg(dst).arg(&nc).arg(&e);
9554        unsafe {
9555            b.launch(cfg)?;
9556        }
9557        Ok(())
9558    }
9559
9560    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9561    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9562    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9563    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9564    /// propagate through gdn_scan and flip argmax on marginal logits.
9565    pub fn l2_norm_decode(
9566        &self,
9567        x: &CudaSlice<f32>,
9568        dst: &mut CudaSlice<f32>,
9569        ncols: usize,
9570        nrows: usize,
9571        eps: f32,
9572    ) -> Result<(), Box<dyn std::error::Error>> {
9573        let f = self.func("l2_norm_f32");
9574        let cfg = LaunchConfig {
9575            grid_dim: (nrows as u32, 1, 1),
9576            block_dim: (32, 1, 1),
9577            shared_mem_bytes: 0,
9578        };
9579        let (nc, e) = (ncols as i32, eps);
9580        let __s_b = self.gpu.stream();
9581        let mut b = __s_b.launch_builder(&f);
9582        b.arg(x).arg(dst).arg(&nc).arg(&e);
9583        unsafe {
9584            b.launch(cfg)?;
9585        }
9586        Ok(())
9587    }
9588
9589    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9590    pub fn rope_neox(
9591        &self,
9592        x: &mut CudaSlice<f32>,
9593        pos: &CudaSlice<i32>,
9594        head_dim: usize,
9595        n_dims: usize,
9596        n_heads: usize,
9597        n_tokens: usize,
9598        freq_base: f32,
9599        freq_scale: f32,
9600    ) -> Result<(), Box<dyn std::error::Error>> {
9601        let f = self.func("rope_neox_f32");
9602        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9603        let grid = (n_heads * n_tokens) as u32;
9604        let cfg = LaunchConfig {
9605            grid_dim: (grid, 1, 1),
9606            block_dim: ((head_dim / 2) as u32, 1, 1),
9607            shared_mem_bytes: 0,
9608        };
9609        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9610        let __s_b = self.gpu.stream();
9611        let mut b = __s_b.launch_builder(&f);
9612        b.arg(x)
9613            .arg(pos)
9614            .arg(&hd)
9615            .arg(&nd)
9616            .arg(&nh)
9617            .arg(&theta_scale)
9618            .arg(&freq_scale);
9619        unsafe {
9620            b.launch(cfg)?;
9621        }
9622        Ok(())
9623    }
9624
9625    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9626    pub fn rope_neox_ff(
9627        &self,
9628        x: &mut CudaSlice<f32>,
9629        pos: &CudaSlice<i32>,
9630        head_dim: usize,
9631        n_dims: usize,
9632        n_heads: usize,
9633        n_tokens: usize,
9634        freq_base: f32,
9635        freq_scale: f32,
9636        ff: &CudaSlice<f32>,
9637    ) -> Result<(), Box<dyn std::error::Error>> {
9638        let f = self.func("rope_neox_ff_f32");
9639        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9640        let grid = (n_heads * n_tokens) as u32;
9641        let cfg = LaunchConfig {
9642            grid_dim: (grid, 1, 1),
9643            block_dim: ((head_dim / 2) as u32, 1, 1),
9644            shared_mem_bytes: 0,
9645        };
9646        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9647        let __s_b = self.gpu.stream();
9648        let mut b = __s_b.launch_builder(&f);
9649        b.arg(x)
9650            .arg(pos)
9651            .arg(&hd)
9652            .arg(&nd)
9653            .arg(&nh)
9654            .arg(&theta_scale)
9655            .arg(&freq_scale)
9656            .arg(ff);
9657        unsafe {
9658            b.launch(cfg)?;
9659        }
9660        Ok(())
9661    }
9662
9663    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9664    #[allow(clippy::too_many_arguments)]
9665    pub fn rope_neox2(
9666        &self,
9667        q: &mut CudaSlice<f32>,
9668        k: &mut CudaSlice<f32>,
9669        pos: &CudaSlice<i32>,
9670        head_dim: usize,
9671        n_dims: usize,
9672        nh_q: usize,
9673        nh_k: usize,
9674        n_tokens: usize,
9675        freq_base: f32,
9676        freq_scale: f32,
9677        ff: Option<&CudaSlice<f32>>,
9678    ) -> Result<(), Box<dyn std::error::Error>> {
9679        let f = self.func("rope_neox2_f32");
9680        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9681        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9682        let cfg = LaunchConfig {
9683            grid_dim: (grid, 1, 1),
9684            block_dim: ((head_dim / 2) as u32, 1, 1),
9685            shared_mem_bytes: 0,
9686        };
9687        let (hd, nd, nq, nk, nt) = (
9688            head_dim as i32,
9689            n_dims as i32,
9690            nh_q as i32,
9691            nh_k as i32,
9692            n_tokens as i32,
9693        );
9694        let __s_b = self.gpu.stream();
9695        let mut b = __s_b.launch_builder(&f);
9696        b.arg(q)
9697            .arg(k)
9698            .arg(pos)
9699            .arg(&hd)
9700            .arg(&nd)
9701            .arg(&nq)
9702            .arg(&nk)
9703            .arg(&nt)
9704            .arg(&theta_scale)
9705            .arg(&freq_scale);
9706        match ff {
9707            Some(ffv) => {
9708                b.arg(ffv);
9709                unsafe {
9710                    b.launch(cfg)?;
9711                }
9712            }
9713            None => {
9714                let null: u64 = 0;
9715                b.arg(&null);
9716                unsafe {
9717                    b.launch(cfg)?;
9718                }
9719            }
9720        }
9721        Ok(())
9722    }
9723
9724    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9725    pub fn gelu_tanh_mul(
9726        &self,
9727        gate: &CudaSlice<f32>,
9728        up: &CudaSlice<f32>,
9729        dst: &mut CudaSlice<f32>,
9730        n: usize,
9731    ) -> Result<(), Box<dyn std::error::Error>> {
9732        let f = self.func("gelu_tanh_mul_f32");
9733        let cfg = LaunchConfig::for_num_elems(n as u32);
9734        let ni = n as i32;
9735        let __s_b = self.gpu.stream();
9736        let mut b = __s_b.launch_builder(&f);
9737        b.arg(gate).arg(up).arg(dst).arg(&ni);
9738        unsafe {
9739            b.launch(cfg)?;
9740        }
9741        Ok(())
9742    }
9743
9744    pub fn silu_mul(
9745        &self,
9746        gate: &CudaSlice<f32>,
9747        up: &CudaSlice<f32>,
9748        dst: &mut CudaSlice<f32>,
9749        n: usize,
9750    ) -> Result<(), Box<dyn std::error::Error>> {
9751        let f = self.func("silu_mul_f32");
9752        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9753        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9754        let ni = n as i32;
9755        let __s_b = self.gpu.stream();
9756        let mut b = __s_b.launch_builder(&f);
9757        b.arg(gate).arg(up).arg(dst).arg(&ni);
9758        unsafe {
9759            b.launch(cfg)?;
9760        }
9761        Ok(())
9762    }
9763
9764    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9765    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9766    pub fn silu_mul_f16out(
9767        &self,
9768        gate: &CudaSlice<f32>,
9769        up: &CudaSlice<f32>,
9770        dst: &mut CudaSlice<f32>,
9771        dst16: &mut CudaSlice<u8>,
9772        n: usize,
9773    ) -> Result<(), Box<dyn std::error::Error>> {
9774        let f = self.func("silu_mul_f16out_f32");
9775        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9776        let ni = n as i32;
9777        let __s_b = self.gpu.stream();
9778        let mut b = __s_b.launch_builder(&f);
9779        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9780        unsafe {
9781            b.launch(cfg)?;
9782        }
9783        Ok(())
9784    }
9785
9786    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9787    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9788    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9789    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9790    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9791    /// launches per dense FFN layer (the gate+up post-matmul scales).
9792    pub fn silu_mul_scaled(
9793        &self,
9794        gate: &CudaSlice<f32>,
9795        up: &CudaSlice<f32>,
9796        gs: f32,
9797        us: f32,
9798        dst: &mut CudaSlice<f32>,
9799        n: usize,
9800    ) -> Result<(), Box<dyn std::error::Error>> {
9801        let f = self.func("silu_mul_scaled_f32");
9802        let cfg = LaunchConfig::for_num_elems(n as u32);
9803        let ni = n as i32;
9804        let (gsf, usf) = (gs, us);
9805        let __s_b = self.gpu.stream();
9806        let mut b = __s_b.launch_builder(&f);
9807        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9808        unsafe {
9809            b.launch(cfg)?;
9810        }
9811        Ok(())
9812    }
9813
9814    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9815    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9816    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9817    #[allow(clippy::too_many_arguments)]
9818    pub fn swigluoai_mul_scaled(
9819        &self,
9820        gate: &CudaSlice<f32>,
9821        up: &CudaSlice<f32>,
9822        gs: f32,
9823        us: f32,
9824        alpha: f32,
9825        limit: f32,
9826        dst: &mut CudaSlice<f32>,
9827        n: usize,
9828    ) -> Result<(), Box<dyn std::error::Error>> {
9829        let f = self.func("swigluoai_mul_scaled_f32");
9830        let cfg = LaunchConfig::for_num_elems(n as u32);
9831        let ni = n as i32;
9832        let __s_b = self.gpu.stream();
9833        let mut b = __s_b.launch_builder(&f);
9834        b.arg(gate)
9835            .arg(up)
9836            .arg(&gs)
9837            .arg(&us)
9838            .arg(&alpha)
9839            .arg(&limit)
9840            .arg(dst)
9841            .arg(&ni);
9842        unsafe {
9843            b.launch(cfg)?;
9844        }
9845        Ok(())
9846    }
9847
9848    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9849    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9850    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9851    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9852    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9853    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9854    /// n must be a multiple of 32 (n_ff always is).
9855    pub fn silu_mul_scaled_q8_1(
9856        &self,
9857        gate: &CudaSlice<f32>,
9858        up: &CudaSlice<f32>,
9859        gs: f32,
9860        us: f32,
9861        n: usize,
9862    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9863        let f = self.func("silu_mul_scaled_q8_1");
9864        let nblk = n / 32;
9865        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9866        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9867        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9868        let cfg = LaunchConfig::for_num_elems(n as u32);
9869        let (gsf, usf, ni) = (gs, us, n as i32);
9870        let __s_b = self.gpu.stream();
9871        let mut b = __s_b.launch_builder(&f);
9872        b.arg(gate)
9873            .arg(up)
9874            .arg(&gsf)
9875            .arg(&usf)
9876            .arg(&mut aq)
9877            .arg(&mut ad)
9878            .arg(&ni);
9879        unsafe {
9880            b.launch(cfg)?;
9881        }
9882        Ok((aq, ad))
9883    }
9884
9885    pub fn add(
9886        &self,
9887        a: &CudaSlice<f32>,
9888        b_in: &CudaSlice<f32>,
9889        dst: &mut CudaSlice<f32>,
9890        n: usize,
9891    ) -> Result<(), Box<dyn std::error::Error>> {
9892        let f = self.func("add_f32");
9893        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9894        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9895        let ni = n as i32;
9896        let __s_bld = self.gpu.stream();
9897        let mut bld = __s_bld.launch_builder(&f);
9898        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9899        unsafe {
9900            bld.launch(cfg)?;
9901        }
9902        Ok(())
9903    }
9904
9905    pub fn mul(
9906        &self,
9907        a: &CudaSlice<f32>,
9908        b_in: &CudaSlice<f32>,
9909        dst: &mut CudaSlice<f32>,
9910        n: usize,
9911    ) -> Result<(), Box<dyn std::error::Error>> {
9912        let f = self.func("mul_f32");
9913        let cfg = LaunchConfig::for_num_elems(n as u32);
9914        let ni = n as i32;
9915        let __s_bld = self.gpu.stream();
9916        let mut bld = __s_bld.launch_builder(&f);
9917        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9918        unsafe {
9919            bld.launch(cfg)?;
9920        }
9921        Ok(())
9922    }
9923
9924    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9925    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9926    pub fn matmul(
9927        &self,
9928        w: &crate::model::GpuTensor,
9929        x: &CudaSlice<f32>,
9930        m: usize,
9931    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9932        use crate::model::GpuTensor;
9933        let in_f = w.in_features();
9934        let out_f = w.out_features();
9935        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9936        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9937        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9938        // gives nothing). Quantize the activation once here then call the GEMM.
9939        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9940        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9941        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9942        #[allow(non_snake_case)]
9943        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9944        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9945        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9946            usize::MAX
9947        } else {
9948            16usize
9949        };
9950
9951        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9952        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9953        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9954        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9955        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9956        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9957        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9958        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9959        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9960        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9961        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9962        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9963        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9964        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9965        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9966        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9967        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9968        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9969        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9970        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9971        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9972        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9973        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9974        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9975        if m >= GEMM_M_THRESHOLD {
9976            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9977                return Ok(y);
9978            }
9979            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9980            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9981            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9982            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9983            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9984            // tile defaults differently by operand source.
9985            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9986                return Ok(y);
9987            }
9988            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
9989            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
9990            if let Some(y) = self.try_f16_gemm(w, x, m)? {
9991                return Ok(y);
9992            }
9993        }
9994        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
9995        // m threshold the rest of this method uses:
9996        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
9997        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
9998        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
9999        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
10000        //     across every tier by construction with no batched twin needed.
10001        //
10002        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
10003        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
10004        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
10005        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
10006        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
10007        // arms is what makes sure it never gets there.
10008        if let GpuTensor::Quant { qtype, .. } = w {
10009            if *qtype == QT_F8_E4M3_BLK {
10010                if m >= GEMM_M_THRESHOLD {
10011                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
10012                        return Ok(y);
10013                    }
10014                }
10015                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10016                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10017                    return Ok(y);
10018                }
10019            }
10020        }
10021        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
10022            return self.qmatvec_mmq(w, x, m);
10023        }
10024        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
10025            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10026            return self.qmatvec_gemm(w, &aq, &ad, m);
10027        }
10028        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
10029        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
10030        if m >= GEMM_M_THRESHOLD {
10031            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
10032                return Ok(y);
10033            }
10034        }
10035        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
10036        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
10037        // to Stage-A f32-dequant (the correctness oracle path).
10038        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
10039        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
10040        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
10041        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
10042        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
10043        if m == 1 && fast {
10044            if let GpuTensor::Quant {
10045                bytes,
10046                qtype,
10047                row_bytes,
10048                rp,
10049                rp4,
10050                scale,
10051                ..
10052            } = w
10053            {
10054                if self.mmvq_supports(*qtype) {
10055                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
10056                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
10057                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
10058                    let (bytes, rp) = match rp4 {
10059                        Some(m4) => (m4, true),
10060                        None => (bytes, *rp),
10061                    };
10062                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10063                    return self.qmatvec_mmvq(
10064                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
10065                    );
10066                }
10067            }
10068        }
10069        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
10070        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
10071        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
10072        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
10073        // block below. MEMRA_NO_BATCHED -> per-m path.
10074        //
10075        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
10076        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
10077        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
10078        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
10079        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
10080        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
10081        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
10082        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
10083        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
10084        if (2..=16).contains(&m)
10085            && fast
10086            && std::env::var("MEMRA_NO_BATCHED").is_err()
10087            && (m <= 4 || Self::b8_enabled())
10088        {
10089            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
10090            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
10091            // is present (rp4) — the mirror pick below then routes to the _rp family.
10092            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
10093            // because the native e4m3 row layout is already aligned and needs no mirror.
10094            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
10095            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
10096            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
10097            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
10098            let m_ok = m <= 8
10099                || matches!(w, GpuTensor::Quant { qtype, .. }
10100                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
10101                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
10102            if m_ok {
10103                if let GpuTensor::Quant {
10104                    bytes,
10105                    qtype,
10106                    row_bytes,
10107                    rp,
10108                    rp4,
10109                    ..
10110                } = w
10111                {
10112                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
10113                        let (bytes, rp) = match rp4 {
10114                            Some(m4) => (m4, true),
10115                            None => (bytes, *rp),
10116                        };
10117                        let mcols = Self::batched_mcols(m);
10118                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10119                        let mut y = self.qmatvec_mmvq_batched(
10120                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
10121                        )?;
10122                        if let GpuTensor::Quant { scale, .. } = w {
10123                            if *scale != 1.0 {
10124                                self.scale_inplace(&mut y, *scale, m * out_f)?;
10125                            }
10126                        }
10127                        return Ok(y);
10128                    }
10129                }
10130            }
10131        }
10132        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
10133        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
10134        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
10135        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
10136        // for this dtype, so the generic match below must never see it under `fast`.
10137        if fast {
10138            if let GpuTensor::Quant {
10139                bytes,
10140                qtype,
10141                row_bytes,
10142                scale,
10143                ..
10144            } = w
10145            {
10146                if *qtype == QT_F8_E4M3 {
10147                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10148                    return self.qmatvec_mmvq(
10149                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
10150                    );
10151                }
10152            }
10153        }
10154        let mut y = match w {
10155            GpuTensor::Quant {
10156                bytes,
10157                qtype,
10158                row_bytes,
10159                ..
10160            } if fast && *qtype == QT_Q8_0 => {
10161                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10162            }
10163            GpuTensor::Quant {
10164                bytes,
10165                qtype,
10166                row_bytes,
10167                ..
10168            } if fast && *qtype == QT_Q4_K => {
10169                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10170            }
10171            GpuTensor::Quant {
10172                bytes,
10173                qtype,
10174                row_bytes,
10175                ..
10176            } if fast && *qtype == QT_Q6_K => {
10177                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10178            }
10179            GpuTensor::Quant {
10180                bytes,
10181                qtype,
10182                row_bytes,
10183                ..
10184            } if fast && *qtype == QT_Q5_K => {
10185                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10186            }
10187            GpuTensor::Quant {
10188                bytes,
10189                qtype,
10190                row_bytes,
10191                ..
10192            } if fast && *qtype == QT_Q3_K => {
10193                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10194            }
10195            GpuTensor::Quant {
10196                bytes,
10197                qtype,
10198                row_bytes,
10199                rp,
10200                ..
10201            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
10202                if *rp {
10203                    "qmatvec_nvfp4_dp4a_rp"
10204                } else {
10205                    "qmatvec_nvfp4_dp4a"
10206                },
10207                bytes,
10208                x,
10209                m,
10210                in_f,
10211                out_f,
10212                *row_bytes,
10213            )?,
10214            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
10215            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
10216            // anomaly (research/kat-anomaly-20260802/).
10217            GpuTensor::Quant {
10218                bytes,
10219                qtype,
10220                row_bytes,
10221                ..
10222            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
10223                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10224            }
10225            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
10226            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
10227            // without first writing the matching kernel, or func() will panic
10228            // "kernel ... not in any fatbin".
10229            GpuTensor::Quant {
10230                bytes,
10231                qtype,
10232                row_bytes,
10233                rp,
10234                ..
10235            } =>
10236            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
10237            // deq(row,j) form cannot address the planes; same value/product order).
10238            {
10239                self.qmatvec(
10240                    bytes,
10241                    x,
10242                    m,
10243                    in_f,
10244                    out_f,
10245                    if *rp && *qtype == QT_NVFP4 {
10246                        QT_NVFP4_RP
10247                    } else {
10248                        *qtype
10249                    },
10250                    *row_bytes,
10251                )?
10252            }
10253            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
10254            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
10255            // cuBLASLt f32 GEMV as the Float arm.
10256            GpuTensor::FloatBf16 { data, .. } => {
10257                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
10258            }
10259        };
10260        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
10261        if let GpuTensor::Quant { scale, .. } = w {
10262            if *scale != 1.0 {
10263                self.scale_inplace(&mut y, *scale, m * out_f)?;
10264            }
10265        }
10266        Ok(y)
10267    }
10268
10269    /// True when `MEMRA_FAST=0`, i.e. the Stage-A f32 oracle is the requested arithmetic and every
10270    /// `matmul_pre` call will take the raw-f32 escape rather than the q8_1 pair.
10271    ///
10272    /// WHY THIS EXISTS AS ITS OWN PREDICATE: `uses_q8_1_fast` needs a weight, but the callers that
10273    /// have to DECIDE WHETHER TO MATERIALIZE an f32 activation sit one level above any weight — the
10274    /// gemma-4 decode trunk emits a q8_1 pair per layer and hands it down, so the f32 has to be
10275    /// produced (or not) by the loop that owns the residual. Cached in a OnceLock like every other
10276    /// `*_on()` flag here: this is read once per layer per token on the decode path, and the daily
10277    /// path must not pay an env lookup for a flag that is off.
10278    pub fn stage_a_raw_needed() -> bool {
10279        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10280        *ON.get_or_init(|| std::env::var("MEMRA_FAST").as_deref() == Ok("0"))
10281    }
10282
10283    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
10284    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
10285    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
10286        use crate::model::GpuTensor;
10287        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
10288            return false;
10289        }
10290        match w {
10291            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
10292            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
10293            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
10294            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
10295            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
10296            // block class has no fused twin yet, so each of its projections takes its own launch.
10297            GpuTensor::Quant { qtype, .. } => {
10298                matches!(
10299                    *qtype,
10300                    QT_Q8_0
10301                        | QT_Q4_K
10302                        | QT_Q6_K
10303                        | QT_Q5_K
10304                        | QT_Q3_K
10305                        | QT_NVFP4
10306                        | QT_F8_E4M3
10307                        | QT_F8_E4M3_BLK
10308                        | QT_Q4_0
10309                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
10310            }
10311            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
10312        }
10313    }
10314
10315    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
10316    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
10317    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
10318    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
10319    pub fn matmul_pre(
10320        &self,
10321        w: &crate::model::GpuTensor,
10322        aq: &CudaSlice<i8>,
10323        ad: &CudaSlice<f32>,
10324        x_fallback: &CudaSlice<f32>,
10325        m: usize,
10326    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10327        use crate::model::GpuTensor;
10328        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
10329        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
10330        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
10331        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
10332        // rc=30013 dig, 2026-07-31).
10333        let x_raw_ok = x_fallback.len() >= m * w.in_features();
10334        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
10335        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
10336        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10337            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
10338                return Ok(y);
10339            }
10340            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
10341            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
10342            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
10343                return Ok(y);
10344            }
10345            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
10346            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
10347                return Ok(y);
10348            }
10349        }
10350        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
10351        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
10352        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
10353        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
10354        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
10355        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10356            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
10357                return Ok(y);
10358            }
10359        }
10360        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10361            return Ok(y);
10362        }
10363        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
10364        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
10365        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
10366        // aq/ad.
10367        if m >= 16
10368            && w.out_features() >= 128
10369            && self.mmq_supports(w)
10370            && !self.verify_exact_on()
10371            && x_raw_ok
10372        {
10373            return self.qmatvec_mmq(w, x_fallback, m);
10374        }
10375        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
10376        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
10377        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10378            if let Some(y) =
10379                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
10380            {
10381                return Ok(y);
10382            }
10383        }
10384        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
10385        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
10386        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
10387            return self.qmatvec_gemm(w, aq, ad, m);
10388        }
10389        // THE SEVENTH ARM. Every raw-f32 arm above is guarded by `x_raw_ok`; this one — the
10390        // Stage-A / Float escape — was not, and it is the ONLY one `MEMRA_FAST=0` opens. So the
10391        // 2026-07-31 E4B rc=30013 fix (add the length guard, keep empty-fallback callers off the
10392        // raw-f32 arms) protected the six arms the FAST path can reach and missed the one the
10393        // ORACLE path reaches. Consequence before this guard: the gemma-4 decode arms, which pass
10394        // `e.zeros(0)` because their trunk emits only a q8_1 pair and never materializes an f32
10395        // attn-normed activation, fell through to Stage-A `qmatvec_f32` (cu/qmatvec.cu:5442),
10396        // which reads `m * in_f` floats out of a 0-byte allocation ->
10397        // CUDA_ERROR_ILLEGAL_ADDRESS at layer 0 of the first decode token. The fault is STICKY:
10398        // it poisons the context, so every LATER request in that process fails with an unrelated
10399        // message ("cache alloc failed: ...") and the true cause appears exactly once, in the
10400        // first failure. That is what made `MEMRA_FAST=0` — the reference named in
10401        // `tools/argmax-margin-gate.sh`'s own header — return an opaque HTTP 500 on the gemma
10402        // dense artifact and left the arm with no working truth instrument.
10403        //
10404        // Refuse loudly instead of reading out of bounds. A named error at the true call site is
10405        // strictly better than an illegal address surfacing later at an unrelated sync point, and
10406        // an oracle that cannot run must say so rather than corrupt the context it runs in.
10407        if !self.uses_q8_1_fast(w) {
10408            if !x_raw_ok {
10409                return Err(format!(
10410                    "matmul_pre: q8_1-fast is off for this weight but x_fallback holds {} f32 \
10411                     (need m*in_f = {}*{} = {}). This call site pre-quantized its activation and \
10412                     dropped the f32, so there is nothing to fall back to — pass the real f32 \
10413                     activation (see Engine::rms_norm_decode, which is bit-identical to \
10414                     rms_norm_q8_1's reduction) or keep the weight on the q8_1 path.",
10415                    x_fallback.len(),
10416                    m,
10417                    w.in_features(),
10418                    m * w.in_features()
10419                )
10420                .into());
10421            }
10422            return self.matmul(w, x_fallback, m);
10423        }
10424        let in_f = w.in_features();
10425        let out_f = w.out_features();
10426        let (bytes, qtype, row_bytes, scale, rp) = match w {
10427            GpuTensor::Quant {
10428                bytes,
10429                qtype,
10430                row_bytes,
10431                scale,
10432                rp,
10433                ..
10434            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10435            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
10436        };
10437        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
10438        // the dp4a/oracle tails below keep the raw GGUF bytes.
10439        let (mbytes, mrp) = match w {
10440            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10441            _ => (bytes, rp),
10442        };
10443        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
10444        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
10445        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
10446        if m == 1 && self.mmvq_supports(qtype) {
10447            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
10448        }
10449        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
10450        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
10451        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
10452        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
10453        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
10454        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
10455        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
10456        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
10457        // m=5..8 on the old per-m path (b8-tier-only seam).
10458        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
10459        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
10460        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
10461        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10462            && std::env::var("MEMRA_NO_BATCHED").is_err()
10463            && (m <= 4 || Self::b8_enabled())
10464            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
10465            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
10466            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
10467            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
10468                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
10469        {
10470            let mcols = Self::batched_mcols(m);
10471            return self.qmatvec_mmvq_batched(
10472                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
10473            );
10474        }
10475        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
10476        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
10477        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
10478        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
10479        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
10480        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
10481            let (b2, r2) = if qtype == QT_Q4_0 {
10482                (mbytes, mrp)
10483            } else {
10484                (bytes, rp)
10485            };
10486            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
10487        }
10488        let name = match qtype {
10489            QT_Q8_0 => "qmatvec_q8_0_dp4a",
10490            QT_Q4_K => "qmatvec_q4_K_dp4a",
10491            QT_Q6_K => "qmatvec_q6_K_dp4a",
10492            QT_Q5_K => "qmatvec_q5_K_dp4a",
10493            QT_Q3_K => "qmatvec_q3_K_dp4a",
10494            QT_NVFP4 => {
10495                if rp {
10496                    "qmatvec_nvfp4_dp4a_rp"
10497                } else {
10498                    "qmatvec_nvfp4_dp4a"
10499                }
10500            }
10501            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
10502            _ => unreachable!(),
10503        };
10504        let f = self.func(name);
10505        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
10506        let cfg = LaunchConfig {
10507            grid_dim: (out_f as u32, m as u32, 1),
10508            block_dim: (128, 1, 1),
10509            shared_mem_bytes: 0,
10510        };
10511        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10512        let __s_b = self.gpu.stream();
10513        let mut b = __s_b.launch_builder(&f);
10514        b.arg(bytes)
10515            .arg(aq)
10516            .arg(ad)
10517            .arg(&mut y)
10518            .arg(&inf)
10519            .arg(&outf)
10520            .arg(&mi)
10521            .arg(&rb);
10522        unsafe {
10523            b.launch(cfg)?;
10524        }
10525        if scale != 1.0 {
10526            self.scale_inplace(&mut y, scale, m * out_f)?;
10527        }
10528        Ok(y)
10529    }
10530
10531    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
10532    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
10533    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
10534    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
10535    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
10536    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
10537    /// reduce as m=1); this method just forces that path unconditionally.
10538    pub fn matmul_decode_exact(
10539        &self,
10540        w: &crate::model::GpuTensor,
10541        x: &CudaSlice<f32>,
10542        m: usize,
10543    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10544        use crate::model::GpuTensor;
10545        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
10546        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
10547        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
10548        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
10549        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
10550        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
10551        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
10552        if let GpuTensor::Float { data, .. } = w {
10553            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
10554        }
10555        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
10556        // float linear (same n-independent reduction contract as the Float arm above).
10557        if let GpuTensor::FloatBf16 { data, .. } = w {
10558            let (in_f, out_f) = (w.in_features(), w.out_features());
10559            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
10560        }
10561        if !self.uses_q8_1_fast(w) {
10562            return self.matmul(w, x, m);
10563        }
10564        let in_f = w.in_features();
10565        let out_f = w.out_features();
10566        let (bytes, qtype, row_bytes, scale, rp) = match w {
10567            GpuTensor::Quant {
10568                bytes,
10569                qtype,
10570                row_bytes,
10571                scale,
10572                rp,
10573                ..
10574            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10575            _ => return self.matmul(w, x, m),
10576        };
10577        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
10578        // which does its own mirror pick).
10579        let (bytes, rp) = match w {
10580            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10581            _ => (bytes, rp),
10582        };
10583        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10584        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10585        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10586        // (token,row) by construction, which is exactly what this method exists to guarantee.
10587        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10588            return Ok(y);
10589        }
10590        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10591        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10592        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10593        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10594        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10595        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10596        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10597        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10598        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10599            && std::env::var("MEMRA_NO_BATCHED").is_err()
10600            && (m <= 4 || Self::b8_enabled())
10601            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10602            // no mirror precondition, `rp` selects the layout only.
10603            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10604                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10605        {
10606            let mcols = Self::batched_mcols(m);
10607            return self.qmatvec_mmvq_batched(
10608                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10609            );
10610        }
10611        if self.mmvq_supports(qtype) {
10612            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10613            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10614            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10615        }
10616        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10617        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10618        self.matmul_pre(w, &aq, &ad, x, m)
10619    }
10620
10621    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10622    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10623    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10624    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10625    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10626    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10627    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10628    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10629    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10630    pub fn matmul_decode_exact_pre(
10631        &self,
10632        w: &crate::model::GpuTensor,
10633        aq: &CudaSlice<i8>,
10634        ad: &CudaSlice<f32>,
10635        m: usize,
10636    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10637        use crate::model::GpuTensor;
10638        debug_assert!(
10639            self.uses_q8_1_fast(w),
10640            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10641        );
10642        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10643        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10644            return Ok(y);
10645        }
10646        let in_f = w.in_features();
10647        let out_f = w.out_features();
10648        let (bytes, qtype, row_bytes, scale, rp) = match w {
10649            GpuTensor::Quant {
10650                bytes,
10651                qtype,
10652                row_bytes,
10653                scale,
10654                rp,
10655                ..
10656            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10657            _ => {
10658                return Err(
10659                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10660                );
10661            }
10662        };
10663        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10664        let (bytes, rp) = match w {
10665            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10666            _ => (bytes, rp),
10667        };
10668        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10669        if (2..=16).contains(&m)
10670            && self.batched_supports(qtype)
10671            && self.mmvq_supports(qtype)
10672            && std::env::var("MEMRA_NO_BATCHED").is_err()
10673            && (m <= 4 || Self::b8_enabled())
10674            && (m <= 8
10675                || qtype == QT_Q4_0
10676                || qtype == QT_Q6_K
10677                || qtype == QT_F8_E4M3
10678                || qtype == QT_NVFP4
10679                || qtype == QT_Q4_K
10680                || qtype == QT_Q5_K
10681                || qtype == QT_Q8_0)
10682        {
10683            let mcols = Self::batched_mcols(m);
10684            return self.qmatvec_mmvq_batched(
10685                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10686            );
10687        }
10688        if self.mmvq_supports(qtype) {
10689            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10690        }
10691        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10692        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10693        let x0 = self.zeros(0)?;
10694        self.matmul_pre(w, aq, ad, &x0, m)
10695    }
10696
10697    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10698    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10699    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10700    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10701    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10702    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10703    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10704    /// per-tensor path.
10705    pub fn matmul_decode_exact_dual_pre(
10706        &self,
10707        w0: &crate::model::GpuTensor,
10708        w1: &crate::model::GpuTensor,
10709        aq: &CudaSlice<i8>,
10710        ad: &CudaSlice<f32>,
10711        m: usize,
10712    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10713    {
10714        use crate::model::GpuTensor;
10715        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10716        let on = *ON.get_or_init(|| {
10717            std::env::var("MEMRA_SPEC_DUAL_T")
10718                .map(|v| v != "0")
10719                .unwrap_or(true)
10720        });
10721        if !on
10722            || !(2..=7).contains(&m)
10723            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10724            || !self.uses_q8_1_fast(w0)
10725            || !self.uses_q8_1_fast(w1)
10726        {
10727            return Ok(None);
10728        }
10729        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10730        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10731        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10732        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10733        if !self.mmvq_supports(QT_NVFP4) {
10734            return Ok(None);
10735        }
10736        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10737        if w1.in_features() != in_f || w1.out_features() != out_f {
10738            return Ok(None);
10739        }
10740        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10741            (
10742                GpuTensor::Quant {
10743                    bytes: b0,
10744                    qtype: q0,
10745                    row_bytes: rb0,
10746                    scale: s0,
10747                    rp: rp0,
10748                    rp4: None,
10749                    ..
10750                },
10751                GpuTensor::Quant {
10752                    bytes: b1,
10753                    qtype: q1,
10754                    row_bytes: rb1,
10755                    scale: s1,
10756                    rp: rp1,
10757                    rp4: None,
10758                    ..
10759                },
10760            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10761                (b0, b1, *rb0, *s0, *s1, *rp0)
10762            }
10763            _ => return Ok(None),
10764        };
10765        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10766        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10767        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10768        {
10769            return Ok(None);
10770        }
10771        let (y0, y1) =
10772            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10773        Ok(Some(((y0, s0), (y1, s1))))
10774    }
10775
10776    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10777    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10778    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10779    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10780    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10781    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10782    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10783    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10784    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10785    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10786    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10787    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10788    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10789    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10790    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10791    pub fn matmul_decode_exact_dual(
10792        &self,
10793        w0: &crate::model::GpuTensor,
10794        w1: &crate::model::GpuTensor,
10795        x: &CudaSlice<f32>,
10796        m: usize,
10797    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10798        use crate::model::GpuTensor;
10799        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10800        let on = *ON.get_or_init(|| {
10801            std::env::var("MEMRA_SPEC_DUAL_T")
10802                .map(|v| v != "0")
10803                .unwrap_or(true)
10804        });
10805        if !on
10806            || !(2..=4).contains(&m)
10807            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10808            || !self.uses_q8_1_fast(w0)
10809            || !self.uses_q8_1_fast(w1)
10810        {
10811            return Ok(None);
10812        }
10813        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10814        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10815        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10816        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10817        if !self.mmvq_supports(QT_NVFP4) {
10818            return Ok(None);
10819        }
10820        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10821        if w1.in_features() != in_f || w1.out_features() != out_f {
10822            return Ok(None);
10823        }
10824        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10825            (
10826                GpuTensor::Quant {
10827                    bytes: b0,
10828                    qtype: q0,
10829                    row_bytes: rb0,
10830                    scale: s0,
10831                    rp: rp0,
10832                    rp4: None,
10833                    ..
10834                },
10835                GpuTensor::Quant {
10836                    bytes: b1,
10837                    qtype: q1,
10838                    row_bytes: rb1,
10839                    scale: s1,
10840                    rp: rp1,
10841                    rp4: None,
10842                    ..
10843                },
10844            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10845                (b0, b1, *rb0, *s0, *s1, *rp0)
10846            }
10847            _ => return Ok(None),
10848        };
10849        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10850        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10851        if std::env::var("MEMRA_DEBUG").is_ok() {
10852            static ONCE: std::sync::Once = std::sync::Once::new();
10853            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10854        }
10855        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10856        let (y0, y1) =
10857            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10858        let mut y0 = y0;
10859        let mut y1 = y1;
10860        if s0 != 1.0 {
10861            self.scale_inplace(&mut y0, s0, m * out_f)?;
10862        }
10863        if s1 != 1.0 {
10864            self.scale_inplace(&mut y1, s1, m * out_f)?;
10865        }
10866        Ok(Some((y0, y1)))
10867    }
10868
10869    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10870    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10871    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10872    /// twins (both buffers must be the repacked layout).
10873    #[allow(clippy::too_many_arguments)]
10874    pub fn qmatvec_batched_dual_raw(
10875        &self,
10876        b0: &CudaSlice<u8>,
10877        b1: &CudaSlice<u8>,
10878        aq: &CudaSlice<i8>,
10879        ad: &CudaSlice<f32>,
10880        m: usize,
10881        in_f: usize,
10882        out_f: usize,
10883        row_bytes: usize,
10884        rp: bool,
10885    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10886        const ROWS_PER_BLOCK: u32 = 4;
10887        let mcols = Self::batched_mcols(m);
10888        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10889        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10890        let tiny_rp1 = rp
10891            && mcols == 4
10892            && out_f <= 128
10893            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10894        let (name, rows_per_block) = if tiny_rp1 {
10895            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10896        } else {
10897            match (mcols, rp, m) {
10898                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10899                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10900                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10901                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10902                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10903                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10904                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10905                _ => {
10906                    return Err(
10907                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10908                    );
10909                }
10910            }
10911        };
10912        let f = self.func(name);
10913        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10914        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10915        let cfg = LaunchConfig {
10916            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10917            block_dim: (32, ROWS_PER_BLOCK, 1),
10918            shared_mem_bytes: 0,
10919        };
10920        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10921        let __s_b = self.gpu.stream();
10922        let mut b = __s_b.launch_builder(&f);
10923        b.arg(b0)
10924            .arg(b1)
10925            .arg(aq)
10926            .arg(ad)
10927            .arg(&mut y0)
10928            .arg(&mut y1)
10929            .arg(&inf)
10930            .arg(&outf)
10931            .arg(&mi)
10932            .arg(&rb);
10933        unsafe {
10934            b.launch(cfg)?;
10935        }
10936        Ok((y0, y1))
10937    }
10938
10939    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10940    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10941    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10942    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10943    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10944    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10945    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10946    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10947    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10948    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10949    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10950    pub fn matmul_pre_dual_noscale(
10951        &self,
10952        w0: &crate::model::GpuTensor,
10953        w1: &crate::model::GpuTensor,
10954        aq: &CudaSlice<i8>,
10955        ad: &CudaSlice<f32>,
10956        m: usize,
10957    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10958    {
10959        use crate::model::GpuTensor;
10960        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10961            return Ok(None);
10962        }
10963        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10964        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10965        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10966        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10967        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10968        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10969        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10970        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10971        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10972        if !self.mmvq_supports(QT_NVFP4) {
10973            return Ok(None);
10974        }
10975        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10976        if w1.in_features() != in_f || w1.out_features() != out_f {
10977            return Ok(None);
10978        }
10979        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10980        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10981        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10982        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10983        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10984        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10985        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10986        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10987        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
10988        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
10989        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
10990        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
10991        let no_mirror =
10992            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
10993        if self.q8_ffn_fuse2_on()
10994            && no_mirror(w0)
10995            && no_mirror(w1)
10996            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
10997        {
10998            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
10999            return Ok(Some(((y0, 1.0), (y1, 1.0))));
11000        }
11001        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
11002        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
11003        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
11004        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
11005        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
11006        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
11007        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
11008        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
11009        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
11010        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11011            let (y0, y1) =
11012                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
11013            return Ok(Some(((y0, p0.3), (y1, p1.3))));
11014        }
11015        let (b0, q0, rb0, s0, rp0) = match w0 {
11016            GpuTensor::Quant {
11017                bytes,
11018                qtype,
11019                row_bytes,
11020                scale,
11021                rp,
11022                ..
11023            } => (bytes, *qtype, *row_bytes, *scale, *rp),
11024            _ => return Ok(None),
11025        };
11026        let (b1, q1, rb1, s1, rp1) = match w1 {
11027            GpuTensor::Quant {
11028                bytes,
11029                qtype,
11030                row_bytes,
11031                scale,
11032                rp,
11033                ..
11034            } => (bytes, *qtype, *row_bytes, *scale, *rp),
11035            _ => return Ok(None),
11036        };
11037        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
11038            return Ok(None);
11039        }
11040        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11041        const RPW: u32 = 2;
11042        let rows_per_block = ROWS_PER_BLOCK * RPW;
11043        let f = self.func(if rp0 {
11044            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
11045        } else {
11046            "qmatvec_nvfp4_mmvq_dual_mr2"
11047        });
11048        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
11049        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
11050        let cfg = LaunchConfig {
11051            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
11052            block_dim: (32, ROWS_PER_BLOCK, 1),
11053            shared_mem_bytes: 0,
11054        };
11055        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
11056        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
11057        // yscale args stay 1.0 here (they exist for the single-tensor callers).
11058        let one = 1.0f32;
11059        let __s_b = self.gpu.stream();
11060        let mut b = __s_b.launch_builder(&f);
11061        b.arg(b0)
11062            .arg(b1)
11063            .arg(aq)
11064            .arg(ad)
11065            .arg(&mut y0)
11066            .arg(&mut y1)
11067            .arg(&inf)
11068            .arg(&outf)
11069            .arg(&mi)
11070            .arg(&rb)
11071            .arg(&one)
11072            .arg(&one);
11073        unsafe {
11074            b.launch(cfg)?;
11075        }
11076        Ok(Some(((y0, s0), (y1, s1))))
11077    }
11078
11079    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
11080    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
11081    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
11082    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
11083    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
11084    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
11085    /// back to the three singles.
11086    #[allow(clippy::too_many_arguments)]
11087    pub fn matmul_nvfp4_fused3(
11088        &self,
11089        w0: &crate::model::GpuTensor,
11090        w1: &crate::model::GpuTensor,
11091        w2: &crate::model::GpuTensor,
11092        aq: &CudaSlice<i8>,
11093        ad: &CudaSlice<f32>,
11094        m: usize,
11095    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11096    {
11097        use crate::model::GpuTensor;
11098        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11099        // read serves all m rows); the fused segments would re-read the weight per row. The
11100        // fusion win is the B=1 decode tick.
11101        if m != 1
11102            || !self.mmvq_supports(QT_NVFP4)
11103            || !self.uses_q8_1_fast(w0)
11104            || !self.uses_q8_1_fast(w1)
11105            || !self.uses_q8_1_fast(w2)
11106        {
11107            return Ok(None);
11108        }
11109        let unpack = |w: &crate::model::GpuTensor| match w {
11110            GpuTensor::Quant {
11111                bytes,
11112                qtype,
11113                scale,
11114                rp,
11115                ..
11116            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11117            _ => None,
11118        };
11119        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
11120            return Ok(None);
11121        };
11122        let in_f = w0.in_features();
11123        if w1.in_features() != in_f || w2.in_features() != in_f {
11124            return Ok(None);
11125        }
11126        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
11127        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11128        const RPW: u32 = 2;
11129        let rows_pb = ROWS_PER_BLOCK * RPW;
11130        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11131        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
11132        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11133        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11134        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11135        let cfg = LaunchConfig {
11136            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
11137            block_dim: (32, ROWS_PER_BLOCK, 1),
11138            shared_mem_bytes: 0,
11139        };
11140        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
11141        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11142        // only dereferenced for the launch-arg build inside this call.
11143        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
11144        let __s_b = self.gpu.stream();
11145        let mut b = __s_b.launch_builder(&f);
11146        b.arg(b0)
11147            .arg(b1)
11148            .arg(b2)
11149            .arg(aq)
11150            .arg(ad)
11151            .arg(&mut y0)
11152            .arg(&mut y1)
11153            .arg(&mut y2)
11154            .arg(&inf)
11155            .arg(&oi0)
11156            .arg(&oi1)
11157            .arg(&oi2)
11158            .arg(&mi)
11159            .arg(&p0.1)
11160            .arg(&p1.1)
11161            .arg(&p2.1);
11162        unsafe {
11163            b.launch(cfg)?;
11164        }
11165        Ok(Some((y0, y1, y2)))
11166    }
11167
11168    /// fused2 twin of `matmul_nvfp4_fused3`, for MIXED-type weight groups: the gemma4
11169    /// dense NVFP4mix recipe keeps `attn_v` and `ffn_down` at Q8_0 (full-NVFP4 was
11170    /// measured garbage on this dense class), so its q/k/v trio and gate/up/down never
11171    /// satisfy an all-NVFP4 fused3 — the pairs that ARE uniformly NVFP4 (q,k and
11172    /// gate,up) fuse here instead. m==1 only, same law as fused3/fused4: per
11173    /// (tensor,row) the kernel seg body is VERBATIM, so the fusion is bit-identical to
11174    /// two separate launches. `MEMRA_NVFP4_FUSED2=0` is the rollback seam and the
11175    /// same-binary interleaved A/B arm.
11176    pub fn matmul_nvfp4_fused2(
11177        &self,
11178        w0: &crate::model::GpuTensor,
11179        w1: &crate::model::GpuTensor,
11180        aq: &CudaSlice<i8>,
11181        ad: &CudaSlice<f32>,
11182        m: usize,
11183    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11184        use crate::model::GpuTensor;
11185        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11186        let off =
11187            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11188        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11189        // read serves all m rows); the fused segments would re-read the weight per row.
11190        if off
11191            || m != 1
11192            || !self.mmvq_supports(QT_NVFP4)
11193            || !self.uses_q8_1_fast(w0)
11194            || !self.uses_q8_1_fast(w1)
11195        {
11196            return Ok(None);
11197        }
11198        let unpack = |w: &crate::model::GpuTensor| match w {
11199            GpuTensor::Quant {
11200                bytes,
11201                qtype,
11202                scale,
11203                rp,
11204                ..
11205            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11206            _ => None,
11207        };
11208        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11209            return Ok(None);
11210        };
11211        let in_f = w0.in_features();
11212        if w1.in_features() != in_f {
11213            return Ok(None);
11214        }
11215        let (o0, o1) = (w0.out_features(), w1.out_features());
11216        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11217        const RPW: u32 = 2;
11218        let rows_pb = ROWS_PER_BLOCK * RPW;
11219        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11220        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11221        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11222        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11223        let cfg = LaunchConfig {
11224            grid_dim: (nb(o0) + nb(o1), m as u32, 1),
11225            block_dim: (32, ROWS_PER_BLOCK, 1),
11226            shared_mem_bytes: 0,
11227        };
11228        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, m as i32);
11229        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11230        // only dereferenced for the launch-arg build inside this call.
11231        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11232        // PDL wave-B: the kernel carries MEMRA_PDL_ENTRY — launch overlaps the producer's
11233        // drain (quantize/norm emitting aq/ad). Same math, same order, bit-identical.
11234        if Self::pdl_on() && Self::pdl_mmvq_on() && Self::pdl_nvfp4q8_on() {
11235            {
11236                use cudarc::driver::{DevicePtr, DevicePtrMut};
11237                let s = &self.gpu.stream();
11238                let (pw0, _g0) = b0.device_ptr(s);
11239                let (pw1, _g1) = b1.device_ptr(s);
11240                let (paq, _g2) = aq.device_ptr(s);
11241                let (pad, _g3) = ad.device_ptr(s);
11242                let (py0, _g4) = y0.device_ptr_mut(s);
11243                let (py1, _g5) = y1.device_ptr_mut(s);
11244                let (s0, s1) = (p0.1, p1.1);
11245                let mut ps = [
11246                    &pw0 as *const _ as *mut std::ffi::c_void,
11247                    &pw1 as *const _ as *mut _,
11248                    &paq as *const _ as *mut _,
11249                    &pad as *const _ as *mut _,
11250                    &py0 as *const _ as *mut _,
11251                    &py1 as *const _ as *mut _,
11252                    &inf as *const _ as *mut _,
11253                    &oi0 as *const _ as *mut _,
11254                    &oi1 as *const _ as *mut _,
11255                    &mi as *const _ as *mut _,
11256                    &s0 as *const _ as *mut _,
11257                    &s1 as *const _ as *mut _,
11258                ];
11259                unsafe {
11260                    self.launch_pdl(
11261                        "qmatvec_nvfp4_mmvq_fused2_rp",
11262                        cfg.grid_dim,
11263                        cfg.block_dim,
11264                        &mut ps,
11265                    )?;
11266                }
11267            }
11268            return Ok(Some((y0, y1)));
11269        }
11270        let __s_b = self.gpu.stream();
11271        let mut b = __s_b.launch_builder(&f);
11272        b.arg(b0)
11273            .arg(b1)
11274            .arg(aq)
11275            .arg(ad)
11276            .arg(&mut y0)
11277            .arg(&mut y1)
11278            .arg(&inf)
11279            .arg(&oi0)
11280            .arg(&oi1)
11281            .arg(&mi)
11282            .arg(&p0.1)
11283            .arg(&p1.1);
11284        unsafe {
11285            b.launch(cfg)?;
11286        }
11287        Ok(Some((y0, y1)))
11288    }
11289
11290    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch to
11291    /// `matmul_nvfp4_fused2`, caller-owned outputs — the gemma4 dc_slotted graph body
11292    /// needs zero mem nodes, so the allocating wrapper can't serve it. Returns false
11293    /// (decline) on any non-NVFP4/rp pair; the caller chains or refuses.
11294    pub fn matmul_nvfp4_fused2_into(
11295        &self,
11296        w0: &crate::model::GpuTensor,
11297        w1: &crate::model::GpuTensor,
11298        aq: &CudaSlice<i8>,
11299        ad: &CudaSlice<f32>,
11300        y0: &mut CudaSlice<f32>,
11301        y1: &mut CudaSlice<f32>,
11302    ) -> Result<bool, Box<dyn std::error::Error>> {
11303        use crate::model::GpuTensor;
11304        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11305        let off =
11306            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11307        if off
11308            || !self.mmvq_supports(QT_NVFP4)
11309            || !self.uses_q8_1_fast(w0)
11310            || !self.uses_q8_1_fast(w1)
11311        {
11312            return Ok(false);
11313        }
11314        let unpack = |w: &crate::model::GpuTensor| match w {
11315            GpuTensor::Quant {
11316                bytes,
11317                qtype,
11318                scale,
11319                rp,
11320                ..
11321            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11322            _ => None,
11323        };
11324        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11325            return Ok(false);
11326        };
11327        let in_f = w0.in_features();
11328        if w1.in_features() != in_f {
11329            return Ok(false);
11330        }
11331        let (o0, o1) = (w0.out_features(), w1.out_features());
11332        if y0.len() < o0 || y1.len() < o1 {
11333            return Ok(false);
11334        }
11335        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11336        const RPW: u32 = 2;
11337        let rows_pb = ROWS_PER_BLOCK * RPW;
11338        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11339        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11340        let cfg = LaunchConfig {
11341            grid_dim: (nb(o0) + nb(o1), 1, 1),
11342            block_dim: (32, ROWS_PER_BLOCK, 1),
11343            shared_mem_bytes: 0,
11344        };
11345        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, 1i32);
11346        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11347        // only dereferenced for the launch-arg build inside this call.
11348        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11349        let __s_b = self.gpu.stream();
11350        let mut b = __s_b.launch_builder(&f);
11351        b.arg(b0)
11352            .arg(b1)
11353            .arg(aq)
11354            .arg(ad)
11355            .arg(&mut *y0)
11356            .arg(&mut *y1)
11357            .arg(&inf)
11358            .arg(&oi0)
11359            .arg(&oi1)
11360            .arg(&mi)
11361            .arg(&p0.1)
11362            .arg(&p1.1);
11363        unsafe {
11364            b.launch(cfg)?;
11365        }
11366        Ok(true)
11367    }
11368
11369    /// fused4 twin of `matmul_nvfp4_fused3`: the Linear-mixer projection quartet
11370    /// (wqkv + wqkv_gate + ssm_beta + ssm_alpha) in one launch, m==1 only. Per
11371    /// (tensor,row) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM — bit-identical
11372    /// to four separate launches (rig-native decode increment 2, RIG-NATIVE-DECODE.md).
11373    #[allow(clippy::type_complexity)]
11374    pub fn matmul_nvfp4_fused4(
11375        &self,
11376        w0: &crate::model::GpuTensor,
11377        w1: &crate::model::GpuTensor,
11378        w2: &crate::model::GpuTensor,
11379        w3: &crate::model::GpuTensor,
11380        aq: &CudaSlice<i8>,
11381        ad: &CudaSlice<f32>,
11382        m: usize,
11383    ) -> Result<
11384        Option<(
11385            CudaSlice<f32>,
11386            CudaSlice<f32>,
11387            CudaSlice<f32>,
11388            CudaSlice<f32>,
11389        )>,
11390        Box<dyn std::error::Error>,
11391    > {
11392        use crate::model::GpuTensor;
11393        // MEMRA_NVFP4_FUSED4=0: rollback seam + the same-binary interleaved A/B arm.
11394        if m != 1
11395            || std::env::var("MEMRA_NVFP4_FUSED4").as_deref() == Ok("0")
11396            || !self.mmvq_supports(QT_NVFP4)
11397            || !self.uses_q8_1_fast(w0)
11398            || !self.uses_q8_1_fast(w1)
11399            || !self.uses_q8_1_fast(w2)
11400            || !self.uses_q8_1_fast(w3)
11401        {
11402            return Ok(None);
11403        }
11404        let unpack = |w: &crate::model::GpuTensor| match w {
11405            GpuTensor::Quant {
11406                bytes,
11407                qtype,
11408                scale,
11409                rp,
11410                ..
11411            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11412            _ => None,
11413        };
11414        let (Some(p0), Some(p1), Some(p2), Some(p3)) =
11415            (unpack(w0), unpack(w1), unpack(w2), unpack(w3))
11416        else {
11417            return Ok(None);
11418        };
11419        let in_f = w0.in_features();
11420        if w1.in_features() != in_f || w2.in_features() != in_f || w3.in_features() != in_f {
11421            return Ok(None);
11422        }
11423        let (o0, o1, o2, o3) = (
11424            w0.out_features(),
11425            w1.out_features(),
11426            w2.out_features(),
11427            w3.out_features(),
11428        );
11429        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11430        const RPW: u32 = 2;
11431        let rows_pb = ROWS_PER_BLOCK * RPW;
11432        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11433        let f = self.func("qmatvec_nvfp4_mmvq_fused4_rp");
11434        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11435        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11436        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11437        let mut y3 = self.alloc_uninit::<f32>(m * o3)?;
11438        let cfg = LaunchConfig {
11439            grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), m as u32, 1),
11440            block_dim: (32, ROWS_PER_BLOCK, 1),
11441            shared_mem_bytes: 0,
11442        };
11443        let (inf, oi0, oi1, oi2, oi3, mi) = (
11444            in_f as i32,
11445            o0 as i32,
11446            o1 as i32,
11447            o2 as i32,
11448            o3 as i32,
11449            m as i32,
11450        );
11451        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11452        // only dereferenced for the launch-arg build inside this call.
11453        let (b0, b1, b2, b3) = unsafe { (&*p0.0, &*p1.0, &*p2.0, &*p3.0) };
11454        let __s_b = self.gpu.stream();
11455        let mut b = __s_b.launch_builder(&f);
11456        b.arg(b0)
11457            .arg(b1)
11458            .arg(b2)
11459            .arg(b3)
11460            .arg(aq)
11461            .arg(ad)
11462            .arg(&mut y0)
11463            .arg(&mut y1)
11464            .arg(&mut y2)
11465            .arg(&mut y3)
11466            .arg(&inf)
11467            .arg(&oi0)
11468            .arg(&oi1)
11469            .arg(&oi2)
11470            .arg(&oi3)
11471            .arg(&mi)
11472            .arg(&p0.1)
11473            .arg(&p1.1)
11474            .arg(&p2.1)
11475            .arg(&p3.1);
11476        unsafe {
11477            b.launch(cfg)?;
11478        }
11479        Ok(Some((y0, y1, y2, y3)))
11480    }
11481
11482    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
11483    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
11484    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
11485    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
11486    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
11487    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
11488    /// back to the per-tensor path.
11489    pub fn matmul_q8_fused2(
11490        &self,
11491        w0: &crate::model::GpuTensor,
11492        w1: &crate::model::GpuTensor,
11493        aq: &CudaSlice<i8>,
11494        ad: &CudaSlice<f32>,
11495    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11496        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
11497        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
11498        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
11499        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
11500        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
11501        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11502            return Ok(Some(self.e4m3_fused2_core(
11503                p0.0,
11504                p1.0,
11505                aq,
11506                ad,
11507                w0.in_features(),
11508                p0.1,
11509                p1.1,
11510                p0.2,
11511                p0.3,
11512                p1.3,
11513            )?));
11514        }
11515        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11516            return Ok(None);
11517        };
11518        Ok(Some(self.q8_fused2_core(
11519            p0.0,
11520            p1.0,
11521            aq,
11522            ad,
11523            w0.in_features(),
11524            p0.1,
11525            p1.1,
11526            p0.2,
11527        )?))
11528    }
11529
11530    #[allow(clippy::too_many_arguments)]
11531    fn q8_fused2_core(
11532        &self,
11533        b0: &CudaSlice<u8>,
11534        b1: &CudaSlice<u8>,
11535        aq: &CudaSlice<i8>,
11536        ad: &CudaSlice<f32>,
11537        in_f: usize,
11538        out0: usize,
11539        out1: usize,
11540        row_bytes: usize,
11541    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11542        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11543        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11544        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11545        let f = self.func("qmatvec_q8_0_mmvq_fused2");
11546        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11547        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11548        let cfg = LaunchConfig {
11549            grid_dim: (nb0 + nb1, 1, 1),
11550            block_dim: (32, ROWS_PER_BLOCK, 1),
11551            shared_mem_bytes: 0,
11552        };
11553        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
11554        let __s_b = self.gpu.stream();
11555        let mut b = __s_b.launch_builder(&f);
11556        b.arg(b0)
11557            .arg(b1)
11558            .arg(aq)
11559            .arg(ad)
11560            .arg(&mut y0)
11561            .arg(&mut y1)
11562            .arg(&inf)
11563            .arg(&o0)
11564            .arg(&o1)
11565            .arg(&rbl);
11566        unsafe {
11567            b.launch(cfg)?;
11568        }
11569        Ok((y0, y1))
11570    }
11571
11572    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
11573    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
11574    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
11575    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
11576    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
11577    pub fn matmul_q8_fused2_x(
11578        &self,
11579        w0: &crate::model::GpuTensor,
11580        w1: &crate::model::GpuTensor,
11581        x: &CudaSlice<f32>,
11582    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11583        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
11584            return Ok(None);
11585        }
11586        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11587            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11588            return Ok(Some(self.e4m3_fused2_core(
11589                p0.0,
11590                p1.0,
11591                &aq,
11592                &ad,
11593                w0.in_features(),
11594                p0.1,
11595                p1.1,
11596                p0.2,
11597                p0.3,
11598                p1.3,
11599            )?));
11600        }
11601        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11602            return Ok(None);
11603        };
11604        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11605        Ok(Some(self.q8_fused2_core(
11606            p0.0,
11607            p1.0,
11608            &aq,
11609            &ad,
11610            w0.in_features(),
11611            p0.1,
11612            p1.1,
11613            p0.2,
11614        )?))
11615    }
11616
11617    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
11618    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
11619    #[allow(clippy::too_many_arguments)]
11620    pub fn qmatvec_q8_fused2_raw(
11621        &self,
11622        b0: &CudaSlice<u8>,
11623        b1: &CudaSlice<u8>,
11624        x: &CudaSlice<f32>,
11625        in_f: usize,
11626        out0: usize,
11627        out1: usize,
11628        row_bytes: usize,
11629    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11630        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11631        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
11632    }
11633
11634    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
11635    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
11636    /// (tensor,row) to three separate m=1 MMVQ launches.
11637    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
11638    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
11639    pub fn matmul_q4_fused3(
11640        &self,
11641        w0: &crate::model::GpuTensor,
11642        w1: &crate::model::GpuTensor,
11643        w2: &crate::model::GpuTensor,
11644        aq: &CudaSlice<i8>,
11645        ad: &CudaSlice<f32>,
11646    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11647    {
11648        use crate::model::GpuTensor;
11649        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11650            match w {
11651                GpuTensor::Quant {
11652                    qtype, row_bytes, ..
11653                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11654                _ => None,
11655            }
11656        };
11657        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11658            return Ok(None);
11659        };
11660        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11661            return Ok(None);
11662        }
11663        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
11664        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
11665        // the separate matvecs (each routes its own rp).
11666        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11667            match w {
11668                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11669                    Some(m) => (m, true),
11670                    None => (bytes, *rp),
11671                },
11672                _ => unreachable!(),
11673            }
11674        }
11675        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11676        if rp0 != rp1 || rp1 != rp2 {
11677            return Ok(None);
11678        }
11679        let rp = rp0;
11680        let rpb: u32 = 4;
11681        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
11682        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
11683        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
11684        let mr1 = rp && Self::q40_mr1_on();
11685        let nb = |o: usize| {
11686            if mr1 {
11687                (o as u32).div_ceil(rpb)
11688            } else {
11689                (o as u32).div_ceil(2).div_ceil(rpb)
11690            }
11691        };
11692        let grid = nb(o0) + nb(o1) + nb(o2);
11693        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11694        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11695        let mut y2 = self.alloc_uninit::<f32>(o2)?;
11696        let f = self.func(if mr1 {
11697            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11698        } else if rp {
11699            "qmatvec_q4_0_mmvq_fused3_rp"
11700        } else {
11701            "qmatvec_q4_0_mmvq_fused3"
11702        });
11703        let cfg = LaunchConfig {
11704            grid_dim: (grid, 1, 1),
11705            block_dim: (32, rpb, 1),
11706            shared_mem_bytes: 0,
11707        };
11708        let inf = w0.in_features() as i32;
11709        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11710        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11711        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
11712        // variant may take the programmatic-serialization launch.
11713        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11714            {
11715                use cudarc::driver::{DevicePtr, DevicePtrMut};
11716                let s = &self.gpu.stream();
11717                let (p0, _g0) = b0.device_ptr(s);
11718                let (p1, _g1) = b1.device_ptr(s);
11719                let (p2, _g2) = b2.device_ptr(s);
11720                let (paq, _g3) = aq.device_ptr(s);
11721                let (pad, _g4) = ad.device_ptr(s);
11722                let (py0, _g5) = y0.device_ptr_mut(s);
11723                let (py1, _g6) = y1.device_ptr_mut(s);
11724                let (py2, _g7) = y2.device_ptr_mut(s);
11725                let mut ps = [
11726                    &p0 as *const _ as *mut std::ffi::c_void,
11727                    &p1 as *const _ as *mut _,
11728                    &p2 as *const _ as *mut _,
11729                    &paq as *const _ as *mut _,
11730                    &pad as *const _ as *mut _,
11731                    &py0 as *const _ as *mut _,
11732                    &py1 as *const _ as *mut _,
11733                    &py2 as *const _ as *mut _,
11734                    &inf as *const _ as *mut _,
11735                    &oo0 as *const _ as *mut _,
11736                    &oo1 as *const _ as *mut _,
11737                    &oo2 as *const _ as *mut _,
11738                    &r0 as *const _ as *mut _,
11739                    &r1 as *const _ as *mut _,
11740                    &r2 as *const _ as *mut _,
11741                ];
11742                unsafe {
11743                    self.launch_pdl(
11744                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11745                        (grid, 1, 1),
11746                        (32, rpb, 1),
11747                        &mut ps,
11748                    )?;
11749                }
11750            }
11751            return Ok(Some((y0, y1, y2)));
11752        }
11753        let __s_b = self.gpu.stream();
11754        let mut b = __s_b.launch_builder(&f);
11755        b.arg(b0)
11756            .arg(b1)
11757            .arg(b2)
11758            .arg(aq)
11759            .arg(ad)
11760            .arg(&mut y0)
11761            .arg(&mut y1)
11762            .arg(&mut y2)
11763            .arg(&inf)
11764            .arg(&oo0)
11765            .arg(&oo1)
11766            .arg(&oo2)
11767            .arg(&r0)
11768            .arg(&r1)
11769            .arg(&r2);
11770        unsafe {
11771            b.launch(cfg)?;
11772        }
11773        Ok(Some((y0, y1, y2)))
11774    }
11775
11776    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11777    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
11778    #[allow(clippy::too_many_arguments)]
11779    pub fn matmul_q4_fused3_into(
11780        &self,
11781        w0: &crate::model::GpuTensor,
11782        w1: &crate::model::GpuTensor,
11783        w2: &crate::model::GpuTensor,
11784        aq: &CudaSlice<i8>,
11785        ad: &CudaSlice<f32>,
11786        y0: &mut CudaSlice<f32>,
11787        y1: &mut CudaSlice<f32>,
11788        y2: &mut CudaSlice<f32>,
11789    ) -> Result<bool, Box<dyn std::error::Error>> {
11790        use crate::model::GpuTensor;
11791        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11792            match w {
11793                GpuTensor::Quant {
11794                    qtype, row_bytes, ..
11795                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11796                _ => None,
11797            }
11798        };
11799        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11800            return Ok(false);
11801        };
11802        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11803            return Ok(false);
11804        }
11805        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11806            match w {
11807                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11808                    Some(m) => (m, true),
11809                    None => (bytes, *rp),
11810                },
11811                _ => unreachable!(),
11812            }
11813        }
11814        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11815        if rp0 != rp1 || rp1 != rp2 {
11816            return Ok(false);
11817        }
11818        let rp = rp0;
11819        let rpb: u32 = 4;
11820        let mr1 = rp && Self::q40_mr1_on();
11821        let nb = |o: usize| {
11822            if mr1 {
11823                (o as u32).div_ceil(rpb)
11824            } else {
11825                (o as u32).div_ceil(2).div_ceil(rpb)
11826            }
11827        };
11828        let grid = nb(o0) + nb(o1) + nb(o2);
11829        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
11830        let f = self.func(if mr1 {
11831            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11832        } else if rp {
11833            "qmatvec_q4_0_mmvq_fused3_rp"
11834        } else {
11835            "qmatvec_q4_0_mmvq_fused3"
11836        });
11837        let cfg = LaunchConfig {
11838            grid_dim: (grid, 1, 1),
11839            block_dim: (32, rpb, 1),
11840            shared_mem_bytes: 0,
11841        };
11842        let inf = w0.in_features() as i32;
11843        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11844        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11845        // PDL wave-A: identical to the owned twin (capture-lane parity).
11846        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11847            use cudarc::driver::{DevicePtr, DevicePtrMut};
11848            let s = &self.gpu.stream();
11849            let (p0, _g0) = b0.device_ptr(s);
11850            let (p1, _g1) = b1.device_ptr(s);
11851            let (p2, _g2) = b2.device_ptr(s);
11852            let (paq, _g3) = aq.device_ptr(s);
11853            let (pad, _g4) = ad.device_ptr(s);
11854            let (py0, _g5) = y0.device_ptr_mut(s);
11855            let (py1, _g6) = y1.device_ptr_mut(s);
11856            let (py2, _g7) = y2.device_ptr_mut(s);
11857            let mut ps = [
11858                &p0 as *const _ as *mut std::ffi::c_void,
11859                &p1 as *const _ as *mut _,
11860                &p2 as *const _ as *mut _,
11861                &paq as *const _ as *mut _,
11862                &pad as *const _ as *mut _,
11863                &py0 as *const _ as *mut _,
11864                &py1 as *const _ as *mut _,
11865                &py2 as *const _ as *mut _,
11866                &inf as *const _ as *mut _,
11867                &oo0 as *const _ as *mut _,
11868                &oo1 as *const _ as *mut _,
11869                &oo2 as *const _ as *mut _,
11870                &r0 as *const _ as *mut _,
11871                &r1 as *const _ as *mut _,
11872                &r2 as *const _ as *mut _,
11873            ];
11874            unsafe {
11875                self.launch_pdl(
11876                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11877                    (grid, 1, 1),
11878                    (32, rpb, 1),
11879                    &mut ps,
11880                )?;
11881            }
11882            return Ok(true);
11883        }
11884        let __s_b = self.gpu.stream();
11885        let mut b = __s_b.launch_builder(&f);
11886        b.arg(b0)
11887            .arg(b1)
11888            .arg(b2)
11889            .arg(aq)
11890            .arg(ad)
11891            .arg(&mut *y0)
11892            .arg(&mut *y1)
11893            .arg(&mut *y2)
11894            .arg(&inf)
11895            .arg(&oo0)
11896            .arg(&oo1)
11897            .arg(&oo2)
11898            .arg(&r0)
11899            .arg(&r1)
11900            .arg(&r2);
11901        unsafe {
11902            b.launch(cfg)?;
11903        }
11904        Ok(true)
11905    }
11906
11907    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
11908    pub fn matmul_q4_fused2(
11909        &self,
11910        w0: &crate::model::GpuTensor,
11911        w1: &crate::model::GpuTensor,
11912        aq: &CudaSlice<i8>,
11913        ad: &CudaSlice<f32>,
11914    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11915        use crate::model::GpuTensor;
11916        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11917            match w {
11918                GpuTensor::Quant {
11919                    qtype, row_bytes, ..
11920                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11921                _ => None,
11922            }
11923        };
11924        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11925            return Ok(None);
11926        };
11927        if w0.in_features() != w1.in_features() {
11928            return Ok(None);
11929        }
11930        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
11931        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11932            match w {
11933                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11934                    Some(m) => (m, true),
11935                    None => (bytes, *rp),
11936                },
11937                _ => unreachable!(),
11938            }
11939        }
11940        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11941        if rp0 != rp1 {
11942            return Ok(None);
11943        }
11944        let rp = rp0;
11945        let rpb: u32 = 4;
11946        // mr1 twin — see matmul_q4_fused3.
11947        let mr1 = rp && Self::q40_mr1_on();
11948        let nb = |o: usize| {
11949            if mr1 {
11950                (o as u32).div_ceil(rpb)
11951            } else {
11952                (o as u32).div_ceil(2).div_ceil(rpb)
11953            }
11954        };
11955        let grid = nb(o0) + nb(o1);
11956        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11957        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11958        let f = self.func(if mr1 {
11959            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11960        } else if rp {
11961            "qmatvec_q4_0_mmvq_fused2_rp"
11962        } else {
11963            "qmatvec_q4_0_mmvq_fused2"
11964        });
11965        let cfg = LaunchConfig {
11966            grid_dim: (grid, 1, 1),
11967            block_dim: (32, rpb, 1),
11968            shared_mem_bytes: 0,
11969        };
11970        let inf = w0.in_features() as i32;
11971        let (oo0, oo1) = (o0 as i32, o1 as i32);
11972        let (r0, r1) = (rb0 as i64, rb1 as i64);
11973        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
11974        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11975            {
11976                use cudarc::driver::{DevicePtr, DevicePtrMut};
11977                let s = &self.gpu.stream();
11978                let (p0, _g0) = b0.device_ptr(s);
11979                let (p1, _g1) = b1.device_ptr(s);
11980                let (paq, _g2) = aq.device_ptr(s);
11981                let (pad, _g3) = ad.device_ptr(s);
11982                let (py0, _g4) = y0.device_ptr_mut(s);
11983                let (py1, _g5) = y1.device_ptr_mut(s);
11984                let mut ps = [
11985                    &p0 as *const _ as *mut std::ffi::c_void,
11986                    &p1 as *const _ as *mut _,
11987                    &paq as *const _ as *mut _,
11988                    &pad as *const _ as *mut _,
11989                    &py0 as *const _ as *mut _,
11990                    &py1 as *const _ as *mut _,
11991                    &inf as *const _ as *mut _,
11992                    &oo0 as *const _ as *mut _,
11993                    &oo1 as *const _ as *mut _,
11994                    &r0 as *const _ as *mut _,
11995                    &r1 as *const _ as *mut _,
11996                ];
11997                unsafe {
11998                    self.launch_pdl(
11999                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
12000                        (grid, 1, 1),
12001                        (32, rpb, 1),
12002                        &mut ps,
12003                    )?;
12004                }
12005            }
12006            return Ok(Some((y0, y1)));
12007        }
12008        let __s_b = self.gpu.stream();
12009        let mut b = __s_b.launch_builder(&f);
12010        b.arg(b0)
12011            .arg(b1)
12012            .arg(aq)
12013            .arg(ad)
12014            .arg(&mut y0)
12015            .arg(&mut y1)
12016            .arg(&inf)
12017            .arg(&oo0)
12018            .arg(&oo1)
12019            .arg(&r0)
12020            .arg(&r1);
12021        unsafe {
12022            b.launch(cfg)?;
12023        }
12024        Ok(Some((y0, y1)))
12025    }
12026
12027    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
12028    pub fn matmul_q4_fused2_into(
12029        &self,
12030        w0: &crate::model::GpuTensor,
12031        w1: &crate::model::GpuTensor,
12032        aq: &CudaSlice<i8>,
12033        ad: &CudaSlice<f32>,
12034        y0: &mut CudaSlice<f32>,
12035        y1: &mut CudaSlice<f32>,
12036    ) -> Result<bool, Box<dyn std::error::Error>> {
12037        use crate::model::GpuTensor;
12038        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
12039            match w {
12040                GpuTensor::Quant {
12041                    qtype, row_bytes, ..
12042                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
12043                _ => None,
12044            }
12045        };
12046        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
12047            return Ok(false);
12048        };
12049        if w0.in_features() != w1.in_features() {
12050            return Ok(false);
12051        }
12052        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12053            match w {
12054                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12055                    Some(m) => (m, true),
12056                    None => (bytes, *rp),
12057                },
12058                _ => unreachable!(),
12059            }
12060        }
12061        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
12062        if rp0 != rp1 {
12063            return Ok(false);
12064        }
12065        let rp = rp0;
12066        let rpb: u32 = 4;
12067        let mr1 = rp && Self::q40_mr1_on();
12068        let nb = |o: usize| {
12069            if mr1 {
12070                (o as u32).div_ceil(rpb)
12071            } else {
12072                (o as u32).div_ceil(2).div_ceil(rpb)
12073            }
12074        };
12075        let grid = nb(o0) + nb(o1);
12076        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
12077        let f = self.func(if mr1 {
12078            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
12079        } else if rp {
12080            "qmatvec_q4_0_mmvq_fused2_rp"
12081        } else {
12082            "qmatvec_q4_0_mmvq_fused2"
12083        });
12084        let cfg = LaunchConfig {
12085            grid_dim: (grid, 1, 1),
12086            block_dim: (32, rpb, 1),
12087            shared_mem_bytes: 0,
12088        };
12089        let inf = w0.in_features() as i32;
12090        let (oo0, oo1) = (o0 as i32, o1 as i32);
12091        let (r0, r1) = (rb0 as i64, rb1 as i64);
12092        // PDL wave-A: identical to the owned twin (capture-lane parity).
12093        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
12094            use cudarc::driver::{DevicePtr, DevicePtrMut};
12095            let s = &self.gpu.stream();
12096            let (p0, _g0) = b0.device_ptr(s);
12097            let (p1, _g1) = b1.device_ptr(s);
12098            let (paq, _g2) = aq.device_ptr(s);
12099            let (pad, _g3) = ad.device_ptr(s);
12100            let (py0, _g4) = y0.device_ptr_mut(s);
12101            let (py1, _g5) = y1.device_ptr_mut(s);
12102            let mut ps = [
12103                &p0 as *const _ as *mut std::ffi::c_void,
12104                &p1 as *const _ as *mut _,
12105                &paq as *const _ as *mut _,
12106                &pad as *const _ as *mut _,
12107                &py0 as *const _ as *mut _,
12108                &py1 as *const _ as *mut _,
12109                &inf as *const _ as *mut _,
12110                &oo0 as *const _ as *mut _,
12111                &oo1 as *const _ as *mut _,
12112                &r0 as *const _ as *mut _,
12113                &r1 as *const _ as *mut _,
12114            ];
12115            unsafe {
12116                self.launch_pdl(
12117                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
12118                    (grid, 1, 1),
12119                    (32, rpb, 1),
12120                    &mut ps,
12121                )?;
12122            }
12123            return Ok(true);
12124        }
12125        let __s_b = self.gpu.stream();
12126        let mut b = __s_b.launch_builder(&f);
12127        b.arg(b0)
12128            .arg(b1)
12129            .arg(aq)
12130            .arg(ad)
12131            .arg(&mut *y0)
12132            .arg(&mut *y1)
12133            .arg(&inf)
12134            .arg(&oo0)
12135            .arg(&oo1)
12136            .arg(&r0)
12137            .arg(&r1);
12138        unsafe {
12139            b.launch(cfg)?;
12140        }
12141        Ok(true)
12142    }
12143
12144    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
12145    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
12146    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
12147    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
12148    pub fn matmul_q4_fused2_batched(
12149        &self,
12150        w0: &crate::model::GpuTensor,
12151        w1: &crate::model::GpuTensor,
12152        aq: &CudaSlice<i8>,
12153        ad: &CudaSlice<f32>,
12154        m: usize,
12155    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12156        use crate::model::GpuTensor;
12157        if m < 2 || m > 8 {
12158            return Ok(None);
12159        }
12160        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
12161            match w {
12162                GpuTensor::Quant {
12163                    qtype, row_bytes, ..
12164                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
12165                _ => None,
12166            }
12167        };
12168        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
12169            return Ok(None);
12170        };
12171        if w0.in_features() != w1.in_features() {
12172            return Ok(None);
12173        }
12174        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12175            match w {
12176                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12177                    Some(mr) => (mr, true),
12178                    None => (bytes, *rp),
12179                },
12180                _ => unreachable!(),
12181            }
12182        }
12183        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
12184        if !rp0 || !rp1 {
12185            return Ok(None);
12186        }
12187        let mcols = Self::batched_mcols(m);
12188        let rpb: u32 = 4;
12189        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12190        let grid = nb(o0) + nb(o1);
12191        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12192        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12193        let f = self.func(match mcols {
12194            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
12195            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
12196            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
12197        });
12198        let cfg = LaunchConfig {
12199            grid_dim: (grid, 1, 1),
12200            block_dim: (32, rpb, 1),
12201            shared_mem_bytes: 0,
12202        };
12203        let inf = w0.in_features() as i32;
12204        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
12205        let rb = rb0 as i64;
12206        let __s_b = self.gpu.stream();
12207        let mut b = __s_b.launch_builder(&f);
12208        b.arg(b0)
12209            .arg(b1)
12210            .arg(aq)
12211            .arg(ad)
12212            .arg(&mut y0)
12213            .arg(&mut y1)
12214            .arg(&inf)
12215            .arg(&oo0)
12216            .arg(&oo1)
12217            .arg(&mi)
12218            .arg(&rb);
12219        unsafe {
12220            b.launch(cfg)?;
12221        }
12222        Ok(Some((y0, y1)))
12223    }
12224
12225    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
12226    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
12227    #[allow(clippy::too_many_arguments)]
12228    pub fn matmul_q4_fused3_batched(
12229        &self,
12230        w0: &crate::model::GpuTensor,
12231        w1: &crate::model::GpuTensor,
12232        w2: &crate::model::GpuTensor,
12233        aq: &CudaSlice<i8>,
12234        ad: &CudaSlice<f32>,
12235        m: usize,
12236    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12237    {
12238        use crate::model::GpuTensor;
12239        if m < 2 || m > 8 {
12240            return Ok(None);
12241        }
12242        let q4 = |w: &GpuTensor| -> Option<usize> {
12243            match w {
12244                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
12245                _ => None,
12246            }
12247        };
12248        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
12249            return Ok(None);
12250        };
12251        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
12252            return Ok(None);
12253        }
12254        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12255            match w {
12256                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12257                    Some(mr) => (mr, true),
12258                    None => (bytes, *rp),
12259                },
12260                _ => unreachable!(),
12261            }
12262        }
12263        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
12264        if !rp0 || !rp1 || !rp2 {
12265            return Ok(None);
12266        }
12267        let mcols = Self::batched_mcols(m);
12268        let rpb: u32 = 4;
12269        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12270        let grid = nb(o0) + nb(o1) + nb(o2);
12271        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12272        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12273        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
12274        let f = self.func(match mcols {
12275            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
12276            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
12277            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
12278        });
12279        let cfg = LaunchConfig {
12280            grid_dim: (grid, 1, 1),
12281            block_dim: (32, rpb, 1),
12282            shared_mem_bytes: 0,
12283        };
12284        let inf = w0.in_features() as i32;
12285        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
12286        let rb = 0i64;
12287        let __s_b = self.gpu.stream();
12288        let mut b = __s_b.launch_builder(&f);
12289        b.arg(b0)
12290            .arg(b1)
12291            .arg(b2)
12292            .arg(aq)
12293            .arg(ad)
12294            .arg(&mut y0)
12295            .arg(&mut y1)
12296            .arg(&mut y2)
12297            .arg(&inf)
12298            .arg(&oo0)
12299            .arg(&oo1)
12300            .arg(&oo2)
12301            .arg(&mi)
12302            .arg(&rb);
12303        unsafe {
12304            b.launch(cfg)?;
12305        }
12306        Ok(Some((y0, y1, y2)))
12307    }
12308
12309    pub fn matmul_q8_fused3(
12310        &self,
12311        w0: &crate::model::GpuTensor,
12312        w1: &crate::model::GpuTensor,
12313        w2: &crate::model::GpuTensor,
12314        aq: &CudaSlice<i8>,
12315        ad: &CudaSlice<f32>,
12316    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12317    {
12318        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
12319        // are per-tensor FP8, so native residency without this arm meant three separate launches.
12320        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12321            return Ok(Some(self.e4m3_fused3_core(
12322                p0.0,
12323                p1.0,
12324                p2.0,
12325                aq,
12326                ad,
12327                w0.in_features(),
12328                p0.1,
12329                p1.1,
12330                p2.1,
12331                p0.2,
12332                p0.3,
12333                p1.3,
12334                p2.3,
12335            )?));
12336        }
12337        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12338            return Ok(None);
12339        };
12340        Ok(Some(self.q8_fused3_core(
12341            p0.0,
12342            p1.0,
12343            p2.0,
12344            aq,
12345            ad,
12346            w0.in_features(),
12347            p0.1,
12348            p1.1,
12349            p2.1,
12350            p0.2,
12351        )?))
12352    }
12353
12354    #[allow(clippy::too_many_arguments)]
12355    fn q8_fused3_core(
12356        &self,
12357        b0: &CudaSlice<u8>,
12358        b1: &CudaSlice<u8>,
12359        b2: &CudaSlice<u8>,
12360        aq: &CudaSlice<i8>,
12361        ad: &CudaSlice<f32>,
12362        in_f: usize,
12363        out0: usize,
12364        out1: usize,
12365        out2: usize,
12366        row_bytes: usize,
12367    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12368        const ROWS_PER_BLOCK: u32 = 4;
12369        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12370        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12371        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12372        let f = self.func("qmatvec_q8_0_mmvq_fused3");
12373        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12374        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12375        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12376        let cfg = LaunchConfig {
12377            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12378            block_dim: (32, ROWS_PER_BLOCK, 1),
12379            shared_mem_bytes: 0,
12380        };
12381        let (inf, o0, o1, o2, rbl) = (
12382            in_f as i32,
12383            out0 as i32,
12384            out1 as i32,
12385            out2 as i32,
12386            row_bytes as i64,
12387        );
12388        let __s_b = self.gpu.stream();
12389        let mut b = __s_b.launch_builder(&f);
12390        b.arg(b0)
12391            .arg(b1)
12392            .arg(b2)
12393            .arg(aq)
12394            .arg(ad)
12395            .arg(&mut y0)
12396            .arg(&mut y1)
12397            .arg(&mut y2)
12398            .arg(&inf)
12399            .arg(&o0)
12400            .arg(&o1)
12401            .arg(&o2)
12402            .arg(&rbl);
12403        unsafe {
12404            b.launch(cfg)?;
12405        }
12406        Ok((y0, y1, y2))
12407    }
12408
12409    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
12410    #[allow(clippy::too_many_arguments)]
12411    pub fn qmatvec_q8_fused3_raw(
12412        &self,
12413        b0: &CudaSlice<u8>,
12414        b1: &CudaSlice<u8>,
12415        b2: &CudaSlice<u8>,
12416        x: &CudaSlice<f32>,
12417        in_f: usize,
12418        out0: usize,
12419        out1: usize,
12420        out2: usize,
12421        row_bytes: usize,
12422    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12423        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12424        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
12425    }
12426
12427    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
12428    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
12429    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
12430    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
12431    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
12432    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
12433    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
12434    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
12435    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
12436    /// twin must not introduce a batched program the reference path would not run).
12437    pub fn matmul_q8_fused2_t(
12438        &self,
12439        w0: &crate::model::GpuTensor,
12440        w1: &crate::model::GpuTensor,
12441        aq: &CudaSlice<i8>,
12442        ad: &CudaSlice<f32>,
12443        m: usize,
12444    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12445        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
12446        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
12447        // fuses too — same template body, still bit-identical to the two _b8 launches.
12448        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12449            return Ok(None);
12450        }
12451        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
12452        // so the fused b8 launch would introduce a batched program the reference path would not run.
12453        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
12454            if m > 4 && !Self::b8_enabled() {
12455                return Ok(None);
12456            }
12457            return Ok(Some(self.e4m3_fused2_t_core(
12458                p0.0,
12459                p1.0,
12460                aq,
12461                ad,
12462                m,
12463                w0.in_features(),
12464                p0.1,
12465                p1.1,
12466                p0.2,
12467                p0.3,
12468                p1.3,
12469            )?));
12470        }
12471        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
12472            return Ok(None);
12473        };
12474        Ok(Some(self.q8_fused2_t_core(
12475            p0.0,
12476            p1.0,
12477            aq,
12478            ad,
12479            m,
12480            w0.in_features(),
12481            p0.1,
12482            p1.1,
12483            p0.2,
12484        )?))
12485    }
12486
12487    #[allow(clippy::too_many_arguments)]
12488    fn q8_fused2_t_core(
12489        &self,
12490        b0: &CudaSlice<u8>,
12491        b1: &CudaSlice<u8>,
12492        aq: &CudaSlice<i8>,
12493        ad: &CudaSlice<f32>,
12494        m: usize,
12495        in_f: usize,
12496        out0: usize,
12497        out1: usize,
12498        row_bytes: usize,
12499    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12500        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12501        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12502        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12503        let f = self.func(match Self::batched_mcols(m) {
12504            2 => "qmatvec_q8_0_mmvq_fused2_b2",
12505            4 => "qmatvec_q8_0_mmvq_fused2_b4",
12506            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
12507            _ => "qmatvec_q8_0_mmvq_fused2_b8",
12508        });
12509        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12510        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12511        let cfg = LaunchConfig {
12512            grid_dim: (nb0 + nb1, 1, 1),
12513            block_dim: (32, ROWS_PER_BLOCK, 1),
12514            shared_mem_bytes: 0,
12515        };
12516        let (inf, o0, o1, mi, rbl) = (
12517            in_f as i32,
12518            out0 as i32,
12519            out1 as i32,
12520            m as i32,
12521            row_bytes as i64,
12522        );
12523        let __s_b = self.gpu.stream();
12524        let mut b = __s_b.launch_builder(&f);
12525        b.arg(b0)
12526            .arg(b1)
12527            .arg(aq)
12528            .arg(ad)
12529            .arg(&mut y0)
12530            .arg(&mut y1)
12531            .arg(&inf)
12532            .arg(&o0)
12533            .arg(&o1)
12534            .arg(&mi)
12535            .arg(&rbl);
12536        unsafe {
12537            b.launch(cfg)?;
12538        }
12539        Ok((y0, y1))
12540    }
12541
12542    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
12543    /// q8_1 quant of the [m, in_f] activation), no env gating.
12544    #[allow(clippy::too_many_arguments)]
12545    pub fn qmatvec_q8_fused2_t_raw(
12546        &self,
12547        b0: &CudaSlice<u8>,
12548        b1: &CudaSlice<u8>,
12549        x: &CudaSlice<f32>,
12550        m: usize,
12551        in_f: usize,
12552        out0: usize,
12553        out1: usize,
12554        row_bytes: usize,
12555    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12556        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12557        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
12558    }
12559
12560    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
12561    /// `matmul_q8_fused2_t` with three ranges.
12562    #[allow(clippy::too_many_arguments)]
12563    pub fn matmul_q8_fused3_t(
12564        &self,
12565        w0: &crate::model::GpuTensor,
12566        w1: &crate::model::GpuTensor,
12567        w2: &crate::model::GpuTensor,
12568        aq: &CudaSlice<i8>,
12569        ad: &CudaSlice<f32>,
12570        m: usize,
12571    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12572    {
12573        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12574            return Ok(None);
12575        }
12576        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12577            return Ok(Some(self.e4m3_fused3_t_core(
12578                p0.0,
12579                p1.0,
12580                p2.0,
12581                aq,
12582                ad,
12583                m,
12584                w0.in_features(),
12585                p0.1,
12586                p1.1,
12587                p2.1,
12588                p0.2,
12589                p0.3,
12590                p1.3,
12591                p2.3,
12592            )?));
12593        }
12594        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12595            return Ok(None);
12596        };
12597        Ok(Some(self.q8_fused3_t_core(
12598            p0.0,
12599            p1.0,
12600            p2.0,
12601            aq,
12602            ad,
12603            m,
12604            w0.in_features(),
12605            p0.1,
12606            p1.1,
12607            p2.1,
12608            p0.2,
12609        )?))
12610    }
12611
12612    #[allow(clippy::too_many_arguments)]
12613    fn q8_fused3_t_core(
12614        &self,
12615        b0: &CudaSlice<u8>,
12616        b1: &CudaSlice<u8>,
12617        b2: &CudaSlice<u8>,
12618        aq: &CudaSlice<i8>,
12619        ad: &CudaSlice<f32>,
12620        m: usize,
12621        in_f: usize,
12622        out0: usize,
12623        out1: usize,
12624        out2: usize,
12625        row_bytes: usize,
12626    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12627        const ROWS_PER_BLOCK: u32 = 4;
12628        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12629        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12630        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12631        let f = self.func(if Self::batched_mcols(m) == 2 {
12632            "qmatvec_q8_0_mmvq_fused3_b2"
12633        } else {
12634            "qmatvec_q8_0_mmvq_fused3_b4"
12635        });
12636        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12637        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12638        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12639        let cfg = LaunchConfig {
12640            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12641            block_dim: (32, ROWS_PER_BLOCK, 1),
12642            shared_mem_bytes: 0,
12643        };
12644        let (inf, o0, o1, o2, mi, rbl) = (
12645            in_f as i32,
12646            out0 as i32,
12647            out1 as i32,
12648            out2 as i32,
12649            m as i32,
12650            row_bytes as i64,
12651        );
12652        let __s_b = self.gpu.stream();
12653        let mut b = __s_b.launch_builder(&f);
12654        b.arg(b0)
12655            .arg(b1)
12656            .arg(b2)
12657            .arg(aq)
12658            .arg(ad)
12659            .arg(&mut y0)
12660            .arg(&mut y1)
12661            .arg(&mut y2)
12662            .arg(&inf)
12663            .arg(&o0)
12664            .arg(&o1)
12665            .arg(&o2)
12666            .arg(&mi)
12667            .arg(&rbl);
12668        unsafe {
12669            b.launch(cfg)?;
12670        }
12671        Ok((y0, y1, y2))
12672    }
12673
12674    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
12675    #[allow(clippy::too_many_arguments)]
12676    pub fn qmatvec_q8_fused3_t_raw(
12677        &self,
12678        b0: &CudaSlice<u8>,
12679        b1: &CudaSlice<u8>,
12680        b2: &CudaSlice<u8>,
12681        x: &CudaSlice<f32>,
12682        m: usize,
12683        in_f: usize,
12684        out0: usize,
12685        out1: usize,
12686        out2: usize,
12687        row_bytes: usize,
12688    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12689        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12690        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
12691    }
12692
12693    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
12694    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
12695    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
12696    pub fn q8_ffn_fuse2_on(&self) -> bool {
12697        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12698        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
12699    }
12700
12701    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
12702    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
12703    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
12704    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
12705    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
12706    #[allow(clippy::type_complexity)]
12707    fn q8_fused_params<'w, const N: usize>(
12708        &self,
12709        ws: &[&'w crate::model::GpuTensor; N],
12710    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
12711        use crate::model::GpuTensor;
12712        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12713            return None;
12714        }
12715        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
12716            return None;
12717        }
12718        let in_f = ws[0].in_features();
12719        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
12720        for (i, w) in ws.iter().enumerate() {
12721            match w {
12722                GpuTensor::Quant {
12723                    bytes,
12724                    qtype,
12725                    row_bytes,
12726                    scale,
12727                    ..
12728                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
12729                    out[i] = Some((bytes, w.out_features(), *row_bytes))
12730                }
12731                _ => return None,
12732            }
12733        }
12734        Some(out.map(|o| o.unwrap()))
12735    }
12736
12737    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
12738    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
12739    pub fn e4m3_dual_on(&self) -> bool {
12740        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12741        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
12742    }
12743
12744    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
12745    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
12746    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
12747    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
12748    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
12749    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
12750    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
12751    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
12752    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
12753    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
12754    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
12755    #[allow(clippy::type_complexity)]
12756    fn e4m3_fused_params<'w, const N: usize>(
12757        &self,
12758        ws: &[&'w crate::model::GpuTensor; N],
12759    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
12760        use crate::model::GpuTensor;
12761        if !self.e4m3_dual_on() {
12762            return None;
12763        }
12764        let in_f = ws[0].in_features();
12765        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
12766        for (i, w) in ws.iter().enumerate() {
12767            match w {
12768                GpuTensor::Quant {
12769                    bytes,
12770                    qtype,
12771                    row_bytes,
12772                    scale,
12773                    rp,
12774                    rp4,
12775                    ..
12776                } if *qtype == QT_F8_E4M3
12777                    && w.in_features() == in_f
12778                    && *row_bytes == in_f
12779                    && !*rp
12780                    && rp4.is_none() =>
12781                {
12782                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
12783                }
12784                _ => return None,
12785            }
12786        }
12787        Some(out.map(|o| o.unwrap()))
12788    }
12789
12790    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
12791    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
12792    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
12793    #[allow(clippy::too_many_arguments)]
12794    fn e4m3_fused2_core(
12795        &self,
12796        b0: &CudaSlice<u8>,
12797        b1: &CudaSlice<u8>,
12798        aq: &CudaSlice<i8>,
12799        ad: &CudaSlice<f32>,
12800        in_f: usize,
12801        out0: usize,
12802        out1: usize,
12803        row_bytes: usize,
12804        ws0: f32,
12805        ws1: f32,
12806    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12807        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12808        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12809        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12810        let f = self.func("qmatvec_e4m3_mmvq_fused2");
12811        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12812        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12813        let cfg = LaunchConfig {
12814            grid_dim: (nb0 + nb1, 1, 1),
12815            block_dim: (32, ROWS_PER_BLOCK, 1),
12816            shared_mem_bytes: 0,
12817        };
12818        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
12819        let __s_b = self.gpu.stream();
12820        let mut b = __s_b.launch_builder(&f);
12821        b.arg(b0)
12822            .arg(b1)
12823            .arg(aq)
12824            .arg(ad)
12825            .arg(&mut y0)
12826            .arg(&mut y1)
12827            .arg(&inf)
12828            .arg(&o0)
12829            .arg(&o1)
12830            .arg(&rbl)
12831            .arg(&ws0)
12832            .arg(&ws1);
12833        unsafe {
12834            b.launch(cfg)?;
12835        }
12836        Ok((y0, y1))
12837    }
12838
12839    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
12840    #[allow(clippy::too_many_arguments)]
12841    fn e4m3_fused3_core(
12842        &self,
12843        b0: &CudaSlice<u8>,
12844        b1: &CudaSlice<u8>,
12845        b2: &CudaSlice<u8>,
12846        aq: &CudaSlice<i8>,
12847        ad: &CudaSlice<f32>,
12848        in_f: usize,
12849        out0: usize,
12850        out1: usize,
12851        out2: usize,
12852        row_bytes: usize,
12853        ws0: f32,
12854        ws1: f32,
12855        ws2: f32,
12856    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12857        const ROWS_PER_BLOCK: u32 = 4;
12858        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12859        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12860        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12861        let f = self.func("qmatvec_e4m3_mmvq_fused3");
12862        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12863        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12864        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12865        let cfg = LaunchConfig {
12866            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12867            block_dim: (32, ROWS_PER_BLOCK, 1),
12868            shared_mem_bytes: 0,
12869        };
12870        let (inf, o0, o1, o2, rbl) = (
12871            in_f as i32,
12872            out0 as i32,
12873            out1 as i32,
12874            out2 as i32,
12875            row_bytes as i64,
12876        );
12877        let __s_b = self.gpu.stream();
12878        let mut b = __s_b.launch_builder(&f);
12879        b.arg(b0)
12880            .arg(b1)
12881            .arg(b2)
12882            .arg(aq)
12883            .arg(ad)
12884            .arg(&mut y0)
12885            .arg(&mut y1)
12886            .arg(&mut y2)
12887            .arg(&inf)
12888            .arg(&o0)
12889            .arg(&o1)
12890            .arg(&o2)
12891            .arg(&rbl)
12892            .arg(&ws0)
12893            .arg(&ws1)
12894            .arg(&ws2);
12895        unsafe {
12896            b.launch(cfg)?;
12897        }
12898        Ok((y0, y1, y2))
12899    }
12900
12901    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
12902    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
12903    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
12904    #[allow(clippy::too_many_arguments)]
12905    fn e4m3_fused2_t_core(
12906        &self,
12907        b0: &CudaSlice<u8>,
12908        b1: &CudaSlice<u8>,
12909        aq: &CudaSlice<i8>,
12910        ad: &CudaSlice<f32>,
12911        m: usize,
12912        in_f: usize,
12913        out0: usize,
12914        out1: usize,
12915        row_bytes: usize,
12916        ws0: f32,
12917        ws1: f32,
12918    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12919        const ROWS_PER_BLOCK: u32 = 4;
12920        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12921        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12922        let f = self.func(match Self::batched_mcols(m) {
12923            2 => "qmatvec_e4m3_mmvq_fused2_b2",
12924            4 => "qmatvec_e4m3_mmvq_fused2_b4",
12925            _ => "qmatvec_e4m3_mmvq_fused2_b8",
12926        });
12927        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12928        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12929        let cfg = LaunchConfig {
12930            grid_dim: (nb0 + nb1, 1, 1),
12931            block_dim: (32, ROWS_PER_BLOCK, 1),
12932            shared_mem_bytes: 0,
12933        };
12934        let (inf, o0, o1, mi, rbl) = (
12935            in_f as i32,
12936            out0 as i32,
12937            out1 as i32,
12938            m as i32,
12939            row_bytes as i64,
12940        );
12941        let __s_b = self.gpu.stream();
12942        let mut b = __s_b.launch_builder(&f);
12943        b.arg(b0)
12944            .arg(b1)
12945            .arg(aq)
12946            .arg(ad)
12947            .arg(&mut y0)
12948            .arg(&mut y1)
12949            .arg(&inf)
12950            .arg(&o0)
12951            .arg(&o1)
12952            .arg(&mi)
12953            .arg(&rbl);
12954        unsafe {
12955            b.launch(cfg)?;
12956        }
12957        if ws0 != 1.0 {
12958            self.scale_inplace(&mut y0, ws0, m * out0)?;
12959        }
12960        if ws1 != 1.0 {
12961            self.scale_inplace(&mut y1, ws1, m * out1)?;
12962        }
12963        Ok((y0, y1))
12964    }
12965
12966    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
12967    #[allow(clippy::too_many_arguments)]
12968    fn e4m3_fused3_t_core(
12969        &self,
12970        b0: &CudaSlice<u8>,
12971        b1: &CudaSlice<u8>,
12972        b2: &CudaSlice<u8>,
12973        aq: &CudaSlice<i8>,
12974        ad: &CudaSlice<f32>,
12975        m: usize,
12976        in_f: usize,
12977        out0: usize,
12978        out1: usize,
12979        out2: usize,
12980        row_bytes: usize,
12981        ws0: f32,
12982        ws1: f32,
12983        ws2: f32,
12984    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12985        const ROWS_PER_BLOCK: u32 = 4;
12986        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12987        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12988        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12989        let f = self.func(if Self::batched_mcols(m) == 2 {
12990            "qmatvec_e4m3_mmvq_fused3_b2"
12991        } else {
12992            "qmatvec_e4m3_mmvq_fused3_b4"
12993        });
12994        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12995        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12996        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12997        let cfg = LaunchConfig {
12998            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12999            block_dim: (32, ROWS_PER_BLOCK, 1),
13000            shared_mem_bytes: 0,
13001        };
13002        let (inf, o0, o1, o2, mi, rbl) = (
13003            in_f as i32,
13004            out0 as i32,
13005            out1 as i32,
13006            out2 as i32,
13007            m as i32,
13008            row_bytes as i64,
13009        );
13010        let __s_b = self.gpu.stream();
13011        let mut b = __s_b.launch_builder(&f);
13012        b.arg(b0)
13013            .arg(b1)
13014            .arg(b2)
13015            .arg(aq)
13016            .arg(ad)
13017            .arg(&mut y0)
13018            .arg(&mut y1)
13019            .arg(&mut y2)
13020            .arg(&inf)
13021            .arg(&o0)
13022            .arg(&o1)
13023            .arg(&o2)
13024            .arg(&mi)
13025            .arg(&rbl);
13026        unsafe {
13027            b.launch(cfg)?;
13028        }
13029        if ws0 != 1.0 {
13030            self.scale_inplace(&mut y0, ws0, m * out0)?;
13031        }
13032        if ws1 != 1.0 {
13033            self.scale_inplace(&mut y1, ws1, m * out1)?;
13034        }
13035        if ws2 != 1.0 {
13036            self.scale_inplace(&mut y2, ws2, m * out2)?;
13037        }
13038        Ok((y0, y1, y2))
13039    }
13040
13041    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
13042    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
13043    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
13044    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
13045    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
13046    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
13047    ///
13048    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
13049    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
13050    pub fn qmatvec_e4m3_blk_mmvq(
13051        &self,
13052        bytes: &CudaSlice<u8>,
13053        aq: &CudaSlice<i8>,
13054        ad: &CudaSlice<f32>,
13055        scales: &CudaSlice<f32>,
13056        m: usize,
13057        in_f: usize,
13058        out_f: usize,
13059        row_bytes: usize,
13060        scale_cols: usize,
13061    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13062        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
13063        self.qmatvec_e4m3_blk_mmvq_into(
13064            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
13065        )?;
13066        Ok(y)
13067    }
13068
13069    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
13070    #[allow(clippy::too_many_arguments)]
13071    pub fn qmatvec_e4m3_blk_mmvq_into(
13072        &self,
13073        bytes: &CudaSlice<u8>,
13074        aq: &CudaSlice<i8>,
13075        ad: &CudaSlice<f32>,
13076        scales: &CudaSlice<f32>,
13077        m: usize,
13078        in_f: usize,
13079        out_f: usize,
13080        row_bytes: usize,
13081        scale_cols: usize,
13082        y: &mut CudaSlice<f32>,
13083    ) -> Result<(), Box<dyn std::error::Error>> {
13084        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13085        let f = self.func("qmatvec_e4m3_blk_mmvq");
13086        let cfg = LaunchConfig {
13087            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
13088            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
13089            shared_mem_bytes: 0,                // warp-only reduce
13090        };
13091        let (inf, outf, mi, rb, sc) = (
13092            in_f as i32,
13093            out_f as i32,
13094            m as i32,
13095            row_bytes as i64,
13096            scale_cols as i32,
13097        );
13098        let __s_b = self.gpu.stream();
13099        let mut b = __s_b.launch_builder(&f);
13100        b.arg(bytes)
13101            .arg(aq)
13102            .arg(ad)
13103            .arg(scales)
13104            .arg(&mut *y)
13105            .arg(&inf)
13106            .arg(&outf)
13107            .arg(&mi)
13108            .arg(&rb)
13109            .arg(&sc);
13110        unsafe {
13111            b.launch(cfg)?;
13112        }
13113        Ok(())
13114    }
13115
13116    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
13117    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
13118    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
13119    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
13120    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
13121    #[allow(clippy::too_many_arguments)]
13122    pub fn qmatvec_e4m3_blk_mmvq_batched(
13123        &self,
13124        bytes: &CudaSlice<u8>,
13125        aq: &CudaSlice<i8>,
13126        ad: &CudaSlice<f32>,
13127        scales: &CudaSlice<f32>,
13128        m: usize,
13129        in_f: usize,
13130        out_f: usize,
13131        row_bytes: usize,
13132        scale_cols: usize,
13133        mcols: usize,
13134    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13135        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13136        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
13137        let name = match mcols {
13138            2 => "qmatvec_e4m3_blk_mmvq_b2",
13139            4 => "qmatvec_e4m3_blk_mmvq_b4",
13140            8 => "qmatvec_e4m3_blk_mmvq_b8",
13141            16 => "qmatvec_e4m3_blk_mmvq_b16",
13142            _ => {
13143                return Err(
13144                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
13145                );
13146            }
13147        };
13148        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13149        let f = self.func(name);
13150        let cfg = LaunchConfig {
13151            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
13152            block_dim: (32, ROWS_PER_BLOCK, 1),
13153            shared_mem_bytes: 0,
13154        };
13155        let (inf, outf, mi, rb, sc) = (
13156            in_f as i32,
13157            out_f as i32,
13158            m as i32,
13159            row_bytes as i64,
13160            scale_cols as i32,
13161        );
13162        let __s_b = self.gpu.stream();
13163        let mut b = __s_b.launch_builder(&f);
13164        b.arg(bytes)
13165            .arg(aq)
13166            .arg(ad)
13167            .arg(scales)
13168            .arg(&mut y)
13169            .arg(&inf)
13170            .arg(&outf)
13171            .arg(&mi)
13172            .arg(&rb)
13173            .arg(&sc);
13174        unsafe {
13175            b.launch(cfg)?;
13176        }
13177        Ok(y)
13178    }
13179
13180    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
13181    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
13182    #[allow(clippy::too_many_arguments)]
13183    pub fn qmatvec_e4m3_blk_batched_raw(
13184        &self,
13185        bytes: &CudaSlice<u8>,
13186        x: &CudaSlice<f32>,
13187        scales: &CudaSlice<f32>,
13188        m: usize,
13189        in_f: usize,
13190        out_f: usize,
13191        row_bytes: usize,
13192        scale_cols: usize,
13193        mcols: usize,
13194    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13195        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13196        self.qmatvec_e4m3_blk_mmvq_batched(
13197            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
13198        )
13199    }
13200
13201    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
13202    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
13203    #[allow(clippy::too_many_arguments)]
13204    pub fn qmatvec_e4m3_blk_mmvq_raw(
13205        &self,
13206        bytes: &CudaSlice<u8>,
13207        x: &CudaSlice<f32>,
13208        scales: &CudaSlice<f32>,
13209        m: usize,
13210        in_f: usize,
13211        out_f: usize,
13212        row_bytes: usize,
13213        scale_cols: usize,
13214    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13215        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13216        self.qmatvec_e4m3_blk_mmvq(
13217            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
13218        )
13219    }
13220
13221    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
13222    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
13223    #[allow(clippy::too_many_arguments)]
13224    pub fn qmatvec_e4m3_fused2_raw(
13225        &self,
13226        b0: &CudaSlice<u8>,
13227        b1: &CudaSlice<u8>,
13228        x: &CudaSlice<f32>,
13229        in_f: usize,
13230        out0: usize,
13231        out1: usize,
13232        row_bytes: usize,
13233        ws0: f32,
13234        ws1: f32,
13235    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13236        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13237        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
13238    }
13239
13240    #[allow(clippy::too_many_arguments)]
13241    pub fn qmatvec_e4m3_fused3_raw(
13242        &self,
13243        b0: &CudaSlice<u8>,
13244        b1: &CudaSlice<u8>,
13245        b2: &CudaSlice<u8>,
13246        x: &CudaSlice<f32>,
13247        in_f: usize,
13248        out0: usize,
13249        out1: usize,
13250        out2: usize,
13251        row_bytes: usize,
13252        ws0: f32,
13253        ws1: f32,
13254        ws2: f32,
13255    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13256        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13257        self.e4m3_fused3_core(
13258            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13259        )
13260    }
13261
13262    #[allow(clippy::too_many_arguments)]
13263    pub fn qmatvec_e4m3_fused2_t_raw(
13264        &self,
13265        b0: &CudaSlice<u8>,
13266        b1: &CudaSlice<u8>,
13267        x: &CudaSlice<f32>,
13268        m: usize,
13269        in_f: usize,
13270        out0: usize,
13271        out1: usize,
13272        row_bytes: usize,
13273        ws0: f32,
13274        ws1: f32,
13275    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13276        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13277        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
13278    }
13279
13280    #[allow(clippy::too_many_arguments)]
13281    pub fn qmatvec_e4m3_fused3_t_raw(
13282        &self,
13283        b0: &CudaSlice<u8>,
13284        b1: &CudaSlice<u8>,
13285        b2: &CudaSlice<u8>,
13286        x: &CudaSlice<f32>,
13287        m: usize,
13288        in_f: usize,
13289        out0: usize,
13290        out1: usize,
13291        out2: usize,
13292        row_bytes: usize,
13293        ws0: f32,
13294        ws1: f32,
13295        ws2: f32,
13296    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13297        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13298        self.e4m3_fused3_t_core(
13299            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13300        )
13301    }
13302
13303    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
13304    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
13305    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
13306    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
13307    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
13308    ///
13309    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
13310    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
13311    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
13312    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
13313    fn try_e4m3_blk_pre(
13314        &self,
13315        w: &crate::model::GpuTensor,
13316        aq: &CudaSlice<i8>,
13317        ad: &CudaSlice<f32>,
13318        m: usize,
13319    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13320        use crate::model::GpuTensor;
13321        if let GpuTensor::Quant {
13322            bytes,
13323            qtype,
13324            row_bytes,
13325            blk: Some(g),
13326            ..
13327        } = w
13328        {
13329            if *qtype == QT_F8_E4M3_BLK {
13330                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
13331                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
13332                // below, so the decode-exactness contract is preserved at every width. Gated by
13333                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
13334                // one rollback door covers every dtype's batched tier.
13335                if (2..=16).contains(&m)
13336                    && std::env::var("MEMRA_NO_BATCHED").is_err()
13337                    && (m <= 4 || Self::b8_enabled())
13338                {
13339                    let mcols = Self::batched_mcols(m);
13340                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
13341                        bytes,
13342                        aq,
13343                        ad,
13344                        &g.scales,
13345                        m,
13346                        w.in_features(),
13347                        w.out_features(),
13348                        *row_bytes,
13349                        g.cols,
13350                        mcols,
13351                    )?));
13352                }
13353                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
13354                    bytes,
13355                    aq,
13356                    ad,
13357                    &g.scales,
13358                    m,
13359                    w.in_features(),
13360                    w.out_features(),
13361                    *row_bytes,
13362                    g.cols,
13363                )?));
13364            }
13365        }
13366        Ok(None)
13367    }
13368
13369    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
13370    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
13371    ///
13372    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
13373    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
13374    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
13375    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
13376    /// prefill keeps the floor's arithmetic and the floor's kernels.
13377    ///
13378    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
13379    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
13380    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
13381    /// (projection, prefill call) and frees immediately.
13382    ///
13383    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
13384    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
13385    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
13386    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
13387    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
13388    /// single-variable comparison instead of a two-variable one.
13389    ///
13390    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
13391    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
13392    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
13393    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
13394    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
13395    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
13396    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
13397    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
13398    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
13399    ///
13400    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
13401    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
13402    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
13403    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
13404    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
13405    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
13406    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
13407    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
13408    /// because v2's denominator had its slab already resident while this class's floor must build it
13409    /// every call; same tile, opposite sign, because the question changed.
13410    ///
13411    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
13412    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
13413    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
13414    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
13415    fn try_e4m3_blk_prefill(
13416        &self,
13417        w: &crate::model::GpuTensor,
13418        x: &CudaSlice<f32>,
13419        m: usize,
13420    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13421        use crate::model::GpuTensor;
13422        let GpuTensor::Quant {
13423            bytes,
13424            qtype,
13425            blk: Some(g),
13426            ..
13427        } = w
13428        else {
13429            return Ok(None);
13430        };
13431        if *qtype != QT_F8_E4M3_BLK {
13432            return Ok(None);
13433        }
13434        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
13435        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
13436        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
13437        // through to the dequant below when they do, never silently produce nothing.
13438        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
13439            return Ok(Some(y));
13440        }
13441        let (in_f, out_f) = (w.in_features(), w.out_features());
13442        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
13443        let tmp = GpuTensor::Quant {
13444            bytes: slab,
13445            qtype: QT_Q8_0,
13446            row_bytes: in_f / 32 * 34,
13447            ne: vec![in_f as u64, out_f as u64],
13448            scale: 1.0,
13449            rp: false,
13450            #[cfg(memra_cutlass)]
13451            cutlass: None,
13452            fp8: None,
13453            blk: None,
13454            f16: None,
13455            rp4: None,
13456        };
13457        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
13458        Ok(Some(self.matmul(&tmp, x, m)?))
13459    }
13460
13461    pub fn matmul_pre_noscale(
13462        &self,
13463        w: &crate::model::GpuTensor,
13464        aq: &CudaSlice<i8>,
13465        ad: &CudaSlice<f32>,
13466        m: usize,
13467    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
13468        use crate::model::GpuTensor;
13469        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
13470        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
13471        // rather than let the tail below refuse and cost the caller a re-dispatch.
13472        if m == 1 {
13473            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
13474                return Ok(Some((y, 1.0)));
13475            }
13476        }
13477        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
13478        if m != 1 || !self.uses_q8_1_fast(w) {
13479            return Ok(None);
13480        }
13481        let in_f = w.in_features();
13482        let out_f = w.out_features();
13483        let (bytes, qtype, row_bytes, scale, rp) = match w {
13484            GpuTensor::Quant {
13485                bytes,
13486                qtype,
13487                row_bytes,
13488                scale,
13489                rp,
13490                ..
13491            } => (bytes, *qtype, *row_bytes, *scale, *rp),
13492            _ => return Ok(None),
13493        };
13494        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
13495        if self.mmvq_supports(qtype) {
13496            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
13497            let (mbytes, mrp) = match w {
13498                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
13499                _ => (bytes, rp),
13500            };
13501            let y = self.qmatvec_mmvq(
13502                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
13503            )?;
13504            return Ok(Some((y, scale)));
13505        }
13506        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
13507        let name = match qtype {
13508            QT_Q8_0 => "qmatvec_q8_0_dp4a",
13509            QT_Q4_K => "qmatvec_q4_K_dp4a",
13510            QT_Q6_K => "qmatvec_q6_K_dp4a",
13511            QT_Q5_K => "qmatvec_q5_K_dp4a",
13512            QT_Q3_K => "qmatvec_q3_K_dp4a",
13513            QT_NVFP4 => {
13514                if rp {
13515                    "qmatvec_nvfp4_dp4a_rp"
13516                } else {
13517                    "qmatvec_nvfp4_dp4a"
13518                }
13519            }
13520            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
13521            _ => return Ok(None),
13522        };
13523        let f = self.func(name);
13524        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13525        let cfg = LaunchConfig {
13526            grid_dim: (out_f as u32, m as u32, 1),
13527            block_dim: (128, 1, 1),
13528            shared_mem_bytes: 0,
13529        };
13530        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13531        let __s_b = self.gpu.stream();
13532        let mut b = __s_b.launch_builder(&f);
13533        b.arg(bytes)
13534            .arg(aq)
13535            .arg(ad)
13536            .arg(&mut y)
13537            .arg(&inf)
13538            .arg(&outf)
13539            .arg(&mi)
13540            .arg(&rb);
13541        unsafe {
13542            b.launch(cfg)?;
13543        }
13544        Ok(Some((y, scale)))
13545    }
13546
13547    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
13548    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
13549    pub fn mmvq_supports(&self, qtype: i32) -> bool {
13550        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
13551        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
13552        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
13553        // is a pure function of the dtype — the decode-parity law holds under every env.
13554        if qtype == QT_F8_E4M3 {
13555            return true;
13556        }
13557        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
13558            return false;
13559        }
13560        matches!(
13561            qtype,
13562            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
13563        )
13564    }
13565
13566    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
13567    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
13568    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
13569    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
13570    pub fn qmatvec_mmvq(
13571        &self,
13572        bytes: &CudaSlice<u8>,
13573        aq: &CudaSlice<i8>,
13574        ad: &CudaSlice<f32>,
13575        m: usize,
13576        in_f: usize,
13577        out_f: usize,
13578        qtype: i32,
13579        row_bytes: usize,
13580        scale: f32,
13581        rp: bool,
13582    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13583        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
13584        self.qmatvec_mmvq_into(
13585            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
13586        )?;
13587        Ok(y)
13588    }
13589
13590    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
13591    #[allow(clippy::too_many_arguments)]
13592    pub fn qmatvec_mmvq_into(
13593        &self,
13594        bytes: &CudaSlice<u8>,
13595        aq: &CudaSlice<i8>,
13596        ad: &CudaSlice<f32>,
13597        m: usize,
13598        in_f: usize,
13599        out_f: usize,
13600        qtype: i32,
13601        row_bytes: usize,
13602        scale: f32,
13603        rp: bool,
13604        y: &mut CudaSlice<f32>,
13605    ) -> Result<(), Box<dyn std::error::Error>> {
13606        debug_assert!(y.len() >= m * out_f);
13607        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13608        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
13609        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
13610        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
13611        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
13612        if qtype == QT_Q8_0
13613            && rp
13614            && m == 1
13615            && out_f >= 64
13616            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
13617            && {
13618                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13619                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
13620            }
13621        {
13622            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
13623            let cfg = LaunchConfig {
13624                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
13625                block_dim: (32, 2, 1),
13626                shared_mem_bytes: 0,
13627            };
13628            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
13629            let __s_b = self.gpu.stream();
13630            let mut b = __s_b.launch_builder(&f);
13631            b.arg(bytes)
13632                .arg(aq)
13633                .arg(ad)
13634                .arg(&mut *y)
13635                .arg(&inf)
13636                .arg(&outf)
13637                .arg(&mi)
13638                .arg(&rb);
13639            unsafe {
13640                b.launch(cfg)?;
13641            }
13642            if scale != 1.0 {
13643                self.scale_inplace(y, scale, out_f)?;
13644            }
13645            return Ok(());
13646        }
13647        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
13648        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
13649        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
13650        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
13651        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
13652        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
13653        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
13654        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
13655        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
13656            2
13657        } else {
13658            1
13659        };
13660        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
13661        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
13662        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
13663        // valid-window interleaved, bit-identical per row — same dot program).
13664        if m == 1 && qtype == QT_Q4_0 {
13665            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13666            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
13667            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
13668            mr = *Q40MR.get_or_init(|| {
13669                std::env::var("MEMRA_Q40_MR")
13670                    .ok()
13671                    .and_then(|v| v.parse().ok())
13672                    .unwrap_or(1)
13673            });
13674        }
13675        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
13676        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
13677        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
13678        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
13679        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
13680        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
13681        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
13682        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
13683        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
13684        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
13685        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
13686        let q5_force = q5_mode.as_deref() == Some("2");
13687        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
13688        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
13689        let q5_il = qtype == QT_Q5_K
13690            && m == 1
13691            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
13692        if q5_il && !q5_force && out_f > 65536 {
13693            mr = 1;
13694        }
13695        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
13696        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
13697        if qtype == QT_Q4_0 && rp && mr != 1 {
13698            mr = 2;
13699        }
13700        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
13701        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
13702        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
13703        if qtype == QT_Q8_0 && rp {
13704            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13705            mr = *Q80MR.get_or_init(|| {
13706                std::env::var("MEMRA_Q80_MR")
13707                    .ok()
13708                    .and_then(|v| v.parse().ok())
13709                    .unwrap_or(1)
13710            });
13711        }
13712        let name = match (qtype, mr, rp) {
13713            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
13714            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
13715            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
13716            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
13717            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
13718            (QT_Q5_K, 2, _) => {
13719                if q5_il {
13720                    "qmatvec_q5_K_mmvq_mr2_il"
13721                } else {
13722                    "qmatvec_q5_K_mmvq_mr2"
13723                }
13724            }
13725            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
13726            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
13727            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
13728            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
13729            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
13730            (QT_Q8_0, _, true)
13731                if in_f % 1024 == 0 && {
13732                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13733                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
13734                } =>
13735            {
13736                "qmatvec_q8_0_mmvq_rpca"
13737            }
13738            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
13739            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
13740            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
13741            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
13742            // reach a GGUF-layout kernel or vice versa.
13743            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
13744            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
13745            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
13746            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
13747            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
13748            (QT_Q5_K, _, _) => {
13749                if q5_il {
13750                    "qmatvec_q5_K_mmvq_il"
13751                } else {
13752                    "qmatvec_q5_K_mmvq"
13753                }
13754            }
13755            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
13756            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
13757            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
13758            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
13759        };
13760        let f = self.func(name);
13761        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
13762        let rows_per_block = ROWS_PER_BLOCK * mr;
13763        let cfg = LaunchConfig {
13764            grid_dim: (
13765                (out_f as u32 + rows_per_block - 1) / rows_per_block,
13766                m as u32,
13767                1,
13768            ),
13769            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
13770            shared_mem_bytes: 0,                // warp-only reduce at m=1
13771        };
13772        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13773        let __s_b = self.gpu.stream();
13774        let mut b = __s_b.launch_builder(&f);
13775        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
13776        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
13777        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
13778        // weight_scale). Other mmvq kernels keep the 8-arg signature.
13779        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
13780            // PDL wave-B: the nvfp4 mr2_rp single (gemma wo / generic rp singles) joins
13781            // the wave-A launch class — 9-arg flavor (fused macro-scale epilogue).
13782            if Self::pdl_on()
13783                && Self::pdl_mmvq_on()
13784                && Self::pdl_nvfp4q8_on()
13785                && name == "qmatvec_nvfp4_mmvq_mr2_rp"
13786            {
13787                use cudarc::driver::{DevicePtr, DevicePtrMut};
13788                let s = &self.gpu.stream();
13789                let (pw, _g0) = bytes.device_ptr(s);
13790                let (paq, _g1) = aq.device_ptr(s);
13791                let (pad, _g2) = ad.device_ptr(s);
13792                let (py, _g3) = y.device_ptr_mut(s);
13793                let mut ps = [
13794                    &pw as *const _ as *mut std::ffi::c_void,
13795                    &paq as *const _ as *mut _,
13796                    &pad as *const _ as *mut _,
13797                    &py as *const _ as *mut _,
13798                    &inf as *const _ as *mut _,
13799                    &outf as *const _ as *mut _,
13800                    &mi as *const _ as *mut _,
13801                    &rb as *const _ as *mut _,
13802                    &scale as *const _ as *mut _,
13803                ];
13804                unsafe {
13805                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13806                }
13807                return Ok(());
13808            }
13809            b.arg(bytes)
13810                .arg(aq)
13811                .arg(ad)
13812                .arg(&mut *y)
13813                .arg(&inf)
13814                .arg(&outf)
13815                .arg(&mi)
13816                .arg(&rb)
13817                .arg(&scale);
13818            unsafe {
13819                b.launch(cfg)?;
13820            }
13821        } else if Self::pdl_on()
13822            && Self::pdl_mmvq_on()
13823            && (matches!(
13824                name,
13825                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
13826            ) || (Self::pdl_nvfp4q8_on()
13827                && matches!(name, "qmatvec_q8_0_mmvq_rp" | "qmatvec_q8_0_mmvq_mr2_rp")))
13828        {
13829            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
13830            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
13831            // names may take this launch (unmarked kernels would read unordered).
13832            {
13833                use cudarc::driver::{DevicePtr, DevicePtrMut};
13834                let s = &self.gpu.stream();
13835                let (pw, _g0) = bytes.device_ptr(s);
13836                let (paq, _g1) = aq.device_ptr(s);
13837                let (pad, _g2) = ad.device_ptr(s);
13838                let (py, _g3) = y.device_ptr_mut(s);
13839                let mut ps = [
13840                    &pw as *const _ as *mut std::ffi::c_void,
13841                    &paq as *const _ as *mut _,
13842                    &pad as *const _ as *mut _,
13843                    &py as *const _ as *mut _,
13844                    &inf as *const _ as *mut _,
13845                    &outf as *const _ as *mut _,
13846                    &mi as *const _ as *mut _,
13847                    &rb as *const _ as *mut _,
13848                ];
13849                unsafe {
13850                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13851                }
13852            }
13853            if scale != 1.0 {
13854                self.scale_inplace(y, scale, m * out_f)?;
13855            }
13856        } else {
13857            b.arg(bytes)
13858                .arg(aq)
13859                .arg(ad)
13860                .arg(&mut *y)
13861                .arg(&inf)
13862                .arg(&outf)
13863                .arg(&mi)
13864                .arg(&rb);
13865            unsafe {
13866                b.launch(cfg)?;
13867            }
13868            if scale != 1.0 {
13869                self.scale_inplace(y, scale, m * out_f)?;
13870            }
13871        }
13872        Ok(())
13873    }
13874
13875    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
13876    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
13877    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
13878    pub fn qmatvec_mmvq_raw(
13879        &self,
13880        bytes: &CudaSlice<u8>,
13881        x: &CudaSlice<f32>,
13882        m: usize,
13883        in_f: usize,
13884        out_f: usize,
13885        qtype: i32,
13886        row_bytes: usize,
13887        rp: bool,
13888    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13889        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13890        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
13891    }
13892
13893    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
13894    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
13895    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
13896    pub fn batched_supports(&self, qtype: i32) -> bool {
13897        matches!(
13898            qtype,
13899            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
13900        )
13901    }
13902
13903    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
13904    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
13905    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
13906    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
13907    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
13908    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
13909    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
13910    pub fn iq_fast_enabled() -> bool {
13911        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13912        *ON.get_or_init(|| {
13913            std::env::var("MEMRA_IQ_FAST")
13914                .map(|v| v != "0")
13915                .unwrap_or(true)
13916        })
13917    }
13918
13919    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
13920    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
13921    pub fn b8_enabled() -> bool {
13922        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13923        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
13924    }
13925
13926    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
13927    pub fn batched_mcols(m: usize) -> usize {
13928        if m == 2 {
13929            2
13930        } else if m <= 4 {
13931            4
13932        } else if m <= 8 {
13933            8
13934        } else {
13935            16
13936        }
13937    }
13938
13939    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
13940    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
13941    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
13942    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
13943    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
13944        Some(match (qtype, mcols) {
13945            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
13946            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
13947            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
13948            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
13949            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
13950            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
13951            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
13952            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
13953            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
13954            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
13955            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
13956            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
13957            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
13958            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
13959            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
13960            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
13961            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
13962            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
13963            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
13964            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
13965            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
13966            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
13967            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
13968            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
13969            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
13970            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
13971            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
13972            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
13973            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
13974            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
13975            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
13976            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
13977            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
13978            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
13979            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
13980            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
13981            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
13982            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
13983            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
13984            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
13985            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
13986            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
13987            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
13988            _ => return None,
13989        })
13990    }
13991
13992    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
13993    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
13994    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
13995    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
13996    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
13997    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
13998    ///
13999    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
14000    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
14001    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
14002    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
14003    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
14004    /// msweep on all six 27B shapes (2026-07-03):
14005    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
14006    ///          it applies for b4 (-3..-14%), never loses;
14007    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
14008    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
14009    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
14010    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
14011    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
14012    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
14013    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
14014    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
14015    /// b2: in_f>=6144 -> r2, else base.
14016    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
14017    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
14018    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
14019    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
14020    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
14021    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
14022    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
14023    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
14024    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
14025    /// Device SM count (cached) — grid-fill policy input.
14026    pub fn sm_count(&self) -> i32 {
14027        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
14028        *SMS.get_or_init(|| {
14029            use cudarc::driver::sys::CUdevice_attribute_enum as A;
14030            self.gpu
14031                .ctx
14032                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
14033                .unwrap_or(82)
14034        })
14035    }
14036
14037    pub fn batched_variant(
14038        &self,
14039        _m: usize,
14040        in_f: usize,
14041        out_f: usize,
14042        qtype: i32,
14043        row_bytes: usize,
14044        mcols: usize,
14045        rp: bool,
14046    ) -> &'static str {
14047        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
14048        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
14049        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
14050        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
14051        if qtype == QT_Q8_0 {
14052            return if rp { "rp" } else { "base" };
14053        }
14054        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14055        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
14056            Ok("base") => "base",
14057            Ok("pf") => "pf",
14058            Ok("r2") => "r2",
14059            Ok("r2w8") => "r2w8",
14060            Ok("pfr2") => "pfr2",
14061            Ok("ca") => "ca",
14062            Ok("car2") => "car2",
14063            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
14064            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
14065            Ok("rp") => "rp",
14066            Ok("rpr2") => "rpr2",
14067            Ok("rpr2w8") => "rpr2w8",
14068            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
14069            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
14070            Ok("rpca") => "rpca",
14071            Ok("rpcar2") => "rpcar2",
14072            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
14073            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
14074            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
14075            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
14076            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
14077            // bit-identical to the decode path — measurement corpus ONLY, never auto).
14078            Ok("rpsc") => "rpsc",
14079            Ok("rpms") => "rpms",
14080            Ok("rpmsc") => "rpmsc",
14081            Ok("rpks") => "rpks",
14082            Ok("rpksc") => "rpksc",
14083            _ => "auto",
14084        });
14085        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
14086        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
14087        // shapes qualify; anything else falls back to the register variants.
14088        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
14089        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
14090        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
14091        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
14092        // forced MEMRA_MMVQ_BV values still work).
14093        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14094        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
14095        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
14096        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
14097        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
14098        let sms = *SMS.get_or_init(|| {
14099            use cudarc::driver::sys::CUdevice_attribute_enum as A;
14100            self.gpu
14101                .ctx
14102                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
14103                .unwrap_or(82)
14104        });
14105        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
14106        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
14107        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
14108        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
14109        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
14110        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
14111        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
14112        // AUTO RULE = the measured winners table (differs from NVFP4's!):
14113        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
14114        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
14115        //     r2 1258us) — kernels kept behind the force seam for the corpus;
14116        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
14117        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
14118        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
14119        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
14120        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
14121        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
14122        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
14123        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
14124        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
14125        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
14126        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
14127        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14128        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
14129            Ok("base") => "base",
14130            Ok("r2") => "r2",
14131            Ok("r2w8") => "r2w8",
14132            _ => "auto",
14133        });
14134        let variant: &'static str = if qtype == QT_Q4_0 {
14135            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
14136            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
14137            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
14138            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14139            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
14140                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
14141                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
14142                // + syncs cost more than the stalls, bank-pad made no difference);
14143                // register load-ahead flat (nvcc already reorders). The b-tier limiter
14144                // is still unidentified — see the jsonl row.
14145                Ok("base") => "base",
14146                Ok("r2") => "r2",
14147                Ok("ms") => "ms",
14148                Ok("sm") => "sm",
14149                Ok("la") => "la",
14150                _ => "auto",
14151            });
14152            let v = if q40 != "auto" {
14153                q40
14154            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
14155                "r2"
14156            } else {
14157                "base"
14158            };
14159            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
14160            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
14161            // and the limiter is the per-column activation load chain (long_scoreboard
14162            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
14163            if rp {
14164                match v {
14165                    "ms" => "r2ms_rp",
14166                    "sm" => "r2sm_rp",
14167                    "la" => "r2la_rp",
14168                    "r2" => "r2_rp",
14169                    _ => "rp",
14170                }
14171            } else if matches!(v, "ms" | "sm" | "la") {
14172                "r2"
14173            } else {
14174                v
14175            }
14176        } else if qtype != QT_NVFP4 && !kq_r2 {
14177            "base"
14178        } else if kq_r2 && rp {
14179            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
14180            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
14181            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
14182            "rp"
14183        } else if kq_r2 {
14184            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
14185            // mcols != 4 forced r2w8 falls to unbounded r2.
14186            if kq_bv != "auto" {
14187                if kq_bv == "r2w8" && mcols != 4 {
14188                    "r2"
14189                } else {
14190                    kq_bv
14191                }
14192            } else if bv != "auto" {
14193                match bv {
14194                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
14195                    "r2w8" | "rpr2w8" => {
14196                        if mcols != 4 {
14197                            "r2"
14198                        } else {
14199                            "r2w8"
14200                        }
14201                    }
14202                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
14203                }
14204            } else {
14205                let blocks = (out_f + 7) / 8;
14206                let waves = blocks as f64 / (7 * sms as usize) as f64;
14207                let filled = blocks >= 4 * sms as usize;
14208                let use_r2 = if qtype == QT_Q4_K {
14209                    filled
14210                } else {
14211                    waves >= 2.0
14212                };
14213                if use_r2 { "r2" } else { "base" }
14214            }
14215        } else if bv != "auto" {
14216            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
14217            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
14218            // unsupported (shape, mcols) combos fall back to pf/r2.
14219            // On rp buffers, forced legacy names map to their rp twins (layout law).
14220            let v = if bv == "r2w8" && mcols == 2 {
14221                "r2"
14222            } else if bv == "ca" && (!ca_ok || mcols == 8) {
14223                "pf"
14224            } else if bv == "car2" && (!ca_ok || mcols == 8) {
14225                "r2"
14226            } else if bv == "pfr2" && mcols == 8 {
14227                "r2"
14228            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
14229                "rpr2"
14230            }
14231            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
14232            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
14233                if mcols == 8 { "rpr2w8" } else { "rpr2" }
14234            } else if bv == "rpcar2" && mcols == 2 {
14235                "rpca"
14236            }
14237            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
14238            // (rpms has no smem and no alignment need — always valid on rp buffers).
14239            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
14240                "rpr2"
14241            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
14242                "rpr2"
14243            } else {
14244                bv
14245            };
14246            if rp {
14247                match v {
14248                    "base" | "pf" | "ca" | "rp" => "rp",
14249                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
14250                    "r2w8" | "rpr2w8" => {
14251                        if mcols == 2 {
14252                            "rpr2"
14253                        } else {
14254                            "rpr2w8"
14255                        }
14256                    }
14257                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
14258                }
14259            } else {
14260                v
14261            }
14262        } else if mcols == 8 {
14263            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
14264            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
14265            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
14266            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
14267            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
14268            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
14269            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
14270            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
14271            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
14272            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
14273            if rp {
14274                if sc_ok { "rpsc" } else { "rpr2w8" }
14275            } else {
14276                "r2w8"
14277            }
14278        } else if mcols >= 4 {
14279            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
14280            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
14281            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
14282            let blocks = (out_f + 7) / 8;
14283            let r7 = 7 * sms as usize;
14284            let r8 = 8 * sms as usize;
14285            let waves = blocks as f64 / r7 as f64;
14286            let filled = blocks >= 4 * sms as usize;
14287            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
14288            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
14289            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
14290            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
14291                // the extra residency drops the INTEGER wave count -> the straggler wave a
14292                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
14293                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
14294                if rp { "rpr2w8" } else { "r2w8" }
14295            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
14296                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
14297                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
14298                if rp { "rpr2" } else { "r2" }
14299            } else {
14300                // fractional straggler-wave window with no crossing, or grid too small to fill
14301                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
14302                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
14303                if rp { "rp" } else { "pf" }
14304            }
14305        } else if in_f >= 6144 {
14306            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
14307            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
14308            // stays.
14309            if rp { "rpr2" } else { "r2" }
14310        } else if rp {
14311            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
14312            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
14313            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
14314            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
14315            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
14316            if sc_ok && waves >= 0.9 && waves <= 1.1 {
14317                "rpsc"
14318            } else {
14319                "rp"
14320            }
14321        } else {
14322            "base"
14323        };
14324        variant
14325    }
14326
14327    pub fn qmatvec_mmvq_batched(
14328        &self,
14329        bytes: &CudaSlice<u8>,
14330        aq: &CudaSlice<i8>,
14331        ad: &CudaSlice<f32>,
14332        m: usize,
14333        in_f: usize,
14334        out_f: usize,
14335        qtype: i32,
14336        row_bytes: usize,
14337        mcols: usize,
14338        scale: f32,
14339        rp: bool,
14340    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14341        const ROWS_PER_BLOCK: u32 = 4;
14342        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
14343        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
14344        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
14345        // weight keeps its rp-layout kernel family regardless of the override.
14346        let forced: Option<&'static str> = {
14347            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
14348            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
14349                .as_deref()
14350                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
14351        };
14352        let variant = match forced {
14353            Some(v) if !rp || v.contains("rp") => v,
14354            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
14355        };
14356        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
14357            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
14358        })?;
14359        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
14360        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
14361        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
14362        let variant = if mcols == 16 {
14363            if rp { "rp" } else { "base" }
14364        } else {
14365            variant
14366        };
14367        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
14368        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
14369        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
14370        // per-(token,row) chain (columns c >= m never execute in either form) ->
14371        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
14372        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
14373        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14374        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
14375        if b567
14376            && qtype == QT_NVFP4
14377            && rp
14378            && mcols == 8
14379            && (5..=7).contains(&m)
14380            && matches!(variant, "rpsc" | "rpr2w8")
14381        {
14382            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
14383            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
14384            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14385            let cfg = LaunchConfig {
14386                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14387                block_dim: (32, ROWS_PER_BLOCK, 1),
14388                shared_mem_bytes: 0,
14389            };
14390            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14391            let __s_b = self.gpu.stream();
14392            let mut b = __s_b.launch_builder(&f);
14393            b.arg(bytes)
14394                .arg(aq)
14395                .arg(ad)
14396                .arg(&mut y)
14397                .arg(&inf)
14398                .arg(&outf)
14399                .arg(&mi)
14400                .arg(&rb);
14401            unsafe {
14402                b.launch(cfg)?;
14403            }
14404            if scale != 1.0 {
14405                self.scale_inplace(&mut y, scale, m * out_f)?;
14406            }
14407            return Ok(y);
14408        }
14409        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
14410            "base" => (base_name.into(), ROWS_PER_BLOCK),
14411            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
14412            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
14413            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
14414            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
14415            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
14416            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
14417            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
14418            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
14419            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
14420            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
14421            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
14422            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
14423            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
14424            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
14425        };
14426        debug_assert!(
14427            !rp || name.contains("_rp"),
14428            "rp weight dispatched to a GGUF-layout kernel"
14429        );
14430        let f = self.func(&name);
14431        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14432        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
14433        let smem = if name.contains("_r2sm_rp") {
14434            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
14435        } else {
14436            0
14437        };
14438        let cfg = LaunchConfig {
14439            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14440            block_dim: (32, ROWS_PER_BLOCK, 1),
14441            shared_mem_bytes: smem,
14442        };
14443        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14444        let __s_b = self.gpu.stream();
14445        let mut b = __s_b.launch_builder(&f);
14446        b.arg(bytes)
14447            .arg(aq)
14448            .arg(ad)
14449            .arg(&mut y)
14450            .arg(&inf)
14451            .arg(&outf)
14452            .arg(&mi)
14453            .arg(&rb);
14454        unsafe {
14455            b.launch(cfg)?;
14456        }
14457        if scale != 1.0 {
14458            self.scale_inplace(&mut y, scale, m * out_f)?;
14459        }
14460        Ok(y)
14461    }
14462
14463    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
14464    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
14465    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
14466    pub fn qmatvec_batched_raw(
14467        &self,
14468        bytes: &CudaSlice<u8>,
14469        x: &CudaSlice<f32>,
14470        m: usize,
14471        in_f: usize,
14472        out_f: usize,
14473        qtype: i32,
14474        row_bytes: usize,
14475        mcols: usize,
14476        rp: bool,
14477    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14478        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14479        self.qmatvec_mmvq_batched(
14480            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
14481        )
14482    }
14483
14484    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
14485    pub fn qmatvec_nvfp4_batched_raw(
14486        &self,
14487        bytes: &CudaSlice<u8>,
14488        x: &CudaSlice<f32>,
14489        m: usize,
14490        in_f: usize,
14491        out_f: usize,
14492        row_bytes: usize,
14493        mcols: usize,
14494        rp: bool,
14495    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14496        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
14497    }
14498
14499    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
14500    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
14501    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
14502    fn try_fp4_gemm(
14503        &self,
14504        w: &crate::model::GpuTensor,
14505        x: &CudaSlice<f32>,
14506        m: usize,
14507        in_f: usize,
14508        out_f: usize,
14509    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14510        use crate::model::GpuTensor;
14511        if cfg!(memra_portable_cuda) {
14512            return Ok(None);
14513        }
14514        if std::env::var("MEMRA_FP4").is_err() {
14515            return Ok(None);
14516        }
14517        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
14518        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
14519        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
14520        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
14521        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
14522        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
14523        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
14524        // for the common no-macro-scale case.
14525        #[cfg(memra_cutlass)]
14526        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
14527            if let GpuTensor::Quant {
14528                bytes,
14529                qtype,
14530                scale,
14531                row_bytes,
14532                cutlass,
14533                ..
14534            } = w
14535            {
14536                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
14537                    if let Some(cw) = cutlass {
14538                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
14539                        let y = self.cutlass_fp4_gemm(
14540                            &cw.b_packed,
14541                            &cw.sfb_swizzled,
14542                            x,
14543                            *scale,
14544                            m,
14545                            out_f,
14546                            in_f,
14547                        )?;
14548                        return Ok(Some(y));
14549                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
14550                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
14551                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
14552                        // (the load-time repack ~doubles it) — needed for models that don't fit the
14553                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
14554                        let (b_packed, sfb_sw) =
14555                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
14556                        let y =
14557                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
14558                        return Ok(Some(y));
14559                    }
14560                }
14561            }
14562        }
14563        if let GpuTensor::Quant {
14564            bytes,
14565            qtype,
14566            row_bytes,
14567            scale,
14568            rp,
14569            ..
14570        } = w
14571        {
14572            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
14573            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
14574            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
14575                let y =
14576                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
14577                return Ok(Some(y));
14578            }
14579        }
14580        Ok(None)
14581    }
14582
14583    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
14584    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
14585    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
14586    pub fn rms_norm_f16out(
14587        &self,
14588        x: &CudaSlice<f32>,
14589        w: &CudaSlice<f32>,
14590        dst: &mut CudaSlice<f32>,
14591        dst16: &mut CudaSlice<u8>,
14592        ncols: usize,
14593        nrows: usize,
14594        eps: f32,
14595    ) -> Result<(), Box<dyn std::error::Error>> {
14596        let f = self.func("rms_norm_f16out_f32");
14597        let cfg = LaunchConfig {
14598            grid_dim: (nrows as u32, 1, 1),
14599            block_dim: (rms_block(), 1, 1),
14600            shared_mem_bytes: 0,
14601        };
14602        let (nc, e) = (ncols as i32, eps);
14603        let __s_b = self.gpu.stream();
14604        let mut b = __s_b.launch_builder(&f);
14605        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
14606        unsafe {
14607            b.launch(cfg)?;
14608        }
14609        Ok(())
14610    }
14611
14612    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
14613    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
14614    #[allow(clippy::too_many_arguments)]
14615    pub fn add_rms_norm_f16out(
14616        &self,
14617        a: &CudaSlice<f32>,
14618        b: &CudaSlice<f32>,
14619        w: &CudaSlice<f32>,
14620        res: &mut CudaSlice<f32>,
14621        dst: &mut CudaSlice<f32>,
14622        dst16: &mut CudaSlice<u8>,
14623        ncols: usize,
14624        nrows: usize,
14625        eps: f32,
14626    ) -> Result<(), Box<dyn std::error::Error>> {
14627        let f = self.func("add_rms_norm_f16out_f32");
14628        let cfg = LaunchConfig {
14629            grid_dim: (nrows as u32, 1, 1),
14630            block_dim: (rms_block(), 1, 1),
14631            shared_mem_bytes: 0,
14632        };
14633        let (nc, e) = (ncols as i32, eps);
14634        let __s_lb = self.gpu.stream();
14635        let mut lb = __s_lb.launch_builder(&f);
14636        lb.arg(a)
14637            .arg(b)
14638            .arg(w)
14639            .arg(res)
14640            .arg(dst)
14641            .arg(dst16)
14642            .arg(&nc)
14643            .arg(&e);
14644        unsafe {
14645            lb.launch(cfg)?;
14646        }
14647        Ok(())
14648    }
14649
14650    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
14651    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
14652    pub fn matmul_group_xh(
14653        &self,
14654        ws: &[&crate::model::GpuTensor],
14655        x: &CudaSlice<f32>,
14656        xh: &CudaSlice<u8>,
14657        m: usize,
14658    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14659        let mut out = Vec::with_capacity(ws.len());
14660        let in_f = ws[0].in_features();
14661        for w in ws {
14662            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
14663                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
14664                    out.push(y);
14665                    continue;
14666                }
14667            }
14668            out.push(self.matmul(w, x, m)?);
14669        }
14670        Ok(out)
14671    }
14672
14673    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
14674    /// GDN steps). Layouts [T, H].
14675    pub fn gdn_pad_mask(
14676        &self,
14677        beta: &mut CudaSlice<f32>,
14678        g_log: &mut CudaSlice<f32>,
14679        len_d: &CudaSlice<i32>,
14680        h: usize,
14681        t: usize,
14682    ) -> Result<(), Box<dyn std::error::Error>> {
14683        let f = self.func("gdn_pad_mask_f32");
14684        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
14685        let (hi, ti) = (h as i32, t as i32);
14686        let __s_b = self.gpu.stream();
14687        let mut b = __s_b.launch_builder(&f);
14688        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
14689        unsafe {
14690            b.launch(cfg)?;
14691        }
14692        Ok(())
14693    }
14694
14695    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
14696    /// gather for the padded prime graph's h_seed/hlast.
14697    pub fn row_gather_dev(
14698        &self,
14699        src: &CudaSlice<f32>,
14700        dst: &mut CudaSlice<f32>,
14701        len_d: &CudaSlice<i32>,
14702        ncols: usize,
14703    ) -> Result<(), Box<dyn std::error::Error>> {
14704        let f = self.func("row_gather_dev_f32");
14705        let cfg = LaunchConfig::for_num_elems(ncols as u32);
14706        let nc = ncols as i32;
14707        let __s_b = self.gpu.stream();
14708        let mut b = __s_b.launch_builder(&f);
14709        b.arg(src).arg(dst).arg(len_d).arg(&nc);
14710        unsafe {
14711            b.launch(cfg)?;
14712        }
14713        Ok(())
14714    }
14715
14716    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
14717    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
14718    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
14719    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
14720    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
14721    /// different in_f) falls back to its own `matmul` — behavior unchanged.
14722    pub fn matmul_group(
14723        &self,
14724        ws: &[&crate::model::GpuTensor],
14725        x: &CudaSlice<f32>,
14726        m: usize,
14727    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14728        use crate::model::GpuTensor;
14729        let mut out = Vec::with_capacity(ws.len());
14730        let any_mirror = ws
14731            .iter()
14732            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
14733        if m >= 16 && any_mirror && !self.verify_exact_on() {
14734            let in_f = ws[0].in_features();
14735            let xh = self.f16_act(x, m * in_f, in_f)?;
14736            for w in ws {
14737                if w.in_features() == in_f {
14738                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
14739                        out.push(y);
14740                        continue;
14741                    }
14742                }
14743                out.push(self.matmul(w, x, m)?);
14744            }
14745            return Ok(out);
14746        }
14747        for w in ws {
14748            out.push(self.matmul(w, x, m)?);
14749        }
14750        Ok(out)
14751    }
14752
14753    /// Cross-request grouped matmul (task #13): run ONE projection group over the
14754    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
14755    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
14756    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
14757    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
14758    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
14759    pub fn matmul_group_multi(
14760        &self,
14761        ws: &[&crate::model::GpuTensor],
14762        xs: &[&CudaSlice<f32>],
14763        ms: &[usize],
14764    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
14765        assert_eq!(xs.len(), ms.len());
14766        let in_f = ws[0].in_features();
14767        let total: usize = ms.iter().sum();
14768        let mut xcat = self.uninit(total * in_f)?;
14769        let mut off = 0usize;
14770        for (x, &m) in xs.iter().zip(ms) {
14771            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
14772            off += m;
14773        }
14774        let ys = self.matmul_group(ws, &xcat, total)?;
14775        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
14776        for (w, y) in ws.iter().zip(ys) {
14777            let out_f = w.out_features();
14778            let mut off = 0usize;
14779            for (s, &m) in ms.iter().enumerate() {
14780                let mut ys_s = self.uninit(m * out_f)?;
14781                let src = y.slice(off * out_f..(off + m) * out_f);
14782                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
14783                out[s].push(ys_s);
14784                off += m;
14785            }
14786        }
14787        Ok(out)
14788    }
14789
14790    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
14791    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
14792    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
14793    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
14794    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
14795    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
14796    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
14797    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
14798    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
14799    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
14800        use crate::model::GpuTensor;
14801        if !legacy_quant_gemm_allowed(
14802            cfg!(memra_portable_cuda),
14803            cfg!(memra_hopper_mma),
14804            std::env::var_os("MEMRA_NO_GEMM").is_some(),
14805        ) {
14806            return false;
14807        }
14808        match w {
14809            GpuTensor::Quant { qtype, .. } => {
14810                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
14811                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
14812            }
14813            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
14814        }
14815    }
14816
14817    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
14818    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
14819    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
14820    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
14821    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
14822    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
14823    pub fn qmatvec_gemm(
14824        &self,
14825        w: &crate::model::GpuTensor,
14826        aq: &CudaSlice<i8>,
14827        ad: &CudaSlice<f32>,
14828        m: usize,
14829    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14830        use crate::model::GpuTensor;
14831        let in_f = w.in_features();
14832        let out_f = w.out_features();
14833        let (bytes, qtype, row_bytes, scale, rp) = match w {
14834            GpuTensor::Quant {
14835                bytes,
14836                qtype,
14837                row_bytes,
14838                scale,
14839                rp,
14840                ..
14841            } => (bytes, *qtype, *row_bytes, *scale, *rp),
14842            _ => unreachable!("gemm_supports guaranteed Quant"),
14843        };
14844        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
14845        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
14846        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
14847        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
14848        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
14849        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
14850            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
14851                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
14852                if scale != 1.0 {
14853                    self.scale_inplace(&mut y, scale, m * out_f)?;
14854                }
14855                return Ok(y);
14856            }
14857        }
14858        let name = match qtype {
14859            QT_Q8_0 => "qmatvec_gemm_q8_0",
14860            QT_Q4_K => "qmatvec_gemm_q4_K",
14861            QT_Q4_0 => {
14862                if rp {
14863                    "qmatvec_gemm_q4_0_rp"
14864                } else {
14865                    "qmatvec_gemm_q4_0"
14866                }
14867            }
14868            QT_Q5_K => "qmatvec_gemm_q5_K",
14869            QT_Q6_K => "qmatvec_gemm_q6_K",
14870            QT_NVFP4 => {
14871                if rp {
14872                    "qmatvec_gemm_nvfp4_rp"
14873                } else {
14874                    "qmatvec_gemm_nvfp4"
14875                }
14876            }
14877            _ => unreachable!(),
14878        };
14879        let f = self.func(name);
14880        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14881        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
14882        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
14883        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
14884        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14885        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14886        let k1_tile = if is_k1 {
14887            k1_launch_override().unwrap_or((128, 128, 8))
14888        } else {
14889            (128, 128, 8)
14890        };
14891        let (bm, bn): (u32, u32) = if is_k1 {
14892            (k1_tile.0, k1_tile.1)
14893        } else {
14894            (64, 256)
14895        };
14896        let warps: u32 = if is_k1 {
14897            k1_tile.2
14898        } else {
14899            match qtype {
14900                QT_NVFP4 => 8,
14901                _ => 4,
14902            }
14903        };
14904        let cfg = LaunchConfig {
14905            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14906            block_dim: (32, warps, 1),
14907            shared_mem_bytes: 0,
14908        };
14909        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14910        let __s_b = self.gpu.stream();
14911        let mut b = __s_b.launch_builder(&f);
14912        b.arg(bytes)
14913            .arg(aq)
14914            .arg(ad)
14915            .arg(&mut y)
14916            .arg(&inf)
14917            .arg(&outf)
14918            .arg(&mi)
14919            .arg(&rb);
14920        unsafe {
14921            b.launch(cfg)?;
14922        }
14923        if scale != 1.0 {
14924            self.scale_inplace(&mut y, scale, m * out_f)?;
14925        }
14926        Ok(y)
14927    }
14928
14929    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
14930    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
14931    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
14932    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
14933    pub fn qmatvec_gemm_raw(
14934        &self,
14935        bytes: &CudaSlice<u8>,
14936        x: &CudaSlice<f32>,
14937        m: usize,
14938        in_f: usize,
14939        out_f: usize,
14940        qtype: i32,
14941        row_bytes: usize,
14942    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14943        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14944        let name = match qtype {
14945            QT_Q8_0 => "qmatvec_gemm_q8_0",
14946            QT_Q4_K => "qmatvec_gemm_q4_K",
14947            QT_Q4_0 => "qmatvec_gemm_q4_0",
14948            QT_Q5_K => "qmatvec_gemm_q5_K",
14949            QT_Q6_K => "qmatvec_gemm_q6_K",
14950            QT_NVFP4 => "qmatvec_gemm_nvfp4",
14951            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
14952            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
14953        };
14954        let f = self.func(name);
14955        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14956        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
14957        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
14958        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14959        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14960        let k1_tile = if is_k1 {
14961            k1_launch_override().unwrap_or((128, 128, 8))
14962        } else {
14963            (128, 128, 8)
14964        };
14965        let (bm, bn): (u32, u32) = if is_k1 {
14966            (k1_tile.0, k1_tile.1)
14967        } else {
14968            (64, 256)
14969        };
14970        let warps: u32 = if is_k1 {
14971            k1_tile.2
14972        } else {
14973            match qtype {
14974                QT_NVFP4 | QT_NVFP4_RP => 8,
14975                _ => 4,
14976            }
14977        };
14978        let cfg = LaunchConfig {
14979            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14980            block_dim: (32, warps, 1),
14981            shared_mem_bytes: 0,
14982        };
14983        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14984        let __s_b = self.gpu.stream();
14985        let mut b = __s_b.launch_builder(&f);
14986        b.arg(bytes)
14987            .arg(&aq)
14988            .arg(&ad)
14989            .arg(&mut y)
14990            .arg(&inf)
14991            .arg(&outf)
14992            .arg(&mi)
14993            .arg(&rb);
14994        unsafe {
14995            b.launch(cfg)?;
14996        }
14997        Ok(y)
14998    }
14999
15000    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
15001    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
15002    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
15003    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
15004    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
15005    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
15006    pub fn qmatvec_gemm_q8_0_wgmma_raw(
15007        &self,
15008        rp4: &CudaSlice<u8>,
15009        aq: &CudaSlice<i8>,
15010        ad: &CudaSlice<f32>,
15011        m: usize,
15012        in_f: usize,
15013        out_f: usize,
15014    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15015        assert!(
15016            out_f % 64 == 0 && in_f % 32 == 0,
15017            "wgmma GEMM needs out_f%64==0, in_f%32==0"
15018        );
15019        let f = self.func("qmatvec_gemm_q8_0_wgmma");
15020        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
15021        let cfg = LaunchConfig {
15022            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
15023            block_dim: (128, 1, 1),
15024            shared_mem_bytes: 0,
15025        };
15026        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
15027        let __s_b = self.gpu.stream();
15028        let mut b = __s_b.launch_builder(&f);
15029        b.arg(rp4)
15030            .arg(aq)
15031            .arg(ad)
15032            .arg(&mut y)
15033            .arg(&inf)
15034            .arg(&outf)
15035            .arg(&mi);
15036        unsafe {
15037            b.launch(cfg)?;
15038        }
15039        Ok(y)
15040    }
15041
15042    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
15043    pub fn scale_inplace(
15044        &self,
15045        y: &mut CudaSlice<f32>,
15046        s: f32,
15047        n: usize,
15048    ) -> Result<(), Box<dyn std::error::Error>> {
15049        let f = self.func("scale_f32");
15050        let cfg = LaunchConfig::for_num_elems(n as u32);
15051        let (sf, ni) = (s, n as i32);
15052        let __s_b = self.gpu.stream();
15053        let mut b = __s_b.launch_builder(&f);
15054        b.arg(y).arg(&sf).arg(&ni);
15055        unsafe {
15056            b.launch(cfg)?;
15057        }
15058        Ok(())
15059    }
15060
15061    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
15062    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
15063    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
15064    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
15065    pub fn bf16_to_f32(
15066        &self,
15067        data: &cudarc::driver::CudaView<'_, u8>,
15068        n: usize,
15069    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15070        let mut out = self.alloc_uninit::<f32>(n)?;
15071        let f = self.func("bf16_to_f32");
15072        let cfg = LaunchConfig::for_num_elems(n as u32);
15073        let ni = n as i32;
15074        let __s_b = self.gpu.stream();
15075        let mut b = __s_b.launch_builder(&f);
15076        b.arg(data).arg(&mut out).arg(&ni);
15077        unsafe {
15078            b.launch(cfg)?;
15079        }
15080        Ok(out)
15081    }
15082
15083    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
15084    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
15085    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
15086    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
15087    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
15088    /// calls, the spec-verify contract) vs plain linear.
15089    fn linear_bf16_chunked(
15090        &self,
15091        x: &CudaSlice<f32>,
15092        data: &CudaSlice<u8>,
15093        m: usize,
15094        in_f: usize,
15095        out_f: usize,
15096        exact: bool,
15097    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15098        const CHUNK_BYTES: usize = 256 << 20;
15099        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
15100        if chunk_rows >= out_f {
15101            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
15102            return if exact {
15103                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
15104            } else {
15105                self.linear(x, &wf32, m, in_f, out_f)
15106            };
15107        }
15108        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
15109        let mut r0 = 0usize;
15110        while r0 < out_f {
15111            let rows = chunk_rows.min(out_f - r0);
15112            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
15113            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
15114            let yc = if exact {
15115                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
15116            } else {
15117                self.linear(x, &wf32, m, in_f, rows)?
15118            };
15119            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
15120            for mi in 0..m {
15121                let src = yc.slice(mi * rows..(mi + 1) * rows);
15122                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
15123                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
15124            }
15125            r0 += rows;
15126        }
15127        Ok(y)
15128    }
15129
15130    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
15131    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
15132    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
15133    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
15134    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
15135    /// router/shexp sites and matmul_decode_exact's Float arm.
15136    pub fn linear_decode_exact(
15137        &self,
15138        x: &CudaSlice<f32>,
15139        w: &CudaSlice<f32>,
15140        m_tokens: usize,
15141        in_f: usize,
15142        out_f: usize,
15143    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15144        if m_tokens == 1 {
15145            return self.linear(x, w, 1, in_f, out_f);
15146        }
15147        let xv = self.view(x, m_tokens * in_f);
15148        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
15149        for t in 0..m_tokens {
15150            let row = xv.slice(t * in_f..(t + 1) * in_f);
15151            let mut xr = self.alloc_uninit::<f32>(in_f)?;
15152            self.copy_view_into(&mut xr, 0, &row, in_f)?;
15153            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
15154            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
15155        }
15156        Ok(y)
15157    }
15158
15159    pub fn linear(
15160        &self,
15161        x: &CudaSlice<f32>,
15162        w: &CudaSlice<f32>,
15163        m_tokens: usize,
15164        in_f: usize,
15165        out_f: usize,
15166    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15167        use cudarc::cublaslt::{Matmul, MatmulConfig};
15168        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
15169        let cfg = MatmulConfig {
15170            transa: true,
15171            transb: false,
15172            transc: false,
15173            m: out_f as u64,
15174            n: m_tokens as u64,
15175            k: in_f as u64,
15176            alpha: 1.0,
15177            lda: in_f as i64,
15178            ldb: in_f as i64,
15179            beta: 0.0,
15180            ldc: out_f as i64,
15181            stride_a: None,
15182            stride_b: None,
15183            stride_c: None,
15184            stride_bias: None,
15185            batch_size: None,
15186        };
15187        unsafe {
15188            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
15189        }
15190        Ok(c)
15191    }
15192
15193    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
15194    pub fn sdpa_naive(
15195        &self,
15196        q: &CudaSlice<f32>,
15197        k: &CudaSlice<f32>,
15198        v: &CudaSlice<f32>,
15199        o: &mut CudaSlice<f32>,
15200        head_dim: usize,
15201        n_head: usize,
15202        n_head_kv: usize,
15203        t: usize,
15204        t_kv: usize,
15205        scale: f32,
15206        causal: bool,
15207    ) -> Result<(), Box<dyn std::error::Error>> {
15208        let f = self.func("sdpa_naive_f32");
15209        let cfg = LaunchConfig {
15210            grid_dim: (n_head as u32, t as u32, 1),
15211            block_dim: (128, 1, 1),
15212            shared_mem_bytes: (t_kv * 4) as u32,
15213        };
15214        let (hd, nh, nhkv, ti, tkvi, cz) = (
15215            head_dim as i32,
15216            n_head as i32,
15217            n_head_kv as i32,
15218            t as i32,
15219            t_kv as i32,
15220            causal as i32,
15221        );
15222        let __s_b = self.gpu.stream();
15223        let mut b = __s_b.launch_builder(&f);
15224        b.arg(q)
15225            .arg(k)
15226            .arg(v)
15227            .arg(o)
15228            .arg(&hd)
15229            .arg(&nh)
15230            .arg(&nhkv)
15231            .arg(&ti)
15232            .arg(&tkvi)
15233            .arg(&scale)
15234            .arg(&cz);
15235        unsafe {
15236            b.launch(cfg)?;
15237        }
15238        Ok(())
15239    }
15240
15241    /// Island twin (lane/gemma-vision): causal + sliding-window attention with
15242    /// bidirectional image islands. `span_id` labels each absolute kv position
15243    /// (-1 text, >=0 island id); same-island keys are visible unconditionally,
15244    /// reproducing the reference's non-causal image batch. window 0 = no window.
15245    #[allow(clippy::too_many_arguments)]
15246    pub fn sdpa_naive_island(
15247        &self,
15248        q: &CudaSlice<f32>,
15249        k: &CudaSlice<f32>,
15250        v: &CudaSlice<f32>,
15251        o: &mut CudaSlice<f32>,
15252        span_id: &CudaSlice<i32>,
15253        head_dim: usize,
15254        n_head: usize,
15255        n_head_kv: usize,
15256        t: usize,
15257        t_kv: usize,
15258        scale: f32,
15259        window: usize,
15260    ) -> Result<(), Box<dyn std::error::Error>> {
15261        let f = self.func("sdpa_naive_island_f32");
15262        let cfg = LaunchConfig {
15263            grid_dim: (n_head as u32, t as u32, 1),
15264            block_dim: (128, 1, 1),
15265            shared_mem_bytes: (t_kv * 4) as u32,
15266        };
15267        let (hd, nh, nhkv, ti, tkvi, wi) = (
15268            head_dim as i32,
15269            n_head as i32,
15270            n_head_kv as i32,
15271            t as i32,
15272            t_kv as i32,
15273            window as i32,
15274        );
15275        let __s_b = self.gpu.stream();
15276        let mut b = __s_b.launch_builder(&f);
15277        b.arg(q)
15278            .arg(k)
15279            .arg(v)
15280            .arg(o)
15281            .arg(span_id)
15282            .arg(&hd)
15283            .arg(&nh)
15284            .arg(&nhkv)
15285            .arg(&ti)
15286            .arg(&tkvi)
15287            .arg(&scale)
15288            .arg(&wi);
15289        unsafe {
15290            b.launch(cfg)?;
15291        }
15292        Ok(())
15293    }
15294
15295    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
15296    #[allow(clippy::too_many_arguments)]
15297    pub fn sdpa_naive_w(
15298        &self,
15299        q: &CudaSlice<f32>,
15300        k: &CudaSlice<f32>,
15301        v: &CudaSlice<f32>,
15302        o: &mut CudaSlice<f32>,
15303        head_dim: usize,
15304        n_head: usize,
15305        n_head_kv: usize,
15306        t: usize,
15307        t_kv: usize,
15308        scale: f32,
15309        causal: bool,
15310        window: usize,
15311    ) -> Result<(), Box<dyn std::error::Error>> {
15312        let f = self.func("sdpa_naive_w_f32");
15313        let cfg = LaunchConfig {
15314            grid_dim: (n_head as u32, t as u32, 1),
15315            block_dim: (128, 1, 1),
15316            shared_mem_bytes: (t_kv * 4) as u32,
15317        };
15318        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15319            head_dim as i32,
15320            n_head as i32,
15321            n_head_kv as i32,
15322            t as i32,
15323            t_kv as i32,
15324            causal as i32,
15325            window as i32,
15326        );
15327        let __s_b = self.gpu.stream();
15328        let mut b = __s_b.launch_builder(&f);
15329        b.arg(q)
15330            .arg(k)
15331            .arg(v)
15332            .arg(o)
15333            .arg(&hd)
15334            .arg(&nh)
15335            .arg(&nhkv)
15336            .arg(&ti)
15337            .arg(&tkvi)
15338            .arg(&scale)
15339            .arg(&cz)
15340            .arg(&wi);
15341        unsafe {
15342            b.launch(cfg)?;
15343        }
15344        Ok(())
15345    }
15346
15347    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
15348    pub fn sdpa_naive_view(
15349        &self,
15350        q: &CudaSlice<f32>,
15351        k: &cudarc::driver::CudaView<f32>,
15352        v: &cudarc::driver::CudaView<f32>,
15353        o: &mut CudaSlice<f32>,
15354        head_dim: usize,
15355        n_head: usize,
15356        n_head_kv: usize,
15357        t: usize,
15358        t_kv: usize,
15359        scale: f32,
15360        causal: bool,
15361    ) -> Result<(), Box<dyn std::error::Error>> {
15362        let f = self.func("sdpa_naive_f32");
15363        let cfg = LaunchConfig {
15364            grid_dim: (n_head as u32, t as u32, 1),
15365            block_dim: (128, 1, 1),
15366            shared_mem_bytes: (t_kv * 4) as u32,
15367        };
15368        let (hd, nh, nhkv, ti, tkvi, cz) = (
15369            head_dim as i32,
15370            n_head as i32,
15371            n_head_kv as i32,
15372            t as i32,
15373            t_kv as i32,
15374            causal as i32,
15375        );
15376        let __s_b = self.gpu.stream();
15377        let mut b = __s_b.launch_builder(&f);
15378        b.arg(q)
15379            .arg(k)
15380            .arg(v)
15381            .arg(o)
15382            .arg(&hd)
15383            .arg(&nh)
15384            .arg(&nhkv)
15385            .arg(&ti)
15386            .arg(&tkvi)
15387            .arg(&scale)
15388            .arg(&cz);
15389        unsafe {
15390            b.launch(cfg)?;
15391        }
15392        Ok(())
15393    }
15394
15395    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
15396    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
15397    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
15398    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
15399    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
15400    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
15401    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
15402    #[allow(clippy::too_many_arguments)]
15403    pub fn fa_dequant_kv_view_f32(
15404        &self,
15405        k: &cudarc::driver::CudaView<u8>,
15406        v: &cudarc::driver::CudaView<u8>,
15407        kf: &mut CudaSlice<f32>,
15408        vf: &mut CudaSlice<f32>,
15409        kv_dim_k: usize,
15410        kv_dim_v: usize,
15411        t_kv: usize,
15412        k_tok_bytes: usize,
15413        v_tok_bytes: usize,
15414        g: bool,
15415    ) -> Result<(), Box<dyn std::error::Error>> {
15416        let f = if g {
15417            self.func_g("fa_dequant_kv_ws_f32")
15418        } else {
15419            self.func("fa_dequant_kv_ws_f32")
15420        };
15421        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
15422        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15423        let cfg = LaunchConfig {
15424            grid_dim: (nblk.max(1), 1, 1),
15425            block_dim: (256, 1, 1),
15426            shared_mem_bytes: 0,
15427        };
15428        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
15429        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15430        let __s_b = self.gpu.stream();
15431        let mut b = __s_b.launch_builder(&f);
15432        b.arg(k)
15433            .arg(v)
15434            .arg(&mut *kf)
15435            .arg(&mut *vf)
15436            .arg(&kdk)
15437            .arg(&kdv)
15438            .arg(&tkvi)
15439            .arg(&ktb)
15440            .arg(&vtb);
15441        unsafe {
15442            b.launch(cfg)?;
15443        }
15444        Ok(())
15445    }
15446
15447    #[allow(clippy::too_many_arguments)]
15448    pub fn sdpa_naive_quantized_view(
15449        &self,
15450        q: &CudaSlice<f32>,
15451        k: &cudarc::driver::CudaView<u8>,
15452        v: &cudarc::driver::CudaView<u8>,
15453        o: &mut CudaSlice<f32>,
15454        head_dim: usize,
15455        n_head: usize,
15456        n_head_kv: usize,
15457        t: usize,
15458        t_kv: usize,
15459        scale: f32,
15460        causal: bool,
15461        k_tok_bytes: usize,
15462        v_tok_bytes: usize,
15463    ) -> Result<(), Box<dyn std::error::Error>> {
15464        let kv_dim = n_head_kv * head_dim;
15465        let mut kf = self.uninit(t_kv * kv_dim)?;
15466        let mut vf = self.uninit(t_kv * kv_dim)?;
15467        let f = self.func("fa_dequant_kv_ws_f32");
15468        let total = (2 * t_kv * kv_dim) as u64;
15469        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15470        let cfg = LaunchConfig {
15471            grid_dim: (nblk.max(1), 1, 1),
15472            block_dim: (256, 1, 1),
15473            shared_mem_bytes: 0,
15474        };
15475        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15476        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15477        let __s_b = self.gpu.stream();
15478        let mut b = __s_b.launch_builder(&f);
15479        b.arg(k)
15480            .arg(v)
15481            .arg(&mut kf)
15482            .arg(&mut vf)
15483            .arg(&kv_dim_i)
15484            .arg(&kv_dim_i)
15485            .arg(&t_kv_i)
15486            .arg(&k_tok_bytes_i)
15487            .arg(&v_tok_bytes_i);
15488        unsafe { b.launch(cfg)? };
15489        self.sdpa_naive(
15490            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15491        )
15492    }
15493
15494    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
15495    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
15496    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
15497    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
15498    /// unwindowed function above and produces bit-identical output at window == 0.
15499    ///
15500    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
15501    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
15502    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
15503    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
15504    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
15505    #[allow(clippy::too_many_arguments)]
15506    pub fn sdpa_naive_w_quantized_view(
15507        &self,
15508        q: &CudaSlice<f32>,
15509        k: &cudarc::driver::CudaView<u8>,
15510        v: &cudarc::driver::CudaView<u8>,
15511        o: &mut CudaSlice<f32>,
15512        head_dim: usize,
15513        n_head: usize,
15514        n_head_kv: usize,
15515        t: usize,
15516        t_kv: usize,
15517        scale: f32,
15518        causal: bool,
15519        window: usize,
15520        k_tok_bytes: usize,
15521        v_tok_bytes: usize,
15522    ) -> Result<(), Box<dyn std::error::Error>> {
15523        let kv_dim = n_head_kv * head_dim;
15524        let mut kf = self.uninit(t_kv * kv_dim)?;
15525        let mut vf = self.uninit(t_kv * kv_dim)?;
15526        let f = self.func("fa_dequant_kv_ws_f32");
15527        let total = (2 * t_kv * kv_dim) as u64;
15528        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15529        let cfg = LaunchConfig {
15530            grid_dim: (nblk.max(1), 1, 1),
15531            block_dim: (256, 1, 1),
15532            shared_mem_bytes: 0,
15533        };
15534        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15535        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15536        let __s_b = self.gpu.stream();
15537        let mut b = __s_b.launch_builder(&f);
15538        b.arg(k)
15539            .arg(v)
15540            .arg(&mut kf)
15541            .arg(&mut vf)
15542            .arg(&kv_dim_i)
15543            .arg(&kv_dim_i)
15544            .arg(&t_kv_i)
15545            .arg(&k_tok_bytes_i)
15546            .arg(&v_tok_bytes_i);
15547        unsafe { b.launch(cfg)? };
15548        self.sdpa_naive_w(
15549            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15550        )
15551    }
15552
15553    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
15554    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
15555    /// Q/K/V/O [head_dim, n_head(_kv), T].
15556    pub fn fa_prefill(
15557        &self,
15558        q: &CudaSlice<f32>,
15559        k: &CudaSlice<f32>,
15560        v: &CudaSlice<f32>,
15561        o: &mut CudaSlice<f32>,
15562        head_dim: usize,
15563        n_head: usize,
15564        n_head_kv: usize,
15565        t: usize,
15566        t_kv: usize,
15567        scale: f32,
15568        causal: bool,
15569    ) -> Result<(), Box<dyn std::error::Error>> {
15570        if portable_mma_gated() {
15571            return self.sdpa_naive(
15572                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15573            );
15574        }
15575        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
15576        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
15577        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
15578        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
15579        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
15580        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
15581        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
15582        let fa3_on = head_dim == 256
15583            && causal
15584            && t == t_kv
15585            && match std::env::var("MEMRA_FA3").as_deref() {
15586                Ok("0") => false,
15587                Ok("1") => true,
15588                _ => cfg!(memra_hopper_mma),
15589            };
15590        if fa3_on {
15591            let n = t * n_head * head_dim;
15592            let nkv = t * n_head_kv * head_dim;
15593            let mut q16 = self.alloc_u8_uninit(n * 2)?;
15594            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
15595            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
15596            self.f32_to_bf16_into(q, &mut q16, n)?;
15597            self.f32_to_bf16_into(k, &mut k16, nkv)?;
15598            self.f32_to_bf16_into(v, &mut v16, nkv)?;
15599            let rc = {
15600                use cudarc::driver::{DevicePtr, DevicePtrMut};
15601                let stream = self.gpu.stream();
15602                let (qp, _g1) = q16.device_ptr(&stream);
15603                let (kp, _g2) = k16.device_ptr(&stream);
15604                let (vp, _g3) = v16.device_ptr(&stream);
15605                let (op, _g4) = o.device_ptr_mut(&stream);
15606                unsafe {
15607                    memra_fa3_prefill(
15608                        qp as *const core::ffi::c_void,
15609                        kp as *const core::ffi::c_void,
15610                        vp as *const core::ffi::c_void,
15611                        op as *mut f32,
15612                        t as i32,
15613                        n_head as i32,
15614                        n_head_kv as i32,
15615                        head_dim as i32,
15616                        scale,
15617                        stream.cu_stream() as *mut core::ffi::c_void,
15618                    )
15619                }
15620            };
15621            if rc != 0 {
15622                return Err(format!("memra_fa3_prefill rc={rc}").into());
15623            }
15624            return Ok(());
15625        }
15626        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
15627        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
15628        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
15629        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
15630        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15631        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
15632        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
15633            const BLOCK_Q: usize = 64;
15634            const BKX: usize = 32;
15635            let f = self.func("fa_prefill_bf16_p1");
15636            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
15637                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
15638            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15639            f.set_attribute(
15640                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15641                shmem as i32,
15642            )?;
15643            let cfg = LaunchConfig {
15644                grid_dim: (
15645                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15646                    n_head as u32,
15647                    1,
15648                ),
15649                block_dim: (32, 4, 1),
15650                shared_mem_bytes: shmem,
15651            };
15652            let (hd, nh, nhkv, ti, tkvi, cz) = (
15653                head_dim as i32,
15654                n_head as i32,
15655                n_head_kv as i32,
15656                t as i32,
15657                t_kv as i32,
15658                causal as i32,
15659            );
15660            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15661            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15662            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15663            let __s_b = self.gpu.stream();
15664            let mut b = __s_b.launch_builder(&f);
15665            b.arg(&qb)
15666                .arg(&kb)
15667                .arg(&vb)
15668                .arg(o)
15669                .arg(&hd)
15670                .arg(&nh)
15671                .arg(&nhkv)
15672                .arg(&ti)
15673                .arg(&tkvi)
15674                .arg(&scale)
15675                .arg(&cz);
15676            unsafe {
15677                b.launch(cfg)?;
15678            }
15679            return Ok(());
15680        }
15681        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
15682        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
15683        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
15684        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
15685        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
15686        const BK: usize = 32;
15687        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
15688        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
15689        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
15690        let (block_q, warps, w2_sfx): (usize, u32, &str) =
15691            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
15692        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
15693        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
15694        // other head_dims to sdpa_naive before reaching here.
15695        let hd_sfx = fa_hd_suffix(head_dim)?;
15696        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15697        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
15698        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
15699        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
15700        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
15701        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
15702        let (kb16, vb16) = if bf16kv {
15703            let n = t_kv * n_head_kv * head_dim;
15704            let mut kb = self.alloc_u8_uninit(n * 2)?;
15705            let mut vb = self.alloc_u8_uninit(n * 2)?;
15706            let fcv = self.func("f32_to_bf16_bulk");
15707            let ni = n as i64;
15708            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
15709            let __s_b = self.gpu.stream();
15710            let mut b = __s_b.launch_builder(&fcv);
15711            b.arg(k).arg(&mut kb).arg(&ni);
15712            unsafe {
15713                b.launch(cfgc)?;
15714            }
15715            let __s_b = self.gpu.stream();
15716            let mut b = __s_b.launch_builder(&fcv);
15717            b.arg(v).arg(&mut vb).arg(&ni);
15718            unsafe {
15719                b.launch(cfgc)?;
15720            }
15721            (Some(kb), Some(vb))
15722        } else {
15723            (None, None)
15724        };
15725        let f = self.func(&if bf16kv {
15726            format!("fa_prefill_bf16kv_pp{hd_sfx}")
15727        } else {
15728            format!(
15729                "fa_prefill_f32{}{}{hd_sfx}",
15730                if floor { "" } else { "_pp" },
15731                if floor { "" } else { w2_sfx }
15732            )
15733        });
15734        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
15735        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
15736        let kv_stages = if bf16kv { 2 } else { 1 };
15737        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
15738            + 4 * (block_q * BK + 2 * block_q)) as u32;
15739        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15740        f.set_attribute(
15741            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15742            shmem as i32,
15743        )?;
15744        let cfg = LaunchConfig {
15745            grid_dim: (
15746                (t as u32 + block_q as u32 - 1) / block_q as u32,
15747                n_head as u32,
15748                1,
15749            ),
15750            block_dim: (32, warps, 1),
15751            shared_mem_bytes: shmem,
15752        };
15753        let (hd, nh, nhkv, ti, tkvi, cz) = (
15754            head_dim as i32,
15755            n_head as i32,
15756            n_head_kv as i32,
15757            t as i32,
15758            t_kv as i32,
15759            causal as i32,
15760        );
15761        let __s_b = self.gpu.stream();
15762        let mut b = __s_b.launch_builder(&f);
15763        b.arg(q);
15764        match (&kb16, &vb16) {
15765            (Some(kb), Some(vb)) => {
15766                b.arg(kb).arg(vb);
15767            }
15768            _ => {
15769                b.arg(k).arg(v);
15770            }
15771        }
15772        b.arg(o)
15773            .arg(&hd)
15774            .arg(&nh)
15775            .arg(&nhkv)
15776            .arg(&ti)
15777            .arg(&tkvi)
15778            .arg(&scale)
15779            .arg(&cz);
15780        unsafe {
15781            b.launch(cfg)?;
15782        }
15783        Ok(())
15784    }
15785
15786    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
15787    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
15788    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
15789    #[allow(clippy::too_many_arguments)]
15790    pub fn fa_prefill_w(
15791        &self,
15792        q: &CudaSlice<f32>,
15793        k: &CudaSlice<f32>,
15794        v: &CudaSlice<f32>,
15795        o: &mut CudaSlice<f32>,
15796        head_dim: usize,
15797        n_head: usize,
15798        n_head_kv: usize,
15799        t: usize,
15800        t_kv: usize,
15801        scale: f32,
15802        causal: bool,
15803        window: usize,
15804    ) -> Result<(), Box<dyn std::error::Error>> {
15805        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
15806        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
15807        if portable_mma_gated() {
15808            return self.sdpa_naive_w(
15809                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15810            );
15811        }
15812        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
15813        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
15814        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
15815        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15816        let faw_f32 =
15817            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
15818        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15819        self.fa_prefill_w_arm(
15820            q,
15821            k,
15822            v,
15823            o,
15824            head_dim,
15825            n_head,
15826            n_head_kv,
15827            t,
15828            t_kv,
15829            scale,
15830            causal,
15831            window,
15832            floor || faw_f32,
15833            floor,
15834        )
15835    }
15836
15837    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
15838    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
15839    #[allow(clippy::too_many_arguments)]
15840    pub fn fa_prefill_w_pre(
15841        &self,
15842        qb: &CudaSlice<u8>,
15843        kb: &CudaSlice<u8>,
15844        vb: &CudaSlice<u8>,
15845        o: &mut CudaSlice<f32>,
15846        head_dim: usize,
15847        n_head: usize,
15848        n_head_kv: usize,
15849        t: usize,
15850        t_kv: usize,
15851        scale: f32,
15852        causal: bool,
15853        window: usize,
15854        v_f16: bool,
15855    ) -> Result<(), Box<dyn std::error::Error>> {
15856        const BLOCK_Q: usize = 64;
15857        const BK: usize = 32;
15858        debug_assert_eq!(head_dim, 256);
15859        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15860        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
15861        if hp {
15862            const BLOCK_QH: usize = 32;
15863            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
15864            // else re-encode through the pooled scratch (stream-ordered reuse).
15865            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15866            let vh: &CudaSlice<u8> = if v_f16 {
15867                vb
15868            } else {
15869                let n = t_kv * n_head_kv * head_dim;
15870                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
15871                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
15872                }
15873                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
15874                vguard.as_ref().unwrap()
15875            };
15876            let f = self.func("fa_prefill_w_bf16_p1h2");
15877            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15878            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15879            f.set_attribute(
15880                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15881                shmem as i32,
15882            )?;
15883            let cfg = LaunchConfig {
15884                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15885                block_dim: (32, 4, 1),
15886                shared_mem_bytes: shmem,
15887            };
15888            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15889                head_dim as i32,
15890                n_head as i32,
15891                n_head_kv as i32,
15892                t as i32,
15893                t_kv as i32,
15894                causal as i32,
15895                window as i32,
15896            );
15897            let __s_b = self.gpu.stream();
15898            let mut b = __s_b.launch_builder(&f);
15899            b.arg(qb)
15900                .arg(kb)
15901                .arg(vh)
15902                .arg(o)
15903                .arg(&hd)
15904                .arg(&nh)
15905                .arg(&nhkv)
15906                .arg(&ti)
15907                .arg(&tkvi)
15908                .arg(&scale)
15909                .arg(&cz)
15910                .arg(&wi);
15911            unsafe {
15912                b.launch(cfg)?;
15913            }
15914            return Ok(());
15915        }
15916        let f = self.func("fa_prefill_w_bf16_p1");
15917        let shmem =
15918            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15919        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15920        f.set_attribute(
15921            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15922            shmem as i32,
15923        )?;
15924        let cfg = LaunchConfig {
15925            grid_dim: (
15926                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15927                n_head as u32,
15928                1,
15929            ),
15930            block_dim: (32, 4, 1),
15931            shared_mem_bytes: shmem,
15932        };
15933        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15934            head_dim as i32,
15935            n_head as i32,
15936            n_head_kv as i32,
15937            t as i32,
15938            t_kv as i32,
15939            causal as i32,
15940            window as i32,
15941        );
15942        let __s_b = self.gpu.stream();
15943        let mut b = __s_b.launch_builder(&f);
15944        b.arg(qb)
15945            .arg(kb)
15946            .arg(vb)
15947            .arg(o)
15948            .arg(&hd)
15949            .arg(&nh)
15950            .arg(&nhkv)
15951            .arg(&ti)
15952            .arg(&tkvi)
15953            .arg(&scale)
15954            .arg(&cz)
15955            .arg(&wi);
15956        unsafe {
15957            b.launch(cfg)?;
15958        }
15959        Ok(())
15960    }
15961
15962    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
15963    #[allow(clippy::too_many_arguments)]
15964    pub fn fa_prefill_w_arm(
15965        &self,
15966        q: &CudaSlice<f32>,
15967        k: &CudaSlice<f32>,
15968        v: &CudaSlice<f32>,
15969        o: &mut CudaSlice<f32>,
15970        head_dim: usize,
15971        n_head: usize,
15972        n_head_kv: usize,
15973        t: usize,
15974        t_kv: usize,
15975        scale: f32,
15976        causal: bool,
15977        window: usize,
15978        f32_stage: bool,
15979        floor: bool,
15980    ) -> Result<(), Box<dyn std::error::Error>> {
15981        const BLOCK_Q: usize = 64;
15982        const BK: usize = 32;
15983        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
15984        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
15985        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
15986        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
15987        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15988        let p1 = !floor
15989            && !f32_stage
15990            && *P1_ON.get_or_init(|| {
15991                std::env::var("MEMRA_FAW_P1")
15992                    .map(|v| v != "0")
15993                    .unwrap_or(true)
15994            });
15995        let hp =
15996            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15997        if hp {
15998            const BLOCK_QH: usize = 32;
15999            let f = self.func("fa_prefill_w_bf16_p1h2");
16000            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
16001            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16002            f.set_attribute(
16003                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16004                shmem as i32,
16005            )?;
16006            let cfg = LaunchConfig {
16007                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
16008                block_dim: (32, 4, 1),
16009                shared_mem_bytes: shmem,
16010            };
16011            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16012                head_dim as i32,
16013                n_head as i32,
16014                n_head_kv as i32,
16015                t as i32,
16016                t_kv as i32,
16017                causal as i32,
16018                window as i32,
16019            );
16020            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16021            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16022            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
16023            let __s_b = self.gpu.stream();
16024            let mut b = __s_b.launch_builder(&f);
16025            b.arg(&qb)
16026                .arg(&kb)
16027                .arg(&vh)
16028                .arg(o)
16029                .arg(&hd)
16030                .arg(&nh)
16031                .arg(&nhkv)
16032                .arg(&ti)
16033                .arg(&tkvi)
16034                .arg(&scale)
16035                .arg(&cz)
16036                .arg(&wi);
16037            unsafe {
16038                b.launch(cfg)?;
16039            }
16040            return Ok(());
16041        }
16042        if p1 {
16043            let f = self.func("fa_prefill_w_bf16_p1");
16044            let shmem =
16045                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16046            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16047            f.set_attribute(
16048                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16049                shmem as i32,
16050            )?;
16051            let cfg = LaunchConfig {
16052                grid_dim: (
16053                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16054                    n_head as u32,
16055                    1,
16056                ),
16057                block_dim: (32, 4, 1),
16058                shared_mem_bytes: shmem,
16059            };
16060            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16061                head_dim as i32,
16062                n_head as i32,
16063                n_head_kv as i32,
16064                t as i32,
16065                t_kv as i32,
16066                causal as i32,
16067                window as i32,
16068            );
16069            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16070            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16071            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16072            let __s_b = self.gpu.stream();
16073            let mut b = __s_b.launch_builder(&f);
16074            b.arg(&qb)
16075                .arg(&kb)
16076                .arg(&vb)
16077                .arg(o)
16078                .arg(&hd)
16079                .arg(&nh)
16080                .arg(&nhkv)
16081                .arg(&ti)
16082                .arg(&tkvi)
16083                .arg(&scale)
16084                .arg(&cz)
16085                .arg(&wi);
16086            unsafe {
16087                b.launch(cfg)?;
16088            }
16089            return Ok(());
16090        }
16091        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
16092        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
16093        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16094        let g4 = !floor
16095            && !f32_stage
16096            && n_head_kv == 1
16097            && n_head % 4 == 0
16098            && *G4_ON.get_or_init(|| {
16099                std::env::var("MEMRA_FAW_G4")
16100                    .map(|v| v != "0")
16101                    .unwrap_or(true)
16102            });
16103        if g4 {
16104            const SP_M: usize = 16;
16105            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
16106            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
16107            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16108            let o2 = *O2_ON.get_or_init(|| {
16109                std::env::var("MEMRA_FAW_O2")
16110                    .map(|v| v != "0")
16111                    .unwrap_or(true)
16112            });
16113            let f = self.func(if o2 {
16114                "fa_prefill_w_bf16_g4o2"
16115            } else {
16116                "fa_prefill_w_bf16_g4"
16117            });
16118            let shmem = if o2 {
16119                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
16120            } else {
16121                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
16122                    as u32
16123            };
16124            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16125            f.set_attribute(
16126                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16127                shmem as i32,
16128            )?;
16129            let cfg = LaunchConfig {
16130                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
16131                block_dim: (32, 4, 1),
16132                shared_mem_bytes: shmem,
16133            };
16134            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16135                head_dim as i32,
16136                n_head as i32,
16137                n_head_kv as i32,
16138                t as i32,
16139                t_kv as i32,
16140                causal as i32,
16141                window as i32,
16142            );
16143            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16144            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16145            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16146            let __s_b = self.gpu.stream();
16147            let mut b = __s_b.launch_builder(&f);
16148            b.arg(&qb)
16149                .arg(&kb)
16150                .arg(&vb)
16151                .arg(o)
16152                .arg(&hd)
16153                .arg(&nh)
16154                .arg(&nhkv)
16155                .arg(&ti)
16156                .arg(&tkvi)
16157                .arg(&scale)
16158                .arg(&cz)
16159                .arg(&wi);
16160            unsafe {
16161                b.launch(cfg)?;
16162            }
16163            return Ok(());
16164        }
16165        let f = self.func(if floor {
16166            "fa_prefill_w_f32"
16167        } else if f32_stage {
16168            "fa_prefill_w_f32_pp"
16169        } else {
16170            "fa_prefill_w_bf16_pp"
16171        });
16172        let shmem =
16173            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16174        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16175        f.set_attribute(
16176            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16177            shmem as i32,
16178        )?;
16179        let cfg = LaunchConfig {
16180            grid_dim: (
16181                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16182                n_head as u32,
16183                1,
16184            ),
16185            block_dim: (32, 4, 1),
16186            shared_mem_bytes: shmem,
16187        };
16188        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16189            head_dim as i32,
16190            n_head as i32,
16191            n_head_kv as i32,
16192            t as i32,
16193            t_kv as i32,
16194            causal as i32,
16195            window as i32,
16196        );
16197        if f32_stage {
16198            let __s_b = self.gpu.stream();
16199            let mut b = __s_b.launch_builder(&f);
16200            b.arg(q)
16201                .arg(k)
16202                .arg(v)
16203                .arg(o)
16204                .arg(&hd)
16205                .arg(&nh)
16206                .arg(&nhkv)
16207                .arg(&ti)
16208                .arg(&tkvi)
16209                .arg(&scale)
16210                .arg(&cz)
16211                .arg(&wi);
16212            unsafe {
16213                b.launch(cfg)?;
16214            }
16215        } else {
16216            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16217            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16218            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16219            let __s_b = self.gpu.stream();
16220            let mut b = __s_b.launch_builder(&f);
16221            b.arg(&qb)
16222                .arg(&kb)
16223                .arg(&vb)
16224                .arg(o)
16225                .arg(&hd)
16226                .arg(&nh)
16227                .arg(&nhkv)
16228                .arg(&ti)
16229                .arg(&tkvi)
16230                .arg(&scale)
16231                .arg(&cz)
16232                .arg(&wi);
16233            unsafe {
16234                b.launch(cfg)?;
16235            }
16236        }
16237        Ok(())
16238    }
16239
16240    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
16241    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
16242    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
16243    #[allow(clippy::too_many_arguments)]
16244    pub fn fa_prefill_hd512(
16245        &self,
16246        q: &CudaSlice<f32>,
16247        k: &CudaSlice<f32>,
16248        v: &CudaSlice<f32>,
16249        o: &mut CudaSlice<f32>,
16250        head_dim: usize,
16251        n_head: usize,
16252        n_head_kv: usize,
16253        t: usize,
16254        t_kv: usize,
16255        scale: f32,
16256        causal: bool,
16257    ) -> Result<(), Box<dyn std::error::Error>> {
16258        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
16259        if portable_mma_gated() {
16260            return self.sdpa_naive(
16261                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
16262            );
16263        }
16264        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
16265        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
16266        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
16267        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
16268        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
16269        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16270        let f32_stage =
16271            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
16272        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
16273        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
16274        // Own numeric config (partial-sum order) — battery-gated.
16275        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16276        let sp = !f32_stage
16277            && *SP_ON.get_or_init(|| {
16278                std::env::var("MEMRA_FA512_SP")
16279                    .map(|v| v != "0")
16280                    .unwrap_or(true)
16281            });
16282        self.fa_prefill_hd512_arm(
16283            q,
16284            k,
16285            v,
16286            o,
16287            head_dim,
16288            n_head,
16289            n_head_kv,
16290            t,
16291            t_kv,
16292            scale,
16293            causal,
16294            f32_stage,
16295            sp,
16296            sp && fa_f16pv_on(),
16297        )
16298    }
16299
16300    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
16301    #[allow(clippy::too_many_arguments)]
16302    pub fn fa_prefill_hd512_pre(
16303        &self,
16304        qb: &CudaSlice<u8>,
16305        kb: &CudaSlice<u8>,
16306        vb: &CudaSlice<u8>,
16307        o: &mut CudaSlice<f32>,
16308        head_dim: usize,
16309        n_head: usize,
16310        n_head_kv: usize,
16311        t: usize,
16312        t_kv: usize,
16313        scale: f32,
16314        causal: bool,
16315        v_f16: bool,
16316    ) -> Result<(), Box<dyn std::error::Error>> {
16317        debug_assert_eq!(head_dim, 512);
16318        const SP_M: usize = 16;
16319        const BKS: usize = 32;
16320        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
16321        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
16322        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
16323        let f16pv = fa_f16pv_on();
16324        let nw = if f16pv { fa512_wide_warps() } else { 2 };
16325        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16326        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
16327        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
16328        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
16329            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
16330            let n = t_kv * n_head_kv * head_dim;
16331            let need = n * 2;
16332            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
16333                *vguard = Some(self.alloc_uninit::<u8>(need)?);
16334            }
16335            let dst = vguard.as_mut().unwrap();
16336            self.bf16_to_f16_into(vb, n, dst)?;
16337            vguard.as_ref().unwrap()
16338        } else {
16339            vb
16340        };
16341        let f = self.func(if hp {
16342            "fa_prefill_bf16_hd512_sp16h2"
16343        } else {
16344            match (f16pv, nw) {
16345                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16346                (true, _) => "fa_prefill_bf16_hd512_sp16",
16347                _ => "fa_prefill_bf16_hd512_sp",
16348            }
16349        });
16350        let (nwarp, npart) = if hp {
16351            (4usize, 4usize)
16352        } else if nw > 2 {
16353            (nw, nw)
16354        } else {
16355            (2, 1)
16356        };
16357        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
16358        let shmem = if hp {
16359            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
16360                as u32
16361        } else {
16362            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16363                + 4 * (npart * SP_M * BKS + SP_M)) as u32
16364        };
16365        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16366        f.set_attribute(
16367            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16368            shmem as i32,
16369        )?;
16370        let grid_y = if hp {
16371            (n_head / 2) as u32
16372        } else {
16373            n_head as u32
16374        };
16375        let cfg = LaunchConfig {
16376            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16377            block_dim: (32, nwarp as u32, 1),
16378            shared_mem_bytes: shmem,
16379        };
16380        let (hd, nh, nhkv, ti, tkvi, cz) = (
16381            head_dim as i32,
16382            n_head as i32,
16383            n_head_kv as i32,
16384            t as i32,
16385            t_kv as i32,
16386            causal as i32,
16387        );
16388        let __s_b = self.gpu.stream();
16389        let mut b = __s_b.launch_builder(&f);
16390        b.arg(qb)
16391            .arg(kb)
16392            .arg(vref)
16393            .arg(o)
16394            .arg(&hd)
16395            .arg(&nh)
16396            .arg(&nhkv)
16397            .arg(&ti)
16398            .arg(&tkvi)
16399            .arg(&scale)
16400            .arg(&cz);
16401        unsafe {
16402            b.launch(cfg)?;
16403        }
16404        Ok(())
16405    }
16406
16407    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
16408    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
16409    #[allow(clippy::too_many_arguments)]
16410    pub fn fa_prefill_hd512_arm(
16411        &self,
16412        q: &CudaSlice<f32>,
16413        k: &CudaSlice<f32>,
16414        v: &CudaSlice<f32>,
16415        o: &mut CudaSlice<f32>,
16416        head_dim: usize,
16417        n_head: usize,
16418        n_head_kv: usize,
16419        t: usize,
16420        t_kv: usize,
16421        scale: f32,
16422        causal: bool,
16423        f32_stage: bool,
16424        sp: bool,
16425        f16pv: bool,
16426    ) -> Result<(), Box<dyn std::error::Error>> {
16427        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
16428        if sp && !f32_stage {
16429            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
16430            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
16431            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
16432            const SP_M: usize = 16;
16433            const BKS: usize = 32;
16434            let nw = if f16pv { fa512_wide_warps() } else { 2 };
16435            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16436            let f = self.func(if hp {
16437                "fa_prefill_bf16_hd512_sp16h2"
16438            } else {
16439                match (f16pv, nw) {
16440                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16441                    (true, _) => "fa_prefill_bf16_hd512_sp16",
16442                    _ => "fa_prefill_bf16_hd512_sp",
16443                }
16444            });
16445            let (nwarp, npart) = if hp {
16446                (4usize, 4usize)
16447            } else if nw > 2 {
16448                (nw, nw)
16449            } else {
16450                (2, 1)
16451            };
16452            let shmem = if hp {
16453                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
16454                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
16455            } else {
16456                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16457                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
16458            };
16459            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16460            f.set_attribute(
16461                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16462                shmem as i32,
16463            )?;
16464            let grid_y = if hp {
16465                (n_head / 2) as u32
16466            } else {
16467                n_head as u32
16468            };
16469            let cfg = LaunchConfig {
16470                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16471                block_dim: (32, nwarp as u32, 1),
16472                shared_mem_bytes: shmem,
16473            };
16474            let (hd, nh, nhkv, ti, tkvi, cz) = (
16475                head_dim as i32,
16476                n_head as i32,
16477                n_head_kv as i32,
16478                t as i32,
16479                t_kv as i32,
16480                causal as i32,
16481            );
16482            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16483            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16484            let vb = if f16pv {
16485                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
16486            } else {
16487                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
16488            };
16489            let __s_b = self.gpu.stream();
16490            let mut b = __s_b.launch_builder(&f);
16491            b.arg(&qb)
16492                .arg(&kb)
16493                .arg(&vb)
16494                .arg(o)
16495                .arg(&hd)
16496                .arg(&nh)
16497                .arg(&nhkv)
16498                .arg(&ti)
16499                .arg(&tkvi)
16500                .arg(&scale)
16501                .arg(&cz);
16502            unsafe {
16503                b.launch(cfg)?;
16504            }
16505            return Ok(());
16506        }
16507        const BLOCK_Q: usize = 32;
16508        const BK: usize = 32;
16509        const HALF: usize = 256;
16510        let f = self.func(if f32_stage {
16511            "fa_prefill_f32_hd512"
16512        } else {
16513            "fa_prefill_bf16_hd512"
16514        });
16515        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
16516        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
16517            + 4 * BLOCK_Q) as u32;
16518        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16519        f.set_attribute(
16520            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16521            shmem as i32,
16522        )?;
16523        let cfg = LaunchConfig {
16524            grid_dim: (
16525                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16526                n_head as u32,
16527                2,
16528            ),
16529            block_dim: (32, 2, 1),
16530            shared_mem_bytes: shmem,
16531        };
16532        let (hd, nh, nhkv, ti, tkvi, cz) = (
16533            head_dim as i32,
16534            n_head as i32,
16535            n_head_kv as i32,
16536            t as i32,
16537            t_kv as i32,
16538            causal as i32,
16539        );
16540        if f32_stage {
16541            let __s_b = self.gpu.stream();
16542            let mut b = __s_b.launch_builder(&f);
16543            b.arg(q)
16544                .arg(k)
16545                .arg(v)
16546                .arg(o)
16547                .arg(&hd)
16548                .arg(&nh)
16549                .arg(&nhkv)
16550                .arg(&ti)
16551                .arg(&tkvi)
16552                .arg(&scale)
16553                .arg(&cz);
16554            unsafe {
16555                b.launch(cfg)?;
16556            }
16557        } else {
16558            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16559            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16560            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16561            let __s_b = self.gpu.stream();
16562            let mut b = __s_b.launch_builder(&f);
16563            b.arg(&qb)
16564                .arg(&kb)
16565                .arg(&vb)
16566                .arg(o)
16567                .arg(&hd)
16568                .arg(&nh)
16569                .arg(&nhkv)
16570                .arg(&ti)
16571                .arg(&tkvi)
16572                .arg(&scale)
16573                .arg(&cz);
16574            unsafe {
16575                b.launch(cfg)?;
16576            }
16577        }
16578        Ok(())
16579    }
16580
16581    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
16582    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
16583    /// separate f32_to_bf16 the FA entries would run).
16584    #[allow(clippy::too_many_arguments)]
16585    pub fn rope_neox2_bf16e(
16586        &self,
16587        q: &mut CudaSlice<f32>,
16588        k: &mut CudaSlice<f32>,
16589        qb: &mut CudaSlice<u8>,
16590        kb: &mut CudaSlice<u8>,
16591        pos: &CudaSlice<i32>,
16592        head_dim: usize,
16593        n_dims: usize,
16594        nh_q: usize,
16595        nh_k: usize,
16596        n_tokens: usize,
16597        base: f32,
16598        freq_scale: f32,
16599        ff: Option<&CudaSlice<f32>>,
16600    ) -> Result<(), Box<dyn std::error::Error>> {
16601        let f = self.func("rope_neox2_bf16e_f32");
16602        let rows = ((nh_q + nh_k) * n_tokens) as u32;
16603        let cfg = LaunchConfig {
16604            grid_dim: (rows, 1, 1),
16605            block_dim: ((head_dim / 2) as u32, 1, 1),
16606            shared_mem_bytes: 0,
16607        };
16608        let theta_scale = base.powf(-2.0 / n_dims as f32);
16609        let (hd, nd, nhq, nhk, nt) = (
16610            head_dim as i32,
16611            n_dims as i32,
16612            nh_q as i32,
16613            nh_k as i32,
16614            n_tokens as i32,
16615        );
16616        let __s_b = self.gpu.stream();
16617        let mut b = __s_b.launch_builder(&f);
16618        match ff {
16619            Some(t) => {
16620                b.arg(&mut *q)
16621                    .arg(&mut *k)
16622                    .arg(&mut *qb)
16623                    .arg(&mut *kb)
16624                    .arg(pos)
16625                    .arg(&hd)
16626                    .arg(&nd)
16627                    .arg(&nhq)
16628                    .arg(&nhk)
16629                    .arg(&nt)
16630                    .arg(&theta_scale)
16631                    .arg(&freq_scale)
16632                    .arg(t);
16633                unsafe {
16634                    b.launch(cfg)?;
16635                }
16636            }
16637            None => {
16638                let null: u64 = 0;
16639                b.arg(&mut *q)
16640                    .arg(&mut *k)
16641                    .arg(&mut *qb)
16642                    .arg(&mut *kb)
16643                    .arg(pos)
16644                    .arg(&hd)
16645                    .arg(&nd)
16646                    .arg(&nhq)
16647                    .arg(&nhk)
16648                    .arg(&nt)
16649                    .arg(&theta_scale)
16650                    .arg(&freq_scale)
16651                    .arg(&null);
16652                unsafe {
16653                    b.launch(cfg)?;
16654                }
16655            }
16656        }
16657        Ok(())
16658    }
16659
16660    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
16661    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
16662    pub fn f32_to_bf16(
16663        &self,
16664        x: &CudaSlice<f32>,
16665        n: usize,
16666    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16667        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
16668        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16669        let f = self.func("f32_to_bf16_flat");
16670        let n_i = n as i64;
16671        let cfg = LaunchConfig {
16672            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16673            block_dim: (256, 1, 1),
16674            shared_mem_bytes: 0,
16675        };
16676        let __s_b = self.gpu.stream();
16677        let mut b = __s_b.launch_builder(&f);
16678        b.arg(x).arg(&mut y).arg(&n_i);
16679        unsafe {
16680            b.launch(cfg)?;
16681        }
16682        Ok(y)
16683    }
16684
16685    pub fn f32_to_f16(
16686        &self,
16687        x: &CudaSlice<f32>,
16688        n: usize,
16689    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16690        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
16691        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16692        let f = self.func("f32_to_f16_flat");
16693        let n_i = n as i64;
16694        let cfg = LaunchConfig {
16695            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16696            block_dim: (256, 1, 1),
16697            shared_mem_bytes: 0,
16698        };
16699        let __s_b = self.gpu.stream();
16700        let mut b = __s_b.launch_builder(&f);
16701        b.arg(x).arg(&mut y).arg(&n_i);
16702        unsafe {
16703            b.launch(cfg)?;
16704        }
16705        Ok(y)
16706    }
16707
16708    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
16709    pub fn bf16_to_f16(
16710        &self,
16711        xb: &CudaSlice<u8>,
16712        n: usize,
16713    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16714        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16715        self.bf16_to_f16_into(xb, n, &mut y)?;
16716        Ok(y)
16717    }
16718
16719    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
16720    pub fn bf16_to_f16_into(
16721        &self,
16722        xb: &CudaSlice<u8>,
16723        n: usize,
16724        y: &mut CudaSlice<u8>,
16725    ) -> Result<(), Box<dyn std::error::Error>> {
16726        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
16727        assert!(y.len() >= n * 2);
16728        let f = self.func("bf16_to_f16_flat");
16729        let n2 = (n / 2) as i64;
16730        let cfg = LaunchConfig {
16731            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
16732            block_dim: (256, 1, 1),
16733            shared_mem_bytes: 0,
16734        };
16735        let __s_b = self.gpu.stream();
16736        let mut b = __s_b.launch_builder(&f);
16737        b.arg(xb).arg(y).arg(&n2);
16738        unsafe {
16739            b.launch(cfg)?;
16740        }
16741        Ok(())
16742    }
16743
16744    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
16745    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
16746    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
16747    /// head_dim in {256, 128}, bf16kv lane on.
16748    #[allow(clippy::too_many_arguments)]
16749    pub fn fa_prefill_vl8(
16750        &self,
16751        seqs: &[FaSeqVl],
16752        head_dim: usize,
16753        n_head: usize,
16754        n_head_kv: usize,
16755        scale: f32,
16756    ) -> Result<(), Box<dyn std::error::Error>> {
16757        const BK: usize = 32;
16758        let b = seqs.len();
16759        assert!(b >= 1 && b <= 8);
16760        let mut packed = [FaSeqVl::default(); 8];
16761        packed[..b].copy_from_slice(seqs);
16762        let v = FaVl8(packed);
16763        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16764        let ept = (n_head_kv * head_dim) as i32;
16765        {
16766            let f = self.func("fa_mirror_vl");
16767            let max_n = (max_t as i64) * ept as i64;
16768            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
16769            for which in 0..2i32 {
16770                let cfg = LaunchConfig {
16771                    grid_dim: (blocks, 1, b as u32),
16772                    block_dim: (256, 1, 1),
16773                    shared_mem_bytes: 0,
16774                };
16775                let __s_lb = self.gpu.stream();
16776                let mut lb = __s_lb.launch_builder(&f);
16777                lb.arg(&v).arg(&ept).arg(&which);
16778                unsafe {
16779                    lb.launch(cfg)?;
16780                }
16781            }
16782        }
16783        let hd_sfx = fa_hd_suffix(head_dim)?;
16784        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
16785        let block_q = 64usize;
16786        let kv_stages = 2usize;
16787        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
16788            + 4 * (block_q * BK + 2 * block_q)) as u32;
16789        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16790        f.set_attribute(
16791            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16792            shmem as i32,
16793        )?;
16794        let cfg = LaunchConfig {
16795            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
16796            block_dim: (32, 4, 1),
16797            shared_mem_bytes: shmem,
16798        };
16799        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16800        let __s_lb = self.gpu.stream();
16801        let mut lb = __s_lb.launch_builder(&f);
16802        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
16803        unsafe {
16804            lb.launch(cfg)?;
16805        }
16806        Ok(())
16807    }
16808
16809    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
16810    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
16811    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
16812    #[allow(clippy::too_many_arguments)]
16813    pub fn attn_pre_vl8(
16814        &self,
16815        seqs: &[AttnPreVl],
16816        wq: &CudaSlice<f32>,
16817        wk: &CudaSlice<f32>,
16818        head_dim: usize,
16819        rope_dims: usize,
16820        n_head: usize,
16821        n_head_kv: usize,
16822        eps: f32,
16823        freq_base: f32,
16824        freq_scale: f32,
16825        kv_dim_k: usize,
16826        kv_dim_v: usize,
16827        k_tok_bytes: usize,
16828        v_tok_bytes: usize,
16829    ) -> Result<(), Box<dyn std::error::Error>> {
16830        let b = seqs.len();
16831        assert!(b >= 1 && b <= 8);
16832        let mut packed = [AttnPreVl::default(); 8];
16833        packed[..b].copy_from_slice(seqs);
16834        let v = AttnPreVl8(packed);
16835        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16836        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16837        {
16838            let f = self.func("q_gate_split_vl");
16839            let n = max_t * (n_head * head_dim) as u32;
16840            let cfg = LaunchConfig {
16841                grid_dim: (n.div_ceil(256), 1, b as u32),
16842                block_dim: (256, 1, 1),
16843                shared_mem_bytes: 0,
16844            };
16845            let __s_lb = self.gpu.stream();
16846            let mut lb = __s_lb.launch_builder(&f);
16847            lb.arg(&v).arg(&hd).arg(&nh);
16848            unsafe {
16849                lb.launch(cfg)?;
16850            }
16851        }
16852        {
16853            let f = self.func("attn_rms_vl");
16854            let cfg = LaunchConfig {
16855                grid_dim: (max_t * n_head as u32, 2, b as u32),
16856                block_dim: (rms_block(), 1, 1),
16857                shared_mem_bytes: 0,
16858            };
16859            let __s_lb = self.gpu.stream();
16860            let mut lb = __s_lb.launch_builder(&f);
16861            lb.arg(&v)
16862                .arg(wq)
16863                .arg(wk)
16864                .arg(&hd)
16865                .arg(&nh)
16866                .arg(&nhkv)
16867                .arg(&eps);
16868            unsafe {
16869                lb.launch(cfg)?;
16870            }
16871        }
16872        {
16873            let f = self.func("attn_rope_vl");
16874            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
16875            let nd = rope_dims as i32;
16876            let cfg = LaunchConfig {
16877                grid_dim: (max_t * n_head as u32, 2, b as u32),
16878                block_dim: ((head_dim / 2) as u32, 1, 1),
16879                shared_mem_bytes: 0,
16880            };
16881            let __s_lb = self.gpu.stream();
16882            let mut lb = __s_lb.launch_builder(&f);
16883            lb.arg(&v)
16884                .arg(&hd)
16885                .arg(&nd)
16886                .arg(&nh)
16887                .arg(&nhkv)
16888                .arg(&theta_scale)
16889                .arg(&freq_scale);
16890            unsafe {
16891                lb.launch(cfg)?;
16892            }
16893        }
16894        {
16895            let f = self.func("append_kv_vl");
16896            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
16897            let cfg = LaunchConfig {
16898                grid_dim: (nblk, max_t, b as u32),
16899                block_dim: (32, 1, 1),
16900                shared_mem_bytes: 0,
16901            };
16902            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16903            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16904            let __s_lb = self.gpu.stream();
16905            let mut lb = __s_lb.launch_builder(&f);
16906            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
16907            unsafe {
16908                lb.launch(cfg)?;
16909            }
16910        }
16911        Ok(())
16912    }
16913
16914    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
16915    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
16916    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
16917    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
16918    pub fn fa_prefill_view(
16919        &self,
16920        q: &CudaSlice<f32>,
16921        k: &cudarc::driver::CudaView<u8>,
16922        v: &cudarc::driver::CudaView<u8>,
16923        o: &mut CudaSlice<f32>,
16924        head_dim: usize,
16925        n_head: usize,
16926        n_head_kv: usize,
16927        t: usize,
16928        t_kv: usize,
16929        scale: f32,
16930        causal: bool,
16931        k_tok_bytes: usize,
16932        v_tok_bytes: usize,
16933        g: bool,
16934    ) -> Result<(), Box<dyn std::error::Error>> {
16935        if portable_mma_gated() {
16936            return self.sdpa_naive_quantized_view(
16937                q,
16938                k,
16939                v,
16940                o,
16941                head_dim,
16942                n_head,
16943                n_head_kv,
16944                t,
16945                t_kv,
16946                scale,
16947                causal,
16948                k_tok_bytes,
16949                v_tok_bytes,
16950            );
16951        }
16952        const BLOCK_Q: usize = 64;
16953        const BK: usize = 32;
16954        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
16955        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
16956        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
16957        let f = if g {
16958            self.func_g(&name)
16959        } else {
16960            self.func(&name)
16961        };
16962        let shmem =
16963            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16964        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16965        f.set_attribute(
16966            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16967            shmem as i32,
16968        )?;
16969        let cfg = LaunchConfig {
16970            grid_dim: (
16971                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16972                n_head as u32,
16973                1,
16974            ),
16975            block_dim: (32, 4, 1),
16976            shared_mem_bytes: shmem,
16977        };
16978        let (hd, nh, nhkv, ti, tkvi, cz) = (
16979            head_dim as i32,
16980            n_head as i32,
16981            n_head_kv as i32,
16982            t as i32,
16983            t_kv as i32,
16984            causal as i32,
16985        );
16986        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16987        let __s_b = self.gpu.stream();
16988        let mut b = __s_b.launch_builder(&f);
16989        b.arg(q)
16990            .arg(k)
16991            .arg(v)
16992            .arg(o)
16993            .arg(&hd)
16994            .arg(&nh)
16995            .arg(&nhkv)
16996            .arg(&ti)
16997            .arg(&tkvi)
16998            .arg(&scale)
16999            .arg(&cz)
17000            .arg(&ktb)
17001            .arg(&vtb);
17002        unsafe {
17003            b.launch(cfg)?;
17004        }
17005        Ok(())
17006    }
17007
17008    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
17009    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
17010    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
17011    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
17012    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
17013    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
17014    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
17015    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
17016    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
17017    #[allow(clippy::too_many_arguments)]
17018    pub fn fa_prefill_view_ws(
17019        &self,
17020        q: &CudaSlice<f32>,
17021        k: &cudarc::driver::CudaView<u8>,
17022        v: &cudarc::driver::CudaView<u8>,
17023        o: &mut CudaSlice<f32>,
17024        head_dim: usize,
17025        n_head: usize,
17026        n_head_kv: usize,
17027        t: usize,
17028        t_kv: usize,
17029        scale: f32,
17030        causal: bool,
17031        k_tok_bytes: usize,
17032        v_tok_bytes: usize,
17033        g: bool,
17034    ) -> Result<(), Box<dyn std::error::Error>> {
17035        if portable_mma_gated() {
17036            return self.sdpa_naive_quantized_view(
17037                q,
17038                k,
17039                v,
17040                o,
17041                head_dim,
17042                n_head,
17043                n_head_kv,
17044                t,
17045                t_kv,
17046                scale,
17047                causal,
17048                k_tok_bytes,
17049                v_tok_bytes,
17050            );
17051        }
17052        const BLOCK_Q: usize = 64;
17053        const BK: usize = 32;
17054        let kv_dim_k = n_head_kv * head_dim;
17055        let kv_dim_v = n_head_kv * head_dim;
17056        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
17057        let v_ws_bytes = t_kv * kv_dim_v * 2;
17058        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
17059        let mut guard = self.prime_deqw_ws.lock().unwrap();
17060        let need_grow = match guard.as_ref() {
17061            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
17062            None => true,
17063        };
17064        if need_grow {
17065            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
17066            let (ck, cv) = guard
17067                .as_ref()
17068                .map(|(a, b)| (a.len(), b.len()))
17069                .unwrap_or((0, 0));
17070            *guard = Some((
17071                self.alloc_u8(grow(ck, k_ws_bytes))?,
17072                self.alloc_u8(grow(cv, v_ws_bytes))?,
17073            ));
17074        }
17075        let (kw, vw) = guard.as_mut().unwrap();
17076        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
17077        {
17078            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
17079            let f = if g {
17080                self.func_g("fa_dequant_kv_ws_bf16")
17081            } else {
17082                self.func("fa_dequant_kv_ws_bf16")
17083            };
17084            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17085            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17086            let cfg = LaunchConfig {
17087                grid_dim: (nblk.max(1), 1, 1),
17088                block_dim: (256, 1, 1),
17089                shared_mem_bytes: 0,
17090            };
17091            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17092            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17093            let __s_b = self.gpu.stream();
17094            let mut b = __s_b.launch_builder(&f);
17095            b.arg(k)
17096                .arg(v)
17097                .arg(&mut *kw)
17098                .arg(&mut *vw)
17099                .arg(&kdk)
17100                .arg(&kdv)
17101                .arg(&tkvi)
17102                .arg(&ktb)
17103                .arg(&vtb);
17104            unsafe {
17105                b.launch(cfg)?;
17106            }
17107        }
17108        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
17109        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
17110        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
17111        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
17112        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
17113        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
17114        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
17115        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17116            .map(|v| v != "0")
17117            .unwrap_or(true);
17118        {
17119            let hd_sfx = fa_hd_suffix(head_dim)?;
17120            let f = self.func(&format!(
17121                "fa_prefill_qw{}{hd_sfx}",
17122                if db { "_db" } else { "" }
17123            ));
17124            let shmem = if db {
17125                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
17126                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17127            } else {
17128                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17129            };
17130            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17131            f.set_attribute(
17132                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17133                shmem as i32,
17134            )?;
17135            let cfg = LaunchConfig {
17136                grid_dim: (
17137                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17138                    n_head as u32,
17139                    1,
17140                ),
17141                block_dim: (32, 4, 1),
17142                shared_mem_bytes: shmem,
17143            };
17144            let (hd, nh, nhkv, ti, tkvi, cz) = (
17145                head_dim as i32,
17146                n_head as i32,
17147                n_head_kv as i32,
17148                t as i32,
17149                t_kv as i32,
17150                causal as i32,
17151            );
17152            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17153            let __s_b = self.gpu.stream();
17154            let mut b = __s_b.launch_builder(&f);
17155            b.arg(q)
17156                .arg(&*kw)
17157                .arg(&*vw)
17158                .arg(o)
17159                .arg(&hd)
17160                .arg(&nh)
17161                .arg(&nhkv)
17162                .arg(&ti)
17163                .arg(&tkvi)
17164                .arg(&scale)
17165                .arg(&cz)
17166                .arg(&kdk)
17167                .arg(&kdv);
17168            unsafe {
17169                b.launch(cfg)?;
17170            }
17171        }
17172        Ok(())
17173    }
17174
17175    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
17176    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
17177    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
17178    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
17179    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
17180    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
17181    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
17182    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
17183    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
17184    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
17185    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
17186    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
17187    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
17188    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
17189    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
17190    #[allow(clippy::too_many_arguments)]
17191    pub fn fa_prefill_view_ws_w_hd128(
17192        &self,
17193        q: &CudaSlice<f32>,
17194        k: &cudarc::driver::CudaView<u8>,
17195        v: &cudarc::driver::CudaView<u8>,
17196        o: &mut CudaSlice<f32>,
17197        head_dim: usize,
17198        n_head: usize,
17199        n_head_kv: usize,
17200        t: usize,
17201        t_kv: usize,
17202        scale: f32,
17203        causal: bool,
17204        window: usize,
17205        k_tok_bytes: usize,
17206        v_tok_bytes: usize,
17207    ) -> Result<(), Box<dyn std::error::Error>> {
17208        assert_eq!(
17209            head_dim, 128,
17210            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
17211        );
17212        if portable_mma_gated() {
17213            return self.sdpa_naive_w_quantized_view(
17214                q,
17215                k,
17216                v,
17217                o,
17218                head_dim,
17219                n_head,
17220                n_head_kv,
17221                t,
17222                t_kv,
17223                scale,
17224                causal,
17225                window,
17226                k_tok_bytes,
17227                v_tok_bytes,
17228            );
17229        }
17230        const BLOCK_Q: usize = 64;
17231        const BK: usize = 32;
17232        let kv_dim_k = n_head_kv * head_dim;
17233        let kv_dim_v = n_head_kv * head_dim;
17234        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
17235        let v_ws_bytes = t_kv * kv_dim_v * 2;
17236        let mut guard = self.prime_deqw_ws.lock().unwrap();
17237        let need_grow = match guard.as_ref() {
17238            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
17239            None => true,
17240        };
17241        if need_grow {
17242            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
17243            let (ck, cv) = guard
17244                .as_ref()
17245                .map(|(a, b)| (a.len(), b.len()))
17246                .unwrap_or((0, 0));
17247            *guard = Some((
17248                self.alloc_u8(grow(ck, k_ws_bytes))?,
17249                self.alloc_u8(grow(cv, v_ws_bytes))?,
17250            ));
17251        }
17252        let (kw, vw) = guard.as_mut().unwrap();
17253        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
17254        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
17255        {
17256            let f = self.func("fa_dequant_kv_ws_bf16");
17257            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17258            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17259            let cfg = LaunchConfig {
17260                grid_dim: (nblk.max(1), 1, 1),
17261                block_dim: (256, 1, 1),
17262                shared_mem_bytes: 0,
17263            };
17264            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17265            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17266            let __s_b = self.gpu.stream();
17267            let mut b = __s_b.launch_builder(&f);
17268            b.arg(k)
17269                .arg(v)
17270                .arg(&mut *kw)
17271                .arg(&mut *vw)
17272                .arg(&kdk)
17273                .arg(&kdv)
17274                .arg(&tkvi)
17275                .arg(&ktb)
17276                .arg(&vtb);
17277            unsafe {
17278                b.launch(cfg)?;
17279            }
17280        }
17281        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
17282        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17283            .map(|v| v != "0")
17284            .unwrap_or(true);
17285        {
17286            let f = self.func(if db {
17287                "fa_prefill_qw_db_w_hd128"
17288            } else {
17289                "fa_prefill_qw_w_hd128"
17290            });
17291            let shmem = if db {
17292                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17293            } else {
17294                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17295            };
17296            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17297            f.set_attribute(
17298                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17299                shmem as i32,
17300            )?;
17301            let cfg = LaunchConfig {
17302                grid_dim: (
17303                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17304                    n_head as u32,
17305                    1,
17306                ),
17307                block_dim: (32, 4, 1),
17308                shared_mem_bytes: shmem,
17309            };
17310            let (hd, nh, nhkv, ti, tkvi, cz) = (
17311                head_dim as i32,
17312                n_head as i32,
17313                n_head_kv as i32,
17314                t as i32,
17315                t_kv as i32,
17316                causal as i32,
17317            );
17318            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
17319            let __s_b = self.gpu.stream();
17320            let mut b = __s_b.launch_builder(&f);
17321            b.arg(q)
17322                .arg(&*kw)
17323                .arg(&*vw)
17324                .arg(o)
17325                .arg(&hd)
17326                .arg(&nh)
17327                .arg(&nhkv)
17328                .arg(&ti)
17329                .arg(&tkvi)
17330                .arg(&scale)
17331                .arg(&cz)
17332                .arg(&kdk)
17333                .arg(&kdv)
17334                .arg(&wnd);
17335            unsafe {
17336                b.launch(cfg)?;
17337            }
17338        }
17339        Ok(())
17340    }
17341
17342    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
17343    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
17344    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
17345    pub fn fa_decode(
17346        &self,
17347        q: &CudaSlice<f32>,
17348        k: &cudarc::driver::CudaView<u8>,
17349        v: &cudarc::driver::CudaView<u8>,
17350        o: &mut CudaSlice<f32>,
17351        head_dim: usize,
17352        n_head: usize,
17353        n_head_kv: usize,
17354        t_kv: usize,
17355        scale: f32,
17356        k_tok_bytes: usize,
17357        v_tok_bytes: usize,
17358    ) -> Result<(), Box<dyn std::error::Error>> {
17359        self.fa_decode_kvmod(
17360            q,
17361            k,
17362            v,
17363            o,
17364            head_dim,
17365            n_head,
17366            n_head_kv,
17367            t_kv,
17368            scale,
17369            k_tok_bytes,
17370            v_tok_bytes,
17371            false,
17372        )
17373    }
17374
17375    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
17376    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
17377    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
17378    #[allow(clippy::too_many_arguments)]
17379    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
17380    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
17381    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
17382    #[allow(clippy::too_many_arguments)]
17383    #[allow(clippy::too_many_arguments)]
17384    fn fa_decode_scalar_unified(
17385        &self,
17386        q: &cudarc::driver::CudaView<f32>,
17387        k: &cudarc::driver::CudaView<u8>,
17388        v: &cudarc::driver::CudaView<u8>,
17389        o: &mut cudarc::driver::CudaViewMut<f32>,
17390        head_dim: usize,
17391        n_head: usize,
17392        n_head_kv: usize,
17393        t_kv_host: usize,
17394        t_kv_dev: Option<&CudaSlice<i32>>,
17395        scale: f32,
17396        n_splits: usize,
17397        split_keys: usize,
17398        k_tok_bytes: usize,
17399        v_tok_bytes: usize,
17400        g: bool,
17401        part_o: &mut CudaSlice<f32>,
17402        part_m: &mut CudaSlice<f32>,
17403        part_l: &mut CudaSlice<f32>,
17404        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17405    ) -> Result<(), Box<dyn std::error::Error>> {
17406        let f = if g {
17407            self.func_g("fa_decode_f32")
17408        } else {
17409            self.fa_func("fa_decode_f32", head_dim)
17410        };
17411        let cfg = LaunchConfig {
17412            grid_dim: (n_head as u32, n_splits as u32, 1),
17413            block_dim: (head_dim as u32, 1, 1),
17414            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
17415        };
17416        let (hd, nh, nhkv, nsp) = (
17417            head_dim as i32,
17418            n_head as i32,
17419            n_head_kv as i32,
17420            n_splits as i32,
17421        );
17422        let (ktb, vtb, tkvi, ski) = (
17423            k_tok_bytes as i64,
17424            v_tok_bytes as i64,
17425            t_kv_host as i32,
17426            split_keys as i32,
17427        );
17428        let __s_b = self.gpu.stream();
17429        let mut b = __s_b.launch_builder(&f);
17430        match t_kv_dev {
17431            Some(d) => {
17432                b.arg(q)
17433                    .arg(k)
17434                    .arg(v)
17435                    .arg(&mut *part_o)
17436                    .arg(&mut *part_m)
17437                    .arg(&mut *part_l)
17438                    .arg(&hd)
17439                    .arg(&nh)
17440                    .arg(&nhkv)
17441                    .arg(&tkvi)
17442                    .arg(d)
17443                    .arg(&scale)
17444                    .arg(&nsp)
17445                    .arg(&ski)
17446                    .arg(&ktb)
17447                    .arg(&vtb);
17448                unsafe {
17449                    b.launch(cfg)?;
17450                }
17451            }
17452            None => {
17453                let null: u64 = 0;
17454                b.arg(q)
17455                    .arg(k)
17456                    .arg(v)
17457                    .arg(&mut *part_o)
17458                    .arg(&mut *part_m)
17459                    .arg(&mut *part_l)
17460                    .arg(&hd)
17461                    .arg(&nh)
17462                    .arg(&nhkv)
17463                    .arg(&tkvi)
17464                    .arg(&null)
17465                    .arg(&scale)
17466                    .arg(&nsp)
17467                    .arg(&ski)
17468                    .arg(&ktb)
17469                    .arg(&vtb);
17470                unsafe {
17471                    b.launch(cfg)?;
17472                }
17473            }
17474        }
17475        let cfg2 = LaunchConfig {
17476            grid_dim: (n_head as u32, 1, 1),
17477            block_dim: (head_dim as u32, 1, 1),
17478            shared_mem_bytes: 0,
17479        };
17480        if let Some((oq, od)) = q8_out {
17481            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
17482            let fc = if g {
17483                self.func_g("fa_decode_combine_q8_1")
17484            } else {
17485                self.fa_func("fa_decode_combine_q8_1", head_dim)
17486            };
17487            let __s_b2 = self.gpu.stream();
17488            let mut b2 = __s_b2.launch_builder(&fc);
17489            b2.arg(&*part_o)
17490                .arg(&*part_m)
17491                .arg(&*part_l)
17492                .arg(oq)
17493                .arg(od)
17494                .arg(&hd)
17495                .arg(&nh)
17496                .arg(&nsp);
17497            unsafe {
17498                b2.launch(cfg2)?;
17499            }
17500            return Ok(());
17501        }
17502        let fc = if g {
17503            self.func_g("fa_decode_combine_f32")
17504        } else {
17505            self.fa_func("fa_decode_combine_f32", head_dim)
17506        };
17507        let __s_b2 = self.gpu.stream();
17508        let mut b2 = __s_b2.launch_builder(&fc);
17509        b2.arg(&*part_o)
17510            .arg(&*part_m)
17511            .arg(&*part_l)
17512            .arg(o)
17513            .arg(&hd)
17514            .arg(&nh)
17515            .arg(&nsp);
17516        unsafe {
17517            b2.launch(cfg2)?;
17518        }
17519        Ok(())
17520    }
17521
17522    pub fn fa_decode_kvmod(
17523        &self,
17524        q: &CudaSlice<f32>,
17525        k: &cudarc::driver::CudaView<u8>,
17526        v: &cudarc::driver::CudaView<u8>,
17527        o: &mut CudaSlice<f32>,
17528        head_dim: usize,
17529        n_head: usize,
17530        n_head_kv: usize,
17531        t_kv: usize,
17532        scale: f32,
17533        k_tok_bytes: usize,
17534        v_tok_bytes: usize,
17535        g: bool,
17536    ) -> Result<(), Box<dyn std::error::Error>> {
17537        let q_view = q.as_view();
17538        let mut o_view = o.as_view_mut();
17539        self.fa_decode_kvmod_view(
17540            &q_view,
17541            k,
17542            v,
17543            &mut o_view,
17544            head_dim,
17545            n_head,
17546            n_head_kv,
17547            t_kv,
17548            scale,
17549            k_tok_bytes,
17550            v_tok_bytes,
17551            g,
17552        )
17553    }
17554
17555    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
17556    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
17557    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
17558    /// per-session KV view and FA launch.
17559    #[allow(clippy::too_many_arguments)]
17560    pub fn fa_decode_kvmod_view(
17561        &self,
17562        q: &cudarc::driver::CudaView<f32>,
17563        k: &cudarc::driver::CudaView<u8>,
17564        v: &cudarc::driver::CudaView<u8>,
17565        o: &mut cudarc::driver::CudaViewMut<f32>,
17566        head_dim: usize,
17567        n_head: usize,
17568        n_head_kv: usize,
17569        t_kv: usize,
17570        scale: f32,
17571        k_tok_bytes: usize,
17572        v_tok_bytes: usize,
17573        g: bool,
17574    ) -> Result<(), Box<dyn std::error::Error>> {
17575        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
17576        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
17577        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
17578        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
17579        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
17580        //
17581        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
17582        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
17583        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
17584        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
17585        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
17586        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
17587        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
17588        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
17589        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
17590        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
17591        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
17592        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
17593        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
17594        // fall to the exact scalar there instead of the broken register arm.
17595        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
17596        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
17597        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
17598        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
17599        if g && head_dim == 256 && !fa_v4_at(t_kv) {
17600            fa_vec = false;
17601        }
17602        let sp = fa_split_keys(t_kv, n_head_kv);
17603        let n_splits = if fa_vec {
17604            ((t_kv + sp - 1) / sp).max(1)
17605        } else {
17606            ((t_kv + 255) / 256).max(1)
17607        };
17608        let o_len = n_head * n_splits * head_dim;
17609        let ml_len = n_head * n_splits;
17610        let mut part_guard = self.fa_part_pool.lock().unwrap();
17611        if part_guard
17612            .as_ref()
17613            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17614            .unwrap_or(true)
17615        {
17616            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17617            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17618            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17619            // later live allocations land at those addresses, and the next graph REPLAY writes
17620            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17621            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17622            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17623            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17624            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17625            // (total retired < final size).
17626            let old = part_guard.take();
17627            let (co, cm) = old
17628                .as_ref()
17629                .map(|pp| (pp.0.len(), pp.1.len()))
17630                .unwrap_or((0, 0));
17631            if let Some(old) = old {
17632                self.fa_part_retired.lock().unwrap().push(old);
17633            }
17634            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17635                eprintln!(
17636                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17637                    co, o_len, cm, ml_len
17638                );
17639            }
17640            *part_guard = Some((
17641                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17642                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17643                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17644            ));
17645        }
17646        let pg = part_guard.as_mut().unwrap();
17647        self.gpu
17648            .stream()
17649            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17650        self.gpu
17651            .stream()
17652            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17653        self.gpu
17654            .stream()
17655            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17656        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17657        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17658        let (hd, nh, nhkv, tkvi, nsp) = (
17659            head_dim as i32,
17660            n_head as i32,
17661            n_head_kv as i32,
17662            t_kv as i32,
17663            n_splits as i32,
17664        );
17665        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17666        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
17667        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
17668        // silently truncating the accumulator.
17669        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
17670        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
17671        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
17672        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
17673        // 178.4 -> 173.7 when 512 rode vec unconditionally).
17674        let fa512_min = fa512_min_tkv();
17675        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
17676        // g-module keeps the v4 pick (its class is not the depth-decay class).
17677        let deep = fa_vec
17678            && head_dim == 256
17679            && fa_v4_at(t_kv)
17680            && !g
17681            && fa_deep_at(t_kv)
17682            && !matches!(fa_v4_mode(), "noB3" | "stage");
17683        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
17684            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
17685            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
17686            let gqa = (n_head / n_head_kv).max(1) as u32;
17687            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
17688            (
17689                fv,
17690                LaunchConfig {
17691                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17692                    block_dim: (32, gqa, 1),
17693                    shared_mem_bytes: 0,
17694                },
17695            )
17696        } else if fa_vec && head_dim <= 256 {
17697            let gqa = (n_head / n_head_kv).max(1) as u32;
17698            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
17699            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
17700            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
17701            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
17702            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
17703            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
17704            // dequant each tile ONCE per block.
17705            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
17706            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
17707            // there by 12x — latency, not bandwidth, rules small KV).
17708            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17709            let smem_tkv = *SMEM_TKV.get_or_init(|| {
17710                std::env::var("MEMRA_FA_SMEM_TKV")
17711                    .ok()
17712                    .and_then(|v| v.parse().ok())
17713                    .unwrap_or_else(|| {
17714                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17715                    })
17716            });
17717            if fa_v4_at(t_kv) && head_dim == 256 {
17718                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
17719                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
17720                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
17721                let v4name = match fa_v4_mode() {
17722                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
17723                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
17724                    _ if deep => "fa_decode_vec_q_v4_deep",
17725                    _ => "fa_decode_vec_q_v4",
17726                };
17727                let fv = if g {
17728                    self.func_g(v4name)
17729                } else {
17730                    self.func(v4name)
17731                };
17732                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
17733                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
17734                let shmem = (if deep { 12160 } else { 11520 }
17735                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
17736                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17737                fv.set_attribute(
17738                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17739                    shmem as i32,
17740                )?;
17741                (
17742                    fv,
17743                    LaunchConfig {
17744                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17745                        block_dim: (32, gqa, 1),
17746                        shared_mem_bytes: shmem,
17747                    },
17748                )
17749            } else if fa_v3_active(head_dim) {
17750                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
17751                // smem = sV only (half of v2's).
17752                let fv = if g {
17753                    self.func_g("fa_decode_vec_q_v3")
17754                } else {
17755                    self.func("fa_decode_vec_q_v3")
17756                };
17757                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
17758                (
17759                    fv,
17760                    LaunchConfig {
17761                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17762                        block_dim: (32, gqa, 1),
17763                        shared_mem_bytes: shmem,
17764                    },
17765                )
17766            } else if fa_v2_on() {
17767                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
17768                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
17769                // partials; same 32KB sK+sV tile as the smem twin.
17770                let fv = if g {
17771                    self.func_g("fa_decode_vec_q_v2")
17772                } else {
17773                    self.func("fa_decode_vec_q_v2")
17774                };
17775                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17776                (
17777                    fv,
17778                    LaunchConfig {
17779                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17780                        block_dim: (32, gqa, 1),
17781                        shared_mem_bytes: shmem,
17782                    },
17783                )
17784            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
17785            {
17786                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
17787                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
17788                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
17789                let fv = if g {
17790                    self.func_g("fa_decode_vec_q_smem")
17791                } else {
17792                    self.func("fa_decode_vec_q_smem")
17793                };
17794                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17795                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17796                fv.set_attribute(
17797                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17798                    shmem as i32,
17799                )?;
17800                (
17801                    fv,
17802                    LaunchConfig {
17803                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17804                        block_dim: (32, gqa, 1),
17805                        shared_mem_bytes: shmem,
17806                    },
17807                )
17808            } else {
17809                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
17810                // dequant, zero dynamic shared memory.
17811                let fv = if g {
17812                    self.func_g("fa_decode_vec_q")
17813                } else {
17814                    self.func("fa_decode_vec_q")
17815                };
17816                (
17817                    fv,
17818                    LaunchConfig {
17819                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17820                        block_dim: (32, gqa, 1),
17821                        shared_mem_bytes: 0,
17822                    },
17823                )
17824            }
17825        } else {
17826            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
17827            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
17828            return self.fa_decode_scalar_unified(
17829                q,
17830                k,
17831                v,
17832                o,
17833                head_dim,
17834                n_head,
17835                n_head_kv,
17836                t_kv,
17837                None,
17838                scale,
17839                n_splits,
17840                if fa_vec { sp } else { 256 },
17841                k_tok_bytes,
17842                v_tok_bytes,
17843                g,
17844                part_o,
17845                part_m,
17846                part_l,
17847                None,
17848            );
17849        };
17850        let __s_b = self.gpu.stream();
17851        let mut b = __s_b.launch_builder(&f);
17852        b.arg(q)
17853            .arg(k)
17854            .arg(v)
17855            .arg(&mut *part_o)
17856            .arg(&mut *part_m)
17857            .arg(&mut *part_l)
17858            .arg(&hd)
17859            .arg(&nh)
17860            .arg(&nhkv)
17861            .arg(&tkvi)
17862            .arg(&scale)
17863            .arg(&nsp)
17864            .arg(&ktb)
17865            .arg(&vtb);
17866        unsafe {
17867            b.launch(cfg)?;
17868        }
17869        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
17870        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
17871        let (fc, cfg2) = (
17872            if g {
17873                self.func_g("fa_decode_combine_f32")
17874            } else {
17875                self.fa_func("fa_decode_combine_f32", head_dim)
17876            },
17877            LaunchConfig {
17878                grid_dim: (n_head as u32, 1, 1),
17879                block_dim: (head_dim as u32, 1, 1),
17880                shared_mem_bytes: 0,
17881            },
17882        );
17883        let __s_b2 = self.gpu.stream();
17884        let mut b2 = __s_b2.launch_builder(&fc);
17885        b2.arg(&*part_o)
17886            .arg(&*part_m)
17887            .arg(&*part_l)
17888            .arg(o)
17889            .arg(&hd)
17890            .arg(&nh)
17891            .arg(&nsp);
17892        unsafe {
17893            b2.launch(cfg2)?;
17894        }
17895        Ok(())
17896    }
17897
17898    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
17899    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
17900    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
17901    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
17902    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
17903    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
17904    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
17905    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
17906    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
17907    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
17908    #[allow(clippy::too_many_arguments)]
17909    pub fn fa_decode_batch_seqs_v4(
17910        &self,
17911        q: &CudaSlice<f32>,
17912        kv_ptrs: &cudarc::driver::CudaView<u64>,
17913        pos_seq: &CudaSlice<i32>,
17914        o: &mut CudaSlice<f32>,
17915        head_dim: usize,
17916        n_head: usize,
17917        n_head_kv: usize,
17918        b_n: usize,
17919        t_kv_max: usize,
17920        scale: f32,
17921        split_keys: usize,
17922        k_tok_bytes: usize,
17923        v_tok_bytes: usize,
17924    ) -> Result<(), Box<dyn std::error::Error>> {
17925        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
17926        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
17927        let o_len = b_n * n_head * n_splits_max * head_dim;
17928        let ml_len = b_n * n_head * n_splits_max;
17929        let mut part_guard = self.fa_part_pool.lock().unwrap();
17930        if part_guard
17931            .as_ref()
17932            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17933            .unwrap_or(true)
17934        {
17935            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17936            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17937            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17938            // later live allocations land at those addresses, and the next graph REPLAY writes
17939            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17940            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17941            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17942            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17943            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17944            // (total retired < final size).
17945            let old = part_guard.take();
17946            let (co, cm) = old
17947                .as_ref()
17948                .map(|pp| (pp.0.len(), pp.1.len()))
17949                .unwrap_or((0, 0));
17950            if let Some(old) = old {
17951                self.fa_part_retired.lock().unwrap().push(old);
17952            }
17953            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17954                eprintln!(
17955                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17956                    co, o_len, cm, ml_len
17957                );
17958            }
17959            *part_guard = Some((
17960                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17961                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17962                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17963            ));
17964        }
17965        let pg = part_guard.as_mut().unwrap();
17966        self.gpu
17967            .stream()
17968            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17969        self.gpu
17970            .stream()
17971            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17972        self.gpu
17973            .stream()
17974            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17975        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17976        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17977        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
17978        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17979        let gqa = (n_head / n_head_kv).max(1) as u32;
17980        let f = self.func("fa_decode_vec_q_seqs_v4");
17981        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
17982        let shmem = (11520 + 32 * head_dim * 2) as u32;
17983        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17984        f.set_attribute(
17985            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17986            shmem as i32,
17987        )?;
17988        let cfg = LaunchConfig {
17989            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
17990            block_dim: (32, gqa, 1),
17991            shared_mem_bytes: shmem,
17992        };
17993        {
17994            let __s_b = self.gpu.stream();
17995            let mut b = __s_b.launch_builder(&f);
17996            b.arg(q)
17997                .arg(kv_ptrs)
17998                .arg(pos_seq)
17999                .arg(&mut *part_o)
18000                .arg(&mut *part_m)
18001                .arg(&mut *part_l)
18002                .arg(&hd)
18003                .arg(&nh)
18004                .arg(&nhkv)
18005                .arg(&scale)
18006                .arg(&nspm)
18007                .arg(&spk)
18008                .arg(&ktb)
18009                .arg(&vtb);
18010            unsafe {
18011                b.launch(cfg)?;
18012            }
18013        }
18014        let fc = self.func("fa_decode_combine_seqs");
18015        let cfg2 = LaunchConfig {
18016            grid_dim: (n_head as u32, b_n as u32, 1),
18017            block_dim: (head_dim as u32, 1, 1),
18018            shared_mem_bytes: 0,
18019        };
18020        let __s_b2 = self.gpu.stream();
18021        let mut b2 = __s_b2.launch_builder(&fc);
18022        b2.arg(&*part_o)
18023            .arg(&*part_m)
18024            .arg(&*part_l)
18025            .arg(o)
18026            .arg(&hd)
18027            .arg(&nh)
18028            .arg(pos_seq)
18029            .arg(&nspm)
18030            .arg(&spk);
18031        unsafe {
18032            b2.launch(cfg2)?;
18033        }
18034        Ok(())
18035    }
18036
18037    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
18038    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
18039    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
18040    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
18041    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
18042    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
18043    #[allow(clippy::too_many_arguments)]
18044    pub fn append_kv_quantized_seqs(
18045        &self,
18046        k_rows: &CudaSlice<f32>,
18047        v_rows: &CudaSlice<f32>,
18048        kv_ptrs: &cudarc::driver::CudaView<u64>,
18049        pos_seq: &CudaSlice<i32>,
18050        b_n: usize,
18051        kv_dim_k: usize,
18052        kv_dim_v: usize,
18053        k_tok_bytes: usize,
18054        v_tok_bytes: usize,
18055    ) -> Result<(), Box<dyn std::error::Error>> {
18056        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
18057        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
18058        let cfg = LaunchConfig {
18059            grid_dim: (nblk, b_n as u32, 1),
18060            block_dim: (32, 1, 1),
18061            shared_mem_bytes: 0,
18062        };
18063        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
18064        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18065        let __s_b = self.gpu.stream();
18066        let mut b = __s_b.launch_builder(&f);
18067        b.arg(k_rows)
18068            .arg(v_rows)
18069            .arg(kv_ptrs)
18070            .arg(pos_seq)
18071            .arg(&kdk)
18072            .arg(&kdv)
18073            .arg(&ktb)
18074            .arg(&vtb);
18075        unsafe {
18076            b.launch(cfg)?;
18077        }
18078        Ok(())
18079    }
18080
18081    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
18082    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
18083    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
18084    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
18085    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
18086    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
18087        std::env::var("MEMRA_NO_FA_VEC").is_err()
18088            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
18089            && base_len + 1 >= fa_vec_min_tkv()
18090            && head_dim <= 256
18091            && head_dim % 32 == 0
18092    }
18093
18094    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
18095    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
18096    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
18097    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
18098    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
18099    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
18100    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
18101    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
18102    #[allow(clippy::too_many_arguments)]
18103    pub fn fa_decode_rows(
18104        &self,
18105        q: &CudaSlice<f32>,
18106        k: &cudarc::driver::CudaView<u8>,
18107        v: &cudarc::driver::CudaView<u8>,
18108        o: &mut CudaSlice<f32>,
18109        head_dim: usize,
18110        n_head: usize,
18111        n_head_kv: usize,
18112        base_len: usize,
18113        t: usize,
18114        scale: f32,
18115        k_tok_bytes: usize,
18116        v_tok_bytes: usize,
18117        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
18118        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
18119        // keep the host arg. None is a bug for hd512 (asserted below).
18120        base_dev: Option<(&CudaSlice<i32>, i32)>,
18121        // K and V planes hold the same values (gemma globals, wv:=wk): pick
18122        // the _kv twin — V plane never read, value rides the q8_0 key dq.
18123        kv_shared: bool,
18124        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
18125        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
18126        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
18127        g: bool,
18128        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
18129        // (hd512 path) — the standalone quantize launch folds away.
18130        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18131    ) -> Result<(), Box<dyn std::error::Error>> {
18132        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
18133        let t_kv_max = base_len + t; // LAST row's key bound
18134        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
18135        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
18136        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
18137        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
18138        // (parity law), so the partition is freely tunable — verify and decode move together.
18139        if head_dim == 512 {
18140            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18141            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
18142            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
18143            let v = *SP512.get_or_init(|| {
18144                std::env::var("MEMRA_FA_SP512")
18145                    .ok()
18146                    .and_then(|x| x.parse().ok())
18147                    .unwrap_or(0)
18148            });
18149            sp = if v >= 8 {
18150                v
18151            } else {
18152                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18153            };
18154        }
18155        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18156        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18157        let gqa = (n_head / n_head_kv).max(1) as u32;
18158        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
18159        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
18160        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
18161        // the different partition changes the combine's FP order (greedy tie flips at depth;
18162        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
18163        // consecutive rows by their OWN ladder value and launch once per group — each row then
18164        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
18165        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
18166        // sp override is t_kv-independent by construction).
18167        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
18168        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
18169            groups.push((0, t, sp));
18170        } else {
18171            let mut r0 = 0usize;
18172            while r0 < t {
18173                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
18174                let mut r1 = r0 + 1;
18175                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
18176                    r1 += 1;
18177                }
18178                groups.push((r0, r1 - r0, sp_g));
18179                r0 = r1;
18180            }
18181        }
18182        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
18183        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
18184        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
18185        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18186        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
18187            std::env::var("MEMRA_FA_SMEM_TKV")
18188                .ok()
18189                .and_then(|v| v.parse().ok())
18190                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18191        });
18192        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
18193        let v3 = fa_v3_active(head_dim);
18194        let smem_rows =
18195            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
18196        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
18197        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
18198        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
18199        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
18200        let _ = kv_shared;
18201        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
18202        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
18203        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
18204        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
18205        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
18206        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
18207        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
18208        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
18209        // (kv_head, split) stages its tile once and loops the rows over it — kills the
18210        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
18211        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
18212        // shared by every hd512 caller through this wrapper (decode+verify flip together;
18213        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
18214        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
18215        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
18216        // not unpack-bound; jsonl 2026-07-14.
18217        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18218        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
18219        let tb512 = head_dim == 512
18220            && sp <= 32
18221            && n_head / n_head_kv.max(1) <= 16
18222            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
18223        let fname = if tb512 {
18224            "fa_decode_vec_q_rows_v4_512_tb"
18225        } else if i2 {
18226            "fa_decode_vec_q_rows_dpl16_i2"
18227        } else if head_dim == 512 {
18228            "fa_decode_vec_q_rows_dpl16"
18229        }
18230        // gemma globals (parity law)
18231        else if v4 {
18232            "fa_decode_vec_q_rows_v4"
18233        } else if v3 {
18234            "fa_decode_vec_q_rows_v3"
18235        } else if fa_v2_on() {
18236            "fa_decode_vec_q_rows_v2"
18237        } else if smem_rows {
18238            "fa_decode_vec_q_rows_smem"
18239        } else {
18240            "fa_decode_vec_q_rows"
18241        };
18242        let f = if head_dim == 512 {
18243            self.fa_func(fname, head_dim)
18244        } else if g {
18245            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
18246            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
18247            // g-module rows against decode's g-module v4 — different programs, short-VG
18248            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
18249            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
18250            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
18251            // dq macros are format-aware.
18252            self.func_g(if smem_rows {
18253                "fa_decode_vec_q_rows"
18254            } else {
18255                fname
18256            })
18257        } else {
18258            self.func(fname)
18259        };
18260        let shmem = if tb512 {
18261            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
18262            let gk = Self::gkv_on();
18263            let sh =
18264                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
18265            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18266            f.set_attribute(
18267                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18268                sh as i32,
18269            )?;
18270            sh
18271        } else if v4 || v3 || smem_rows || fa_v2_on() {
18272            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
18273            let sh = (if v4 {
18274                11520 + 32 * head_dim * if g { 1 } else { 2 }
18275            } else if v3 {
18276                32 * head_dim * 2
18277            } else {
18278                2 * 32 * head_dim * 2
18279            }) as u32;
18280            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18281            f.set_attribute(
18282                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18283                sh as i32,
18284            )?;
18285            sh
18286        } else {
18287            0
18288        };
18289        // Per-GROUP launches (single group in the common case — identical to the pre-fix
18290        // single launch there): each group gets its own partials (the rows kernel indexes
18291        // partials by its LOCAL grid.z row) and q/o row-offset views.
18292        for &(r0, t_g, sp_g) in &groups {
18293            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
18294            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
18295            let base_i = (base_len + r0) as i32;
18296            let o_len = t_g * n_head * n_splits_g * head_dim;
18297            let ml_len = t_g * n_head * n_splits_g;
18298            let mut part_guard = self.fa_part_pool.lock().unwrap();
18299            if part_guard
18300                .as_ref()
18301                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18302                .unwrap_or(true)
18303            {
18304                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18305                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18306                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18307                // later live allocations land at those addresses, and the next graph REPLAY writes
18308                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18309                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18310                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18311                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18312                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18313                // (total retired < final size).
18314                let old = part_guard.take();
18315                let (co, cm) = old
18316                    .as_ref()
18317                    .map(|pp| (pp.0.len(), pp.1.len()))
18318                    .unwrap_or((0, 0));
18319                if let Some(old) = old {
18320                    self.fa_part_retired.lock().unwrap().push(old);
18321                }
18322                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18323                    eprintln!(
18324                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18325                        co, o_len, cm, ml_len
18326                    );
18327                }
18328                *part_guard = Some((
18329                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18330                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18331                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18332                ));
18333            }
18334            let pg = part_guard.as_mut().unwrap();
18335            self.gpu
18336                .stream()
18337                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18338            self.gpu
18339                .stream()
18340                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18341            self.gpu
18342                .stream()
18343                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18344            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18345            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
18346            let qv = self.view(q, t * n_head * head_dim);
18347            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18348            let cfg = LaunchConfig {
18349                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
18350                block_dim: (32, gqa, 1),
18351                shared_mem_bytes: shmem,
18352            };
18353            {
18354                let __s_b = self.gpu.stream();
18355                let mut b = __s_b.launch_builder(&f);
18356                if tb512 {
18357                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
18358                    let (bd, plus) =
18359                        base_dev.expect("hd512 rows twin requires a device base counter");
18360                    let plus_g = plus + r0 as i32;
18361                    let nr = t_g as i32;
18362                    if Self::pdl_on() && Self::pdl_wb_on() {
18363                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
18364                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18365                        let s = &self.gpu.stream();
18366                        let (pq, _b0) = q_g.device_ptr(s);
18367                        let (pk, _b1) = k.device_ptr(s);
18368                        let (pv, _b2) = v.device_ptr(s);
18369                        let (po, _b3) = part_o.device_ptr_mut(s);
18370                        let (pm, _b4) = part_m.device_ptr_mut(s);
18371                        let (pl, _b5) = part_l.device_ptr_mut(s);
18372                        let (pb, _b6) = bd.device_ptr(s);
18373                        let mut ps = [
18374                            &pq as *const _ as *mut std::ffi::c_void,
18375                            &pk as *const _ as *mut _,
18376                            &pv as *const _ as *mut _,
18377                            &po as *const _ as *mut _,
18378                            &pm as *const _ as *mut _,
18379                            &pl as *const _ as *mut _,
18380                            &hd as *const _ as *mut _,
18381                            &nh as *const _ as *mut _,
18382                            &nhkv as *const _ as *mut _,
18383                            &pb as *const _ as *mut _,
18384                            &plus_g as *const _ as *mut _,
18385                            &scale as *const _ as *mut _,
18386                            &nspm as *const _ as *mut _,
18387                            &spk as *const _ as *mut _,
18388                            &ktb as *const _ as *mut _,
18389                            &vtb as *const _ as *mut _,
18390                            &nr as *const _ as *mut _,
18391                        ];
18392                        unsafe {
18393                            self.launch_pdl_flash(
18394                                Self::gkv_on(),
18395                                "fa_decode_vec_q_rows_v4_512_tb",
18396                                (n_head_kv as u32, n_splits_g as u32, 1),
18397                                (32, gqa, 1),
18398                                shmem,
18399                                &mut ps,
18400                            )?;
18401                        }
18402                    } else {
18403                        let cfg_tb = LaunchConfig {
18404                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
18405                            block_dim: (32, gqa, 1),
18406                            shared_mem_bytes: shmem,
18407                        };
18408                        b.arg(&q_g)
18409                            .arg(k)
18410                            .arg(v)
18411                            .arg(&mut *part_o)
18412                            .arg(&mut *part_m)
18413                            .arg(&mut *part_l)
18414                            .arg(&hd)
18415                            .arg(&nh)
18416                            .arg(&nhkv)
18417                            .arg(bd)
18418                            .arg(&plus_g)
18419                            .arg(&scale)
18420                            .arg(&nspm)
18421                            .arg(&spk)
18422                            .arg(&ktb)
18423                            .arg(&vtb)
18424                            .arg(&nr);
18425                        unsafe {
18426                            b.launch(cfg_tb)?;
18427                        }
18428                    }
18429                } else if head_dim == 512 {
18430                    let (bd, plus) =
18431                        base_dev.expect("hd512 rows twin requires a device base counter");
18432                    let plus_g = plus + r0 as i32;
18433                    b.arg(&q_g)
18434                        .arg(k)
18435                        .arg(v)
18436                        .arg(&mut *part_o)
18437                        .arg(&mut *part_m)
18438                        .arg(&mut *part_l)
18439                        .arg(&hd)
18440                        .arg(&nh)
18441                        .arg(&nhkv)
18442                        .arg(bd)
18443                        .arg(&plus_g)
18444                        .arg(&scale)
18445                        .arg(&nspm)
18446                        .arg(&spk)
18447                        .arg(&ktb)
18448                        .arg(&vtb);
18449                    unsafe {
18450                        b.launch(cfg)?;
18451                    }
18452                } else {
18453                    b.arg(&q_g)
18454                        .arg(k)
18455                        .arg(v)
18456                        .arg(&mut *part_o)
18457                        .arg(&mut *part_m)
18458                        .arg(&mut *part_l)
18459                        .arg(&hd)
18460                        .arg(&nh)
18461                        .arg(&nhkv)
18462                        .arg(&base_i)
18463                        .arg(&scale)
18464                        .arg(&nspm)
18465                        .arg(&spk)
18466                        .arg(&ktb)
18467                        .arg(&vtb);
18468                    unsafe {
18469                        b.launch(cfg)?;
18470                    }
18471                }
18472            }
18473            let cfg2 = LaunchConfig {
18474                grid_dim: (n_head as u32, t_g as u32, 1),
18475                block_dim: (head_dim as u32, 1, 1),
18476                shared_mem_bytes: 0,
18477            };
18478            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18479            if head_dim == 512 {
18480                // device-len combine (shared by verify/eager/graph — parity by symbol): the
18481                // per-row n_splits derives from the SAME counter the rows kernel read.
18482                let (bd, plus) = base_dev.unwrap();
18483                let plus_g = plus + r0 as i32;
18484                if let Some((oq, od)) = q8_out.as_mut() {
18485                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
18486                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
18487                    if Self::pdl_on() && Self::pdl_wb_on() {
18488                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
18489                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18490                        let s = &self.gpu.stream();
18491                        let (po, _g0) = part_o.device_ptr(s);
18492                        let (pm, _g1) = part_m.device_ptr(s);
18493                        let (pl, _g2) = part_l.device_ptr(s);
18494                        let (pq, _g3) = oq.device_ptr_mut(s);
18495                        let (pd, _g4) = od.device_ptr_mut(s);
18496                        let (pb, _g5) = bd.device_ptr(s);
18497                        let mut ps = [
18498                            &po as *const _ as *mut std::ffi::c_void,
18499                            &pm as *const _ as *mut _,
18500                            &pl as *const _ as *mut _,
18501                            &pq as *const _ as *mut _,
18502                            &pd as *const _ as *mut _,
18503                            &hd as *const _ as *mut _,
18504                            &nh as *const _ as *mut _,
18505                            &pb as *const _ as *mut _,
18506                            &plus_g as *const _ as *mut _,
18507                            &nspm as *const _ as *mut _,
18508                            &spk as *const _ as *mut _,
18509                        ];
18510                        unsafe {
18511                            self.launch_pdl_flash(
18512                                Self::gkv_on(),
18513                                "fa_decode_combine_rows_dc_q8_1",
18514                                cfg2.grid_dim,
18515                                cfg2.block_dim,
18516                                0,
18517                                &mut ps,
18518                            )?;
18519                        }
18520                        continue;
18521                    }
18522                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
18523                    let __s_b2 = self.gpu.stream();
18524                    let mut b2 = __s_b2.launch_builder(&fc);
18525                    b2.arg(&*part_o)
18526                        .arg(&*part_m)
18527                        .arg(&*part_l)
18528                        .arg(&mut **oq)
18529                        .arg(&mut **od)
18530                        .arg(&hd)
18531                        .arg(&nh)
18532                        .arg(bd)
18533                        .arg(&plus_g)
18534                        .arg(&nspm)
18535                        .arg(&spk);
18536                    unsafe {
18537                        b2.launch(cfg2)?;
18538                    }
18539                    continue;
18540                }
18541                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
18542                let __s_b2 = self.gpu.stream();
18543                let mut b2 = __s_b2.launch_builder(&fc);
18544                b2.arg(&*part_o)
18545                    .arg(&*part_m)
18546                    .arg(&*part_l)
18547                    .arg(&mut o_g)
18548                    .arg(&hd)
18549                    .arg(&nh)
18550                    .arg(bd)
18551                    .arg(&plus_g)
18552                    .arg(&nspm)
18553                    .arg(&spk);
18554                unsafe {
18555                    b2.launch(cfg2)?;
18556                }
18557            } else {
18558                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
18559                // leave the caller's pair unwritten (consumer would read garbage).
18560                assert!(
18561                    q8_out.is_none(),
18562                    "rows q8 emit requires the hd512 dc combine"
18563                );
18564                let fc = self.func("fa_decode_combine_rows");
18565                let __s_b2 = self.gpu.stream();
18566                let mut b2 = __s_b2.launch_builder(&fc);
18567                b2.arg(&*part_o)
18568                    .arg(&*part_m)
18569                    .arg(&*part_l)
18570                    .arg(&mut o_g)
18571                    .arg(&hd)
18572                    .arg(&nh)
18573                    .arg(&base_i)
18574                    .arg(&nspm)
18575                    .arg(&spk);
18576                unsafe {
18577                    b2.launch(cfg2)?;
18578                }
18579            }
18580        }
18581        Ok(())
18582    }
18583
18584    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
18585    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
18586    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
18587    #[allow(clippy::too_many_arguments)]
18588    pub fn fa_decode_rows_w(
18589        &self,
18590        q: &CudaSlice<f32>,
18591        k: &cudarc::driver::CudaView<u8>,
18592        v: &cudarc::driver::CudaView<u8>,
18593        o: &mut CudaSlice<f32>,
18594        head_dim: usize,
18595        n_head: usize,
18596        n_head_kv: usize,
18597        base_dev: &CudaSlice<i32>,
18598        base_plus: i32,
18599        t: usize,
18600        scale: f32,
18601        window: usize,
18602        k_tok_bytes: usize,
18603        v_tok_bytes: usize,
18604        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18605    ) -> Result<(), Box<dyn std::error::Error>> {
18606        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
18607        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
18608        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
18609        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
18610        debug_assert!(head_dim == 256);
18611        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
18612        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
18613        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
18614        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
18615        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
18616        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
18617        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
18618        let sp = {
18619            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18620            let v = *SPW.get_or_init(|| {
18621                std::env::var("MEMRA_FA_SPW")
18622                    .ok()
18623                    .and_then(|x| x.parse().ok())
18624                    .unwrap_or(0)
18625            });
18626            if v >= 8 {
18627                v
18628            } else {
18629                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18630            }
18631        };
18632        let n_splits_max = (window + sp - 1) / sp;
18633        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18634        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
18635        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18636        let gqa = (n_head / n_head_kv).max(1) as u32;
18637        let o_len = t * n_head * n_splits_max * head_dim;
18638        let ml_len = t * n_head * n_splits_max;
18639        let mut part_guard = self.fa_part_pool.lock().unwrap();
18640        if part_guard
18641            .as_ref()
18642            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18643            .unwrap_or(true)
18644        {
18645            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18646            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18647            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18648            // later live allocations land at those addresses, and the next graph REPLAY writes
18649            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18650            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18651            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18652            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18653            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18654            // (total retired < final size).
18655            let old = part_guard.take();
18656            let (co, cm) = old
18657                .as_ref()
18658                .map(|pp| (pp.0.len(), pp.1.len()))
18659                .unwrap_or((0, 0));
18660            if let Some(old) = old {
18661                self.fa_part_retired.lock().unwrap().push(old);
18662            }
18663            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18664                eprintln!(
18665                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18666                    co, o_len, cm, ml_len
18667                );
18668            }
18669            *part_guard = Some((
18670                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18671                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18672                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18673            ));
18674        }
18675        let pg = part_guard.as_mut().unwrap();
18676        self.gpu
18677            .stream()
18678            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18679        self.gpu
18680            .stream()
18681            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18682        self.gpu
18683            .stream()
18684            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18685        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18686        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
18687        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
18688        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
18689        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
18690        // floor (deep-ctx broadcast win); register twin between.
18691        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18692        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
18693            std::env::var("MEMRA_FA_SMEM_TKV")
18694                .ok()
18695                .and_then(|v| v.parse().ok())
18696                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18697        });
18698        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
18699        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
18700        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
18701        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
18702        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
18703        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18704        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
18705        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
18706        // per (lane, format-module) keeps parity structural; the old register-i2 detour
18707        // (-33%) is retired.
18708        let wg = Self::wkv_on();
18709        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
18710        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
18711        let sp2 =
18712            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
18713        if sp2 {
18714            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18715            if Self::pdl_on() && Self::pdl_wb_on() {
18716                // wave-B2b: flavor mirrors wg.
18717                use cudarc::driver::{DevicePtr, DevicePtrMut};
18718                let s = &self.gpu.stream();
18719                let (pq, _b0) = q.device_ptr(s);
18720                let (pk, _b1) = k.device_ptr(s);
18721                let (pv, _b2) = v.device_ptr(s);
18722                let (po, _b3) = part_o.device_ptr_mut(s);
18723                let (pm, _b4) = part_m.device_ptr_mut(s);
18724                let (pl, _b5) = part_l.device_ptr_mut(s);
18725                let (pb, _b6) = base_dev.device_ptr(s);
18726                let mut ps = [
18727                    &pq as *const _ as *mut std::ffi::c_void,
18728                    &pk as *const _ as *mut _,
18729                    &pv as *const _ as *mut _,
18730                    &po as *const _ as *mut _,
18731                    &pm as *const _ as *mut _,
18732                    &pl as *const _ as *mut _,
18733                    &hd as *const _ as *mut _,
18734                    &nh as *const _ as *mut _,
18735                    &nhkv as *const _ as *mut _,
18736                    &pb as *const _ as *mut _,
18737                    &base_plus as *const _ as *mut _,
18738                    &scale as *const _ as *mut _,
18739                    &nspm as *const _ as *mut _,
18740                    &spk as *const _ as *mut _,
18741                    &ktb as *const _ as *mut _,
18742                    &vtb as *const _ as *mut _,
18743                    &wini as *const _ as *mut _,
18744                ];
18745                unsafe {
18746                    self.launch_pdl_flash(
18747                        wg,
18748                        "fa_decode_vec_q_rows_v4_w_sp",
18749                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18750                        (32, gqa + 1, 1),
18751                        sh,
18752                        &mut ps,
18753                    )?;
18754                }
18755            } else {
18756                let f = if wg {
18757                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
18758                } else {
18759                    self.func("fa_decode_vec_q_rows_v4_w_sp")
18760                };
18761                f.set_attribute(
18762                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18763                    sh as i32,
18764                )?;
18765                let cfg = LaunchConfig {
18766                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18767                    block_dim: (32, gqa + 1, 1),
18768                    shared_mem_bytes: sh,
18769                };
18770                let __s_b = self.gpu.stream();
18771                let mut b = __s_b.launch_builder(&f);
18772                b.arg(q)
18773                    .arg(k)
18774                    .arg(v)
18775                    .arg(&mut *part_o)
18776                    .arg(&mut *part_m)
18777                    .arg(&mut *part_l)
18778                    .arg(&hd)
18779                    .arg(&nh)
18780                    .arg(&nhkv)
18781                    .arg(base_dev)
18782                    .arg(&base_plus)
18783                    .arg(&scale)
18784                    .arg(&nspm)
18785                    .arg(&spk)
18786                    .arg(&ktb)
18787                    .arg(&vtb)
18788                    .arg(&wini);
18789                unsafe {
18790                    b.launch(cfg)?;
18791                }
18792            }
18793        } else {
18794            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
18795                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
18796                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18797                use cudarc::driver::{DevicePtr, DevicePtrMut};
18798                let s = &self.gpu.stream();
18799                let (pq, _b0) = q.device_ptr(s);
18800                let (pk, _b1) = k.device_ptr(s);
18801                let (pv, _b2) = v.device_ptr(s);
18802                let (po, _b3) = part_o.device_ptr_mut(s);
18803                let (pm, _b4) = part_m.device_ptr_mut(s);
18804                let (pl, _b5) = part_l.device_ptr_mut(s);
18805                let (pb, _b6) = base_dev.device_ptr(s);
18806                let mut ps = [
18807                    &pq as *const _ as *mut std::ffi::c_void,
18808                    &pk as *const _ as *mut _,
18809                    &pv as *const _ as *mut _,
18810                    &po as *const _ as *mut _,
18811                    &pm as *const _ as *mut _,
18812                    &pl as *const _ as *mut _,
18813                    &hd as *const _ as *mut _,
18814                    &nh as *const _ as *mut _,
18815                    &nhkv as *const _ as *mut _,
18816                    &pb as *const _ as *mut _,
18817                    &base_plus as *const _ as *mut _,
18818                    &scale as *const _ as *mut _,
18819                    &nspm as *const _ as *mut _,
18820                    &spk as *const _ as *mut _,
18821                    &ktb as *const _ as *mut _,
18822                    &vtb as *const _ as *mut _,
18823                    &wini as *const _ as *mut _,
18824                ];
18825                unsafe {
18826                    self.launch_pdl_flash(
18827                        wg,
18828                        "fa_decode_vec_q_rows_v4_w",
18829                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18830                        (32, gqa, 1),
18831                        sh,
18832                        &mut ps,
18833                    )?;
18834                }
18835            } else {
18836                let pick = |name: &str| {
18837                    if wg {
18838                        self.func_g(name)
18839                    } else {
18840                        self.func(name)
18841                    }
18842                };
18843                let (f, sh) = if fa_v4_at(window) {
18844                    let f = pick("fa_decode_vec_q_rows_v4_w");
18845                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
18846                } else if smem_tkv > 0 && window >= smem_tkv {
18847                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
18848                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
18849                    (
18850                        pick("fa_decode_vec_q_rows_smem_w"),
18851                        (2 * 32 * head_dim * 2) as u32,
18852                    )
18853                } else {
18854                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
18855                };
18856                f.set_attribute(
18857                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18858                    sh as i32,
18859                )?;
18860                let cfg = LaunchConfig {
18861                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18862                    block_dim: (32, gqa, 1),
18863                    shared_mem_bytes: sh,
18864                };
18865                let __s_b = self.gpu.stream();
18866                let mut b = __s_b.launch_builder(&f);
18867                b.arg(q)
18868                    .arg(k)
18869                    .arg(v)
18870                    .arg(&mut *part_o)
18871                    .arg(&mut *part_m)
18872                    .arg(&mut *part_l)
18873                    .arg(&hd)
18874                    .arg(&nh)
18875                    .arg(&nhkv)
18876                    .arg(base_dev)
18877                    .arg(&base_plus)
18878                    .arg(&scale)
18879                    .arg(&nspm)
18880                    .arg(&spk)
18881                    .arg(&ktb)
18882                    .arg(&vtb)
18883                    .arg(&wini);
18884                unsafe {
18885                    b.launch(cfg)?;
18886                }
18887            }
18888        }
18889        let cfg2 = LaunchConfig {
18890            grid_dim: (n_head as u32, t as u32, 1),
18891            block_dim: (head_dim as u32, 1, 1),
18892            shared_mem_bytes: 0,
18893        };
18894        if let Some((oq, od)) = q8_out {
18895            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
18896            // consumes the pair directly; the standalone quantize launch folds away.
18897            if Self::pdl_on() && Self::pdl_wb_on() {
18898                // wave-B2: flavor mirrors the builder's wg choice.
18899                use cudarc::driver::{DevicePtr, DevicePtrMut};
18900                let s = &self.gpu.stream();
18901                let (po, _g0) = part_o.device_ptr(s);
18902                let (pm, _g1) = part_m.device_ptr(s);
18903                let (pl, _g2) = part_l.device_ptr(s);
18904                let (pq, _g3) = oq.device_ptr_mut(s);
18905                let (pd, _g4) = od.device_ptr_mut(s);
18906                let mut ps = [
18907                    &po as *const _ as *mut std::ffi::c_void,
18908                    &pm as *const _ as *mut _,
18909                    &pl as *const _ as *mut _,
18910                    &pq as *const _ as *mut _,
18911                    &pd as *const _ as *mut _,
18912                    &hd as *const _ as *mut _,
18913                    &nh as *const _ as *mut _,
18914                    &nspm as *const _ as *mut _,
18915                    &spk as *const _ as *mut _,
18916                    &wini as *const _ as *mut _,
18917                ];
18918                unsafe {
18919                    self.launch_pdl_flash(
18920                        wg,
18921                        "fa_decode_combine_rows_w_q8_1",
18922                        cfg2.grid_dim,
18923                        cfg2.block_dim,
18924                        0,
18925                        &mut ps,
18926                    )?;
18927                }
18928                return Ok(());
18929            }
18930            let fc = if wg {
18931                self.func_g("fa_decode_combine_rows_w_q8_1")
18932            } else {
18933                self.func("fa_decode_combine_rows_w_q8_1")
18934            };
18935            let __s_b2 = self.gpu.stream();
18936            let mut b2 = __s_b2.launch_builder(&fc);
18937            b2.arg(&*part_o)
18938                .arg(&*part_m)
18939                .arg(&*part_l)
18940                .arg(oq)
18941                .arg(od)
18942                .arg(&hd)
18943                .arg(&nh)
18944                .arg(&nspm)
18945                .arg(&spk)
18946                .arg(&wini);
18947            unsafe {
18948                b2.launch(cfg2)?;
18949            }
18950            return Ok(());
18951        }
18952        let fc = if wg {
18953            self.func_g("fa_decode_combine_rows_w")
18954        } else {
18955            self.func("fa_decode_combine_rows_w")
18956        };
18957        let __s_b2 = self.gpu.stream();
18958        let mut b2 = __s_b2.launch_builder(&fc);
18959        b2.arg(&*part_o)
18960            .arg(&*part_m)
18961            .arg(&*part_l)
18962            .arg(o)
18963            .arg(&hd)
18964            .arg(&nh)
18965            .arg(&nspm)
18966            .arg(&spk)
18967            .arg(&wini);
18968        unsafe {
18969            b2.launch(cfg2)?;
18970        }
18971        Ok(())
18972    }
18973
18974    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
18975    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
18976    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
18977    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
18978    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
18979    #[allow(clippy::too_many_arguments)]
18980    pub fn fa_decode_rows_dc(
18981        &self,
18982        q: &CudaSlice<f32>,
18983        k: &cudarc::driver::CudaView<u8>,
18984        v: &cudarc::driver::CudaView<u8>,
18985        o: &mut CudaSlice<f32>,
18986        head_dim: usize,
18987        n_head: usize,
18988        n_head_kv: usize,
18989        base_dev: &CudaSlice<i32>,
18990        t_kv_upper: usize,
18991        t: usize,
18992        scale: f32,
18993        k_tok_bytes: usize,
18994        v_tok_bytes: usize,
18995        base_plus: i32,
18996        g: bool,
18997    ) -> Result<(), Box<dyn std::error::Error>> {
18998        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
18999        assert!(
19000            v4 || fa_v3_active(head_dim),
19001            "stream fa rows requires the v3 or v4 lane"
19002        );
19003        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
19004        if v4 {
19005            let sp = fa_split_keys(t_kv_upper, n_head_kv);
19006            let n_splits_max = (t_kv_upper + sp - 1) / sp;
19007            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
19008            let (nspm, spk) = (n_splits_max as i32, sp as i32);
19009            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19010            let gqa = (n_head / n_head_kv).max(1) as u32;
19011            let o_len = t * n_head * n_splits_max * head_dim;
19012            let ml_len = t * n_head * n_splits_max;
19013            let mut part_guard = self.fa_part_pool.lock().unwrap();
19014            if part_guard
19015                .as_ref()
19016                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19017                .unwrap_or(true)
19018            {
19019                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19020                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19021                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19022                // later live allocations land at those addresses, and the next graph REPLAY writes
19023                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19024                // output corruption began the burst after the trunk's t_kv growth first realloc'd
19025                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19026                // the baked addresses alive (single-stream: eager writes the new buffers, replays
19027                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19028                // (total retired < final size).
19029                let old = part_guard.take();
19030                let (co, cm) = old
19031                    .as_ref()
19032                    .map(|pp| (pp.0.len(), pp.1.len()))
19033                    .unwrap_or((0, 0));
19034                if let Some(old) = old {
19035                    self.fa_part_retired.lock().unwrap().push(old);
19036                }
19037                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19038                    eprintln!(
19039                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19040                        co, o_len, cm, ml_len
19041                    );
19042                }
19043                *part_guard = Some((
19044                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19045                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19046                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19047                ));
19048            }
19049            let pg = part_guard.as_mut().unwrap();
19050            self.gpu
19051                .stream()
19052                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19053            self.gpu
19054                .stream()
19055                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19056            self.gpu
19057                .stream()
19058                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19059            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19060            let f = if g {
19061                self.func_g("fa_decode_vec_q_rows_v4_dc")
19062            } else {
19063                self.func("fa_decode_vec_q_rows_v4_dc")
19064            };
19065            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
19066            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19067            f.set_attribute(
19068                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19069                sh as i32,
19070            )?;
19071            let cfg = LaunchConfig {
19072                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
19073                block_dim: (32, gqa, 1),
19074                shared_mem_bytes: sh,
19075            };
19076            let __s_b = self.gpu.stream();
19077            let mut b = __s_b.launch_builder(&f);
19078            b.arg(q)
19079                .arg(k)
19080                .arg(v)
19081                .arg(&mut *part_o)
19082                .arg(&mut *part_m)
19083                .arg(&mut *part_l)
19084                .arg(&hd)
19085                .arg(&nh)
19086                .arg(&nhkv)
19087                .arg(base_dev)
19088                .arg(&base_plus)
19089                .arg(&scale)
19090                .arg(&nspm)
19091                .arg(&spk)
19092                .arg(&ktb)
19093                .arg(&vtb);
19094            unsafe {
19095                b.launch(cfg)?;
19096            }
19097            let fc = self.func("fa_decode_combine_rows_dc");
19098            let cfg2 = LaunchConfig {
19099                grid_dim: (n_head as u32, t as u32, 1),
19100                block_dim: (head_dim as u32, 1, 1),
19101                shared_mem_bytes: 0,
19102            };
19103            let __s_b2 = self.gpu.stream();
19104            let mut b2 = __s_b2.launch_builder(&fc);
19105            b2.arg(&*part_o)
19106                .arg(&*part_m)
19107                .arg(&*part_l)
19108                .arg(o)
19109                .arg(&hd)
19110                .arg(&nh)
19111                .arg(base_dev)
19112                .arg(&base_plus)
19113                .arg(&nspm)
19114                .arg(&spk);
19115            unsafe {
19116                b2.launch(cfg2)?;
19117            }
19118            return Ok(());
19119        }
19120        let sp = fa_split_keys(t_kv_upper, n_head_kv);
19121        let n_splits_max = (t_kv_upper + sp - 1) / sp;
19122        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
19123        let (nspm, spk) = (n_splits_max as i32, sp as i32);
19124        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19125        let gqa = (n_head / n_head_kv).max(1) as u32;
19126        let o_len = t * n_head * n_splits_max * head_dim;
19127        let ml_len = t * n_head * n_splits_max;
19128        let mut part_guard = self.fa_part_pool.lock().unwrap();
19129        if part_guard
19130            .as_ref()
19131            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19132            .unwrap_or(true)
19133        {
19134            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19135            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19136            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19137            // later live allocations land at those addresses, and the next graph REPLAY writes
19138            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19139            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19140            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19141            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19142            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19143            // (total retired < final size).
19144            let old = part_guard.take();
19145            let (co, cm) = old
19146                .as_ref()
19147                .map(|pp| (pp.0.len(), pp.1.len()))
19148                .unwrap_or((0, 0));
19149            if let Some(old) = old {
19150                self.fa_part_retired.lock().unwrap().push(old);
19151            }
19152            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19153                eprintln!(
19154                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19155                    co, o_len, cm, ml_len
19156                );
19157            }
19158            *part_guard = Some((
19159                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19160                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19161                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19162            ));
19163        }
19164        let pg = part_guard.as_mut().unwrap();
19165        self.gpu
19166            .stream()
19167            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19168        self.gpu
19169            .stream()
19170            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19171        self.gpu
19172            .stream()
19173            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19174        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19175        let f = self.func("fa_decode_vec_q_rows_v3_dc");
19176        let sh = (32 * head_dim * 2) as u32;
19177        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19178        f.set_attribute(
19179            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19180            sh as i32,
19181        )?;
19182        let cfg = LaunchConfig {
19183            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
19184            block_dim: (32, gqa, 1),
19185            shared_mem_bytes: sh,
19186        };
19187        let __s_b = self.gpu.stream();
19188        let mut b = __s_b.launch_builder(&f);
19189        b.arg(q)
19190            .arg(k)
19191            .arg(v)
19192            .arg(&mut *part_o)
19193            .arg(&mut *part_m)
19194            .arg(&mut *part_l)
19195            .arg(&hd)
19196            .arg(&nh)
19197            .arg(&nhkv)
19198            .arg(base_dev)
19199            .arg(&scale)
19200            .arg(&nspm)
19201            .arg(&spk)
19202            .arg(&ktb)
19203            .arg(&vtb);
19204        unsafe {
19205            b.launch(cfg)?;
19206        }
19207        let fc = self.func("fa_decode_combine_rows_dc");
19208        let cfg2 = LaunchConfig {
19209            grid_dim: (n_head as u32, t as u32, 1),
19210            block_dim: (head_dim as u32, 1, 1),
19211            shared_mem_bytes: 0,
19212        };
19213        let plus0 = 0i32;
19214        let __s_b2 = self.gpu.stream();
19215        let mut b2 = __s_b2.launch_builder(&fc);
19216        b2.arg(&*part_o)
19217            .arg(&*part_m)
19218            .arg(&*part_l)
19219            .arg(o)
19220            .arg(&hd)
19221            .arg(&nh)
19222            .arg(base_dev)
19223            .arg(&plus0)
19224            .arg(&nspm)
19225            .arg(&spk);
19226        unsafe {
19227            b2.launch(cfg2)?;
19228        }
19229        Ok(())
19230    }
19231
19232    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
19233    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
19234    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
19235    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
19236    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
19237    ///
19238    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
19239    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
19240    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
19241    /// grouping (different but mathematically-equal log-sum-exp merge).
19242    pub fn fa_decode_dc(
19243        &self,
19244        q: &CudaSlice<f32>,
19245        k: &cudarc::driver::CudaView<u8>,
19246        v: &cudarc::driver::CudaView<u8>,
19247        o: &mut CudaSlice<f32>,
19248        head_dim: usize,
19249        n_head: usize,
19250        n_head_kv: usize,
19251        t_kv_dev: &CudaSlice<i32>,
19252        bucket_max: usize,
19253        scale: f32,
19254        k_tok_bytes: usize,
19255        v_tok_bytes: usize,
19256        g: bool,
19257    ) -> Result<(), Box<dyn std::error::Error>> {
19258        self.fa_decode_dc_q8(
19259            q,
19260            k,
19261            v,
19262            o,
19263            head_dim,
19264            n_head,
19265            n_head_kv,
19266            t_kv_dev,
19267            bucket_max,
19268            scale,
19269            k_tok_bytes,
19270            v_tok_bytes,
19271            g,
19272            None,
19273        )
19274    }
19275
19276    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
19277    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
19278    #[allow(clippy::too_many_arguments)]
19279    pub fn fa_decode_dc_q8(
19280        &self,
19281        q: &CudaSlice<f32>,
19282        k: &cudarc::driver::CudaView<u8>,
19283        v: &cudarc::driver::CudaView<u8>,
19284        o: &mut CudaSlice<f32>,
19285        head_dim: usize,
19286        n_head: usize,
19287        n_head_kv: usize,
19288        t_kv_dev: &CudaSlice<i32>,
19289        bucket_max: usize,
19290        scale: f32,
19291        k_tok_bytes: usize,
19292        v_tok_bytes: usize,
19293        g: bool,
19294        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
19295    ) -> Result<(), Box<dyn std::error::Error>> {
19296        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
19297        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
19298        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
19299        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
19300        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
19301        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
19302        // 2026-07-12).
19303        let mut fa_vec =
19304            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
19305        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
19306            fa_vec = false;
19307        } // mirror kvmod/geom
19308        let sp = fa_split_keys(bucket_max, n_head_kv);
19309        let n_splits = if fa_vec {
19310            ((bucket_max + sp - 1) / sp).max(1)
19311        } else {
19312            ((bucket_max + 255) / 256).max(1)
19313        };
19314        let o_len = n_head * n_splits * head_dim;
19315        let ml_len = n_head * n_splits;
19316        let mut part_guard = self.fa_part_pool.lock().unwrap();
19317        if part_guard
19318            .as_ref()
19319            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19320            .unwrap_or(true)
19321        {
19322            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19323            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19324            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19325            // later live allocations land at those addresses, and the next graph REPLAY writes
19326            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19327            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19328            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19329            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19330            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19331            // (total retired < final size).
19332            let old = part_guard.take();
19333            let (co, cm) = old
19334                .as_ref()
19335                .map(|pp| (pp.0.len(), pp.1.len()))
19336                .unwrap_or((0, 0));
19337            if let Some(old) = old {
19338                self.fa_part_retired.lock().unwrap().push(old);
19339            }
19340            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19341                eprintln!(
19342                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19343                    co, o_len, cm, ml_len
19344                );
19345            }
19346            *part_guard = Some((
19347                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19348                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19349                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19350            ));
19351        }
19352        let pg = part_guard.as_mut().unwrap();
19353        self.gpu
19354            .stream()
19355            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19356        self.gpu
19357            .stream()
19358            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19359        self.gpu
19360            .stream()
19361            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19362        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19363        let (hd, nh, nhkv, nsp) = (
19364            head_dim as i32,
19365            n_head as i32,
19366            n_head_kv as i32,
19367            n_splits as i32,
19368        );
19369        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19370        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
19371        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
19372        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
19373        let deep = fa_vec
19374            && head_dim == 256
19375            && fa_v4_at(bucket_max)
19376            && !g
19377            && fa_deep_at(bucket_max)
19378            && !matches!(fa_v4_mode(), "noB3" | "stage");
19379        let (f, cfg) = if fa_vec
19380            && head_dim == 512
19381            && bucket_max >= {
19382                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19383                *FA512_MIN_DC.get_or_init(|| {
19384                    std::env::var("MEMRA_FA512_MIN")
19385                        .ok()
19386                        .and_then(|v| v.parse().ok())
19387                        .unwrap_or(512)
19388                })
19389            } {
19390            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
19391            let gqa = (n_head / n_head_kv).max(1) as u32;
19392            (
19393                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
19394                LaunchConfig {
19395                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19396                    block_dim: (32, gqa, 1),
19397                    shared_mem_bytes: 0,
19398                },
19399            )
19400        } else if fa_vec && head_dim == 512 {
19401            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
19402            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
19403            let q_view = q.as_view();
19404            let mut o_view = o.as_view_mut();
19405            return self.fa_decode_scalar_unified(
19406                &q_view,
19407                k,
19408                v,
19409                &mut o_view,
19410                head_dim,
19411                n_head,
19412                n_head_kv,
19413                0,
19414                Some(t_kv_dev),
19415                scale,
19416                n_splits,
19417                sp,
19418                k_tok_bytes,
19419                v_tok_bytes,
19420                g,
19421                &mut *part_o,
19422                &mut *part_m,
19423                &mut *part_l,
19424                q8_out,
19425            );
19426        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
19427            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
19428            // incl the g-module route + raw-e4m3 sV sizing.
19429            let gqa = (n_head / n_head_kv).max(1) as u32;
19430            let fv = if g {
19431                self.func_g("fa_decode_vec_q_v4_dc")
19432            } else if deep {
19433                self.func("fa_decode_vec_q_v4_deep_dc")
19434            } else {
19435                self.func("fa_decode_vec_q_v4_dc")
19436            };
19437            let shmem =
19438                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
19439            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19440            fv.set_attribute(
19441                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19442                shmem as i32,
19443            )?;
19444            (
19445                fv,
19446                LaunchConfig {
19447                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19448                    block_dim: (32, gqa, 1),
19449                    shared_mem_bytes: shmem,
19450                },
19451            )
19452        } else if fa_vec && fa_v3_active(head_dim) {
19453            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
19454            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
19455            let gqa = (n_head / n_head_kv).max(1) as u32;
19456            let fv = if g {
19457                self.func_g("fa_decode_vec_q_v3_dc")
19458            } else {
19459                self.func("fa_decode_vec_q_v3_dc")
19460            };
19461            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
19462            (
19463                fv,
19464                LaunchConfig {
19465                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19466                    block_dim: (32, gqa, 1),
19467                    shared_mem_bytes: shmem,
19468                },
19469            )
19470        } else if fa_vec && fa_v2_on() {
19471            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
19472            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
19473            // a numeric config; eager, rows-verify and graph all switch together).
19474            let gqa = (n_head / n_head_kv).max(1) as u32;
19475            let fv = if g {
19476                self.func_g("fa_decode_vec_q_v2_dc")
19477            } else {
19478                self.func("fa_decode_vec_q_v2_dc")
19479            };
19480            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
19481            (
19482                fv,
19483                LaunchConfig {
19484                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19485                    block_dim: (32, gqa, 1),
19486                    shared_mem_bytes: shmem,
19487                },
19488            )
19489        } else if fa_vec {
19490            let gqa = (n_head / n_head_kv).max(1) as u32;
19491            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
19492            let fv = if g {
19493                self.func_g("fa_decode_vec_q_dc")
19494            } else {
19495                self.func("fa_decode_vec_q_dc")
19496            };
19497            (
19498                fv,
19499                LaunchConfig {
19500                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19501                    block_dim: (32, gqa, 1),
19502                    shared_mem_bytes: 0,
19503                },
19504            )
19505        } else {
19506            let q_view = q.as_view();
19507            let mut o_view = o.as_view_mut();
19508            return self.fa_decode_scalar_unified(
19509                &q_view,
19510                k,
19511                v,
19512                &mut o_view,
19513                head_dim,
19514                n_head,
19515                n_head_kv,
19516                0,
19517                Some(t_kv_dev),
19518                scale,
19519                n_splits,
19520                if fa_vec { sp } else { 256 },
19521                k_tok_bytes,
19522                v_tok_bytes,
19523                g,
19524                &mut *part_o,
19525                &mut *part_m,
19526                &mut *part_l,
19527                q8_out,
19528            );
19529        };
19530        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
19531        let __s_b = self.gpu.stream();
19532        let mut b = __s_b.launch_builder(&f);
19533        b.arg(q)
19534            .arg(k)
19535            .arg(v)
19536            .arg(&mut *part_o)
19537            .arg(&mut *part_m)
19538            .arg(&mut *part_l)
19539            .arg(&hd)
19540            .arg(&nh)
19541            .arg(&nhkv)
19542            .arg(t_kv_dev)
19543            .arg(&scale)
19544            .arg(&nsp)
19545            .arg(&ski)
19546            .arg(&ktb)
19547            .arg(&vtb);
19548        unsafe {
19549            b.launch(cfg)?;
19550        }
19551        let cfg2 = LaunchConfig {
19552            grid_dim: (n_head as u32, 1, 1),
19553            block_dim: (head_dim as u32, 1, 1),
19554            shared_mem_bytes: 0,
19555        };
19556        if let Some((oq, od)) = q8_out {
19557            let fc = if g {
19558                self.func_g("fa_decode_combine_q8_1")
19559            } else {
19560                self.fa_func("fa_decode_combine_q8_1", head_dim)
19561            };
19562            let __s_b2 = self.gpu.stream();
19563            let mut b2 = __s_b2.launch_builder(&fc);
19564            b2.arg(&*part_o)
19565                .arg(&*part_m)
19566                .arg(&*part_l)
19567                .arg(oq)
19568                .arg(od)
19569                .arg(&hd)
19570                .arg(&nh)
19571                .arg(&nsp);
19572            unsafe {
19573                b2.launch(cfg2)?;
19574            }
19575            return Ok(());
19576        }
19577        let fc = if g {
19578            self.func_g("fa_decode_combine_f32")
19579        } else {
19580            self.fa_func("fa_decode_combine_f32", head_dim)
19581        };
19582        let __s_b2 = self.gpu.stream();
19583        let mut b2 = __s_b2.launch_builder(&fc);
19584        b2.arg(&*part_o)
19585            .arg(&*part_m)
19586            .arg(&*part_l)
19587            .arg(o)
19588            .arg(&hd)
19589            .arg(&nh)
19590            .arg(&nsp);
19591        unsafe {
19592            b2.launch(cfg2)?;
19593        }
19594        Ok(())
19595    }
19596
19597    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
19598    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
19599    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
19600    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
19601    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
19602    pub fn fa_geom_eager(
19603        &self,
19604        t_kv: usize,
19605        head_dim: usize,
19606        n_head_kv: usize,
19607        g: bool,
19608    ) -> (bool, usize) {
19609        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
19610        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
19611        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
19612        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
19613        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
19614        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
19615        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
19616        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
19617        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
19618        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
19619        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
19620        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
19621        // family; everything else falls to the g-module scalar.
19622        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
19623        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
19624        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
19625        if g && head_dim == 256 && !fa_v4_at(t_kv) {
19626            fa_vec = false;
19627        }
19628        let sp = fa_split_keys(t_kv, n_head_kv);
19629        let n_splits = if fa_vec {
19630            ((t_kv + sp - 1) / sp).max(1)
19631        } else {
19632            ((t_kv + 255) / 256).max(1)
19633        };
19634        (fa_vec, n_splits)
19635    }
19636
19637    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
19638    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
19639    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
19640    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
19641    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
19642    pub fn fa_bucket_key(
19643        &self,
19644        t_kv: usize,
19645        head_dim: usize,
19646        n_head_kv: usize,
19647        g: bool,
19648    ) -> (bool, usize) {
19649        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
19650    }
19651
19652    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
19653    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
19654    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
19655    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
19656    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
19657    /// device data) — every per-step varying scalar must come from a device counter. Returns the
19658    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
19659    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
19660    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
19661    /// replays (transients returning to the pool get reused by unrelated work and corrupt
19662    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
19663    pub fn capture_graph_retained<F>(
19664        &self,
19665        step: F,
19666    ) -> Result<
19667        (
19668            cudarc::driver::CudaGraph,
19669            Vec<Box<dyn std::any::Any + Send>>,
19670        ),
19671        Box<dyn std::error::Error>,
19672    >
19673    where
19674        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19675    {
19676        use cudarc::driver::sys::CUgraphInstantiate_flags;
19677        self.capture_graph_retained_flags(
19678            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19679            step,
19680        )
19681    }
19682
19683    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
19684    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
19685    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
19686    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
19687    pub fn capture_graph_retained_flags<F>(
19688        &self,
19689        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
19690        mut step: F,
19691    ) -> Result<
19692        (
19693            cudarc::driver::CudaGraph,
19694            Vec<Box<dyn std::any::Any + Send>>,
19695        ),
19696        Box<dyn std::error::Error>,
19697    >
19698    where
19699        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19700    {
19701        use cudarc::driver::sys::CUstreamCaptureMode;
19702        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
19703        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
19704        // while the capture region is open become dead copy NODES replayed every launch
19705        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
19706        // warmup runs allocate the same transient sequence at the same pool addresses, so
19707        // retaining the warmup clones preserves the draft-graph fix without polluting the
19708        // captured graph.
19709        self.capture_keep.lock().unwrap().clear();
19710        let was_tracking = self.gpu.ctx.is_event_tracking();
19711        if was_tracking {
19712            unsafe {
19713                self.gpu.ctx.disable_event_tracking();
19714            }
19715        }
19716        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19717            self.capture_keep_on
19718                .store(true, std::sync::atomic::Ordering::Relaxed);
19719            let w = (|| {
19720                step(self)?;
19721                step(self)
19722            })();
19723            self.capture_keep_on
19724                .store(false, std::sync::atomic::Ordering::Relaxed);
19725            w?;
19726            self.gpu.stream().synchronize()?;
19727            self.gpu
19728                .stream()
19729                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19730            let r = step(self);
19731            let g = self.gpu.stream().end_capture(flags);
19732            r?;
19733            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19734            graph.upload()?;
19735            Ok(graph)
19736        };
19737        let result = run();
19738        self.capture_keep_on
19739            .store(false, std::sync::atomic::Ordering::Relaxed);
19740        if was_tracking {
19741            unsafe {
19742                self.gpu.ctx.enable_event_tracking();
19743            }
19744        }
19745        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
19746        Ok((result?, keeper))
19747    }
19748
19749    pub fn capture_graph<F>(
19750        &self,
19751        mut step: F,
19752    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
19753    where
19754        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19755    {
19756        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
19757        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
19758        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
19759        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
19760        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
19761        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
19762        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
19763        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
19764        let was_tracking = self.gpu.ctx.is_event_tracking();
19765        if was_tracking {
19766            unsafe {
19767                self.gpu.ctx.disable_event_tracking();
19768            }
19769        }
19770        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
19771        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
19772        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
19773        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
19774        // measure that scan's real cost on the generic path. Diagnostic door only; the
19775        // default stays AUTO_FREE until a measured A/B justifies moving it.
19776        let iflag = {
19777            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
19778            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
19779                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
19780                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
19781                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
19782                Ok("priority") => {
19783                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
19784                }
19785                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19786            })
19787        };
19788        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
19789        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
19790        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
19791        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
19792        // eager step executions and are node-count-invariant. Printing the split bounds the
19793        // refactor's ceiling instead of assuming it.
19794        let ct = {
19795            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19796            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
19797        };
19798        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
19799        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
19800        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
19801        // chased, and node-count-invariant, so no capture-body refactor could touch it.
19802        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
19803        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
19804        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
19805        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
19806        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
19807        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
19808        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
19809        // grow and never frees, resident counters/scratch, cache set in place), and the
19810        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
19811        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
19812        // settling and pool mapping. Arbitrated adversarially, not by taste:
19813        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
19814        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
19815        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
19816        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
19817        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
19818        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
19819        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
19820        let warmups = {
19821            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19822            *W.get_or_init(|| {
19823                std::env::var("MEMRA_GRAPH_WARMUPS")
19824                    .ok()
19825                    .and_then(|v| v.parse().ok())
19826                    .filter(|n| *n >= 1)
19827                    .unwrap_or(1)
19828            })
19829        };
19830        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19831            let t_w = std::time::Instant::now();
19832            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
19833            for _ in 0..warmups {
19834                step(self)?;
19835            }
19836            self.gpu.stream().synchronize()?;
19837            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
19838            // capture the third run.
19839            let t_c = std::time::Instant::now();
19840            self.gpu
19841                .stream()
19842                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19843            // If the body errors mid-capture, end the capture before propagating so the stream isn't
19844            // left in a capturing state.
19845            let r = step(self);
19846            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
19847            let t_i = std::time::Instant::now();
19848            let g = self.gpu.stream().end_capture(iflag);
19849            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
19850            r?;
19851            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19852            let t_u = std::time::Instant::now();
19853            graph.upload()?;
19854            if ct {
19855                println!(
19856                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
19857                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
19858                    t_u.elapsed().as_secs_f64() * 1e3
19859                );
19860            }
19861            Ok(graph)
19862        };
19863        let result = run();
19864        if was_tracking {
19865            unsafe {
19866                self.gpu.ctx.enable_event_tracking();
19867            }
19868        }
19869        result
19870    }
19871
19872    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
19873    pub fn gdn_scan_s128_view(
19874        &self,
19875        q: &CudaSlice<f32>,
19876        k: &CudaSlice<f32>,
19877        v: &CudaSlice<f32>,
19878        g: &CudaSlice<f32>,
19879        beta: &CudaSlice<f32>,
19880        state_in: &cudarc::driver::CudaView<f32>,
19881        state_out: &mut cudarc::driver::CudaViewMut<f32>,
19882        o: &mut CudaSlice<f32>,
19883        n_head: usize,
19884        t: usize,
19885        scale: f32,
19886    ) -> Result<(), Box<dyn std::error::Error>> {
19887        let f = self.func("gdn_scan_s128");
19888        const S_V: u32 = 128;
19889        const WARP: u32 = 32;
19890        const COLS: u32 = 4;
19891        let cfg = LaunchConfig {
19892            grid_dim: (n_head as u32, 1, S_V / COLS),
19893            block_dim: (WARP, COLS, 1),
19894            shared_mem_bytes: 0,
19895        };
19896        let (h, ti) = (n_head as i32, t as i32);
19897        let __s_b = self.gpu.stream();
19898        let mut b = __s_b.launch_builder(&f);
19899        b.arg(q)
19900            .arg(k)
19901            .arg(v)
19902            .arg(g)
19903            .arg(beta)
19904            .arg(state_in)
19905            .arg(state_out)
19906            .arg(o)
19907            .arg(&h)
19908            .arg(&ti)
19909            .arg(&scale);
19910        unsafe {
19911            b.launch(cfg)?;
19912        }
19913        Ok(())
19914    }
19915
19916    /// conv1d where the input is a CudaView (resident conv state assembled in place).
19917    pub fn ssm_conv1d_view(
19918        &self,
19919        x: &cudarc::driver::CudaView<f32>,
19920        w: &CudaSlice<f32>,
19921        y: &mut CudaSlice<f32>,
19922        conv_dim: usize,
19923        t: usize,
19924        d_conv: usize,
19925        silu: bool,
19926    ) -> Result<(), Box<dyn std::error::Error>> {
19927        let f = self.func("ssm_conv1d_silu_f32");
19928        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
19929        let cfg = LaunchConfig {
19930            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19931            block_dim: (256, 1, 1),
19932            shared_mem_bytes: 0,
19933        };
19934        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19935        let __s_b = self.gpu.stream();
19936        let mut b = __s_b.launch_builder(&f);
19937        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19938        unsafe {
19939            b.launch(cfg)?;
19940        }
19941        Ok(())
19942    }
19943
19944    /// Depthwise causal conv1d + optional SiLU.
19945    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
19946    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
19947    /// FUSED prefill conv (token-major input, zero left-state): replaces
19948    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
19949    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
19950    pub fn ssm_conv1d_tm(
19951        &self,
19952        qkv_tm: &CudaSlice<f32>,
19953        w: &CudaSlice<f32>,
19954        y: &mut CudaSlice<f32>,
19955        conv_dim: usize,
19956        t: usize,
19957        d_conv: usize,
19958    ) -> Result<(), Box<dyn std::error::Error>> {
19959        let f = self.func("ssm_conv1d_tm_f32");
19960        let cfg = LaunchConfig {
19961            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19962            block_dim: (256, 1, 1),
19963            shared_mem_bytes: 0,
19964        };
19965        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19966        let __s_b = self.gpu.stream();
19967        let mut b = __s_b.launch_builder(&f);
19968        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
19969        unsafe {
19970            b.launch(cfg)?;
19971        }
19972        Ok(())
19973    }
19974
19975    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
19976    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
19977    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
19978    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
19979    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
19980    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
19981    /// columns; the final ring == what T sequential decode ring rolls leave).
19982    pub fn ssm_conv1d_tm_state(
19983        &self,
19984        qkv_tm: &CudaSlice<f32>,
19985        conv_state: &mut CudaSlice<f32>,
19986        w: &CudaSlice<f32>,
19987        y: &mut CudaSlice<f32>,
19988        conv_dim: usize,
19989        t: usize,
19990        d_conv: usize,
19991    ) -> Result<(), Box<dyn std::error::Error>> {
19992        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
19993    }
19994
19995    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
19996    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
19997    #[allow(clippy::too_many_arguments)]
19998    pub fn ssm_conv1d_tm_state_pad(
19999        &self,
20000        qkv_tm: &CudaSlice<f32>,
20001        conv_state: &mut CudaSlice<f32>,
20002        w: &CudaSlice<f32>,
20003        y: &mut CudaSlice<f32>,
20004        conv_dim: usize,
20005        t: usize,
20006        d_conv: usize,
20007        pad_len: Option<&CudaSlice<i32>>,
20008    ) -> Result<(), Box<dyn std::error::Error>> {
20009        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
20010        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
20011        // the window kernel both read the pre-roll ring; the roll launches after both) — but
20012        // cloning first keeps the ordering trivially correct under any future stream split.
20013        let ring_old = if t < d_conv - 1 {
20014            Some(self.clone_dtod(conv_state)?)
20015        } else {
20016            None
20017        };
20018        {
20019            let f = self.func("ssm_conv1d_tm_state_f32");
20020            let cfg = LaunchConfig {
20021                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20022                block_dim: (256, 1, 1),
20023                shared_mem_bytes: 0,
20024            };
20025            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20026            let __s_b = self.gpu.stream();
20027            let mut b = __s_b.launch_builder(&f);
20028            b.arg(qkv_tm)
20029                .arg(&*conv_state)
20030                .arg(w)
20031                .arg(y)
20032                .arg(&cd)
20033                .arg(&ti)
20034                .arg(&dc);
20035            unsafe {
20036                b.launch(cfg)?;
20037            }
20038        }
20039        match (ring_old, pad_len) {
20040            (None, Some(len_d)) => {
20041                let f = self.func("ssm_conv_ring_update_dev_f32");
20042                let n = conv_dim * (d_conv - 1);
20043                let cfg = LaunchConfig::for_num_elems(n as u32);
20044                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20045                let __s_b = self.gpu.stream();
20046                let mut b = __s_b.launch_builder(&f);
20047                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20048                unsafe {
20049                    b.launch(cfg)?;
20050                }
20051            }
20052            (None, None) => {
20053                let f = self.func("ssm_conv_ring_update_f32");
20054                let n = conv_dim * (d_conv - 1);
20055                let cfg = LaunchConfig::for_num_elems(n as u32);
20056                let (cd, ti, dc) = (conv_dim as i32, t 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(&cd).arg(&ti).arg(&dc);
20060                unsafe {
20061                    b.launch(cfg)?;
20062                }
20063            }
20064            (Some(old), _) => {
20065                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
20066            }
20067        }
20068        Ok(())
20069    }
20070
20071    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
20072    pub fn ssm_conv1d_tm_state_pad_v(
20073        &self,
20074        qkv_tm: &cudarc::driver::CudaView<f32>,
20075        conv_state: &mut CudaSlice<f32>,
20076        w: &CudaSlice<f32>,
20077        y: &mut CudaSlice<f32>,
20078        conv_dim: usize,
20079        t: usize,
20080        d_conv: usize,
20081        pad_len: Option<&CudaSlice<i32>>,
20082    ) -> Result<(), Box<dyn std::error::Error>> {
20083        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
20084        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
20085        // the window kernel both read the pre-roll ring; the roll launches after both) — but
20086        // cloning first keeps the ordering trivially correct under any future stream split.
20087        let ring_old = if t < d_conv - 1 {
20088            Some(self.clone_dtod(conv_state)?)
20089        } else {
20090            None
20091        };
20092        {
20093            let f = self.func("ssm_conv1d_tm_state_f32");
20094            let cfg = LaunchConfig {
20095                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20096                block_dim: (256, 1, 1),
20097                shared_mem_bytes: 0,
20098            };
20099            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20100            let __s_b = self.gpu.stream();
20101            let mut b = __s_b.launch_builder(&f);
20102            b.arg(qkv_tm)
20103                .arg(&*conv_state)
20104                .arg(w)
20105                .arg(y)
20106                .arg(&cd)
20107                .arg(&ti)
20108                .arg(&dc);
20109            unsafe {
20110                b.launch(cfg)?;
20111            }
20112        }
20113        match (ring_old, pad_len) {
20114            (None, Some(len_d)) => {
20115                let f = self.func("ssm_conv_ring_update_dev_f32");
20116                let n = conv_dim * (d_conv - 1);
20117                let cfg = LaunchConfig::for_num_elems(n as u32);
20118                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20119                let __s_b = self.gpu.stream();
20120                let mut b = __s_b.launch_builder(&f);
20121                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20122                unsafe {
20123                    b.launch(cfg)?;
20124                }
20125            }
20126            (None, None) => {
20127                let f = self.func("ssm_conv_ring_update_f32");
20128                let n = conv_dim * (d_conv - 1);
20129                let cfg = LaunchConfig::for_num_elems(n as u32);
20130                let (cd, ti, dc) = (conv_dim as i32, t 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(&cd).arg(&ti).arg(&dc);
20134                unsafe {
20135                    b.launch(cfg)?;
20136                }
20137            }
20138            (Some(_), _) => unreachable!(
20139                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
20140            ),
20141        }
20142        Ok(())
20143    }
20144
20145    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
20146    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
20147    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
20148    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
20149    pub fn ssm_conv_ring_rebuild(
20150        &self,
20151        qkv_tm: &CudaSlice<f32>,
20152        ring_old: &CudaSlice<f32>,
20153        conv_state: &mut CudaSlice<f32>,
20154        conv_dim: usize,
20155        tc: usize,
20156        d_conv: usize,
20157    ) -> Result<(), Box<dyn std::error::Error>> {
20158        let f = self.func("ssm_conv_ring_rebuild_f32");
20159        let n = conv_dim * (d_conv - 1);
20160        let cfg = LaunchConfig::for_num_elems(n as u32);
20161        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
20162        let __s_b = self.gpu.stream();
20163        let mut b = __s_b.launch_builder(&f);
20164        b.arg(qkv_tm)
20165            .arg(ring_old)
20166            .arg(conv_state)
20167            .arg(&cd)
20168            .arg(&ti)
20169            .arg(&dc);
20170        unsafe {
20171            b.launch(cfg)?;
20172        }
20173        Ok(())
20174    }
20175
20176    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
20177    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
20178    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
20179    /// the argmax + run-spec gates are the authority.
20180    #[allow(clippy::too_many_arguments)]
20181    pub fn gdn_prep_decode(
20182        &self,
20183        conv_out: &CudaSlice<f32>,
20184        beta_raw: &CudaSlice<f32>,
20185        alpha: &CudaSlice<f32>,
20186        dt_bias: &CudaSlice<f32>,
20187        a: &CudaSlice<f32>,
20188        q_l2: &mut CudaSlice<f32>,
20189        k_l2: &mut CudaSlice<f32>,
20190        v_g: &mut CudaSlice<f32>,
20191        beta: &mut CudaSlice<f32>,
20192        g_log: &mut CudaSlice<f32>,
20193        d_state: usize,
20194        num_v: usize,
20195        num_k: usize,
20196        key_dim: usize,
20197        eps: f32,
20198    ) -> Result<(), Box<dyn std::error::Error>> {
20199        let f = self.func("gdn_prep_decode_f32");
20200        let cfg = LaunchConfig {
20201            grid_dim: (num_v as u32, 1, 1),
20202            block_dim: (32, 4, 1),
20203            shared_mem_bytes: 0,
20204        };
20205        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20206        let __s_b = self.gpu.stream();
20207        let mut b = __s_b.launch_builder(&f);
20208        b.arg(conv_out)
20209            .arg(beta_raw)
20210            .arg(alpha)
20211            .arg(dt_bias)
20212            .arg(a)
20213            .arg(q_l2)
20214            .arg(k_l2)
20215            .arg(v_g)
20216            .arg(beta)
20217            .arg(g_log)
20218            .arg(&ds)
20219            .arg(&nv)
20220            .arg(&nk)
20221            .arg(&kd)
20222            .arg(&eps);
20223        unsafe {
20224            b.launch(cfg)?;
20225        }
20226        Ok(())
20227    }
20228
20229    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
20230    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
20231    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
20232    #[allow(clippy::too_many_arguments)]
20233    pub fn ssm_conv1d_gdn(
20234        &self,
20235        qkv_tm: &CudaSlice<f32>,
20236        w: &CudaSlice<f32>,
20237        q_g: &mut CudaSlice<f32>,
20238        k_g: &mut CudaSlice<f32>,
20239        v_g: &mut CudaSlice<f32>,
20240        conv_dim: usize,
20241        t: usize,
20242        d_conv: usize,
20243        d_state: usize,
20244        num_v: usize,
20245        num_k: usize,
20246        key_dim: usize,
20247    ) -> Result<(), Box<dyn std::error::Error>> {
20248        let f = self.func("ssm_conv1d_gdn_f32");
20249        let cfg = LaunchConfig {
20250            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20251            block_dim: (256, 1, 1),
20252            shared_mem_bytes: 0,
20253        };
20254        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20255        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20256        let __s_b = self.gpu.stream();
20257        let mut b = __s_b.launch_builder(&f);
20258        b.arg(qkv_tm)
20259            .arg(w)
20260            .arg(q_g)
20261            .arg(k_g)
20262            .arg(v_g)
20263            .arg(&cd)
20264            .arg(&ti)
20265            .arg(&dc)
20266            .arg(&ds)
20267            .arg(&nv)
20268            .arg(&nk)
20269            .arg(&kd);
20270        unsafe {
20271            b.launch(cfg)?;
20272        }
20273        Ok(())
20274    }
20275
20276    pub fn ssm_conv1d(
20277        &self,
20278        x: &CudaSlice<f32>,
20279        w: &CudaSlice<f32>,
20280        y: &mut CudaSlice<f32>,
20281        conv_dim: usize,
20282        t: usize,
20283        d_conv: usize,
20284        silu: bool,
20285    ) -> Result<(), Box<dyn std::error::Error>> {
20286        let f = self.func("ssm_conv1d_silu_f32");
20287        let cfg = LaunchConfig {
20288            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
20289            block_dim: (256, 1, 1),
20290            shared_mem_bytes: 0,
20291        };
20292        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
20293        let __s_b = self.gpu.stream();
20294        let mut b = __s_b.launch_builder(&f);
20295        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
20296        unsafe {
20297            b.launch(cfg)?;
20298        }
20299        Ok(())
20300    }
20301
20302    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
20303    /// o:[128,H,T]. Single sequence.
20304    pub fn gdn_scan_s128(
20305        &self,
20306        q: &CudaSlice<f32>,
20307        k: &CudaSlice<f32>,
20308        v: &CudaSlice<f32>,
20309        g: &CudaSlice<f32>,
20310        beta: &CudaSlice<f32>,
20311        state_in: &CudaSlice<f32>,
20312        state_out: &mut CudaSlice<f32>,
20313        o: &mut CudaSlice<f32>,
20314        n_head: usize,
20315        t: usize,
20316        scale: f32,
20317    ) -> Result<(), Box<dyn std::error::Error>> {
20318        let f = self.func("gdn_scan_s128");
20319        const S_V: u32 = 128;
20320        const WARP: u32 = 32;
20321        const COLS_PER_BLOCK: u32 = 4;
20322        let cfg = LaunchConfig {
20323            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
20324            block_dim: (WARP, COLS_PER_BLOCK, 1),
20325            shared_mem_bytes: 0,
20326        };
20327        let (h, ti) = (n_head as i32, t as i32);
20328        let __s_b = self.gpu.stream();
20329        let mut b = __s_b.launch_builder(&f);
20330        b.arg(q)
20331            .arg(k)
20332            .arg(v)
20333            .arg(g)
20334            .arg(beta)
20335            .arg(state_in)
20336            .arg(state_out)
20337            .arg(o)
20338            .arg(&h)
20339            .arg(&ti)
20340            .arg(&scale);
20341        unsafe {
20342            b.launch(cfg)?;
20343        }
20344        Ok(())
20345    }
20346
20347    // ==== B2' batched decode state ops (decode_batch.rs) ====
20348    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
20349    // Bodies are the single-seq kernels per sequence — bit-identical per row.
20350
20351    #[allow(clippy::too_many_arguments)]
20352    pub fn ssm_conv1d_fused_decode_b(
20353        &self,
20354        qkv_cols: &CudaSlice<f32>,
20355        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20356        w: &CudaSlice<f32>,
20357        conv_outs: &mut CudaSlice<f32>,
20358        conv_dim: usize,
20359        d_conv: usize,
20360        b_n: usize,
20361    ) -> Result<(), Box<dyn std::error::Error>> {
20362        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20363        let cfg = LaunchConfig {
20364            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20365            block_dim: (256, 1, 1),
20366            shared_mem_bytes: 0,
20367        };
20368        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20369        let __s_b = self.gpu.stream();
20370        let mut b = __s_b.launch_builder(&f);
20371        b.arg(qkv_cols)
20372            .arg(conv_state_ptrs)
20373            .arg(w)
20374            .arg(conv_outs)
20375            .arg(&cd)
20376            .arg(&dc);
20377        unsafe {
20378            b.launch(cfg)?;
20379        }
20380        Ok(())
20381    }
20382
20383    #[allow(clippy::too_many_arguments)]
20384    pub fn gdn_prep_decode_b(
20385        &self,
20386        conv_outs: &CudaSlice<f32>,
20387        beta_raws: &CudaSlice<f32>,
20388        alphas: &CudaSlice<f32>,
20389        dt_bias: &CudaSlice<f32>,
20390        a: &CudaSlice<f32>,
20391        q_l2: &mut CudaSlice<f32>,
20392        k_l2: &mut CudaSlice<f32>,
20393        v_g: &mut CudaSlice<f32>,
20394        beta: &mut CudaSlice<f32>,
20395        g_log: &mut CudaSlice<f32>,
20396        d_state: usize,
20397        num_v: usize,
20398        num_k: usize,
20399        key_dim: usize,
20400        eps: f32,
20401        conv_dim: usize,
20402        b_n: usize,
20403    ) -> Result<(), Box<dyn std::error::Error>> {
20404        let f = self.func("gdn_prep_decode_b_f32");
20405        let cfg = LaunchConfig {
20406            grid_dim: (num_v as u32, 1, b_n as u32),
20407            block_dim: (32, 4, 1),
20408            shared_mem_bytes: 0,
20409        };
20410        let (ds, nv, nk, kd, cd) = (
20411            d_state as i32,
20412            num_v as i32,
20413            num_k as i32,
20414            key_dim as i32,
20415            conv_dim as i32,
20416        );
20417        let __s_b = self.gpu.stream();
20418        let mut b = __s_b.launch_builder(&f);
20419        b.arg(conv_outs)
20420            .arg(beta_raws)
20421            .arg(alphas)
20422            .arg(dt_bias)
20423            .arg(a)
20424            .arg(q_l2)
20425            .arg(k_l2)
20426            .arg(v_g)
20427            .arg(beta)
20428            .arg(g_log)
20429            .arg(&ds)
20430            .arg(&nv)
20431            .arg(&nk)
20432            .arg(&kd)
20433            .arg(&eps)
20434            .arg(&cd);
20435        unsafe {
20436            b.launch(cfg)?;
20437        }
20438        Ok(())
20439    }
20440
20441    #[allow(clippy::too_many_arguments)]
20442    pub fn gdn_scan_s128_batched(
20443        &self,
20444        q: &CudaSlice<f32>,
20445        k: &CudaSlice<f32>,
20446        v: &CudaSlice<f32>,
20447        g: &CudaSlice<f32>,
20448        beta: &CudaSlice<f32>,
20449        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20450        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20451        o: &mut CudaSlice<f32>,
20452        n_head: usize,
20453        b_n: usize,
20454        scale: f32,
20455    ) -> Result<(), Box<dyn std::error::Error>> {
20456        let f = self.func("gdn_scan_s128_b");
20457        const S_V: u32 = 128;
20458        const WARP: u32 = 32;
20459        const COLS_PER_BLOCK: u32 = 4;
20460        let cfg = LaunchConfig {
20461            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20462            block_dim: (WARP, COLS_PER_BLOCK, 1),
20463            shared_mem_bytes: 0,
20464        };
20465        let h = n_head as i32;
20466        let __s_b = self.gpu.stream();
20467        let mut b = __s_b.launch_builder(&f);
20468        b.arg(q)
20469            .arg(k)
20470            .arg(v)
20471            .arg(g)
20472            .arg(beta)
20473            .arg(state_in_ptrs)
20474            .arg(state_out_ptrs)
20475            .arg(o)
20476            .arg(&h)
20477            .arg(&scale);
20478        unsafe {
20479            b.launch(cfg)?;
20480        }
20481        Ok(())
20482    }
20483
20484    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
20485    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
20486    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
20487    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
20488    /// numeric class; only the pointer arithmetic moved host-side.
20489    #[allow(clippy::too_many_arguments)]
20490    pub fn ssm_conv1d_fused_decode_b_view(
20491        &self,
20492        qkv_cols: &cudarc::driver::CudaView<f32>,
20493        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20494        w: &CudaSlice<f32>,
20495        conv_outs: &mut CudaSlice<f32>,
20496        conv_dim: usize,
20497        d_conv: usize,
20498        b_n: usize,
20499    ) -> Result<(), Box<dyn std::error::Error>> {
20500        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20501        let cfg = LaunchConfig {
20502            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20503            block_dim: (256, 1, 1),
20504            shared_mem_bytes: 0,
20505        };
20506        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20507        let __s_b = self.gpu.stream();
20508        let mut b = __s_b.launch_builder(&f);
20509        b.arg(qkv_cols)
20510            .arg(conv_state_ptrs)
20511            .arg(w)
20512            .arg(conv_outs)
20513            .arg(&cd)
20514            .arg(&dc);
20515        unsafe {
20516            b.launch(cfg)?;
20517        }
20518        Ok(())
20519    }
20520
20521    #[allow(clippy::too_many_arguments)]
20522    pub fn gdn_prep_decode_b_view(
20523        &self,
20524        conv_outs: &CudaSlice<f32>,
20525        beta_raws: &cudarc::driver::CudaView<f32>,
20526        alphas: &cudarc::driver::CudaView<f32>,
20527        dt_bias: &CudaSlice<f32>,
20528        a: &CudaSlice<f32>,
20529        q_l2: &mut CudaSlice<f32>,
20530        k_l2: &mut CudaSlice<f32>,
20531        v_g: &mut CudaSlice<f32>,
20532        beta: &mut CudaSlice<f32>,
20533        g_log: &mut CudaSlice<f32>,
20534        d_state: usize,
20535        num_v: usize,
20536        num_k: usize,
20537        key_dim: usize,
20538        eps: f32,
20539        conv_dim: usize,
20540        b_n: usize,
20541    ) -> Result<(), Box<dyn std::error::Error>> {
20542        let f = self.func("gdn_prep_decode_b_f32");
20543        let cfg = LaunchConfig {
20544            grid_dim: (num_v as u32, 1, b_n as u32),
20545            block_dim: (32, 4, 1),
20546            shared_mem_bytes: 0,
20547        };
20548        let (ds, nv, nk, kd, cd) = (
20549            d_state as i32,
20550            num_v as i32,
20551            num_k as i32,
20552            key_dim as i32,
20553            conv_dim as i32,
20554        );
20555        let __s_b = self.gpu.stream();
20556        let mut b = __s_b.launch_builder(&f);
20557        b.arg(conv_outs)
20558            .arg(beta_raws)
20559            .arg(alphas)
20560            .arg(dt_bias)
20561            .arg(a)
20562            .arg(q_l2)
20563            .arg(k_l2)
20564            .arg(v_g)
20565            .arg(beta)
20566            .arg(g_log)
20567            .arg(&ds)
20568            .arg(&nv)
20569            .arg(&nk)
20570            .arg(&kd)
20571            .arg(&eps)
20572            .arg(&cd);
20573        unsafe {
20574            b.launch(cfg)?;
20575        }
20576        Ok(())
20577    }
20578
20579    #[allow(clippy::too_many_arguments)]
20580    pub fn gdn_scan_s128_batched_view(
20581        &self,
20582        q: &CudaSlice<f32>,
20583        k: &CudaSlice<f32>,
20584        v: &CudaSlice<f32>,
20585        g: &CudaSlice<f32>,
20586        beta: &CudaSlice<f32>,
20587        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20588        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20589        o: &mut cudarc::driver::CudaViewMut<f32>,
20590        n_head: usize,
20591        b_n: usize,
20592        scale: f32,
20593    ) -> Result<(), Box<dyn std::error::Error>> {
20594        let f = self.func("gdn_scan_s128_b");
20595        const S_V: u32 = 128;
20596        const WARP: u32 = 32;
20597        const COLS_PER_BLOCK: u32 = 4;
20598        let cfg = LaunchConfig {
20599            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20600            block_dim: (WARP, COLS_PER_BLOCK, 1),
20601            shared_mem_bytes: 0,
20602        };
20603        let h = n_head as i32;
20604        let __s_b = self.gpu.stream();
20605        let mut b = __s_b.launch_builder(&f);
20606        b.arg(q)
20607            .arg(k)
20608            .arg(v)
20609            .arg(g)
20610            .arg(beta)
20611            .arg(state_in_ptrs)
20612            .arg(state_out_ptrs)
20613            .arg(o)
20614            .arg(&h)
20615            .arg(&scale);
20616        unsafe {
20617            b.launch(cfg)?;
20618        }
20619        Ok(())
20620    }
20621
20622    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
20623    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
20624    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
20625    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
20626    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
20627    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
20628    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
20629    /// identity law); prime_cache/forward/forward_last are the only callers.
20630    pub fn gdn_chunked_enabled() -> bool {
20631        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
20632        *E.get_or_init(|| {
20633            std::env::var("MEMRA_GDN_CHUNKED")
20634                .map(|v| v != "0")
20635                .unwrap_or(true)
20636        })
20637    }
20638
20639    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
20640    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
20641    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
20642    /// of 32 in [32, 128] (kernel row mappings require it).
20643    pub fn gdn_chunk_size() -> usize {
20644        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
20645        *C.get_or_init(|| {
20646            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
20647                .ok()
20648                .and_then(|v| v.parse().ok())
20649                .unwrap_or(32);
20650            c.clamp(32, 128) / 32 * 32
20651        })
20652    }
20653
20654    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
20655    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
20656    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
20657    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
20658    #[allow(clippy::too_many_arguments)]
20659    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
20660    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
20661    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
20662    #[allow(clippy::too_many_arguments)]
20663    pub fn gdn_chunk_k123(
20664        &self,
20665        q: &CudaSlice<f32>,
20666        k: &CudaSlice<f32>,
20667        v: &CudaSlice<f32>,
20668        g: &CudaSlice<f32>,
20669        beta: &CudaSlice<f32>,
20670        wb16: Option<&mut CudaSlice<u8>>,
20671        n_head: usize,
20672        t: usize,
20673        c: usize,
20674        hk: usize,
20675        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
20676    ) -> Result<
20677        (
20678            CudaSlice<f32>,
20679            CudaSlice<f32>,
20680            CudaSlice<f32>,
20681            CudaSlice<f32>,
20682        ),
20683        Box<dyn std::error::Error>,
20684    > {
20685        const D: usize = 128;
20686        let h = n_head;
20687        let nc = (t + c - 1) / c;
20688        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20689        let mut gcum = self.uninit(t * h)?;
20690        let mut a = self.uninit(nc * h * c * c)?;
20691        let mut p = self.uninit(nc * h * c * c)?;
20692        let mut u = self.uninit(nc * h * c * D)?;
20693        let mut w = self.uninit(nc * h * c * D)?;
20694        {
20695            // K1
20696            let f = self.func("gdn_chunk_cumgate_f32");
20697            let cfg = LaunchConfig {
20698                grid_dim: (nc as u32, h as u32, 1),
20699                block_dim: (32, 1, 1),
20700                shared_mem_bytes: 0,
20701            };
20702            let __s_b = self.gpu.stream();
20703            let mut b = __s_b.launch_builder(&f);
20704            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
20705            unsafe {
20706                b.launch(cfg)?;
20707            }
20708        }
20709        if let Some((qb, kb, pb)) = k2w {
20710            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
20711            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
20712            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
20713            let f = self.func("gdn_k2_wgmma");
20714            let cfg = LaunchConfig {
20715                grid_dim: (nc as u32, h as u32, 1),
20716                block_dim: (128, 1, 1),
20717                shared_mem_bytes: 0,
20718            };
20719            let hki = hk as i32;
20720            let __s_b = self.gpu.stream();
20721            let mut b = __s_b.launch_builder(&f);
20722            b.arg(qb)
20723                .arg(kb)
20724                .arg(&gcum)
20725                .arg(beta)
20726                .arg(&mut a)
20727                .arg(&mut *pb)
20728                .arg(&hi)
20729                .arg(&ti)
20730                .arg(&ci)
20731                .arg(&hki);
20732            unsafe {
20733                b.launch(cfg)?;
20734            }
20735        } else if c <= 64 && !portable_mma_gated() {
20736            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
20737            let f = self.func("gdn_chunk_attn_f32");
20738            let jt = ((c + 31) / 32) as u32;
20739            let cfg = LaunchConfig {
20740                grid_dim: (nc as u32, h as u32, jt),
20741                block_dim: (256, 1, 1),
20742                shared_mem_bytes: 0,
20743            };
20744            let hki = hk as i32;
20745            let __s_b = self.gpu.stream();
20746            let mut b = __s_b.launch_builder(&f);
20747            b.arg(q)
20748                .arg(k)
20749                .arg(&gcum)
20750                .arg(beta)
20751                .arg(&mut a)
20752                .arg(&mut p)
20753                .arg(&hi)
20754                .arg(&ti)
20755                .arg(&ci)
20756                .arg(&hki);
20757            unsafe {
20758                b.launch(cfg)?;
20759            }
20760        } else {
20761            // K2 generic (C = 128, or the portable target's low-smem fallback)
20762            assert!(
20763                hk == h,
20764                "generic K2 is broadcast-only (de-broadcast rides C==32)"
20765            );
20766            let f = self.func("gdn_chunk_attn_g_f32");
20767            let cfg = LaunchConfig {
20768                grid_dim: (nc as u32, h as u32, 1),
20769                block_dim: (32, 8, 1),
20770                shared_mem_bytes: 0,
20771            };
20772            let __s_b = self.gpu.stream();
20773            let mut b = __s_b.launch_builder(&f);
20774            b.arg(q)
20775                .arg(k)
20776                .arg(&gcum)
20777                .arg(beta)
20778                .arg(&mut a)
20779                .arg(&mut p)
20780                .arg(&hi)
20781                .arg(&ti)
20782                .arg(&ci);
20783            unsafe {
20784                b.launch(cfg)?;
20785            }
20786        }
20787        {
20788            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
20789            let cfg = LaunchConfig {
20790                grid_dim: (nc as u32, h as u32, 1),
20791                block_dim: (256, 1, 1),
20792                shared_mem_bytes: 0,
20793            };
20794            match c {
20795                32 | 64 => {
20796                    let f = self.func(if c == 32 {
20797                        "gdn_chunk_solve32_f32"
20798                    } else {
20799                        "gdn_chunk_solve64_f32"
20800                    });
20801                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
20802                    let wb: u64 = match wb16 {
20803                        Some(d) => self.addr_u8(d),
20804                        None => 0,
20805                    };
20806                    let hki = hk as i32;
20807                    let __s_b = self.gpu.stream();
20808                    let mut b = __s_b.launch_builder(&f);
20809                    b.arg(v)
20810                        .arg(k)
20811                        .arg(&a)
20812                        .arg(&gcum)
20813                        .arg(&mut u)
20814                        .arg(&mut w)
20815                        .arg(&wb)
20816                        .arg(&hi)
20817                        .arg(&ti)
20818                        .arg(&hki);
20819                    unsafe {
20820                        b.launch(cfg)?;
20821                    }
20822                }
20823                _ => {
20824                    assert!(hk == h, "generic K3 is broadcast-only");
20825                    let f = self.func("gdn_chunk_solve_f32");
20826                    let __s_b = self.gpu.stream();
20827                    let mut b = __s_b.launch_builder(&f);
20828                    b.arg(v)
20829                        .arg(k)
20830                        .arg(&a)
20831                        .arg(&gcum)
20832                        .arg(&mut u)
20833                        .arg(&mut w)
20834                        .arg(&hi)
20835                        .arg(&ti)
20836                        .arg(&ci);
20837                    unsafe {
20838                        b.launch(cfg)?;
20839                    }
20840                }
20841            }
20842        }
20843        Ok((gcum, p, u, w))
20844    }
20845
20846    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
20847    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
20848    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
20849    pub fn gdn_db_on() -> bool {
20850        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
20851    }
20852
20853    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
20854    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
20855    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
20856        !portable_mma_gated()
20857            && c == 32
20858            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20859                Ok("1") => true,
20860                Ok("0") => false,
20861                _ => cfg!(memra_hopper_mma),
20862            }
20863    }
20864
20865    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
20866    /// mma config; same per-call env read discipline).
20867    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
20868        self.gdn_mma_enabled(c)
20869            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20870                Ok("0") => false,
20871                Ok("1") => true,
20872                _ => cfg!(memra_hopper_mma),
20873            }
20874    }
20875
20876    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
20877    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
20878    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
20879    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
20880    #[allow(clippy::too_many_arguments)]
20881    pub fn ssm_conv1d_gdn_state_pad(
20882        &self,
20883        qkv_tm: &cudarc::driver::CudaView<f32>,
20884        conv_state: &mut CudaSlice<f32>,
20885        w: &CudaSlice<f32>,
20886        q_g: &mut CudaSlice<f32>,
20887        k_g: &mut CudaSlice<f32>,
20888        v_g: &mut CudaSlice<f32>,
20889        conv_dim: usize,
20890        t: usize,
20891        d_conv: usize,
20892        d_state: usize,
20893        num_v: usize,
20894        num_k: usize,
20895        key_dim: usize,
20896        hk: usize,
20897        pad_len: Option<&CudaSlice<i32>>,
20898    ) -> Result<(), Box<dyn std::error::Error>> {
20899        assert!(
20900            t >= d_conv - 1,
20901            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
20902        );
20903        {
20904            let f = self.func("ssm_conv1d_gdn_state_f32");
20905            let cfg = LaunchConfig {
20906                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20907                block_dim: (256, 1, 1),
20908                shared_mem_bytes: 0,
20909            };
20910            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20911            let (ds, nv, nk, kd, hki) = (
20912                d_state as i32,
20913                num_v as i32,
20914                num_k as i32,
20915                key_dim as i32,
20916                hk as i32,
20917            );
20918            let __s_b = self.gpu.stream();
20919            let mut b = __s_b.launch_builder(&f);
20920            b.arg(qkv_tm)
20921                .arg(&*conv_state)
20922                .arg(w)
20923                .arg(q_g)
20924                .arg(k_g)
20925                .arg(v_g)
20926                .arg(&cd)
20927                .arg(&ti)
20928                .arg(&dc)
20929                .arg(&ds)
20930                .arg(&nv)
20931                .arg(&nk)
20932                .arg(&kd)
20933                .arg(&hki);
20934            unsafe {
20935                b.launch(cfg)?;
20936            }
20937        }
20938        match pad_len {
20939            Some(len_d) => {
20940                let f = self.func("ssm_conv_ring_update_dev_f32");
20941                let n = conv_dim * (d_conv - 1);
20942                let cfg = LaunchConfig::for_num_elems(n as u32);
20943                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20944                let __s_b = self.gpu.stream();
20945                let mut b = __s_b.launch_builder(&f);
20946                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20947                unsafe {
20948                    b.launch(cfg)?;
20949                }
20950            }
20951            None => {
20952                let f = self.func("ssm_conv_ring_update_f32");
20953                let n = conv_dim * (d_conv - 1);
20954                let cfg = LaunchConfig::for_num_elems(n as u32);
20955                let (cd, ti, dc) = (conv_dim as i32, t 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(&cd).arg(&ti).arg(&dc);
20959                unsafe {
20960                    b.launch(cfg)?;
20961                }
20962            }
20963        }
20964        Ok(())
20965    }
20966
20967    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
20968    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
20969    /// K2/K3 can write them.
20970    pub fn gdn_chunk_alloc(
20971        &self,
20972        n_head: usize,
20973        t: usize,
20974        c: usize,
20975        hk: usize,
20976    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
20977        const D: usize = 128;
20978        assert!(
20979            c == 32,
20980            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
20981        );
20982        let h = n_head;
20983        let nc = (t + c - 1) / c;
20984        Ok(GdnChunkBufs {
20985            gcum: self.uninit(t * h)?,
20986            a: self.uninit(nc * h * c * c)?,
20987            p: self.uninit(nc * h * c * c)?,
20988            u: self.uninit(nc * h * c * D)?,
20989            w: self.uninit(nc * h * c * D)?,
20990            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20991            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20992            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20993            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
20994            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20995            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
20996            o: self.uninit(D * h * t)?,
20997            t,
20998            nc,
20999        })
21000    }
21001
21002    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
21003    pub fn f32_to_bf16_v(
21004        &self,
21005        x: &cudarc::driver::CudaView<f32>,
21006        dst: &mut CudaSlice<u8>,
21007        n: usize,
21008    ) -> Result<(), Box<dyn std::error::Error>> {
21009        let f = self.func("f32_to_bf16_bulk");
21010        let ni = n as i64;
21011        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
21012        let __s_b = self.gpu.stream();
21013        let mut b = __s_b.launch_builder(&f);
21014        b.arg(x).arg(dst).arg(&ni);
21015        unsafe {
21016            b.launch(cfg)?;
21017        }
21018        Ok(())
21019    }
21020
21021    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
21022    pub fn f32_to_bf16_into(
21023        &self,
21024        x: &CudaSlice<f32>,
21025        dst: &mut CudaSlice<u8>,
21026        n: usize,
21027    ) -> Result<(), Box<dyn std::error::Error>> {
21028        let f = self.func("f32_to_bf16_bulk");
21029        let ni = n as i64;
21030        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
21031        let __s_b = self.gpu.stream();
21032        let mut b = __s_b.launch_builder(&f);
21033        b.arg(x).arg(dst).arg(&ni);
21034        unsafe {
21035            b.launch(cfg)?;
21036        }
21037        Ok(())
21038    }
21039
21040    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
21041    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
21042    pub fn gdn_chunk_k123_vl8(
21043        &self,
21044        seqs: &[GdnSeqVl],
21045        n_head: usize,
21046        hk: usize,
21047        wq: Option<&GdnWVl8>,
21048    ) -> Result<(), Box<dyn std::error::Error>> {
21049        let b = seqs.len();
21050        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
21051        let mut packed = [GdnSeqVl::default(); 8];
21052        packed[..b].copy_from_slice(seqs);
21053        let v = GdnVl8(packed);
21054        let (hi, ci) = (n_head as i32, 32i32);
21055        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
21056        {
21057            let f = self.func("gdn_chunk_cumgate_vl");
21058            let cfg = LaunchConfig {
21059                grid_dim: (max_nc, n_head as u32, b as u32),
21060                block_dim: (32, 1, 1),
21061                shared_mem_bytes: 0,
21062            };
21063            let __s_lb = self.gpu.stream();
21064            let mut lb = __s_lb.launch_builder(&f);
21065            lb.arg(&v).arg(&hi).arg(&ci);
21066            unsafe {
21067                lb.launch(cfg)?;
21068            }
21069        }
21070        let hki = hk as i32;
21071        if let Some(w) = wq {
21072            // K2-wgmma vl twin (writes A + pre-masked Pb16)
21073            let f = self.func("gdn_k2_wgmma_vl");
21074            let cfg = LaunchConfig {
21075                grid_dim: (max_nc, n_head as u32, b as u32),
21076                block_dim: (128, 1, 1),
21077                shared_mem_bytes: 0,
21078            };
21079            let __s_lb = self.gpu.stream();
21080            let mut lb = __s_lb.launch_builder(&f);
21081            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
21082            unsafe {
21083                lb.launch(cfg)?;
21084            }
21085        } else {
21086            let f = self.func("gdn_chunk_attn_vl");
21087            let cfg = LaunchConfig {
21088                grid_dim: (max_nc, n_head as u32, b as u32),
21089                block_dim: (256, 1, 1),
21090                shared_mem_bytes: 0,
21091            };
21092            let __s_lb = self.gpu.stream();
21093            let mut lb = __s_lb.launch_builder(&f);
21094            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21095            unsafe {
21096                lb.launch(cfg)?;
21097            }
21098        }
21099        {
21100            let f = self.func("gdn_chunk_solve32_vl");
21101            let cfg = LaunchConfig {
21102                grid_dim: (max_nc, n_head as u32, b as u32),
21103                block_dim: (256, 1, 1),
21104                shared_mem_bytes: 0,
21105            };
21106            let __s_lb = self.gpu.stream();
21107            let mut lb = __s_lb.launch_builder(&f);
21108            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21109            unsafe {
21110                lb.launch(cfg)?;
21111            }
21112        }
21113        Ok(())
21114    }
21115
21116    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
21117    /// fused gate-prep, 5 launches for every sequence (per-element math identical
21118    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
21119    #[allow(clippy::too_many_arguments)]
21120    pub fn gdn_prep_vl8(
21121        &self,
21122        seqs: &[GdnPrepVl],
21123        conv_w: &CudaSlice<f32>,
21124        dt_bias: &CudaSlice<f32>,
21125        a: &CudaSlice<f32>,
21126        conv_dim: usize,
21127        d_conv: usize,
21128        d_state: usize,
21129        num_v: usize,
21130        num_k: usize,
21131        key_dim: usize,
21132        hk: usize,
21133        eps: f32,
21134    ) -> Result<(), Box<dyn std::error::Error>> {
21135        let b = seqs.len();
21136        assert!(b >= 1 && b <= 8);
21137        let mut packed = [GdnPrepVl::default(); 8];
21138        packed[..b].copy_from_slice(seqs);
21139        let v = GdnPrepVl8(packed);
21140        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21141        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
21142        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
21143        assert!(
21144            conv_fuse || hk == num_v,
21145            "de-broadcast requires the fused conv"
21146        );
21147        if conv_fuse {
21148            let f = self.func("ssm_conv1d_gdn_state_vl");
21149            let cfg = LaunchConfig {
21150                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21151                block_dim: (256, 1, 1),
21152                shared_mem_bytes: 0,
21153            };
21154            let (dsi, nvi, nki, kdi, hki) = (
21155                d_state as i32,
21156                num_v as i32,
21157                num_k as i32,
21158                key_dim as i32,
21159                hk as i32,
21160            );
21161            let __s_lb = self.gpu.stream();
21162            let mut lb = __s_lb.launch_builder(&f);
21163            lb.arg(&v)
21164                .arg(conv_w)
21165                .arg(&cdi)
21166                .arg(&dci)
21167                .arg(&dsi)
21168                .arg(&nvi)
21169                .arg(&nki)
21170                .arg(&kdi)
21171                .arg(&hki);
21172            unsafe {
21173                lb.launch(cfg)?;
21174            }
21175        } else {
21176            let f = self.func("ssm_conv1d_tm_state_vl");
21177            let cfg = LaunchConfig {
21178                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21179                block_dim: (256, 1, 1),
21180                shared_mem_bytes: 0,
21181            };
21182            let __s_lb = self.gpu.stream();
21183            let mut lb = __s_lb.launch_builder(&f);
21184            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
21185            unsafe {
21186                lb.launch(cfg)?;
21187            }
21188        }
21189        {
21190            let f = self.func("ssm_conv_ring_update_vl");
21191            let n = (conv_dim * (d_conv - 1)) as u32;
21192            let cfg = LaunchConfig {
21193                grid_dim: (n.div_ceil(256), 1, b as u32),
21194                block_dim: (256, 1, 1),
21195                shared_mem_bytes: 0,
21196            };
21197            let __s_lb = self.gpu.stream();
21198            let mut lb = __s_lb.launch_builder(&f);
21199            lb.arg(&v).arg(&cdi).arg(&dci);
21200            unsafe {
21201                lb.launch(cfg)?;
21202            }
21203        }
21204        if !conv_fuse {
21205            let f = self.func("qkv_to_gdn_repack_vl");
21206            let n = max_t * (num_v * d_state) as u32;
21207            let cfg = LaunchConfig {
21208                grid_dim: (n.div_ceil(256), 1, b as u32),
21209                block_dim: (256, 1, 1),
21210                shared_mem_bytes: 0,
21211            };
21212            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
21213            let __s_lb = self.gpu.stream();
21214            let mut lb = __s_lb.launch_builder(&f);
21215            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
21216            unsafe {
21217                lb.launch(cfg)?;
21218            }
21219        }
21220        if Self::l2_v2_on(d_state) {
21221            let f = self.func("gdn_l2_v2_vl");
21222            let cfg = LaunchConfig {
21223                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
21224                block_dim: (256, 1, 1),
21225                shared_mem_bytes: 0,
21226            };
21227            let (dsi, nvi) = (d_state as i32, hk as i32);
21228            let __s_lb = self.gpu.stream();
21229            let mut lb = __s_lb.launch_builder(&f);
21230            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21231            unsafe {
21232                lb.launch(cfg)?;
21233            }
21234        } else {
21235            let f = self.func("gdn_l2_vl");
21236            let cfg = LaunchConfig {
21237                grid_dim: (max_t * hk as u32, 2, b as u32),
21238                block_dim: (256, 1, 1),
21239                shared_mem_bytes: 0,
21240            };
21241            let (dsi, nvi) = (d_state as i32, hk as i32);
21242            let __s_lb = self.gpu.stream();
21243            let mut lb = __s_lb.launch_builder(&f);
21244            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21245            unsafe {
21246                lb.launch(cfg)?;
21247            }
21248        }
21249        {
21250            let f = self.func("gdn_gate_prep_vl");
21251            let n = max_t * num_v as u32;
21252            let cfg = LaunchConfig {
21253                grid_dim: (n.div_ceil(256), 1, b as u32),
21254                block_dim: (256, 1, 1),
21255                shared_mem_bytes: 0,
21256            };
21257            let nvi = num_v as i32;
21258            let __s_lb = self.gpu.stream();
21259            let mut lb = __s_lb.launch_builder(&f);
21260            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
21261            unsafe {
21262                lb.launch(cfg)?;
21263            }
21264        }
21265        Ok(())
21266    }
21267
21268    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
21269    pub fn gdn_mirror_vl8(
21270        &self,
21271        seqs: &[GdnSeqVl],
21272        n_head: usize,
21273        which: i32,
21274        hk: usize,
21275    ) -> Result<(), Box<dyn std::error::Error>> {
21276        let b = seqs.len();
21277        assert!(b >= 1 && b <= 8);
21278        let mut packed = [GdnSeqVl::default(); 8];
21279        packed[..b].copy_from_slice(seqs);
21280        let v = GdnVl8(packed);
21281        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
21282        let max_n = seqs
21283            .iter()
21284            .map(|s| {
21285                if which == 0 {
21286                    s.t as i64 * ept as i64
21287                } else {
21288                    s.nc as i64 * ept as i64 * 32
21289                }
21290            })
21291            .max()
21292            .unwrap();
21293        let f = self.func("gdn_mirror_vl");
21294        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
21295        let cfg = LaunchConfig {
21296            grid_dim: (blocks, 1, b as u32),
21297            block_dim: (256, 1, 1),
21298            shared_mem_bytes: 0,
21299        };
21300        let __s_lb = self.gpu.stream();
21301        let mut lb = __s_lb.launch_builder(&f);
21302        lb.arg(&v).arg(&ept).arg(&which);
21303        unsafe {
21304            lb.launch(cfg)?;
21305        }
21306        Ok(())
21307    }
21308
21309    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
21310    pub fn gdn_tail_vl8(
21311        &self,
21312        seqs: &[GdnPrepVl],
21313        norm_w: &CudaSlice<f32>,
21314        d_state: usize,
21315        num_v: usize,
21316        eps: f32,
21317    ) -> Result<(), Box<dyn std::error::Error>> {
21318        let b = seqs.len();
21319        assert!(b >= 1 && b <= 8);
21320        let mut packed = [GdnPrepVl::default(); 8];
21321        packed[..b].copy_from_slice(seqs);
21322        let v = GdnPrepVl8(packed);
21323        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21324        let f = self.func("gated_rmsnorm_f16out_vl");
21325        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21326        let cfg = LaunchConfig {
21327            grid_dim: (max_t * num_v as u32, 1, b as u32),
21328            block_dim: (128, 1, 1),
21329            shared_mem_bytes: 0,
21330        };
21331        let (dsi, nvi) = (d_state as i32, num_v as i32);
21332        let __s_lb = self.gpu.stream();
21333        let mut lb = __s_lb.launch_builder(&f);
21334        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
21335        unsafe {
21336            lb.launch(cfg)?;
21337        }
21338        Ok(())
21339    }
21340
21341    /// Raw device address helpers for the varlen by-value arg struct (single-stream
21342    /// launches; every buffer outlives the call — the f16 FFI discipline).
21343    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
21344        use cudarc::driver::DevicePtr;
21345        let s = self.gpu.stream();
21346        let (p, _g) = x.device_ptr(&s);
21347        p as u64
21348    }
21349    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
21350        use cudarc::driver::DevicePtrMut;
21351        let s = self.gpu.stream();
21352        let (p, _g) = x.device_ptr_mut(&s);
21353        p as u64
21354    }
21355    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<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_u8(&self, x: &CudaSlice<u8>) -> u64 {
21362        use cudarc::driver::DevicePtr;
21363        let s = self.gpu.stream();
21364        let (p, _g) = x.device_ptr(&s);
21365        p as u64
21366    }
21367
21368    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
21369    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
21370    /// launches, so this is strictly bit-gateable against them).
21371    pub fn gdn_chunk_vl8(
21372        &self,
21373        seqs: &[GdnSeqVl],
21374        n_head: usize,
21375        scale: f32,
21376        hk: usize,
21377        wq: Option<&GdnWVl8>,
21378    ) -> Result<(), Box<dyn std::error::Error>> {
21379        const NSPLIT: u32 = 4;
21380        let b = seqs.len();
21381        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
21382        let mut packed = [GdnSeqVl::default(); 8];
21383        packed[..b].copy_from_slice(seqs);
21384        let v = GdnVl8(packed);
21385        let (hi, ci) = (n_head as i32, 32i32);
21386        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
21387        let hki = hk as i32;
21388        if let Some(w) = wq {
21389            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
21390            let f = self.func("gdn_k45_wgmma_vl");
21391            let cfg = LaunchConfig {
21392                grid_dim: (n_head as u32, NSPLIT, b as u32),
21393                block_dim: (256, 1, 1),
21394                shared_mem_bytes: 0,
21395            };
21396            let __s_lb = self.gpu.stream();
21397            let mut lb = __s_lb.launch_builder(&f);
21398            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
21399            unsafe {
21400                lb.launch(cfg)?;
21401            }
21402            let _ = max_nc;
21403            return Ok(());
21404        }
21405        {
21406            let f = self.func("gdn_chunk_state_mma_vl");
21407            let cfg = LaunchConfig {
21408                grid_dim: (n_head as u32, NSPLIT, b as u32),
21409                block_dim: (256, 1, 1),
21410                shared_mem_bytes: 0,
21411            };
21412            let __s_lb = self.gpu.stream();
21413            let mut lb = __s_lb.launch_builder(&f);
21414            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21415            unsafe {
21416                lb.launch(cfg)?;
21417            }
21418        }
21419        {
21420            let f = self.func("gdn_chunk_output_mma_vl");
21421            let cfg = LaunchConfig {
21422                grid_dim: (max_nc, n_head as u32, b as u32),
21423                block_dim: (256, 1, 1),
21424                shared_mem_bytes: 0,
21425            };
21426            let __s_lb = self.gpu.stream();
21427            let mut lb = __s_lb.launch_builder(&f);
21428            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
21429            unsafe {
21430                lb.launch(cfg)?;
21431            }
21432        }
21433        Ok(())
21434    }
21435    pub fn gdn_scan_chunked(
21436        &self,
21437        q: &CudaSlice<f32>,
21438        k: &CudaSlice<f32>,
21439        v: &CudaSlice<f32>,
21440        g: &CudaSlice<f32>,
21441        beta: &CudaSlice<f32>,
21442        kb16_pre: Option<&CudaSlice<u8>>,
21443        qb16_pre: Option<&CudaSlice<u8>>,
21444        state_in: &CudaSlice<f32>,
21445        state_out: &mut CudaSlice<f32>,
21446        o: &mut CudaSlice<f32>,
21447        n_head: usize,
21448        t: usize,
21449        scale: f32,
21450        c: usize,
21451        hk: usize,
21452    ) -> Result<(), Box<dyn std::error::Error>> {
21453        const D: usize = 128;
21454        const NSPLIT: u32 = 4;
21455        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
21456        let h = n_head;
21457        let nc = (t + c - 1) / c;
21458        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
21459        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
21460        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
21461        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
21462        let gdn_mma_pre = !portable_mma_gated()
21463            && c == 32
21464            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21465                Ok("1") => true,
21466                Ok("0") => false,
21467                _ => cfg!(memra_hopper_mma),
21468            };
21469        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
21470            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
21471        } else {
21472            None
21473        };
21474        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
21475        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
21476        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
21477        let gdn_wgmma_pre = gdn_mma_pre
21478            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
21479                Ok("0") => false,
21480                Ok("1") => true,
21481                _ => cfg!(memra_hopper_mma),
21482            };
21483        let nk = t * hk * D;
21484        let mut kb16_local: Option<CudaSlice<u8>> = None;
21485        if gdn_mma_pre && kb16_pre.is_none() {
21486            let mut kb = self.alloc_u8_uninit(nk * 2)?;
21487            let f = self.func("f32_to_bf16_bulk");
21488            let n2 = nk as i64;
21489            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21490            let __s_b = self.gpu.stream();
21491            let mut b = __s_b.launch_builder(&f);
21492            b.arg(k).arg(&mut kb).arg(&n2);
21493            unsafe {
21494                b.launch(cfg2)?;
21495            }
21496            kb16_local = Some(kb);
21497        }
21498        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
21499        if let Some(kb) = kb16_pre {
21500            assert!(kb.len() >= nk * 2, "kb16_pre too small");
21501        }
21502        let mut qb16: Option<CudaSlice<u8>> = None;
21503        let mut pb16: Option<CudaSlice<u8>> = None;
21504        if gdn_wgmma_pre {
21505            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
21506            // the standalone bulk cvt only serves callers without the prep mirror.
21507            if qb16_pre.is_none() {
21508                let mut qb = self.alloc_u8_uninit(nk * 2)?;
21509                let f = self.func("f32_to_bf16_bulk");
21510                let n2 = nk as i64;
21511                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21512                let __s_b = self.gpu.stream();
21513                let mut b = __s_b.launch_builder(&f);
21514                b.arg(q).arg(&mut qb).arg(&n2);
21515                unsafe {
21516                    b.launch(cfg2)?;
21517                }
21518                qb16 = Some(qb);
21519            } else if let Some(qb) = qb16_pre {
21520                assert!(qb.len() >= nk * 2, "qb16_pre too small");
21521            }
21522            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
21523        }
21524        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
21525        let k2w = if gdn_wgmma_pre {
21526            Some((
21527                *qb16_ref0.as_ref().unwrap(),
21528                *kb16_ref0.as_ref().unwrap(),
21529                pb16.as_mut().unwrap(),
21530            ))
21531        } else {
21532            None
21533        };
21534        let (gcum, p, u, w) =
21535            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
21536        let _ = &w;
21537        let mut y = self.uninit(nc * h * c * D)?;
21538        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
21539        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
21540        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
21541        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
21542        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
21543        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
21544        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
21545        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
21546        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
21547        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
21548        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
21549        let gdn_mma = !portable_mma_gated()
21550            && c == 32
21551            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21552                Ok("1") => true,
21553                Ok("0") => false,
21554                _ => cfg!(memra_hopper_mma),
21555            };
21556        if gdn_mma {
21557            let wb16 = wb16_pre
21558                .take()
21559                .expect("mma path pre-allocates wb16 (K3 store fold)");
21560            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
21561            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
21562            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
21563            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
21564            // pass runs inside the persistent-M kernel; Y and Ssnap are never
21565            // materialized. New numeric class (gk folds into k^T instead of ys) —
21566            // explicit opt-in until the state-carry battery promotes it. Env read per
21567            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
21568            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
21569            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
21570            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
21571            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
21572            if gdn_wgmma_pre {
21573                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
21574                let qb16 = qb16_ref0.unwrap();
21575                let pb16 = pb16.as_ref().unwrap();
21576                {
21577                    let f = self.func("gdn_k45_wgmma");
21578                    let cfg = LaunchConfig {
21579                        grid_dim: (h as u32, 4, 1),
21580                        block_dim: (256, 1, 1),
21581                        shared_mem_bytes: 0,
21582                    };
21583                    let hki = hk as i32;
21584                    let __s_b = self.gpu.stream();
21585                    let mut b = __s_b.launch_builder(&f);
21586                    b.arg(kb16_ref)
21587                        .arg(&gcum)
21588                        .arg(beta)
21589                        .arg(&u)
21590                        .arg(&wb16)
21591                        .arg(qb16)
21592                        .arg(pb16)
21593                        .arg(o)
21594                        .arg(&scale)
21595                        .arg(state_in)
21596                        .arg(&mut *state_out)
21597                        .arg(&hi)
21598                        .arg(&ti)
21599                        .arg(&ci)
21600                        .arg(&hki);
21601                    unsafe {
21602                        b.launch(cfg)?;
21603                    }
21604                }
21605                return Ok(());
21606            }
21607            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
21608            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
21609            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
21610            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
21611            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
21612            {
21613                let f = self.func("gdn_chunk_state_mma");
21614                let cfg = LaunchConfig {
21615                    grid_dim: (h as u32, NSPLIT, 1),
21616                    block_dim: (256, 1, 1),
21617                    shared_mem_bytes: 0,
21618                };
21619                let hki = hk as i32;
21620                let __s_b = self.gpu.stream();
21621                let mut b = __s_b.launch_builder(&f);
21622                b.arg(kb16_ref)
21623                    .arg(&gcum)
21624                    .arg(beta)
21625                    .arg(&u)
21626                    .arg(&wb16)
21627                    .arg(&mut y16)
21628                    .arg(&mut ssnap16)
21629                    .arg(state_in)
21630                    .arg(&mut *state_out)
21631                    .arg(&hi)
21632                    .arg(&ti)
21633                    .arg(&ci)
21634                    .arg(&hki);
21635                unsafe {
21636                    b.launch(cfg)?;
21637                }
21638            }
21639            {
21640                // K5-mma (bf16 St/Y consumers)
21641                let f = self.func("gdn_chunk_output_mma");
21642                let jt = ((c + 31) / 32) as u32;
21643                let cfg = LaunchConfig {
21644                    grid_dim: (nc as u32, h as u32, jt),
21645                    block_dim: (256, 1, 1),
21646                    shared_mem_bytes: 0,
21647                };
21648                let hki = hk as i32;
21649                let __s_b = self.gpu.stream();
21650                let mut b = __s_b.launch_builder(&f);
21651                b.arg(q)
21652                    .arg(&gcum)
21653                    .arg(&p)
21654                    .arg(&y16)
21655                    .arg(&ssnap16)
21656                    .arg(o)
21657                    .arg(&hi)
21658                    .arg(&ti)
21659                    .arg(&ci)
21660                    .arg(&scale)
21661                    .arg(&hki);
21662                unsafe {
21663                    b.launch(cfg)?;
21664                }
21665            }
21666            return Ok(());
21667        }
21668        {
21669            // K4 (sequential over chunks inside; blocks col-partition the state)
21670            let f = self.func("gdn_chunk_state_f32");
21671            let cfg = LaunchConfig {
21672                grid_dim: (h as u32, NSPLIT, 1),
21673                block_dim: (256, 1, 1),
21674                shared_mem_bytes: 0,
21675            };
21676            let __s_b = self.gpu.stream();
21677            let mut b = __s_b.launch_builder(&f);
21678            b.arg(k)
21679                .arg(&gcum)
21680                .arg(beta)
21681                .arg(&u)
21682                .arg(&w)
21683                .arg(&mut y)
21684                .arg(&mut ssnap)
21685                .arg(state_in)
21686                .arg(&mut *state_out)
21687                .arg(&hi)
21688                .arg(&ti)
21689                .arg(&ci);
21690            unsafe {
21691                b.launch(cfg)?;
21692            }
21693        }
21694        {
21695            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
21696            let f = self.func("gdn_chunk_output_f32");
21697            let jt = ((c + 31) / 32) as u32;
21698            let cfg = LaunchConfig {
21699                grid_dim: (nc as u32, h as u32, jt),
21700                block_dim: (256, 1, 1),
21701                shared_mem_bytes: 0,
21702            };
21703            let __s_b = self.gpu.stream();
21704            let mut b = __s_b.launch_builder(&f);
21705            b.arg(q)
21706                .arg(&gcum)
21707                .arg(&p)
21708                .arg(&y)
21709                .arg(&ssnap)
21710                .arg(o)
21711                .arg(&hi)
21712                .arg(&ti)
21713                .arg(&ci)
21714                .arg(&scale);
21715            unsafe {
21716                b.launch(cfg)?;
21717            }
21718        }
21719        Ok(())
21720    }
21721
21722    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
21723    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
21724    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
21725    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
21726    ///
21727    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
21728    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
21729    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
21730    #[allow(clippy::too_many_arguments)]
21731    #[allow(clippy::too_many_arguments)]
21732    pub fn gdn_scan_prefill(
21733        &self,
21734        q: &CudaSlice<f32>,
21735        k: &CudaSlice<f32>,
21736        v: &CudaSlice<f32>,
21737        g: &CudaSlice<f32>,
21738        beta: &CudaSlice<f32>,
21739        kb16_pre: Option<&CudaSlice<u8>>,
21740        qb16_pre: Option<&CudaSlice<u8>>,
21741        state_in: &CudaSlice<f32>,
21742        state_out: &mut CudaSlice<f32>,
21743        o: &mut CudaSlice<f32>,
21744        n_head: usize,
21745        t: usize,
21746        scale: f32,
21747        hk: usize,
21748    ) -> Result<(), Box<dyn std::error::Error>> {
21749        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
21750            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
21751            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
21752        }
21753        if Self::gdn_chunked_enabled() && t >= 16 {
21754            self.gdn_scan_chunked(
21755                q,
21756                k,
21757                v,
21758                g,
21759                beta,
21760                kb16_pre,
21761                qb16_pre,
21762                state_in,
21763                state_out,
21764                o,
21765                n_head,
21766                t,
21767                scale,
21768                Self::gdn_chunk_size(),
21769                hk,
21770            )
21771        } else {
21772            assert!(
21773                hk == n_head,
21774                "s128 scan is broadcast-only (prep guarantees by predicate)"
21775            );
21776            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
21777        }
21778    }
21779
21780    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
21781    #[allow(clippy::too_many_arguments)]
21782    fn gdn_scan_diff(
21783        &self,
21784        q: &CudaSlice<f32>,
21785        k: &CudaSlice<f32>,
21786        v: &CudaSlice<f32>,
21787        g: &CudaSlice<f32>,
21788        beta: &CudaSlice<f32>,
21789        state_in: &CudaSlice<f32>,
21790        state_out: &mut CudaSlice<f32>,
21791        o: &mut CudaSlice<f32>,
21792        n_head: usize,
21793        t: usize,
21794        scale: f32,
21795    ) -> Result<(), Box<dyn std::error::Error>> {
21796        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
21797        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21798        let mut o_c = self.uninit(o.len())?;
21799        let mut st_c = self.uninit(state_out.len())?;
21800        self.gdn_scan_chunked(
21801            q,
21802            k,
21803            v,
21804            g,
21805            beta,
21806            None,
21807            None,
21808            state_in,
21809            &mut st_c,
21810            &mut o_c,
21811            n_head,
21812            t,
21813            scale,
21814            Self::gdn_chunk_size(),
21815            n_head,
21816        )?;
21817        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
21818        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
21819        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
21820        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
21821            let mut max_abs = 0f32;
21822            let mut max_rel = 0f32;
21823            let mut sum_rel = 0f64;
21824            for (x, y) in a.iter().zip(b) {
21825                let ad = (x - y).abs();
21826                let rel = ad / x.abs().max(y.abs()).max(1e-3);
21827                if ad > max_abs {
21828                    max_abs = ad;
21829                }
21830                if rel > max_rel {
21831                    max_rel = rel;
21832                }
21833                sum_rel += rel as f64;
21834            }
21835            (max_abs, max_rel, sum_rel / a.len() as f64)
21836        };
21837        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
21838        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
21839        println!(
21840            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
21841                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
21842            Self::gdn_chunk_size()
21843        );
21844        Ok(())
21845    }
21846
21847    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
21848    pub fn gdn_glog(
21849        &self,
21850        alpha: &CudaSlice<f32>,
21851        dt_bias: &CudaSlice<f32>,
21852        a: &CudaSlice<f32>,
21853        g_log: &mut CudaSlice<f32>,
21854        n_head: usize,
21855        t: usize,
21856    ) -> Result<(), Box<dyn std::error::Error>> {
21857        let f = self.func("gdn_glog_f32");
21858        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21859        let (h, ti) = (n_head as i32, t as i32);
21860        let __s_b = self.gpu.stream();
21861        let mut b = __s_b.launch_builder(&f);
21862        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21863        unsafe {
21864            b.launch(cfg)?;
21865        }
21866        Ok(())
21867    }
21868
21869    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
21870    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
21871    pub fn sigmoid_v(
21872        &self,
21873        x: &cudarc::driver::CudaView<f32>,
21874        y: &mut CudaSlice<f32>,
21875        n: usize,
21876    ) -> Result<(), Box<dyn std::error::Error>> {
21877        let f = self.func("sigmoid_f32");
21878        let cfg = LaunchConfig::for_num_elems(n as u32);
21879        let ni = n as i32;
21880        let __s_b = self.gpu.stream();
21881        let mut b = __s_b.launch_builder(&f);
21882        b.arg(x).arg(y).arg(&ni);
21883        unsafe {
21884            b.launch(cfg)?;
21885        }
21886        Ok(())
21887    }
21888
21889    pub fn gdn_glog_v(
21890        &self,
21891        alpha: &cudarc::driver::CudaView<f32>,
21892        dt_bias: &CudaSlice<f32>,
21893        a: &CudaSlice<f32>,
21894        g_log: &mut CudaSlice<f32>,
21895        n_head: usize,
21896        t: usize,
21897    ) -> Result<(), Box<dyn std::error::Error>> {
21898        let f = self.func("gdn_glog_f32");
21899        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21900        let (h, ti) = (n_head as i32, t as i32);
21901        let __s_b = self.gpu.stream();
21902        let mut b = __s_b.launch_builder(&f);
21903        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21904        unsafe {
21905            b.launch(cfg)?;
21906        }
21907        Ok(())
21908    }
21909
21910    pub fn sigmoid(
21911        &self,
21912        x: &CudaSlice<f32>,
21913        y: &mut CudaSlice<f32>,
21914        n: usize,
21915    ) -> Result<(), Box<dyn std::error::Error>> {
21916        let f = self.func("sigmoid_f32");
21917        let cfg = LaunchConfig::for_num_elems(n as u32);
21918        let ni = n as i32;
21919        let __s_b = self.gpu.stream();
21920        let mut b = __s_b.launch_builder(&f);
21921        b.arg(x).arg(y).arg(&ni);
21922        unsafe {
21923            b.launch(cfg)?;
21924        }
21925        Ok(())
21926    }
21927
21928    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
21929    /// (replaces sigmoid + mul + convert). Bit-identical class.
21930    pub fn sig_mul_f16out(
21931        &self,
21932        a: &CudaSlice<f32>,
21933        g: &CudaSlice<f32>,
21934        dst: &mut CudaSlice<f32>,
21935        dst16: &mut CudaSlice<u8>,
21936        n: usize,
21937    ) -> Result<(), Box<dyn std::error::Error>> {
21938        let f = self.func("sig_mul_f16out_f32");
21939        let cfg = LaunchConfig::for_num_elems(n as u32);
21940        let ni = n as i32;
21941        let __s_b = self.gpu.stream();
21942        let mut b = __s_b.launch_builder(&f);
21943        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
21944        unsafe {
21945            b.launch(cfg)?;
21946        }
21947        Ok(())
21948    }
21949
21950    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
21951    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
21952    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
21953    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
21954    ///
21955    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
21956    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
21957    /// applies the wrong number of distinct gate values.
21958    #[allow(clippy::too_many_arguments)]
21959    pub fn attn_head_gate(
21960        &self,
21961        a: &CudaSlice<f32>,
21962        g: &CudaSlice<f32>,
21963        dst: &mut CudaSlice<f32>,
21964        dst16: Option<&mut CudaSlice<u8>>,
21965        head_dim: usize,
21966        n_head: usize,
21967        t: usize,
21968    ) -> Result<(), Box<dyn std::error::Error>> {
21969        let f = self.func("attn_head_gate_f32");
21970        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21971        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21972        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
21973        let d16: u64 = match dst16 {
21974            Some(d) => self.addr_u8(d),
21975            None => 0,
21976        };
21977        let __s_b = self.gpu.stream();
21978        let mut b = __s_b.launch_builder(&f);
21979        b.arg(a)
21980            .arg(g)
21981            .arg(dst)
21982            .arg(&d16)
21983            .arg(&hd)
21984            .arg(&nh)
21985            .arg(&ti);
21986        unsafe {
21987            b.launch(cfg)?;
21988        }
21989        Ok(())
21990    }
21991
21992    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
21993    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
21994    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
21995    ///
21996    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
21997    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
21998    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
21999    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
22000    #[allow(clippy::too_many_arguments)]
22001    pub fn swiglu_clamped_mul_scaled(
22002        &self,
22003        gate: &CudaSlice<f32>,
22004        up: &CudaSlice<f32>,
22005        gs: f32,
22006        us: f32,
22007        limit: f32,
22008        dst: &mut CudaSlice<f32>,
22009        n: usize,
22010    ) -> Result<(), Box<dyn std::error::Error>> {
22011        debug_assert!(
22012            limit > 1e-6,
22013            "swiglu_clamped needs a live limit; use silu_mul_scaled"
22014        );
22015        let f = self.func("swiglu_clamped_mul_scaled_f32");
22016        let cfg = LaunchConfig::for_num_elems(n as u32);
22017        let ni = n as i32;
22018        let __s_b = self.gpu.stream();
22019        let mut b = __s_b.launch_builder(&f);
22020        b.arg(gate)
22021            .arg(up)
22022            .arg(&gs)
22023            .arg(&us)
22024            .arg(&limit)
22025            .arg(dst)
22026            .arg(&ni);
22027        unsafe {
22028            b.launch(cfg)?;
22029        }
22030        Ok(())
22031    }
22032
22033    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
22034    pub fn gated_rmsnorm(
22035        &self,
22036        o: &CudaSlice<f32>,
22037        w: &CudaSlice<f32>,
22038        z: &CudaSlice<f32>,
22039        dst: &mut CudaSlice<f32>,
22040        ncols: usize,
22041        nrows: usize,
22042        eps: f32,
22043    ) -> Result<(), Box<dyn std::error::Error>> {
22044        let f = self.func("gated_rmsnorm_f32");
22045        let cfg = LaunchConfig {
22046            grid_dim: (nrows as u32, 1, 1),
22047            block_dim: (128, 1, 1),
22048            shared_mem_bytes: 0,
22049        };
22050        let (nc, e) = (ncols as i32, eps);
22051        let __s_b = self.gpu.stream();
22052        let mut b = __s_b.launch_builder(&f);
22053        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
22054        unsafe {
22055            b.launch(cfg)?;
22056        }
22057        Ok(())
22058    }
22059
22060    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
22061    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
22062    pub fn gated_rmsnorm_f16out(
22063        &self,
22064        o: &CudaSlice<f32>,
22065        w: &CudaSlice<f32>,
22066        z: &CudaSlice<f32>,
22067        dst: &mut CudaSlice<f32>,
22068        dst16: &mut CudaSlice<u8>,
22069        ncols: usize,
22070        nrows: usize,
22071        eps: f32,
22072    ) -> Result<(), Box<dyn std::error::Error>> {
22073        let f = self.func("gated_rmsnorm_f16out_f32");
22074        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
22075        let cfg = LaunchConfig {
22076            grid_dim: (nrows as u32, 1, 1),
22077            block_dim: (128, 1, 1),
22078            shared_mem_bytes: 0,
22079        };
22080        let (nc, e) = (ncols as i32, eps);
22081        let __s_b = self.gpu.stream();
22082        let mut b = __s_b.launch_builder(&f);
22083        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22084        unsafe {
22085            b.launch(cfg)?;
22086        }
22087        Ok(())
22088    }
22089
22090    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
22091    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
22092    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
22093    #[allow(clippy::too_many_arguments)]
22094    pub fn add_rms_norm_zq8(
22095        &self,
22096        a: &CudaSlice<f32>,
22097        b_in: &CudaSlice<f32>,
22098        w: &CudaSlice<f32>,
22099        res: &mut CudaSlice<f32>,
22100        z: &mut CudaSlice<f32>,
22101        ncols: usize,
22102        nrows: usize,
22103        eps: f32,
22104    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22105        assert!(ncols % 32 == 0);
22106        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
22107        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22108        let f = self.func("add_rms_norm_zq8");
22109        let cfg = LaunchConfig {
22110            grid_dim: (nrows as u32, 1, 1),
22111            block_dim: (1024, 1, 1),
22112            shared_mem_bytes: 0,
22113        };
22114        let (nc, ep) = (ncols as i32, eps);
22115        let __s_b = self.gpu.stream();
22116        let mut b = __s_b.launch_builder(&f);
22117        b.arg(a)
22118            .arg(b_in)
22119            .arg(w)
22120            .arg(res)
22121            .arg(z)
22122            .arg(&mut q)
22123            .arg(&mut d)
22124            .arg(&nc)
22125            .arg(&ep);
22126        unsafe {
22127            b.launch(cfg)?;
22128        }
22129        Ok((q, d))
22130    }
22131
22132    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
22133    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
22134    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
22135    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
22136    pub fn gated_rmsnorm_zv(
22137        &self,
22138        o: &CudaSlice<f32>,
22139        w: &CudaSlice<f32>,
22140        z: &cudarc::driver::CudaView<f32>,
22141        dst: &mut CudaSlice<f32>,
22142        ncols: usize,
22143        nrows: usize,
22144        eps: f32,
22145    ) -> Result<(), Box<dyn std::error::Error>> {
22146        let f = self.func("gated_rmsnorm_f32");
22147        let cfg = LaunchConfig {
22148            grid_dim: (nrows as u32, 1, 1),
22149            block_dim: (128, 1, 1),
22150            shared_mem_bytes: 0,
22151        };
22152        let (nc, e) = (ncols as i32, eps);
22153        let __s_b = self.gpu.stream();
22154        let mut b = __s_b.launch_builder(&f);
22155        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
22156        unsafe {
22157            b.launch(cfg)?;
22158        }
22159        Ok(())
22160    }
22161
22162    pub fn gated_rmsnorm_f16out_zv(
22163        &self,
22164        o: &CudaSlice<f32>,
22165        w: &CudaSlice<f32>,
22166        z: &cudarc::driver::CudaView<f32>,
22167        dst: &mut CudaSlice<f32>,
22168        dst16: &mut CudaSlice<u8>,
22169        ncols: usize,
22170        nrows: usize,
22171        eps: f32,
22172    ) -> Result<(), Box<dyn std::error::Error>> {
22173        let f = self.func("gated_rmsnorm_f16out_f32");
22174        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
22175        let cfg = LaunchConfig {
22176            grid_dim: (nrows as u32, 1, 1),
22177            block_dim: (128, 1, 1),
22178            shared_mem_bytes: 0,
22179        };
22180        let (nc, e) = (ncols as i32, eps);
22181        let __s_b = self.gpu.stream();
22182        let mut b = __s_b.launch_builder(&f);
22183        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22184        unsafe {
22185            b.launch(cfg)?;
22186        }
22187        Ok(())
22188    }
22189
22190    pub fn gated_rmsnorm_q8_1(
22191        &self,
22192        o: &CudaSlice<f32>,
22193        w: &CudaSlice<f32>,
22194        z: &CudaSlice<f32>,
22195        ncols: usize,
22196        nrows: usize,
22197        eps: f32,
22198    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22199        assert!(ncols % 32 == 0);
22200        let f = self.func("gated_rmsnorm_q8_1");
22201        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
22202        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22203        let cfg = LaunchConfig {
22204            grid_dim: (nrows as u32, 1, 1),
22205            block_dim: (128, 1, 1),
22206            shared_mem_bytes: 0,
22207        };
22208        let (nc, ep) = (ncols as i32, eps);
22209        let __s_b = self.gpu.stream();
22210        let mut b = __s_b.launch_builder(&f);
22211        b.arg(o)
22212            .arg(w)
22213            .arg(z)
22214            .arg(&mut out_q)
22215            .arg(&mut out_d)
22216            .arg(&nc)
22217            .arg(&ep);
22218        unsafe {
22219            b.launch(cfg)?;
22220        }
22221        Ok((out_q, out_d))
22222    }
22223
22224    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
22225    pub fn transpose(
22226        &self,
22227        inp: &CudaSlice<f32>,
22228        rows: usize,
22229        cols: usize,
22230    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22231        let f = self.func("transpose_f32");
22232        let mut out = self.zeros(rows * cols)?;
22233        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
22234        let (r, c) = (rows as i32, cols as i32);
22235        let __s_b = self.gpu.stream();
22236        let mut b = __s_b.launch_builder(&f);
22237        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
22238        unsafe {
22239            b.launch(cfg)?;
22240        }
22241        Ok(out)
22242    }
22243
22244    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
22245    pub fn repeat_heads(
22246        &self,
22247        inp: &CudaSlice<f32>,
22248        out: &mut CudaSlice<f32>,
22249        head_dim: usize,
22250        n_in: usize,
22251        n_out: usize,
22252        t: usize,
22253    ) -> Result<(), Box<dyn std::error::Error>> {
22254        let f = self.func("repeat_heads_f32");
22255        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
22256        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
22257        let __s_b = self.gpu.stream();
22258        let mut b = __s_b.launch_builder(&f);
22259        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
22260        unsafe {
22261            b.launch(cfg)?;
22262        }
22263        Ok(())
22264    }
22265
22266    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
22267    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
22268    ///
22269    /// Bounds-checked against `qf` before the launch: the kernel reads `2*head_dim*n_head*t`
22270    /// floats, and running it on a `wq` output that carries no fused gate reads 2x off the end
22271    /// (silently, on the device). A layout mismatch is a typed `FusedQGateExtent` here instead.
22272    pub fn q_gate_split(
22273        &self,
22274        qf: &CudaSlice<f32>,
22275        q_out: &mut CudaSlice<f32>,
22276        gate_out: &mut CudaSlice<f32>,
22277        head_dim: usize,
22278        n_head: usize,
22279        t: usize,
22280    ) -> Result<(), Box<dyn std::error::Error>> {
22281        memra_gguf::config::check_fused_q_gate_extent(qf.len(), head_dim, n_head, t)?;
22282        let out_need = head_dim * n_head * t;
22283        if q_out.len() < out_need || gate_out.len() < out_need {
22284            return Err(format!(
22285                "q_gate_split destinations too small: need {out_need} each, have q={} gate={}",
22286                q_out.len(),
22287                gate_out.len()
22288            )
22289            .into());
22290        }
22291        let f = self.func("q_gate_split_f32");
22292        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
22293        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
22294        let __s_b = self.gpu.stream();
22295        let mut b = __s_b.launch_builder(&f);
22296        b.arg(qf)
22297            .arg(q_out)
22298            .arg(gate_out)
22299            .arg(&hd)
22300            .arg(&nh)
22301            .arg(&ti);
22302        unsafe {
22303            b.launch(cfg)?;
22304        }
22305        Ok(())
22306    }
22307
22308    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
22309    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
22310    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
22311    pub fn qkv_to_gdn_repack(
22312        &self,
22313        conv_out: &CudaSlice<f32>,
22314        q_g: &mut CudaSlice<f32>,
22315        k_g: &mut CudaSlice<f32>,
22316        v_g: &mut CudaSlice<f32>,
22317        d_state: usize,
22318        num_v: usize,
22319        num_k: usize,
22320        key_dim: usize,
22321        t: usize,
22322    ) -> Result<(), Box<dyn std::error::Error>> {
22323        let f = self.func("qkv_to_gdn_repack_f32");
22324        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
22325        let (ds, nv, nk, kd, ti) = (
22326            d_state as i32,
22327            num_v as i32,
22328            num_k as i32,
22329            key_dim as i32,
22330            t as i32,
22331        );
22332        let __s_b = self.gpu.stream();
22333        let mut b = __s_b.launch_builder(&f);
22334        b.arg(conv_out)
22335            .arg(q_g)
22336            .arg(k_g)
22337            .arg(v_g)
22338            .arg(&ds)
22339            .arg(&nv)
22340            .arg(&nk)
22341            .arg(&kd)
22342            .arg(&ti);
22343        unsafe {
22344            b.launch(cfg)?;
22345        }
22346        Ok(())
22347    }
22348
22349    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
22350    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
22351    pub fn conv_left_pad(
22352        &self,
22353        src: &CudaSlice<f32>,
22354        dst: &mut CudaSlice<f32>,
22355        conv_dim: usize,
22356        t: usize,
22357        pad: usize,
22358    ) -> Result<(), Box<dyn std::error::Error>> {
22359        let f = self.func("conv_left_pad_f32");
22360        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
22361        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
22362        let __s_b = self.gpu.stream();
22363        let mut b = __s_b.launch_builder(&f);
22364        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
22365        unsafe {
22366            b.launch(cfg)?;
22367        }
22368        Ok(())
22369    }
22370
22371    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
22372    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
22373    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
22374    pub fn conv_assemble_and_roll(
22375        &self,
22376        qkv_col: &CudaSlice<f32>,
22377        conv_state: &mut CudaSlice<f32>,
22378        conv_in: &mut CudaSlice<f32>,
22379        conv_dim: usize,
22380        pad: usize,
22381    ) -> Result<(), Box<dyn std::error::Error>> {
22382        let f = self.func("conv_assemble_and_roll_f32");
22383        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22384        let (cd, p) = (conv_dim as i32, pad as i32);
22385        let __s_b = self.gpu.stream();
22386        let mut b = __s_b.launch_builder(&f);
22387        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
22388        unsafe {
22389            b.launch(cfg)?;
22390        }
22391        Ok(())
22392    }
22393
22394    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
22395    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
22396    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
22397    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
22398    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
22399    pub fn ssm_conv1d_fused_decode(
22400        &self,
22401        qkv_col: &CudaSlice<f32>,
22402        conv_state: &mut CudaSlice<f32>,
22403        w: &CudaSlice<f32>,
22404        conv_out: &mut CudaSlice<f32>,
22405        conv_dim: usize,
22406        d_conv: usize,
22407    ) -> Result<(), Box<dyn std::error::Error>> {
22408        let f = self.func("ssm_conv1d_fused_decode_f32");
22409        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22410        let (cd, dc) = (conv_dim as i32, d_conv as i32);
22411        let __s_b = self.gpu.stream();
22412        let mut b = __s_b.launch_builder(&f);
22413        b.arg(qkv_col)
22414            .arg(conv_state)
22415            .arg(w)
22416            .arg(conv_out)
22417            .arg(&cd)
22418            .arg(&dc);
22419        unsafe {
22420            b.launch(cfg)?;
22421        }
22422        Ok(())
22423    }
22424
22425    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
22426    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
22427    pub fn slice_range(
22428        &self,
22429        src: &CudaSlice<f32>,
22430        start: usize,
22431        len: usize,
22432    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22433        let host = self.gpu.stream().clone_dtoh(src)?;
22434        self.gpu.stream().synchronize()?;
22435        Ok(self.htod(&host[start..start + len])?)
22436    }
22437}
22438
22439#[cfg(test)]
22440mod target_dispatch_tests {
22441    use super::legacy_quant_gemm_allowed;
22442
22443    #[test]
22444    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
22445        // sm_120a native lane
22446        assert!(legacy_quant_gemm_allowed(false, false, false));
22447        assert!(!legacy_quant_gemm_allowed(false, false, true));
22448        // pure portable lane (sm_89): gated
22449        assert!(!legacy_quant_gemm_allowed(true, false, false));
22450        assert!(!legacy_quant_gemm_allowed(true, false, true));
22451        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
22452        assert!(legacy_quant_gemm_allowed(true, true, false));
22453        assert!(!legacy_quant_gemm_allowed(true, true, true));
22454    }
22455
22456    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
22457    #[test]
22458    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
22459        assert!(!legacy_quant_gemm_allowed(
22460            cfg!(memra_portable_cuda),
22461            cfg!(memra_hopper_mma),
22462            false
22463        ));
22464    }
22465
22466    #[cfg(memra_hopper_mma)]
22467    #[test]
22468    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
22469        assert!(legacy_quant_gemm_allowed(
22470            cfg!(memra_portable_cuda),
22471            cfg!(memra_hopper_mma),
22472            false
22473        ));
22474        assert!(super::portable_mma_gated() == false);
22475    }
22476}
22477
22478/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
22479/// inherent methods (inherent methods win name resolution, so no recursion).
22480impl memra_kv::KvDev for Engine {
22481    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22482        Engine::zeros(self, n)
22483    }
22484    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22485        Engine::uninit(self, n)
22486    }
22487    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
22488        Engine::alloc_u8(self, n)
22489    }
22490    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
22491        Engine::htod_i32(self, v)
22492    }
22493    fn clone_dtod(
22494        &self,
22495        src: &CudaSlice<f32>,
22496    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22497        Engine::clone_dtod(self, src)
22498    }
22499    fn copy_into(
22500        &self,
22501        dst: &mut CudaSlice<f32>,
22502        off: usize,
22503        src: &CudaSlice<f32>,
22504        len: usize,
22505    ) -> Result<(), Box<dyn std::error::Error>> {
22506        Engine::copy_into(self, dst, off, src, len)
22507    }
22508    fn set_i32_one(
22509        &self,
22510        d: &mut CudaSlice<i32>,
22511        v: i32,
22512    ) -> Result<(), Box<dyn std::error::Error>> {
22513        Engine::set_i32_one(self, d, v)
22514    }
22515}
22516
22517#[cfg(test)]
22518mod fused_gate_bounds_tests {
22519    use super::*;
22520
22521    /// The fused `[q|gate]` split's read-site guard, on the device.
22522    ///
22523    /// `q_gate_split_f32` reads `2*head_dim*n_head*T` floats out of `qf`. A checkpoint whose gate
22524    /// is a SEPARATE tensor produces a `wq` output of exactly half that, so before 2026-08-19 the
22525    /// kernel launched and read 2x past the end of the allocation — an out-of-bounds DEVICE read:
22526    /// no panic, no error, just whatever memory follows. The guard turns it into a typed
22527    /// `FusedQGateExtent` before the launch.
22528    ///
22529    /// Catch demonstration for this test (guard temporarily removed, then restored):
22530    /// `compute-sanitizer --tool memcheck` on the half-width case reported invalid `__global__`
22531    /// reads of size 4 in `q_gate_split_f32`; with the guard in place the same run is clean and
22532    /// the call returns `Err`. Receipt in the lane report.
22533    #[test]
22534    #[ignore = "requires a CUDA GPU"]
22535    fn q_gate_split_refuses_a_separate_gate_wq_instead_of_reading_past_it() {
22536        let e = Engine::new(0).unwrap();
22537        let (head_dim, n_head, t) = (8usize, 4usize, 2usize);
22538        let fused = 2 * head_dim * n_head * t;
22539        let out_n = head_dim * n_head * t;
22540
22541        // half-width `qf` = the separate-gate / ungated layout. MUST be refused.
22542        let narrow = e.htod(&vec![1.0f32; out_n]).unwrap();
22543        let mut q = e.uninit(out_n).unwrap();
22544        let mut gate = e.uninit(out_n).unwrap();
22545        let err = e
22546            .q_gate_split(&narrow, &mut q, &mut gate, head_dim, n_head, t)
22547            .expect_err("half-width wq must be refused, not read past")
22548            .to_string();
22549        assert!(err.contains("NO fused gate"), "{err}");
22550        assert!(err.contains(&format!("{fused}")), "{err}");
22551
22552        // full-width `qf` = a real qwen3.5 fused layout. MUST still run, and split correctly:
22553        // per head hh the block is [q(head_dim) | gate(head_dim)] at stride 2*head_dim.
22554        let host: Vec<f32> = (0..fused).map(|i| i as f32).collect();
22555        let wide = e.htod(&host).unwrap();
22556        e.q_gate_split(&wide, &mut q, &mut gate, head_dim, n_head, t)
22557            .expect("full-width wq splits");
22558        let (qh, gh) = (e.dtoh(&q).unwrap(), e.dtoh(&gate).unwrap());
22559        for tok in 0..t {
22560            for hh in 0..n_head {
22561                for d in 0..head_dim {
22562                    let base = tok * (n_head * 2 * head_dim) + hh * (2 * head_dim);
22563                    let idx = tok * (n_head * head_dim) + hh * head_dim + d;
22564                    assert_eq!(qh[idx], host[base + d], "q t{tok} h{hh} d{d}");
22565                    assert_eq!(gh[idx], host[base + head_dim + d], "gate t{tok} h{hh} d{d}");
22566                }
22567            }
22568        }
22569
22570        // undersized destinations are refused too (the other half of the extent contract)
22571        let mut small = e.uninit(out_n - 1).unwrap();
22572        assert!(
22573            e.q_gate_split(&wide, &mut small, &mut gate, head_dim, n_head, t)
22574                .is_err()
22575        );
22576    }
22577}
22578
22579/// FULL-WIDTH-ROPE CONTRACT on the fused rms_norm+qkv+rope kernels
22580/// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up). CPU-only: the guard runs before
22581/// any launch, so the refusal is testable without a device.
22582#[cfg(test)]
22583mod fused_rope_width_tests {
22584    use super::Engine;
22585
22586    /// gemma-4: rotary width == head width on both classes (GGUF 256/256 and 512/512, and the
22587    /// safetensors route derives the same), which is why the fusion is legal there today.
22588    #[test]
22589    fn full_width_is_accepted() {
22590        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 256).is_ok());
22591        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_cat", 512, 512).is_ok());
22592        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append", 128, 128).is_ok());
22593    }
22594
22595    /// The widths the gemma-4 31B OFFICIAL artifact declares, read from its own GGUF header
22596    /// (`gemma-4-31B-it-official-Q8_0-MTP.gguf`, box3, 2026-08-19):
22597    ///
22598    /// ```text
22599    /// attention.key_length     512   rope.dimension_count     512   (global class)
22600    /// attention.key_length_swa 256   rope.dimension_count_swa 256   (SWA class)
22601    /// ```
22602    ///
22603    /// Both classes satisfy `n_rot == head_dim`, which is why the fusion is legal for gemma and
22604    /// why `HybridModel::gemma4_rope_dims` can feed this guard without refusing what we serve.
22605    /// An artifact that ever declares otherwise gets a loud refusal at the first fused launch
22606    /// instead of a silently over-rotated head.
22607    #[test]
22608    fn gemma4_official_artifact_widths_pass() {
22609        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 512, 512).is_ok());
22610        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 256, 256).is_ok());
22611    }
22612
22613    /// The bug this guard exists to make impossible: a partial-rotary arch fused onto a kernel
22614    /// with no `n_dims`, silently rotating the pass-through band.
22615    #[test]
22616    fn partial_rotary_is_refused_with_the_geometry_named() {
22617        // qwen3.5: n_rot 64 of head_dim 256 (the shape probe O-10 pinned in the split path).
22618        let err = Engine::full_width_rope_only("rms_norm_qkv_rope", 64, 256)
22619            .expect_err("partial rotary must refuse");
22620        let msg = err.to_string();
22621        assert!(msg.contains("PARTIAL ROTARY REFUSED"), "{msg}");
22622        assert!(msg.contains("n_rot 64"), "{msg}");
22623        assert!(msg.contains("head_dim 256"), "{msg}");
22624        assert!(
22625            msg.contains("64..256"),
22626            "names the band it would corrupt: {msg}"
22627        );
22628        // step35 full-attn: 64 of 128 (upstream halves n_rot_full).
22629        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 64, 128).is_err());
22630        // and the reverse mismatch (a wider rope than the head) is not "close enough" either.
22631        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 128).is_err());
22632    }
22633}