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::sys::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES;
4use cudarc::driver::{
5    CudaContext, CudaFunction, CudaModule, CudaSlice, CudaStream, DeviceSlice, LaunchConfig,
6    PushKernelArg,
7};
8use cudarc::nvrtc::Ptx;
9use std::sync::{Arc, Mutex};
10
11const GDN_K2_DYNAMIC_SHARED_BYTES: u32 = 67_072;
12
13/// The default dynamic-shared-memory launch bound the naive SDPA family lives under: past
14/// `T_kv * 4 > 48KB` (T_kv > 12288) the smem kernel cannot launch — the measured
15/// dspark/full-attn long-ctx crash class. `sdpa_naive` dispatches to the byte-identical
16/// gmem-scores twin above this line.
17const SDPA_NAIVE_SMEM_MAX: usize = 48 * 1024;
18
19/// Guard on the gmem twin's `n_head * T * T_kv * 4`-byte scores workspace. The shapes that
20/// legitimately hit the smem bound are tall-KV blocks (T <= draft block size), which land in
21/// the tens of MB; 1 GiB refuses a square T==T_kv misuse before it silently eats the card.
22const SDPA_NAIVE_GMEM_WS_MAX: usize = 1 << 30;
23
24#[cfg(debug_assertions)]
25pub(crate) fn debug_assert_tensor_stream_device<T>(
26    tensor: &CudaSlice<T>,
27    stream: &CudaStream,
28    site: &str,
29) {
30    let tensor_dev = tensor.ordinal();
31    let stream_dev = stream.context().ordinal();
32    assert_eq!(
33        tensor_dev, stream_dev,
34        "PP cross-device tensor read at {site}: tensor on dev{tensor_dev}, stream on dev{stream_dev}"
35    );
36}
37
38fn ensure_tensor_stream_device<T>(
39    tensor: &impl DeviceSlice<T>,
40    stream: &CudaStream,
41    site: &str,
42) -> Result<(), Box<dyn std::error::Error>> {
43    let tensor_dev = tensor.stream().context().ordinal();
44    let stream_dev = stream.context().ordinal();
45    if tensor_dev != stream_dev {
46        return Err(format!(
47            "PP cross-device tensor access at {site}: tensor on dev{tensor_dev}, \
48             stream on dev{stream_dev}"
49        )
50        .into());
51    }
52    Ok(())
53}
54
55pub use memra_gguf;
56pub use memra_runtime;
57
58pub mod forward;
59pub mod hybrid;
60pub mod hybrid_forward;
61pub mod model;
62pub mod sigrouter_contract;
63pub mod vision;
64pub mod vision_gemma;
65pub mod vision_pre;
66/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
67/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
68pub mod cache {
69    pub use memra_kv::*;
70}
71pub mod decode;
72pub mod decode_batch;
73pub mod dflash;
74pub mod eagle;
75pub mod gemma_spec;
76pub mod graph_update;
77/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
78/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
79/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
80pub mod mla;
81pub mod moesd;
82pub mod parallel;
83pub mod plan_backend;
84pub mod pp;
85pub mod round_stream;
86pub mod spec;
87pub mod tp;
88pub use memra_sampling as sampler;
89
90/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
91/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
92/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
93/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
94/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
95///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
96///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
97///                     stream sync per projection (round-47 ledgered defect).
98///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
99///                     construction, zero syncs, f32 C with the act row-scale folded in.
100/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
101/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
102/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
103/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
104/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
105/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
106///
107/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
108/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
109/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
110/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
111/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
112/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
113/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
114/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
115///
116/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
117/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
118/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
119/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
120/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
121/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
122/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
123///
124/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
125/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
126/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
127/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
128/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
129/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
130/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
131/// the k-quant-only admission survives as the rollback seam, not the default.
132/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
133pub fn moe_f16g_mode() -> u8 {
134    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
135    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
136        Ok("0") => 0,
137        Ok("2") => 2,
138        Ok("3") => 3,
139        Ok(_) => 1,
140        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
141        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
142        Err(_) => 2,
143    })
144}
145/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
146/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
147/// (shape_sel, cross) for the FFI:
148///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
149///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
150///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
151///                         back to 32x64 in-launcher when the device/in_f can't take it).
152///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
153///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
154///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
155///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
156///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
157///                         verdict was stale).
158pub fn moe_f16g_sk_params() -> (i32, i32) {
159    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
160    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
161        Ok("0") => (-1, 0),
162        Ok("32") => (0, i32::MAX),
163        Ok("128") => (0, 1),
164        _ => {
165            let cross = std::env::var("MEMRA_F16G_SK_CROSS")
166                .ok()
167                .and_then(|v| v.parse().ok())
168                .unwrap_or(64);
169            (0, cross)
170        }
171    })
172}
173/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
174/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
175/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
176/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
177/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
178/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
179/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
180/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
181/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
182/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
183pub fn moe_f16g_direct_on(qtype: i32) -> bool {
184    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
185    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
186        Ok("0") => 0,
187        Ok("kq") => 1,
188        _ => 2,
189    });
190    match m {
191        0 => false,
192        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
193        _ => true,
194    }
195}
196/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
197/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
198/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
199/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
200/// stage under q35's routing skew. Bit-identical to every other sk form by construction
201/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
202/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
203/// tail. in_f % 64 != 0 falls back in-launcher.
204pub fn moe_f16g_tail_on() -> bool {
205    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
206    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
207}
208
209/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
210/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
211/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
212/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
213/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
214/// still opens this door for A/B.
215pub fn moe_f16g_gemma_on() -> bool {
216    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
217    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
218}
219
220/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
221/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
222/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
223pub fn moe_fuse_actq_on() -> bool {
224    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
225    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
226}
227
228/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
229/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
230/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
231/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
232/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
233/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
234/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
235/// verify already use (dispatch parity, one router kernel for every t).
236/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
237pub fn router_prefill_exact_on() -> bool {
238    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
239    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
240}
241
242pub fn router_kernel_on() -> bool {
243    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
244    *ON.get_or_init(|| {
245        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
246        if !on {
247            eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)");
248        }
249        on
250    })
251}
252
253/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
254/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
255/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
256/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
257/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
258/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
259/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
260/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
261/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
262/// seam, perf-only: bits are equal by the kernel-check gate).
263/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
264/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
265/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
266/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
267pub const ROUTER_BATCH_MIN_T: usize = 8;
268pub fn router_batch_on() -> bool {
269    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
270    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
271}
272mod cpu_experts;
273#[cfg(memra_cutlass)]
274pub mod cutlass_ffi;
275pub mod dsv4_ffi;
276pub mod dsv4_gpu;
277pub mod f16_ffi;
278pub mod fp8_ffi;
279pub mod mmq_ffi;
280pub mod moe_cache;
281pub mod prime_graph;
282pub mod spill;
283mod spill_pread;
284
285// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
286// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
287// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
288// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
289// broke every machine that wasn't the build machine. Same bytes, same module image;
290// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
291const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
292const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
293const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
294const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
295const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
296const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
297/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
298const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
299
300/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
301/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
302/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
303/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
304/// compile-time default (zero behavior change).
305fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
306    assert!(
307        !(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
308        "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane"
309    );
310    match std::env::var("MEMRA_GEMM_FATBIN") {
311        Ok(path) => std::borrow::Cow::Owned(
312            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}")),
313        ),
314        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
315    }
316}
317
318/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
319/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
320/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
321/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
322/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
323/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
324pub(crate) const fn portable_mma_gated() -> bool {
325    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
326}
327
328/// Refuse an env force that would reach a kernel THIS BUILD DOES NOT CONTAIN.
329///
330/// Doors of the shape `MEMRA_X=1 => true` are arch-blind: they were written so an operator could
331/// force a promoted path on, and the default arm (`cfg!(memra_hopper_mma)` or similar) is the only
332/// thing that consulted the arch. On a portable build the forced path then reaches
333/// `Engine::func`, which resolves lazily and ends in `panic!("kernel {name} not in any fatbin")` —
334/// a confusing crash naming a kernel the operator never heard of, several frames from the switch
335/// they actually flipped.
336///
337/// Found 2026-08-23 by tools/fatbin-lookup-census.py, which listed 20 looked-up kernels absent
338/// from the sm_89 fatbins. 18 of those turned out to be correctly unreachable (the GDN varlen
339/// chain is gated through `gdn_mma_enabled`, which starts with `!portable_mma_gated()`); these
340/// env doors were the two that were genuinely reachable, and only by explicit operator action.
341///
342/// Same shape and same message style as `gemm_fatbin_bytes`'s refusal above — one idiom for
343/// "this switch cannot work on this build", so it fails at the switch instead of at the lookup.
344#[track_caller]
345pub(crate) fn refuse_portable_force(var: &str, needs: &str) {
346    assert!(
347        !portable_mma_gated(),
348        "{var} forces a kernel path this build does not contain: it needs {needs}, and this is a \
349         portable-CUDA build (sm_89). Unset {var} — the default path serves this arch."
350    );
351}
352
353/// The GDN K4/K5 mma pair's UNSET-env default — ONE definition for the three read sites
354/// (gdn_mma_enabled, the k123 pre-work, gdn_scan_chunked's dispatch). They read the env
355/// per call ON PURPOSE (kernel-check toggles it to pin both configs), so the shared part
356/// is this compile-time constant: ON for Hopper-MMA builds (the original 90a promotion)
357/// and for sm_120a builds (lane/moeprime-nvfp4-direct, 2026-08-21 — measured on one RTX
358/// PRO 6000 ornith15 pp14715 +6-8% and the local 5090 q38-27b +1-2%, both orders both
359/// rigs). A site defaulting differently from its peers arms the mma pre-work while the
360/// scan takes the scalar route — measured as a 0.8% LOSS, the drift this helper kills.
361pub(crate) const fn gdn_mma_default_on() -> bool {
362    cfg!(memra_hopper_mma) || konst_eq(env!("MEMRA_BUILT_CUDA_ARCH"), "120a")
363}
364
365/// const str-eq (std `==` on &str is not const-stable on this toolchain floor).
366const fn konst_eq(a: &str, b: &str) -> bool {
367    let (a, b) = (a.as_bytes(), b.as_bytes());
368    if a.len() != b.len() {
369        return false;
370    }
371    let mut i = 0;
372    while i < a.len() {
373        if a[i] != b[i] {
374            return false;
375        }
376        i += 1;
377    }
378    true
379}
380
381/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
382/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
383/// in a pure helper so the dispatch guard can be regression-tested without constructing an
384/// Engine or allocating a GPU tensor.
385const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
386    (!portable_cuda || hopper_mma) && !no_gemm
387}
388
389// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
390// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
391// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
392// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
393// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
394// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
395// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
396const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
397const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
398const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
399const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
400const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
401
402/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
403/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
404pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
405
406/// The flash_attn fatbin matching the selected KV formats.
407fn flash_fatbin_bytes() -> &'static [u8] {
408    match kv_cache_formats() {
409        ("q8_0", "q5_1") => FLASH_FATBIN,
410        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
411        ("q8_0", "fp8") => FLASH_FATBIN_VF8,
412        ("fp8", "q5_1") => FLASH_FATBIN_KF8,
413        ("fp8", "q4_0") => FLASH_FATBIN_KF8VQ4,
414        ("fp8", "fp8") => FLASH_FATBIN_KF8VF8,
415        other => unreachable!("kv_cache_formats returned {other:?}"),
416    }
417}
418
419/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
420/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
421/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
422/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
423/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
424/// defaults (zero behavior change).
425fn k1_launch_override() -> Option<(u32, u32, u32)> {
426    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
427    *K1.get_or_init(|| {
428        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
429        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
430        match p.as_slice() {
431            [bm, bn, w] => Some((*bm, *bn, *w)),
432            _ => None,
433        }
434    })
435}
436
437/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
438/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
439/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
440/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
441/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
442/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
443pub(crate) fn wgmma_gemm_enabled() -> bool {
444    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
445    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
446}
447
448/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
449/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
450/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
451/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
452/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
453/// the split count changes the combine's FP summation order, and the spec verify's batched forward
454/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
455/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
456/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
457/// adaptive retries (any retry MUST pass run-spec self-consistency first).
458/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
459/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
460/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
461/// between eager decode and the verify (the spec-exactness law).
462pub const FA_VEC_MIN_TKV: usize = 96;
463/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
464/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
465/// which moves the crossover — sweep per model, adopt per the battery.
466pub fn fa_vec_min_tkv() -> usize {
467    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
468    *V.get_or_init(|| {
469        std::env::var("MEMRA_FA_VEC_MIN")
470            .ok()
471            .and_then(|v| v.parse().ok())
472            .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
473    })
474}
475
476/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
477/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
478/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
479///
480/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
481/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
482/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
483/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
484/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
485/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
486pub fn fa_f16pv_on() -> bool {
487    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
488    *ON.get_or_init(|| {
489        std::env::var("MEMRA_FA_F16PV")
490            .map(|v| v != "0")
491            .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
492    })
493}
494
495/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
496/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
497/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
498pub fn fa512_hp_on() -> bool {
499    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
500    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
501}
502
503/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
504/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
505/// accumulation. Even n_head and even GQA group required (guarded per call).
506pub fn faw_hp_on() -> bool {
507    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
508    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
509}
510
511/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
512/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
513/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
514pub fn fa512_wide_warps() -> usize {
515    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
516    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
517        Ok("1") => 4,
518        _ => 2,
519    })
520}
521
522/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
523/// and the gemma global-layer rows/parity call sites.
524pub fn fa512_min_tkv() -> usize {
525    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
526    *FA512_MIN.get_or_init(|| {
527        std::env::var("MEMRA_FA512_MIN")
528            .ok()
529            .and_then(|v| v.parse().ok())
530            .unwrap_or(512)
531    })
532}
533/// Per-model crossover default, set at model load BEFORE the first decode (per-model
534/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
535/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
536pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
537    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
538/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
539/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
540/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
541pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(32);
542/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
543/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
544/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
545/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
546/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
547pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
548    std::sync::atomic::AtomicBool::new(false);
549/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
550/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
551/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
552/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
553/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
554/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
555pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
556    std::sync::atomic::AtomicBool::new(true);
557pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
558    std::sync::atomic::AtomicUsize::new(16);
559/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
560/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
561/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
562/// latency-bound at 256 threads — 7us/launch measured).
563pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
564/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
565pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
566/// Per-model stream-k entry override for SPEC serving (-1 = unset → env/default;
567/// 0 = force tiling; 1 = admit the deterministic form selector). The former timing
568/// selector made identical boots choose different fold orders; `MEMRA_MMQ_SK_FORM` is the
569/// explicit numerical-form seam. mmq_ffi reads this before the env.
570pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
571/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
572/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
573pub use memra_kv::KV_FP8_FORCE;
574/// bf16 matvec family block size (MEMRA_MMV_BLOCK, default 128, clamped to [64, 256] and a
575/// multiple of 32 — the f32acc twin's shared reduce caps at 256). NUMERIC-CLASS knob: the
576/// per-thread stride and reduction order change with the block, same acceptance class as
577/// MEMRA_RMS_BLOCK (fresh-tape identity + battery at the pinned value).
578pub(crate) fn mmv_block() -> u32 {
579    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
580    *V.get_or_init(|| {
581        std::env::var("MEMRA_MMV_BLOCK")
582            .ok()
583            .and_then(|v| v.parse().ok())
584            .filter(|&b: &u32| (64..=256).contains(&b) && b % 32 == 0)
585            .unwrap_or(128)
586    })
587}
588
589/// MEMRA_TOPK_FAST=1: barrier-lean sigmoid top-k twin (warp-local top-k + one merge).
590/// Selection and weight arithmetic identical to the round-robin kernel — a latency twin.
591/// MEMRA_SIG_EXPF_DEV=1: device-libm expf sigmoid router (numeric-class door — the
592/// host-glibc transcription is FP64-rate-bound on consumer Blackwell). New tape + battery.
593pub(crate) fn sig_expf_dev_on() -> bool {
594    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
595    *ON.get_or_init(|| std::env::var("MEMRA_SIG_EXPF_DEV").as_deref() == Ok("1"))
596}
597
598pub(crate) fn topk_fast_on() -> bool {
599    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
600    *ON.get_or_init(|| std::env::var("MEMRA_TOPK_FAST").as_deref() == Ok("1"))
601}
602
603pub(crate) fn rms_block() -> u32 {
604    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
605    *V.get_or_init(|| {
606        std::env::var("MEMRA_RMS_BLOCK")
607            .ok()
608            .and_then(|v| v.parse().ok())
609            .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
610    })
611}
612
613pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
614    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
615    if let Some(forced) = *S.get_or_init(|| {
616        std::env::var("MEMRA_FA_SPLIT")
617            .ok()
618            .and_then(|v| v.parse().ok())
619            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
620    }) {
621        return forced;
622    }
623    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
624    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
625    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
626    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
627    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
628    //
629    // SM-AWARE SHORT-CTX RUNG (2026-07-06 rtx6000): the 32-key rung was tuned on the 82-SM 5090.
630    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
631    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on rtx6000 (N=1 sweep + N=3
632    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
633    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
634    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
635    // rig-divergence law: this branch is measured on 188 SMs only).
636    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
637    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
638    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
639    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
640    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
641        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1")
642    {
643        return if t_kv <= 8192 {
644            16
645        } else if t_kv <= 16384 {
646            64
647        } else {
648            128
649        };
650    }
651    let big_rig = fa_sm_count() >= 128;
652    if big_rig {
653        let _ = n_head_kv;
654        if t_kv <= 2048 {
655            // MEMRA_FA_SP_SHORT=N: the SHORT rung only (the SWA layers' capped t_kv lands
656            // here on step37: 33 of 45 layers at t_kv=512). At 16 the tile loop runs
657            // HALF-EMPTY (FA_DEC_TILE=32 -> nt=16 per split), so the V staging pass moves a
658            // half tile per iteration and the combine carries 2x the partials; 32 makes each
659            // split exactly one full tile. A global MEMRA_FA_SPLIT cannot isolate this — it
660            // moves the deep-ctx rung too, where more splits measured worse.
661            // NUMERIC-CLASS door (key partition -> different per-split partials/combine):
662            // new tape + battery, exactly like every other split-ladder change.
663            static SHORT: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
664            if let Some(sp) = *SHORT.get_or_init(|| {
665                std::env::var("MEMRA_FA_SP_SHORT")
666                    .ok()
667                    .and_then(|v| v.parse().ok())
668                    .filter(|&s: &usize| s >= 8 && s % 8 == 0)
669            }) {
670                return sp;
671            }
672            16
673        } else if t_kv <= 16384 {
674            64
675        } else {
676            128
677        }
678    } else if n_head_kv <= 4 {
679        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
680        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
681        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
682        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
683        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
684        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
685        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
686        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
687        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
688        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
689        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
690        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
691        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
692        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
693        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
694        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
695        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
696        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
697        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
698        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
699        if t_kv <= 512 {
700            8
701        } else if t_kv <= 16384 {
702            64
703        } else {
704            128
705        }
706    } else {
707        if t_kv <= 8192 {
708            32
709        } else if t_kv <= 16384 {
710            64
711        } else {
712            128
713        }
714    }
715}
716
717/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
718/// same attribute Engine::batched_variant reads).
719fn fa_sm_count() -> i32 {
720    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
721    *N.get_or_init(|| {
722        cudarc::driver::result::init().ok();
723        cudarc::driver::result::device::get(0)
724            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
725                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
726            .unwrap_or(82)
727    })
728}
729
730/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
731/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
732/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
733fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
734    match head_dim {
735        256 => Ok(""),
736        128 => Ok("_hd128"),
737        d => Err(format!(
738            "fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
739                          callers must gate to sdpa_naive"
740        )
741        .into()),
742    }
743}
744
745/// Quant type codes matching qmatvec.cu QType enum.
746pub const QT_Q8_0: i32 = 0;
747pub const QT_Q4_K: i32 = 1;
748pub const QT_Q6_K: i32 = 2;
749pub const QT_Q5_K: i32 = 3;
750pub const QT_Q3_K: i32 = 4;
751pub const QT_IQ4_XS: i32 = 5;
752pub const QT_IQ3_S: i32 = 6;
753pub const QT_NVFP4: i32 = 7;
754/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
755/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
756/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
757/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
758/// — ONE weight copy total, no Q8_0 re-encode duplicate.
759pub const QT_F8_E4M3: i32 = 10;
760/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
761/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
762pub const QT_NVFP4_RP: i32 = 9;
763/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
764pub const QT_F32: i32 = 8;
765pub const QT_BF16: i32 = 11;
766pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
767/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
768/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
769/// dp4a/MMQ implementation exists.
770pub const QT_Q2_K: i32 = 13;
771/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
772/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
773/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
774/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
775/// scalar `scale` field is 1.0 by the layout contract.
776///
777/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
778/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
779/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
780/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
781/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
782/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
783/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
784/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
785/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
786pub const QT_F8_E4M3_BLK: i32 = 14;
787
788/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
789pub struct Engine {
790    pub gpu: memra_runtime::Gpu,
791    module: Arc<CudaModule>,
792    hybrid: Arc<CudaModule>,
793    qmatvec: Arc<CudaModule>,
794    flash: Arc<CudaModule>,
795    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
796    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
797    /// Lazy: loaded on first global-format use; None until then.
798    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
799    gemm: Arc<CudaModule>,
800    router: Arc<CudaModule>,
801    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
802    sample: Arc<CudaModule>,
803    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
804    /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
805    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
806    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
807    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
808    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
809    /// the single largest block. The cache still owns every address for its full lifetime.
810    moe_cache_layout: Mutex<Option<Vec<usize>>>,
811    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
812    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
813    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
814    /// verify between replays) reuse their addresses and the replay reads/writes live memory
815    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
816    capture_keep_on: std::sync::atomic::AtomicBool,
817    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
818    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
819    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
820    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
821    verify_exact: std::sync::atomic::AtomicBool,
822    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
823    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
824    pub copy_stream: Arc<CudaStream>,
825    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
826    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
827    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
828    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
829    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
830    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
831    #[cfg(memra_cutlass)]
832    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
833    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
834    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
835    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
836    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
837    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
838    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
839    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
840    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
841    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
842    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
843    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
844    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
845    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
846    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
847    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
848    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
849    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
850    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
851    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
852    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
853    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
854    /// before capture under the generate_graph tracking-off window so it carries no events).
855    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
856    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
857    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
858    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
859    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
860    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
861    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
862    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
863    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
864    router_stage: Mutex<Option<PinnedStage>>,
865}
866
867/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
868/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
869/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
870/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
871/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
872/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
873/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
874/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
875/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
876fn fa_v2_on() -> bool {
877    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
878    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
879    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
880    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
881    // + graph bit-identity green on all three models.
882    std::env::var("MEMRA_FA_V2")
883        .map(|v| v != "0")
884        .unwrap_or(true)
885}
886
887/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
888/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
889/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
890/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
891/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
892/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
893/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
894pub(crate) fn fa_v3_on() -> bool {
895    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
896    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
897    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
898    std::env::var("MEMRA_FA_V3")
899        .map(|v| v != "0")
900        .unwrap_or(true)
901}
902
903/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
904/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
905/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
906/// predicate so the twins can never diverge.
907fn fa_v4_mode() -> &'static str {
908    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
909    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
910}
911fn fa_v4_on() -> bool {
912    fa_v4_mode() != "0"
913} // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
914/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
915/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
916/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
917/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
918/// stays kernel-family-identical to decode at the same t_kv.
919/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
920/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
921pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
922    std::sync::atomic::AtomicUsize::new(1024);
923pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
924    std::sync::atomic::AtomicUsize::new(usize::MAX);
925pub fn fa_v4_at_pub(t_kv: usize) -> bool {
926    fa_v4_at(t_kv)
927}
928fn fa_v4_at(t_kv: usize) -> bool {
929    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
930    let mx = *M.get_or_init(|| {
931        std::env::var("MEMRA_FA_V4_MAX")
932            .ok()
933            .and_then(|v| v.parse().ok())
934            .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
935    });
936    fa_v4_on() && t_kv < mx
937}
938/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
939/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
940/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
941/// (same split partition, same softmax/accumulation order, same partials/combine) and only
942/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
943/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
944/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
945/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
946/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
947/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
948/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
949/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
950/// within one process (the v2/v3 pattern).
951pub const FA_DEEP_MIN_DEFAULT: usize = 0;
952fn fa_deep_at(t_kv: usize) -> bool {
953    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") {
954        return false;
955    }
956    let min = std::env::var("MEMRA_FA_DEEP_MIN")
957        .ok()
958        .and_then(|v| v.parse().ok())
959        .unwrap_or(FA_DEEP_MIN_DEFAULT);
960    t_kv >= min
961}
962/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
963pub fn fa_deep_at_pub(t_kv: usize) -> bool {
964    fa_deep_at(t_kv)
965}
966
967fn fa_v3_active(head_dim: usize) -> bool {
968    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
969    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
970    fa_v3_on()
971        && head_dim % 128 == 0
972        && kv_cache_formats() == ("q8_0", "q5_1")
973        && !Engine::kv_fp8_on()
974}
975
976/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
977/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
978/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
979/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
980/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
981/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
982/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
983pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
984    std::env::var("MEMRA_NO_FA_VEC").is_err()
985        && t_kv >= fa_vec_min_tkv()
986        && head_dim == 256
987        && fa_v4_at(t_kv)
988        && !matches!(fa_v4_mode(), "noB3" | "stage")
989        && !Engine::kv_fp8_on()
990}
991/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
992pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize {
993    fa_split_keys(t_kv, n_head_kv)
994}
995
996/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
997/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
998/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
999/// so we allocate through `result::malloc_host` with flags=0 directly.
1000struct PinnedStage {
1001    ptr: *mut u8,
1002    cap: usize,
1003}
1004unsafe impl Send for PinnedStage {}
1005impl PinnedStage {
1006    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
1007        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
1008        Ok(PinnedStage { ptr, cap })
1009    }
1010}
1011impl Drop for PinnedStage {
1012    fn drop(&mut self) {
1013        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
1014    }
1015}
1016
1017/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
1018/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
1019pub const ARGMAX_NB: usize = 256;
1020
1021/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
1022pub(crate) use memra_fa3_vl as fa3_vl_raw;
1023
1024unsafe extern "C" {
1025    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
1026    fn memra_fa3_prefill(
1027        q16: *const core::ffi::c_void,
1028        k16: *const core::ffi::c_void,
1029        v16: *const core::ffi::c_void,
1030        o: *mut f32,
1031        t: i32,
1032        h: i32,
1033        hkv: i32,
1034        d: i32,
1035        scale: f32,
1036        stream: *mut core::ffi::c_void,
1037    ) -> i32;
1038    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
1039    pub(crate) fn memra_fa3_vl(
1040        q16s: *const *const core::ffi::c_void,
1041        k16s: *const *const core::ffi::c_void,
1042        v16s: *const *const core::ffi::c_void,
1043        os: *const *mut f32,
1044        ts: *const i32,
1045        b: i32,
1046        h: i32,
1047        hkv: i32,
1048        d: i32,
1049        scale: f32,
1050        stream: *mut core::ffi::c_void,
1051    ) -> i32;
1052}
1053
1054/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
1055/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
1056/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
1057/// (slots are never re-allocated), so passing raw values is stable across the launch.
1058#[repr(C)]
1059#[derive(Clone, Copy)]
1060pub struct WPtr8(pub [u64; 8]);
1061unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
1062
1063/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
1064/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
1065/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
1066/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
1067#[repr(C)]
1068#[derive(Clone, Copy, Default)]
1069pub struct GdnSeqVl {
1070    pub kb16: u64,
1071    pub gcum: u64,
1072    pub beta: u64,
1073    pub u: u64,
1074    pub wb16: u64,
1075    pub y: u64,
1076    pub ssnap: u64,
1077    pub state_in: u64,
1078    pub state_out: u64,
1079    pub q: u64,
1080    pub p: u64,
1081    pub o: u64,
1082    pub k: u64,
1083    pub v: u64,
1084    pub g: u64,
1085    pub a: u64,
1086    pub w: u64,
1087    pub t: i32,
1088    pub nc: i32,
1089}
1090unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
1091#[repr(C)]
1092#[derive(Clone, Copy)]
1093pub struct GdnVl8(pub [GdnSeqVl; 8]);
1094unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
1095
1096/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
1097/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
1098#[repr(C)]
1099#[derive(Clone, Copy, Default)]
1100pub struct GdnWVl {
1101    pub qb16: u64,
1102    pub pb16: u64,
1103}
1104unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
1105#[repr(C)]
1106#[derive(Clone, Copy)]
1107pub struct GdnWVl8(pub [GdnWVl; 8]);
1108unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
1109
1110/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
1111#[repr(C)]
1112#[derive(Clone, Copy, Default)]
1113pub struct GdnPrepVl {
1114    pub qkv: u64,
1115    pub conv_state: u64,
1116    pub conv_out: u64,
1117    pub q_g: u64,
1118    pub k_g: u64,
1119    pub v_g: u64,
1120    pub q_l2: u64,
1121    pub k_l2: u64,
1122    pub beta_raw: u64,
1123    pub alpha: u64,
1124    pub beta: u64,
1125    pub g_log: u64,
1126    pub o: u64,
1127    pub z: u64,
1128    pub gn: u64,
1129    pub gn16: u64,
1130    pub kb16: u64,
1131    pub qb16: u64,
1132    pub t: i32,
1133    pub pad: i32,
1134}
1135unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
1136#[repr(C)]
1137#[derive(Clone, Copy)]
1138pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
1139unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
1140
1141/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
1142#[repr(C)]
1143#[derive(Clone, Copy, Default)]
1144pub struct FaSeqVl {
1145    pub q: u64,
1146    pub k16: u64,
1147    pub v16: u64,
1148    pub o: u64,
1149    pub kf: u64,
1150    pub vf: u64,
1151    pub t: i32,
1152    pub pad: i32,
1153}
1154unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
1155#[repr(C)]
1156#[derive(Clone, Copy)]
1157pub struct FaVl8(pub [FaSeqVl; 8]);
1158unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
1159
1160/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
1161#[repr(C)]
1162#[derive(Clone, Copy, Default)]
1163pub struct AttnPreVl {
1164    pub qf: u64,
1165    pub kf: u64,
1166    pub vf: u64,
1167    pub q: u64,
1168    pub gate: u64,
1169    pub qn: u64,
1170    pub kn: u64,
1171    pub kc: u64,
1172    pub vc: u64,
1173    pub t: i32,
1174    pub pad: i32,
1175}
1176unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
1177#[repr(C)]
1178#[derive(Clone, Copy)]
1179pub struct AttnPreVl8(pub [AttnPreVl; 8]);
1180unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
1181
1182/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
1183/// varlen K1-K5 chain fills them).
1184pub struct GdnChunkBufs {
1185    pub gcum: CudaSlice<f32>,
1186    pub a: CudaSlice<f32>,
1187    pub p: CudaSlice<f32>,
1188    pub u: CudaSlice<f32>,
1189    pub w: CudaSlice<f32>,
1190    pub kb16: CudaSlice<u8>,
1191    pub wb16: CudaSlice<u8>,
1192    pub y16: CudaSlice<u8>,
1193    pub ssnap16: CudaSlice<u8>,
1194    pub qb16: CudaSlice<u8>,
1195    pub pb16: CudaSlice<u8>,
1196    pub o: CudaSlice<f32>,
1197    pub t: usize,
1198    pub nc: usize,
1199}
1200
1201/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
1202#[repr(C)]
1203#[derive(Clone, Copy)]
1204pub struct F32x8(pub [f32; 8]);
1205unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
1206
1207/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
1208/// process. Bench binaries read it right after the call to print gen-only throughput without the
1209/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
1210pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1211
1212impl Engine {
1213    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
1214        let gpu = memra_runtime::Gpu::new(ordinal)?;
1215        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
1216        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
1217        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
1218        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
1219            use cudarc::driver::sys::CUdevice_attribute_enum as A;
1220            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
1221                .and_then(|d| unsafe {
1222                    Ok((
1223                        cudarc::driver::result::device::get_attribute(
1224                            d,
1225                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
1226                        )?,
1227                        cudarc::driver::result::device::get_attribute(
1228                            d,
1229                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
1230                        )?,
1231                    ))
1232                })
1233                .unwrap_or((0, 0));
1234            let built = env!("MEMRA_BUILT_CUDA_ARCH");
1235            let ok = matches!(
1236                (built, maj, min),
1237                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9)
1238            );
1239            if !ok {
1240                return Err(format!(
1241                    "memra was built for sm_{built} but device {ordinal} reports compute \
1242                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
1243                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass."
1244                )
1245                .into());
1246            }
1247        }
1248        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
1249        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
1250        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
1251        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
1252        unsafe {
1253            use cudarc::driver::sys;
1254            let dev: sys::CUdevice = ordinal as sys::CUdevice;
1255            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1256            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
1257                let mut thresh: u64 = u64::MAX;
1258                let _ = sys::cuMemPoolSetAttribute(
1259                    pool,
1260                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
1261                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
1262                );
1263            }
1264        }
1265        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
1266        let hybrid = gpu
1267            .ctx
1268            .load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
1269        let qmatvec = gpu
1270            .ctx
1271            .load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
1272        let flash = gpu
1273            .ctx
1274            .load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
1275        let gemm = gpu
1276            .ctx
1277            .load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
1278        let router = gpu
1279            .ctx
1280            .load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
1281        let sample = gpu
1282            .ctx
1283            .load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
1284        let copy_stream = gpu.ctx.new_stream()?;
1285        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
1286        // cudarc is in multi-stream mode (main stream +
1287        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
1288        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
1289        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
1290        // (~7 ms/tok host time, measured nsys 2026-07-04 rtx6000), and +4.6% measured on 27B decode —
1291        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
1292        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
1293        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
1294        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
1295        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
1296        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
1297        // implicit event tracking.
1298        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
1299        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
1300        if std::env::var("MEMRA_EVT")
1301            .map(|v| v == "1")
1302            .unwrap_or(false)
1303        {
1304            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1305        } else {
1306            unsafe {
1307                gpu.ctx.disable_event_tracking();
1308            }
1309        }
1310        Ok(Self {
1311            gpu,
1312            module,
1313            hybrid,
1314            qmatvec,
1315            flash,
1316            flash_g: std::sync::OnceLock::new(),
1317            gemm,
1318            router,
1319            sample,
1320            moe_cache: Mutex::new(None),
1321            moe_cache_layout: Mutex::new(None),
1322            copy_stream,
1323            capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1324            verify_exact: std::sync::atomic::AtomicBool::new(false),
1325            capture_keep: Mutex::new(Vec::new()),
1326            argmax_partials: Mutex::new(None),
1327            prime_deqw_ws: Mutex::new(None),
1328            router_stage: Mutex::new(None),
1329            fp8_scratch: Mutex::new(None),
1330            fa_vf16_scratch: Mutex::new(None),
1331            fa_part_pool: Mutex::new(None),
1332            fa_part_retired: Mutex::new(Vec::new()),
1333            fn_cache: Mutex::new(Default::default()),
1334            f16_scratch: Mutex::new(None),
1335            #[cfg(memra_cutlass)]
1336            cutlass_scratch: Mutex::new(None),
1337        })
1338    }
1339
1340    pub fn ctx(&self) -> &Arc<CudaContext> {
1341        &self.gpu.ctx
1342    }
1343
1344    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1345    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1346    ///
1347    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1348    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1349    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1350    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1351    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1352    ///
1353    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1354    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1355    /// under-count headroom does not belong in a gate that queues real work, but the honest
1356    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1357    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1358    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1359    ///
1360    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1361    pub fn pool_cached_bytes(&self) -> usize {
1362        let (reserved, used) = self.pool_reserved_used();
1363        reserved.saturating_sub(used)
1364    }
1365
1366    /// Bytes the driver's per-device CUDA GRAPH memory pool currently holds RESERVED
1367    /// (`cuDeviceGetGraphMemAttribute` RESERVED_MEM_CURRENT) — the backing store of every
1368    /// captured alloc node, which on this engine means the dspark verify-graph pool
1369    /// (decode/step graphs bake pre-allocated buffers and own no alloc nodes). This memory
1370    /// is DISTINCT from the async pool above: `mem_get_info`'s `free` already excludes it,
1371    /// it is never released back (the vgraph pool has no eviction by design), and it GROWS
1372    /// as new (segment, vt)/(vt, rung, hi) keys capture — the growth is what
1373    /// `dspark_vg_admission_debt` charges at admission. Returns 0 if the attribute cannot
1374    /// be queried (never a false headroom claim, matching `pool_cached_bytes`).
1375    pub fn device_graph_mem_reserved(&self) -> usize {
1376        use cudarc::driver::sys as cus;
1377        let Ok(dev) = cudarc::driver::result::device::get(self.gpu.ctx.ordinal() as i32) else {
1378            return 0;
1379        };
1380        let mut bytes: u64 = 0;
1381        let rc = unsafe {
1382            cus::cuDeviceGetGraphMemAttribute(
1383                dev,
1384                cus::CUgraphMem_attribute::CU_GRAPH_MEM_ATTR_RESERVED_MEM_CURRENT,
1385                &mut bytes as *mut u64 as *mut std::ffi::c_void,
1386            )
1387        };
1388        if rc == cus::cudaError_enum::CUDA_SUCCESS {
1389            bytes as usize
1390        } else {
1391            0
1392        }
1393    }
1394
1395    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1396    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1397    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1398    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1399    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1400    /// (0, 0) if the pool cannot be queried.
1401    pub fn pool_reserved_used(&self) -> (usize, usize) {
1402        use cudarc::driver::sys;
1403        unsafe {
1404            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1405            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1406                != sys::CUresult::CUDA_SUCCESS
1407            {
1408                return (0, 0);
1409            }
1410            let (mut reserved, mut used) = (0u64, 0u64);
1411            if sys::cuMemPoolGetAttribute(
1412                pool,
1413                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1414                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1415            ) != sys::CUresult::CUDA_SUCCESS
1416            {
1417                return (0, 0);
1418            }
1419            if sys::cuMemPoolGetAttribute(
1420                pool,
1421                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1422                &mut used as *mut u64 as *mut core::ffi::c_void,
1423            ) != sys::CUresult::CUDA_SUCCESS
1424            {
1425                return (0, 0);
1426            }
1427            (reserved as usize, used as usize)
1428        }
1429    }
1430
1431    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1432    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1433    pub fn stream(&self) -> Arc<CudaStream> {
1434        self.gpu.stream()
1435    }
1436    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1437    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1438    pub fn gkv_on() -> bool {
1439        memra_kv::gkv_on()
1440    }
1441
1442    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1443    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1444    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1445    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1446    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1447    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1448    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1449    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1450    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1451    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1452    /// ON for both — no acceptance cost measured.
1453    pub fn wkv_on() -> bool {
1454        memra_kv::wkv_on()
1455    }
1456
1457    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1458    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1459    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1460    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1461    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1462    pub fn kv_fp8_on() -> bool {
1463        memra_kv::kv_fp8_on()
1464    }
1465
1466    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1467    /// when the fp8-globals arm is on; everything else from the default flash module.
1468    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1469        if head_dim == 512 && Self::gkv_on() {
1470            self.func_g(name)
1471        } else {
1472            self.func(name)
1473        }
1474    }
1475
1476    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1477    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1478    /// per-format fatbins; fall back to the base modules for those.
1479    fn func_g(&self, name: &str) -> CudaFunction {
1480        let m = self.flash_g.get_or_init(|| {
1481            self.gpu
1482                .ctx
1483                .load_module(cudarc::nvrtc::Ptx::from_binary(
1484                    FLASH_FATBIN_KF8VF8.to_vec(),
1485                ))
1486                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1487        });
1488        let key = format!("g:{name}");
1489        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) {
1490            return f.clone();
1491        }
1492        let f = match m.load_function(name) {
1493            Ok(f) => f,
1494            Err(_) => self.func(name),
1495        };
1496        self.fn_cache.lock().unwrap().insert(key, f.clone());
1497        f
1498    }
1499
1500    fn func(&self, name: &str) -> CudaFunction {
1501        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1502        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1503        if let Some(f) = self.fn_cache.lock().unwrap().get(name) {
1504            return f.clone();
1505        }
1506        let f = self
1507            .module
1508            .load_function(name)
1509            .or_else(|_| self.hybrid.load_function(name))
1510            .or_else(|_| self.qmatvec.load_function(name))
1511            .or_else(|_| self.flash.load_function(name))
1512            .or_else(|_| self.gemm.load_function(name))
1513            .or_else(|_| self.router.load_function(name))
1514            .or_else(|_| self.sample.load_function(name))
1515            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1516        self.fn_cache
1517            .lock()
1518            .unwrap()
1519            .insert(name.to_string(), f.clone());
1520        f
1521    }
1522
1523    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1524    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1525    pub fn scatter_trim_logits(
1526        &self,
1527        src: &CudaSlice<f32>,
1528        d2t: &CudaSlice<u32>,
1529        dst: &mut CudaSlice<f32>,
1530        d_vocab: usize,
1531        n_vocab: usize,
1532    ) -> Result<(), Box<dyn std::error::Error>> {
1533        let f1 = self.func("scatter_trim_logits_f32");
1534        let f2 = self.func("scatter_trim_logits_pass2_f32");
1535        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1536        let cfg1 = LaunchConfig {
1537            grid_dim: (256, 1, 1),
1538            block_dim: (256, 1, 1),
1539            shared_mem_bytes: 0,
1540        };
1541        let __s_b1 = self.gpu.stream();
1542        let mut b1 = __s_b1.launch_builder(&f1);
1543        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1544        unsafe {
1545            b1.launch(cfg1)?;
1546        }
1547        let cfg2 = LaunchConfig {
1548            grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1),
1549            block_dim: (256, 1, 1),
1550            shared_mem_bytes: 0,
1551        };
1552        let __s_b2 = self.gpu.stream();
1553        let mut b2 = __s_b2.launch_builder(&f2);
1554        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1555        unsafe {
1556            b2.launch(cfg2)?;
1557        }
1558        Ok(())
1559    }
1560
1561    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1562    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1563
1564    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1565    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1566    #[allow(clippy::too_many_arguments)]
1567    pub fn filter_stats(
1568        &self,
1569        x: &CudaSlice<f32>,
1570        row_stride: usize,
1571        rows: &CudaSlice<i32>,
1572        out_th: &mut CudaSlice<f32>,
1573        out_z: &mut CudaSlice<f32>,
1574        out_max: &mut CudaSlice<f32>,
1575        n: usize,
1576        nrow: usize,
1577        temp: f32,
1578        top_k: i32,
1579        top_p: f32,
1580        min_p: f32,
1581    ) -> Result<(), Box<dyn std::error::Error>> {
1582        // A top-K-selection form of this kernel (3 vocab passes vs the search's ~51) was
1583        // implemented and REFUTED on 2026-08-21 (lane/moebatch-q35moe): the 248k-vocab row is
1584        // L2-resident, so the extra passes are near-free while the per-thread selection list
1585        // spills to local memory — B=8 tick 12.8/11.2 ms (cap 64/32) vs 10.4 ms for this
1586        // kernel, and serve c8 agg ~648 vs ~666. The receipts row is the record; the real
1587        // filtered-sampling win was batching the per-row launches (decode_batch.rs).
1588        //
1589        // COOPERATIVE MULTI-BLOCK FORM (lane/samplat, 2026-08-21): the surviving inefficiency
1590        // was WIDTH, not passes — one block per row left ~94% of the device idle for ~620us
1591        // per B=8 serve tick (5.9% of the tick, box4 nsys receipt). filter_stats_coop_f32
1592        // splits each row across 16 blocks with grid-synced bisection totals — same algorithm,
1593        // slice-partial f32 sums (accepted device-sampling class; sample-check arbitrates).
1594        // Admission: cooperative grid must co-reside (16*nrow blocks vs SM count).
1595        // MEMRA_FILTER_COOP=0 is the rollback seam to the single-block form.
1596        static COOP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1597        let coop_on =
1598            *COOP_ON.get_or_init(|| std::env::var("MEMRA_FILTER_COOP").as_deref() != Ok("0"));
1599        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1600        if coop_on && 16 * nrow <= self.sm_count() as usize {
1601            let f = self.func("filter_stats_coop_f32");
1602            let mut ws = self.alloc_uninit::<f32>(nrow * (2 * 16 + 2))?;
1603            let cfg = LaunchConfig {
1604                grid_dim: (16, nrow as u32, 1),
1605                block_dim: (512, 1, 1),
1606                shared_mem_bytes: 0,
1607            };
1608            let __s_b = self.gpu.stream();
1609            let mut b = __s_b.launch_builder(&f);
1610            b.arg(x)
1611                .arg(&rs)
1612                .arg(rows)
1613                .arg(&mut *out_th)
1614                .arg(&mut *out_z)
1615                .arg(&mut *out_max)
1616                .arg(&mut ws)
1617                .arg(&ni)
1618                .arg(&nr)
1619                .arg(&temp)
1620                .arg(&top_k)
1621                .arg(&top_p)
1622                .arg(&min_p);
1623            unsafe {
1624                b.launch_cooperative(cfg)?;
1625            }
1626            return Ok(());
1627        }
1628        let f = self.func("filter_stats_f32");
1629        let cfg = LaunchConfig {
1630            grid_dim: (nrow as u32, 1, 1),
1631            block_dim: (1024, 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(x)
1637            .arg(&rs)
1638            .arg(rows)
1639            .arg(&mut *out_th)
1640            .arg(&mut *out_z)
1641            .arg(&mut *out_max)
1642            .arg(&ni)
1643            .arg(&nr)
1644            .arg(&temp)
1645            .arg(&top_k)
1646            .arg(&top_p)
1647            .arg(&min_p);
1648        unsafe {
1649            b.launch(cfg)?;
1650        }
1651        Ok(())
1652    }
1653
1654    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1655    #[allow(clippy::too_many_arguments)]
1656    pub fn softmax_gather_filtered(
1657        &self,
1658        x: &CudaSlice<f32>,
1659        row_stride: usize,
1660        ids: &CudaSlice<u32>,
1661        rows: &CudaSlice<i32>,
1662        th: &CudaSlice<f32>,
1663        z: &CudaSlice<f32>,
1664        out: &mut CudaSlice<f32>,
1665        n: usize,
1666        npair: usize,
1667        temp: f32,
1668    ) -> Result<(), Box<dyn std::error::Error>> {
1669        let f = self.func("softmax_gather_filtered_f32");
1670        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1671        let cfg = LaunchConfig {
1672            grid_dim: (npair as u32, 1, 1),
1673            block_dim: (256, 1, 1),
1674            shared_mem_bytes: 0,
1675        };
1676        let __s_b = self.gpu.stream();
1677        let mut b = __s_b.launch_builder(&f);
1678        b.arg(x)
1679            .arg(&rs)
1680            .arg(ids)
1681            .arg(rows)
1682            .arg(th)
1683            .arg(z)
1684            .arg(&mut *out)
1685            .arg(&ni)
1686            .arg(&np)
1687            .arg(&temp);
1688        unsafe {
1689            b.launch(cfg)?;
1690        }
1691        Ok(())
1692    }
1693
1694    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1695    #[allow(clippy::too_many_arguments)]
1696    pub fn residual_sample_filtered(
1697        &self,
1698        p: &CudaSlice<f32>,
1699        q: Option<&CudaSlice<f32>>,
1700        n: usize,
1701        temp: f32,
1702        seed: u64,
1703        stream_pos: u32,
1704        p_stats: (f32, f32, f32),
1705        q_stats: (f32, f32, f32),
1706        out_tok: &mut CudaSlice<u32>,
1707    ) -> Result<(), Box<dyn std::error::Error>> {
1708        let f = self.func("residual_sample_filtered_f32");
1709        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1710        let has_q: i32 = q.is_some() as i32;
1711        let qbuf = q.unwrap_or(p);
1712        let (pm, pth, pz) = p_stats;
1713        let (qm, qth, qz) = q_stats;
1714        let cfg = LaunchConfig {
1715            grid_dim: (1, 1, 1),
1716            block_dim: (1024, 1, 1),
1717            shared_mem_bytes: 0,
1718        };
1719        let __s_b = self.gpu.stream();
1720        let mut b = __s_b.launch_builder(&f);
1721        b.arg(p)
1722            .arg(qbuf)
1723            .arg(&has_q)
1724            .arg(&ni)
1725            .arg(&temp)
1726            .arg(&slo)
1727            .arg(&shi)
1728            .arg(&stream_pos)
1729            .arg(&pm)
1730            .arg(&pth)
1731            .arg(&pz)
1732            .arg(&qm)
1733            .arg(&qth)
1734            .arg(&qz)
1735            .arg(&mut *out_tok);
1736        unsafe {
1737            b.launch(cfg)?;
1738        }
1739        Ok(())
1740    }
1741
1742    /// Sparse-q filtered residual sample: token ~ norm(max(0, fp - q)) where fp is the
1743    /// FILTERED softmax of `p` (stats from `filter_stats`) and q is a PROBABILITY vector
1744    /// supported on `cand_ids` (<=32 ids — the DFlash2 selector's candidate-set proposal;
1745    /// lane/dspark-sampled-admission-20260820). Same event semantics/Philox tag as
1746    /// `residual_sample_filtered` — one uniform per (seed, stream_pos).
1747    #[allow(clippy::too_many_arguments)]
1748    pub fn residual_sample_sparse_q(
1749        &self,
1750        p: &CudaSlice<f32>,
1751        cand_ids: &CudaSlice<u32>,
1752        q_probs: &CudaSlice<f32>,
1753        n_cand: usize,
1754        n: usize,
1755        temp: f32,
1756        seed: u64,
1757        stream_pos: u32,
1758        p_stats: (f32, f32, f32),
1759        out_tok: &mut CudaSlice<u32>,
1760    ) -> Result<(), Box<dyn std::error::Error>> {
1761        assert!(
1762            n_cand >= 1 && n_cand <= 32,
1763            "residual_sample_sparse_q supports 1..=32 candidates, got {n_cand}"
1764        );
1765        let f = self.func("residual_sample_sparse_q_f32");
1766        let (ni, nc) = (n as i32, n_cand as i32);
1767        let (slo, shi) = ((seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1768        let (pm, pth, pz) = p_stats;
1769        let cfg = LaunchConfig {
1770            grid_dim: (1, 1, 1),
1771            block_dim: (1024, 1, 1),
1772            shared_mem_bytes: 0,
1773        };
1774        let __s_b = self.gpu.stream();
1775        let mut b = __s_b.launch_builder(&f);
1776        b.arg(p)
1777            .arg(cand_ids)
1778            .arg(q_probs)
1779            .arg(&nc)
1780            .arg(&ni)
1781            .arg(&temp)
1782            .arg(&slo)
1783            .arg(&shi)
1784            .arg(&stream_pos)
1785            .arg(&pm)
1786            .arg(&pth)
1787            .arg(&pz)
1788            .arg(&mut *out_tok);
1789        unsafe {
1790            b.launch(cfg)?;
1791        }
1792        Ok(())
1793    }
1794
1795    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1796    #[allow(clippy::too_many_arguments)]
1797    pub fn gumbel_perturb_filtered(
1798        &self,
1799        x: &CudaSlice<f32>,
1800        y: &mut CudaSlice<f32>,
1801        n: usize,
1802        seed: u64,
1803        stream_pos: u32,
1804        temp: f32,
1805        row_max: f32,
1806        th: f32,
1807    ) -> Result<(), Box<dyn std::error::Error>> {
1808        let f = self.func("gumbel_perturb_filtered_f32");
1809        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1810        let cfg = LaunchConfig {
1811            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1812            block_dim: (256, 1, 1),
1813            shared_mem_bytes: 0,
1814        };
1815        let __s_b = self.gpu.stream();
1816        let mut b = __s_b.launch_builder(&f);
1817        b.arg(x)
1818            .arg(&mut *y)
1819            .arg(&ni)
1820            .arg(&slo)
1821            .arg(&shi)
1822            .arg(&stream_pos)
1823            .arg(&temp)
1824            .arg(&row_max)
1825            .arg(&th);
1826        unsafe {
1827            b.launch(cfg)?;
1828        }
1829        Ok(())
1830    }
1831
1832    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1833    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1834    /// filtered rejection sampling exact for the penalized target.
1835    #[allow(clippy::too_many_arguments)]
1836    pub fn penalize_logits(
1837        &self,
1838        x: &mut CudaSlice<f32>,
1839        hist: &CudaSlice<u32>,
1840        n_hist: usize,
1841        rep: f32,
1842        freq: f32,
1843        present: f32,
1844        n: usize,
1845    ) -> Result<(), Box<dyn std::error::Error>> {
1846        if n_hist == 0 {
1847            return Ok(());
1848        }
1849        let f = self.func("penalize_logits_f32");
1850        let (nh, ni) = (n_hist as i32, n as i32);
1851        let cfg = LaunchConfig {
1852            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1853            block_dim: (128, 1, 1),
1854            shared_mem_bytes: 0,
1855        };
1856        let __s_b = self.gpu.stream();
1857        let mut b = __s_b.launch_builder(&f);
1858        b.arg(&mut *x)
1859            .arg(hist)
1860            .arg(&nh)
1861            .arg(&rep)
1862            .arg(&freq)
1863            .arg(&present)
1864            .arg(&ni);
1865        unsafe {
1866            b.launch(cfg)?;
1867        }
1868        Ok(())
1869    }
1870
1871    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1872    #[allow(clippy::too_many_arguments)]
1873    pub fn penalize_logits_rows(
1874        &self,
1875        x: &mut CudaSlice<f32>,
1876        hist: &CudaSlice<u32>,
1877        n_hist: usize,
1878        rep: f32,
1879        freq: f32,
1880        present: f32,
1881        n: usize,
1882        nrow: usize,
1883    ) -> Result<(), Box<dyn std::error::Error>> {
1884        if n_hist == 0 || nrow == 0 {
1885            return Ok(());
1886        }
1887        let f = self.func("penalize_logits_rows_f32");
1888        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1889        let cfg = LaunchConfig {
1890            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1891            block_dim: (128, 1, 1),
1892            shared_mem_bytes: 0,
1893        };
1894        let __s_b = self.gpu.stream();
1895        let mut b = __s_b.launch_builder(&f);
1896        b.arg(&mut *x)
1897            .arg(hist)
1898            .arg(&nh)
1899            .arg(&rep)
1900            .arg(&freq)
1901            .arg(&present)
1902            .arg(&ni)
1903            .arg(&nr);
1904        unsafe {
1905            b.launch(cfg)?;
1906        }
1907        Ok(())
1908    }
1909
1910    /// ROW-INCREMENTAL penalties (dspark penalized-sampled admission): row r of `x`
1911    /// penalizes over the last `min(win, n_hist0 + r)` entries of `hist[..n_hist0 + r]`,
1912    /// where `hist` = [session window (n_hist0) ++ per-row drafted tokens (nrow-1)]. This
1913    /// is the within-round evolving penalty state block drafting needs: verify row r's
1914    /// target is penalized by every token committed before it INCLUDING same-round
1915    /// accepts — `penalize_logits_rows` (one shared window) is the frozen-window
1916    /// approximation this exists to replace on the dspark route.
1917    #[allow(clippy::too_many_arguments)]
1918    pub fn penalize_logits_rows_inc(
1919        &self,
1920        x: &mut CudaSlice<f32>,
1921        hist: &CudaSlice<u32>,
1922        n_hist0: usize,
1923        rep: f32,
1924        freq: f32,
1925        present: f32,
1926        n: usize,
1927        nrow: usize,
1928        win: usize,
1929    ) -> Result<(), Box<dyn std::error::Error>> {
1930        if nrow == 0 || win == 0 || (n_hist0 == 0 && nrow == 1) {
1931            return Ok(());
1932        }
1933        debug_assert!(
1934            hist.len() >= n_hist0 + nrow - 1,
1935            "rows-inc hist must carry n_hist0 + nrow - 1 ids"
1936        );
1937        let f = self.func("penalize_logits_rows_inc_f32");
1938        let max_len = win.min(n_hist0 + nrow - 1).max(1);
1939        let (nh, ni, nr, wi) = (n_hist0 as i32, n as i32, nrow as i32, win as i32);
1940        let cfg = LaunchConfig {
1941            grid_dim: (max_len.div_ceil(128) as u32, nrow as u32, 1),
1942            block_dim: (128, 1, 1),
1943            shared_mem_bytes: 0,
1944        };
1945        let __s_b = self.gpu.stream();
1946        let mut b = __s_b.launch_builder(&f);
1947        b.arg(&mut *x)
1948            .arg(hist)
1949            .arg(&nh)
1950            .arg(&rep)
1951            .arg(&freq)
1952            .arg(&present)
1953            .arg(&ni)
1954            .arg(&nr)
1955            .arg(&wi);
1956        unsafe {
1957            b.launch(cfg)?;
1958        }
1959        Ok(())
1960    }
1961
1962    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1963    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1964    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1965    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1966    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1967    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1968    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1969    pub fn wpf_level() -> u32 {
1970        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1971        *ON.get_or_init(|| {
1972            std::env::var("MEMRA_WPF")
1973                .ok()
1974                .and_then(|v| v.parse().ok())
1975                .unwrap_or(1)
1976        })
1977    }
1978
1979    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1980    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1981    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1982    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1983    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1984    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1985    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1986    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1987    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1988    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1989    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1990    pub fn set_verify_exact(&self, on: bool) {
1991        self.verify_exact
1992            .store(on, std::sync::atomic::Ordering::Relaxed);
1993    }
1994    pub(crate) fn verify_exact_on(&self) -> bool {
1995        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1996    }
1997
1998    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1999    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
2000    pub fn qkv_append_on() -> bool {
2001        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2002        *ON.get_or_init(|| {
2003            std::env::var("MEMRA_QKV_APPEND")
2004                .map(|v| v != "0")
2005                .unwrap_or(true)
2006        })
2007    }
2008
2009    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
2010    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
2011    pub fn pdl_wb_on() -> bool {
2012        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2013        *ON.get_or_init(|| {
2014            std::env::var("MEMRA_PDL_WB")
2015                .map(|v| v != "0")
2016                .unwrap_or(true)
2017        })
2018    }
2019
2020    /// Trunk-kernels norm ILP seam (lane/dspark-trunk-kernels-20260820): the T-row verify
2021    /// norms (rms_norm_f32 / add_rms_norm_f32 at grid=T, block=256) are serial-latency
2022    /// chains — 20 strided scalar load->fma rounds measured 11.8-12.2us/inst (nsys-B verify
2023    /// scope: 130 inst/rd = 1.51 ms/rd). The `_v2` twins unroll the element loop 4-deep
2024    /// (independent loads in flight; SAME per-thread element order into ONE accumulator,
2025    /// reduce VERBATIM) — BIT-IDENTICAL per row at every (ncols, blockDim).
2026    /// MEMRA_NORM_ILP=0 reverts to the v1 kernels alone.
2027    pub fn norm_ilp_on() -> bool {
2028        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2029        *ON.get_or_init(|| {
2030            std::env::var("MEMRA_NORM_ILP")
2031                .map(|v| v != "0")
2032                .unwrap_or(true)
2033        })
2034    }
2035
2036    /// Trunk-kernels FFN dual seam (lane/dspark-trunk-kernels-20260820): the qwen35
2037    /// t-parallel verify FFN pair rides the PROVEN dual gate+up doors
2038    /// (`matmul_decode_exact_dual_pre` + `silu_mul_scaled_q8_1`, the q27 verify shape —
2039    /// bit-identical per (tensor,token,row), kernel-check-pinned, MEMRA_SPEC_DUAL_T
2040    /// receipts) instead of two singles + silu_mul + a standalone quantize. The doors
2041    /// existed but the qwen35 body never called them (nsys-B verify scope: gate+up singles
2042    /// = 107 launches/rd at grid 4352). MEMRA_TK_FFN_DUAL=0 reverts to the singles chain.
2043    pub fn tk_ffn_dual_on() -> bool {
2044        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2045        *ON.get_or_init(|| {
2046            std::env::var("MEMRA_TK_FFN_DUAL")
2047                .map(|v| v != "0")
2048                .unwrap_or(true)
2049        })
2050    }
2051
2052    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
2053    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
2054    /// per-model no-harm bisect knob.
2055    pub fn pdl_mmvq_on() -> bool {
2056        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2057        *ON.get_or_init(|| {
2058            std::env::var("MEMRA_PDL_MMVQ")
2059                .map(|v| v != "0")
2060                .unwrap_or(true)
2061        })
2062    }
2063
2064    pub fn pdl_on() -> bool {
2065        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2066        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
2067    }
2068
2069    /// PDL wave-B seam (gap-diagnosis arc, GAP-DIAGNOSIS.md verdict 8): the gemma
2070    /// NVFP4mix decode chain's hot kernels — nvfp4 fused2/mr2 and the q8_0 `_rp`
2071    /// singles — join the wave-A launch class. Scheduling-only (the entry macro waits
2072    /// on the producer before any read), bit-identical by construction.
2073    /// MEMRA_PDL_NVFP4=0 reverts wave-B alone.
2074    pub fn pdl_nvfp4q8_on() -> bool {
2075        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2076        *ON.get_or_init(|| {
2077            std::env::var("MEMRA_PDL_NVFP4")
2078                .map(|v| v != "0")
2079                .unwrap_or(true)
2080        })
2081    }
2082
2083    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
2084    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
2085    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
2086    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
2087    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
2088    fn q40_mr1_on() -> bool {
2089        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
2090        match *Q40MR.get_or_init(|| {
2091            std::env::var("MEMRA_Q40_MR")
2092                .ok()
2093                .and_then(|v| v.parse().ok())
2094        }) {
2095            Some(v) => v == 1,
2096            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2097        }
2098    }
2099
2100    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
2101    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
2102    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
2103    /// writes wrong bytes silently.
2104    fn pdl_func_flash(
2105        &self,
2106        g: bool,
2107        name: &'static str,
2108    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
2109        use cudarc::driver::sys as cu;
2110        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
2111        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
2112        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
2113        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
2114        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
2115        // this engine's CUcontext; single-context runs behave exactly as before.
2116        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
2117            std::sync::Mutex::new(None);
2118        static FNS: std::sync::Mutex<
2119            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
2120        > = std::sync::Mutex::new(None);
2121        let ctx_key = self.ctx().cu_ctx() as usize;
2122        if let Some(&f) = FNS
2123            .lock()
2124            .unwrap()
2125            .get_or_insert_with(Default::default)
2126            .get(&(ctx_key, g, name))
2127        {
2128            return Ok(f as cu::CUfunction);
2129        }
2130        let module = {
2131            let mut mods = MODS.lock().unwrap();
2132            let map = mods.get_or_insert_with(Default::default);
2133            match map.get(&(ctx_key, g)) {
2134                Some(&m) => m,
2135                None => {
2136                    let m = self.pdl_load_module_in_ctx(if g {
2137                        FLASH_FATBIN_KF8VF8
2138                    } else {
2139                        FLASH_FATBIN
2140                    })?;
2141                    map.insert((ctx_key, g), m);
2142                    m
2143                }
2144            }
2145        };
2146        let cname = std::ffi::CString::new(name)?;
2147        let mut f: cu::CUfunction = std::ptr::null_mut();
2148        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
2149        if r != cu::CUresult::CUDA_SUCCESS {
2150            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
2151        }
2152        FNS.lock()
2153            .unwrap()
2154            .get_or_insert_with(Default::default)
2155            .insert((ctx_key, g, name), f as usize);
2156        Ok(f)
2157    }
2158
2159    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
2160    /// the module to the thread's CURRENT context — a remote-stage engine must not
2161    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
2162    /// current context before returning.
2163    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
2164        use cudarc::driver::sys as cu;
2165        let mut prev: cu::CUcontext = std::ptr::null_mut();
2166        unsafe {
2167            cu::cuCtxGetCurrent(&mut prev).result()?;
2168        }
2169        self.ctx().bind_to_thread()?;
2170        let mut m: cu::CUmodule = std::ptr::null_mut();
2171        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
2172        let restore = if prev.is_null() {
2173            cu::CUresult::CUDA_SUCCESS
2174        } else {
2175            unsafe { cu::cuCtxSetCurrent(prev) }
2176        };
2177        if r != cu::CUresult::CUDA_SUCCESS {
2178            return Err(format!("pdl module load: {r:?}").into());
2179        }
2180        if restore != cu::CUresult::CUDA_SUCCESS {
2181            return Err(format!("pdl module load: ctx restore {restore:?}").into());
2182        }
2183        Ok(m as usize)
2184    }
2185
2186    /// Raw CUfunction for prebuilt-args dispatch experiments (M4 probe): same duplicate
2187    /// raw-module loading as the PDL path, WITHOUT the PDL launch attribute.
2188    pub fn raw_kernel_function(
2189        &self,
2190        name: &'static str,
2191    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
2192        self.pdl_func(name)
2193    }
2194
2195    fn pdl_func(
2196        &self,
2197        name: &'static str,
2198    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
2199        use cudarc::driver::sys as cu;
2200        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
2201        // are context-scoped; key everything by this engine's CUcontext).
2202        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
2203            std::sync::Mutex::new(None);
2204        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
2205        // duplicate module, loaded lazily on the first kernels-module miss.
2206        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
2207            std::sync::Mutex::new(None);
2208        static FNS: std::sync::Mutex<
2209            Option<std::collections::HashMap<(usize, &'static str), usize>>,
2210        > = std::sync::Mutex::new(None);
2211        let ctx_key = self.ctx().cu_ctx() as usize;
2212        if let Some(&f) = FNS
2213            .lock()
2214            .unwrap()
2215            .get_or_insert_with(Default::default)
2216            .get(&(ctx_key, name))
2217        {
2218            return Ok(f as cu::CUfunction);
2219        }
2220        let module = {
2221            let mut mods = MODULES.lock().unwrap();
2222            let map = mods.get_or_insert_with(Default::default);
2223            match map.get(&ctx_key) {
2224                Some(&m) => m,
2225                None => {
2226                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
2227                    map.insert(ctx_key, m);
2228                    m
2229                }
2230            }
2231        };
2232        let cname = std::ffi::CString::new(name)?;
2233        let mut f: cu::CUfunction = std::ptr::null_mut();
2234        let mut r =
2235            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
2236        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
2237            let qmodule = {
2238                let mut mods = QMODULES.lock().unwrap();
2239                let map = mods.get_or_insert_with(Default::default);
2240                match map.get(&ctx_key) {
2241                    Some(&m) => m,
2242                    None => {
2243                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
2244                        map.insert(ctx_key, m);
2245                        m
2246                    }
2247                }
2248            };
2249            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
2250        }
2251        if r != cu::CUresult::CUDA_SUCCESS {
2252            return Err(format!("pdl_func {name}: {r:?}").into());
2253        }
2254        FNS.lock()
2255            .unwrap()
2256            .get_or_insert_with(Default::default)
2257            .insert((ctx_key, name), f as usize);
2258        Ok(f)
2259    }
2260
2261    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
2262    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
2263    ///
2264    /// # Safety
2265    /// `params` must match the kernel's exact parameter list (order, types, count) —
2266    /// a mismatch corrupts the launch silently.
2267    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
2268    /// builder path's fa_func/func_g choice exactly).
2269    ///
2270    /// # Safety
2271    /// Same contract as `launch_pdl`.
2272    unsafe fn launch_pdl_flash(
2273        &self,
2274        g: bool,
2275        name: &'static str,
2276        grid: (u32, u32, u32),
2277        block: (u32, u32, u32),
2278        smem: u32,
2279        params: &mut [*mut std::ffi::c_void],
2280    ) -> Result<(), Box<dyn std::error::Error>> {
2281        use cudarc::driver::sys as cu;
2282        let f = self.pdl_func_flash(g, name)?;
2283        if smem > 0 {
2284            // mirror the builder path's opt-in ceiling (idempotent host-side set).
2285            let r =
2286                unsafe {
2287                    cu::cuFuncSetAttribute(f,
2288                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
2289                smem as i32)
2290                };
2291            if r != cu::CUresult::CUDA_SUCCESS {
2292                return Err(format!("pdl smem attr {name}: {r:?}").into());
2293            }
2294        }
2295        let mut attr = cu::CUlaunchAttribute {
2296            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
2297            pad: [0; 4],
2298            value: cu::CUlaunchAttributeValue {
2299                programmaticStreamSerializationAllowed: 1,
2300            },
2301        };
2302        let cfg = cu::CUlaunchConfig {
2303            gridDimX: grid.0,
2304            gridDimY: grid.1,
2305            gridDimZ: grid.2,
2306            blockDimX: block.0,
2307            blockDimY: block.1,
2308            blockDimZ: block.2,
2309            sharedMemBytes: smem,
2310            hStream: self.gpu.stream().cu_stream(),
2311            attrs: &mut attr,
2312            numAttrs: 1,
2313        };
2314        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
2315        if r != cu::CUresult::CUDA_SUCCESS {
2316            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
2317        }
2318        Ok(())
2319    }
2320
2321    unsafe fn launch_pdl(
2322        &self,
2323        name: &'static str,
2324        grid: (u32, u32, u32),
2325        block: (u32, u32, u32),
2326        params: &mut [*mut std::ffi::c_void],
2327    ) -> Result<(), Box<dyn std::error::Error>> {
2328        use cudarc::driver::sys as cu;
2329        let f = self.pdl_func(name)?;
2330        let mut attr = cu::CUlaunchAttribute {
2331            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
2332            pad: [0; 4],
2333            value: cu::CUlaunchAttributeValue {
2334                programmaticStreamSerializationAllowed: 1,
2335            },
2336        };
2337        let cfg = cu::CUlaunchConfig {
2338            gridDimX: grid.0,
2339            gridDimY: grid.1,
2340            gridDimZ: grid.2,
2341            blockDimX: block.0,
2342            blockDimY: block.1,
2343            blockDimZ: block.2,
2344            sharedMemBytes: 0,
2345            hStream: self.gpu.stream().cu_stream(),
2346            attrs: &mut attr,
2347            numAttrs: 1,
2348        };
2349        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
2350        if r != cu::CUresult::CUDA_SUCCESS {
2351            return Err(format!("launch_pdl {name}: {r:?}").into());
2352        }
2353        Ok(())
2354    }
2355
2356    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
2357    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
2358    pub fn prefetch_weight_l2(
2359        &self,
2360        w: &crate::model::GpuTensor,
2361    ) -> Result<(), Box<dyn std::error::Error>> {
2362        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
2363            let p = rp4.as_ref().unwrap_or(bytes);
2364            self.prefetch_l2(p, p.len())?;
2365        }
2366        Ok(())
2367    }
2368
2369    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
2370    /// by the DEVICE token id at tok[idx] into f32.
2371    pub fn gather_row_bf16(
2372        &self,
2373        table: &CudaSlice<u8>,
2374        tok: &CudaSlice<u32>,
2375        idx: usize,
2376        dst: &mut CudaSlice<f32>,
2377        ncols: usize,
2378    ) -> Result<(), Box<dyn std::error::Error>> {
2379        let f = self.func("gather_row_bf16_f32");
2380        let cfg = LaunchConfig {
2381            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2382            block_dim: (256, 1, 1),
2383            shared_mem_bytes: 0,
2384        };
2385        let (nc, ix) = (ncols as i32, idx as i32);
2386        let __s_b = self.gpu.stream();
2387        let mut b = __s_b.launch_builder(&f);
2388        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2389        unsafe {
2390            b.launch(cfg)?;
2391        }
2392        Ok(())
2393    }
2394
2395    /// DFlash2 grouped dynamic causal conv (dflash lane, DFLASH2-EVAL-20260820.md):
2396    /// out[p,c] = sum_{o<ksize, o<=p} (base[half][o][c] + dyn[p][half][o][group(c)])
2397    /// * x[p-o][c]. `dyn_` is the kernel_projection GEMM output [rows, 2*ksize*groups];
2398    /// `base` is base_kernel [2, ksize, hidden] flattened; `half` picks prepare(0) /
2399    /// finish(1).
2400    #[allow(clippy::too_many_arguments)]
2401    pub fn dflash2_dynconv(
2402        &self,
2403        x: &CudaSlice<f32>,
2404        dyn_: &CudaSlice<f32>,
2405        base: &CudaSlice<f32>,
2406        out: &mut CudaSlice<f32>,
2407        rows: usize,
2408        hidden: usize,
2409        group_size: usize,
2410        ksize: usize,
2411        half: usize,
2412    ) -> Result<(), Box<dyn std::error::Error>> {
2413        assert_eq!(hidden % group_size, 0, "hidden % group_size != 0");
2414        let f = self.func("dflash2_dynconv_f32");
2415        let n = rows * hidden;
2416        let cfg = LaunchConfig {
2417            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2418            block_dim: (256, 1, 1),
2419            shared_mem_bytes: 0,
2420        };
2421        let (ri, hi, gi, ki, hf) = (
2422            rows as i32,
2423            hidden as i32,
2424            group_size as i32,
2425            ksize as i32,
2426            half as i32,
2427        );
2428        let __s_b = self.gpu.stream();
2429        let mut b = __s_b.launch_builder(&f);
2430        b.arg(x)
2431            .arg(dyn_)
2432            .arg(base)
2433            .arg(out)
2434            .arg(&ri)
2435            .arg(&hi)
2436            .arg(&gi)
2437            .arg(&ki)
2438            .arg(&hf);
2439        unsafe {
2440            b.launch(cfg)?;
2441        }
2442        Ok(())
2443    }
2444
2445    /// Per-row top-k (k <= 32) over a [n_rows, n_cols] logits matrix (DFlash2
2446    /// candidate selector). Returns (values [n_rows, k], column indices [n_rows, k]),
2447    /// value-descending, ties to the lower index.
2448    pub fn topk_rows(
2449        &self,
2450        logits: &CudaSlice<f32>,
2451        n_rows: usize,
2452        n_cols: usize,
2453        k: usize,
2454    ) -> Result<(CudaSlice<f32>, CudaSlice<u32>), Box<dyn std::error::Error>> {
2455        assert!(k <= 32 && k >= 1, "topk_rows supports 1..=32, got {k}");
2456        assert!(k <= n_cols, "topk_rows: k {k} > n_cols {n_cols}");
2457        let f = self.func("topk_rows_f32");
2458        let nth = 256usize;
2459        let mut vals = self.uninit(n_rows * k)?;
2460        let mut idxs = self.gpu.stream().alloc_zeros::<u32>(n_rows * k)?;
2461        let cfg = LaunchConfig {
2462            grid_dim: (n_rows as u32, 1, 1),
2463            block_dim: (nth as u32, 1, 1),
2464            shared_mem_bytes: (nth * k * 8) as u32,
2465        };
2466        let (nr, nc, ki) = (n_rows as i32, n_cols as i32, k as i32);
2467        let __s_b = self.gpu.stream();
2468        let mut b = __s_b.launch_builder(&f);
2469        b.arg(logits)
2470            .arg(&nr)
2471            .arg(&nc)
2472            .arg(&ki)
2473            .arg(&mut vals)
2474            .arg(&mut idxs);
2475        unsafe {
2476            b.launch(cfg)?;
2477        }
2478        Ok((vals, idxs))
2479    }
2480
2481    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2482    pub fn add_row_inplace(
2483        &self,
2484        logits: &mut CudaSlice<f32>,
2485        bias: &CudaSlice<f32>,
2486        n: usize,
2487        row_off: usize,
2488    ) -> Result<(), Box<dyn std::error::Error>> {
2489        let f = self.func("add_row_inplace_f32");
2490        let cfg = LaunchConfig {
2491            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2492            block_dim: (256, 1, 1),
2493            shared_mem_bytes: 0,
2494        };
2495        let (ni, off) = (n as i32, row_off as i64);
2496        let __s_b = self.gpu.stream();
2497        let mut b = __s_b.launch_builder(&f);
2498        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2499        unsafe {
2500            b.launch(cfg)?;
2501        }
2502        Ok(())
2503    }
2504
2505    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2506    pub fn prefetch_l2(
2507        &self,
2508        p: &CudaSlice<u8>,
2509        n: usize,
2510    ) -> Result<(), Box<dyn std::error::Error>> {
2511        let f = self.func("prefetch_l2_bytes");
2512        let lines = n.div_ceil(128);
2513        let ni = n as i64;
2514        let cfg = LaunchConfig {
2515            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2516            block_dim: (256, 1, 1),
2517            shared_mem_bytes: 0,
2518        };
2519        let __s_b = self.gpu.stream();
2520        let mut b = __s_b.launch_builder(&f);
2521        b.arg(p).arg(&ni);
2522        unsafe {
2523            b.launch(cfg)?;
2524        }
2525        Ok(())
2526    }
2527
2528    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2529    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2530    pub fn router_gemv(
2531        &self,
2532        w: &CudaSlice<f32>,
2533        x: &CudaSlice<f32>,
2534        n_embd: usize,
2535        n_experts: usize,
2536        t: usize,
2537    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2538        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2539        // stream differs) — too small to justify a numeric config change; deleted.
2540        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2541        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2542        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2543        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2544            Ok("0") => false,
2545            Ok(_) => true,
2546            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2547        };
2548        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2549        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2550        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2551        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2552        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2553        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2554        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2555        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2556        // (perf-only, bits equal).
2557        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2558        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2559    }
2560
2561    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2562    /// force both forms; `batch` requires `w8`).
2563    pub fn router_gemv_form(
2564        &self,
2565        w: &CudaSlice<f32>,
2566        x: &CudaSlice<f32>,
2567        n_embd: usize,
2568        n_experts: usize,
2569        t: usize,
2570        w8: bool,
2571        batch: bool,
2572    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2573        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2574        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2575        let f = if batch {
2576            self.func("router_gemv_f32_w8_batch")
2577        } else if w8 {
2578            self.func("router_gemv_f32_w8")
2579        } else {
2580            self.func("router_gemv_f32")
2581        };
2582        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2583        let cfg = if batch {
2584            LaunchConfig {
2585                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2586                block_dim: (32, 8, 1),
2587                shared_mem_bytes: 0,
2588            }
2589        } else {
2590            LaunchConfig {
2591                grid_dim: (n_experts as u32, t as u32, 1),
2592                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2593                shared_mem_bytes: 0,
2594            }
2595        };
2596        let __s_b = self.gpu.stream();
2597        let mut b = __s_b.launch_builder(&f);
2598        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2599        unsafe {
2600            b.launch(cfg)?;
2601        }
2602        Ok(y)
2603    }
2604
2605    /// `router_gemv` (decode form selection) writing into a caller-owned [t*n_experts]
2606    /// buffer — token-graph alloc-free.
2607    pub fn router_gemv_into(
2608        &self,
2609        w: &CudaSlice<f32>,
2610        x: &CudaSlice<f32>,
2611        y: &mut CudaSlice<f32>,
2612        n_embd: usize,
2613        n_experts: usize,
2614        t: usize,
2615    ) -> Result<(), Box<dyn std::error::Error>> {
2616        if y.len() < t * n_experts {
2617            return Err("router_gemv_into output too small".into());
2618        }
2619        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2620            Ok("0") => false,
2621            Ok(_) => true,
2622            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2623        };
2624        let f = if w8 {
2625            self.func("router_gemv_f32_w8")
2626        } else {
2627            self.func("router_gemv_f32")
2628        };
2629        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2630        let cfg = LaunchConfig {
2631            grid_dim: (n_experts as u32, t as u32, 1),
2632            block_dim: (32, if w8 { 8 } else { 1 }, 1),
2633            shared_mem_bytes: 0,
2634        };
2635        let __s_b = self.gpu.stream();
2636        let mut b = __s_b.launch_builder(&f);
2637        b.arg(w).arg(x).arg(&mut *y).arg(&ne).arg(&nx).arg(&ti);
2638        unsafe {
2639            b.launch(cfg)?;
2640        }
2641        Ok(())
2642    }
2643
2644    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2645    pub fn rows_permute(
2646        &self,
2647        src: &CudaSlice<f32>,
2648        idx: &CudaSlice<i32>,
2649        nrows: usize,
2650        ncols: usize,
2651    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2652        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2653        let f = self.func("rows_permute_f32");
2654        let (nc, nr) = (ncols as i32, nrows as i32);
2655        let cfg = LaunchConfig {
2656            grid_dim: (nrows as u32, 1, 1),
2657            block_dim: (256, 1, 1),
2658            shared_mem_bytes: 0,
2659        };
2660        let __s_b = self.gpu.stream();
2661        let mut b = __s_b.launch_builder(&f);
2662        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2663        unsafe {
2664            b.launch(cfg)?;
2665        }
2666        Ok(dst)
2667    }
2668
2669    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2670    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2671    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2672    /// decode chain and the small-t spec-verify chain match per row by construction.
2673    pub fn sigmoid_dot_rows(
2674        &self,
2675        x: &CudaSlice<f32>,
2676        w: &CudaSlice<f32>,
2677        n_embd: usize,
2678        t: usize,
2679    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2680        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2681        // config; same class as MEMRA_ROUTER_V2).
2682        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2683        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2684            let gs = self.linear(x, w, t, n_embd, 1)?;
2685            let mut g = self.uninit(t)?;
2686            self.sigmoid(&gs, &mut g, t)?;
2687            return Ok(g);
2688        }
2689        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2690        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2691        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2692        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2693        // flags doctrine; this per-token form serves every t.
2694        let mut g = self.alloc_uninit::<f32>(t)?;
2695        let f = self.func("sigmoid_dot_rows_f32");
2696        let (ne, ti) = (n_embd as i32, t as i32);
2697        let cfg = LaunchConfig {
2698            grid_dim: (t as u32, 1, 1),
2699            block_dim: (32, 8, 1),
2700            shared_mem_bytes: 0,
2701        };
2702        let __s_b = self.gpu.stream();
2703        let mut b = __s_b.launch_builder(&f);
2704        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2705        unsafe {
2706            b.launch(cfg)?;
2707        }
2708        Ok(g)
2709    }
2710
2711    /// `sigmoid_dot_rows` writing into a caller-owned [t] buffer (token-graph alloc-free).
2712    pub fn sigmoid_dot_rows_into(
2713        &self,
2714        x: &CudaSlice<f32>,
2715        w: &CudaSlice<f32>,
2716        g: &mut CudaSlice<f32>,
2717        n_embd: usize,
2718        t: usize,
2719    ) -> Result<(), Box<dyn std::error::Error>> {
2720        if g.len() < t {
2721            return Err("sigmoid_dot_rows_into output too small".into());
2722        }
2723        let f = self.func("sigmoid_dot_rows_f32");
2724        let (ne, ti) = (n_embd as i32, t as i32);
2725        let cfg = LaunchConfig {
2726            grid_dim: (t as u32, 1, 1),
2727            block_dim: (32, 8, 1),
2728            shared_mem_bytes: 0,
2729        };
2730        let __s_b = self.gpu.stream();
2731        let mut b = __s_b.launch_builder(&f);
2732        b.arg(x).arg(w).arg(&mut *g).arg(&ne).arg(&ti);
2733        unsafe {
2734            b.launch(cfg)?;
2735        }
2736        Ok(())
2737    }
2738
2739    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2740    pub fn spec_rollback_stream(
2741        &self,
2742        len_ptrs: &CudaSlice<u64>,
2743        pos_start: &CudaSlice<i32>,
2744        acc: &CudaSlice<u32>,
2745        base: usize,
2746        n_rows: usize,
2747    ) -> Result<(), Box<dyn std::error::Error>> {
2748        let f = self.func("spec_rollback_stream");
2749        let (b, nr) = (base as i32, n_rows as i32);
2750        let cfg = LaunchConfig {
2751            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2752            block_dim: (64, 1, 1),
2753            shared_mem_bytes: 0,
2754        };
2755        let __s_bl = self.gpu.stream();
2756        let mut bl = __s_bl.launch_builder(&f);
2757        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2758        unsafe {
2759            bl.launch(cfg)?;
2760        }
2761        Ok(())
2762    }
2763
2764    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2765    pub fn plain_tok_ring(
2766        &self,
2767        vam: &CudaSlice<u32>,
2768        pos_start: &CudaSlice<i32>,
2769        base: usize,
2770        ring: &mut CudaSlice<u32>,
2771    ) -> Result<(), Box<dyn std::error::Error>> {
2772        let f = self.func("plain_tok_ring");
2773        let (b, cap) = (base as i32, ring.len() as i32);
2774        let cfg = LaunchConfig {
2775            grid_dim: (1, 1, 1),
2776            block_dim: (32, 1, 1),
2777            shared_mem_bytes: 0,
2778        };
2779        let __s_bl = self.gpu.stream();
2780        let mut bl = __s_bl.launch_builder(&f);
2781        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2782        unsafe {
2783            bl.launch(cfg)?;
2784        }
2785        Ok(())
2786    }
2787
2788    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2789    pub fn spec_ring_commit(
2790        &self,
2791        vtok: &CudaSlice<u32>,
2792        acc: &CudaSlice<u32>,
2793        brk: &CudaSlice<u32>,
2794        ring: &mut CudaSlice<u32>,
2795        pend: &mut CudaSlice<u32>,
2796    ) -> Result<(), Box<dyn std::error::Error>> {
2797        let f = self.func("spec_ring_commit");
2798        let cfg = LaunchConfig {
2799            grid_dim: (1, 1, 1),
2800            block_dim: (32, 1, 1),
2801            shared_mem_bytes: 0,
2802        };
2803        let __s_b = self.gpu.stream();
2804        let mut b = __s_b.launch_builder(&f);
2805        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2806        unsafe {
2807            b.launch(cfg)?;
2808        }
2809        Ok(())
2810    }
2811    pub fn i32_copy_add(
2812        &self,
2813        src: &CudaSlice<i32>,
2814        dst: &mut CudaSlice<i32>,
2815        delta: i32,
2816    ) -> Result<(), Box<dyn std::error::Error>> {
2817        let f = self.func("i32_copy_add");
2818        let cfg = LaunchConfig {
2819            grid_dim: (1, 1, 1),
2820            block_dim: (32, 1, 1),
2821            shared_mem_bytes: 0,
2822        };
2823        let __s_b = self.gpu.stream();
2824        let mut b = __s_b.launch_builder(&f);
2825        b.arg(src).arg(dst).arg(&delta);
2826        unsafe {
2827            b.launch(cfg)?;
2828        }
2829        Ok(())
2830    }
2831    pub fn u32_copy(
2832        &self,
2833        src: &CudaSlice<u32>,
2834        dst: &mut CudaSlice<u32>,
2835    ) -> Result<(), Box<dyn std::error::Error>> {
2836        let f = self.func("u32_copy");
2837        let cfg = LaunchConfig {
2838            grid_dim: (1, 1, 1),
2839            block_dim: (32, 1, 1),
2840            shared_mem_bytes: 0,
2841        };
2842        let __s_b = self.gpu.stream();
2843        let mut b = __s_b.launch_builder(&f);
2844        b.arg(src).arg(dst);
2845        unsafe {
2846            b.launch(cfg)?;
2847        }
2848        Ok(())
2849    }
2850
2851    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2852    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2853    /// caps acceptance exactly like drafting fewer tokens).
2854    pub fn spec_adapt_k(
2855        &self,
2856        acc: &CudaSlice<u32>,
2857        brk: &mut CudaSlice<u32>,
2858        floor: usize,
2859        cap: usize,
2860    ) -> Result<(), Box<dyn std::error::Error>> {
2861        let f = self.func("spec_adapt_k");
2862        let (fl, cp) = (floor as i32, cap as i32);
2863        let cfg = LaunchConfig {
2864            grid_dim: (1, 1, 1),
2865            block_dim: (32, 1, 1),
2866            shared_mem_bytes: 0,
2867        };
2868        let __s_b = self.gpu.stream();
2869        let mut b = __s_b.launch_builder(&f);
2870        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2871        unsafe {
2872            b.launch(cfg)?;
2873        }
2874        Ok(())
2875    }
2876
2877    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2878    pub fn spec_accept_greedy_dc(
2879        &self,
2880        preds: &CudaSlice<u32>,
2881        vtok: &CudaSlice<u32>,
2882        last_pred: &CudaSlice<u32>,
2883        brk: &CudaSlice<u32>,
2884        out: &mut CudaSlice<u32>,
2885    ) -> Result<(), Box<dyn std::error::Error>> {
2886        let f = self.func("spec_accept_greedy_dc");
2887        let cfg = LaunchConfig {
2888            grid_dim: (1, 1, 1),
2889            block_dim: (32, 1, 1),
2890            shared_mem_bytes: 0,
2891        };
2892        let __s_b = self.gpu.stream();
2893        let mut b = __s_b.launch_builder(&f);
2894        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2895        unsafe {
2896            b.launch(cfg)?;
2897        }
2898        Ok(())
2899    }
2900
2901    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2902    pub fn pos_iota(
2903        &self,
2904        pos0: &CudaSlice<i32>,
2905        out: &mut CudaSlice<i32>,
2906        t: usize,
2907    ) -> Result<(), Box<dyn std::error::Error>> {
2908        let f = self.func("pos_iota_i32");
2909        let ti = t as i32;
2910        let cfg = LaunchConfig {
2911            grid_dim: (1, 1, 1),
2912            block_dim: (t.max(1) as u32, 1, 1),
2913            shared_mem_bytes: 0,
2914        };
2915        let __s_b = self.gpu.stream();
2916        let mut b = __s_b.launch_builder(&f);
2917        b.arg(pos0).arg(out).arg(&ti);
2918        unsafe {
2919            b.launch(cfg)?;
2920        }
2921        Ok(())
2922    }
2923    #[allow(clippy::too_many_arguments)]
2924    pub fn append_kv_quantized_rows_dc(
2925        &self,
2926        k_rows: &CudaSlice<f32>,
2927        v_rows: &CudaSlice<f32>,
2928        kc: &mut CudaSlice<u8>,
2929        vc: &mut CudaSlice<u8>,
2930        t0_dev: &CudaSlice<i32>,
2931        t: usize,
2932        kv_dim_k: usize,
2933        kv_dim_v: usize,
2934        k_tok_bytes: usize,
2935        v_tok_bytes: usize,
2936        g: bool,
2937    ) -> Result<(), Box<dyn std::error::Error>> {
2938        let f = if g {
2939            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2940        } else {
2941            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2942        };
2943        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2944        let cfg = LaunchConfig {
2945            grid_dim: (nblk, t as u32, 1),
2946            block_dim: (32, 1, 1),
2947            shared_mem_bytes: 0,
2948        };
2949        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2950        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2951        let __s_b = self.gpu.stream();
2952        let mut b = __s_b.launch_builder(&f);
2953        b.arg(k_rows)
2954            .arg(v_rows)
2955            .arg(kc)
2956            .arg(vc)
2957            .arg(t0_dev)
2958            .arg(&kdk)
2959            .arg(&kdv)
2960            .arg(&ktb)
2961            .arg(&vtb);
2962        unsafe {
2963            b.launch(cfg)?;
2964        }
2965        Ok(())
2966    }
2967
2968    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2969    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2970    #[allow(clippy::too_many_arguments)]
2971    pub fn append_kv_quantized_row_dc_inc(
2972        &self,
2973        k_row: &CudaSlice<f32>,
2974        v_row: &CudaSlice<f32>,
2975        kc: &mut CudaSlice<u8>,
2976        vc: &mut CudaSlice<u8>,
2977        t0_dev: &mut CudaSlice<i32>,
2978        kv_dim_k: usize,
2979        kv_dim_v: usize,
2980        k_tok_bytes: usize,
2981        v_tok_bytes: usize,
2982        g: bool,
2983    ) -> Result<(), Box<dyn std::error::Error>> {
2984        let f = if g {
2985            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2986        } else {
2987            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2988        };
2989        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2990        let cfg = LaunchConfig {
2991            grid_dim: (1, 1, 1),
2992            block_dim: (nthreads, 1, 1),
2993            shared_mem_bytes: 0,
2994        };
2995        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2996        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2997        let __s_b = self.gpu.stream();
2998        let mut b = __s_b.launch_builder(&f);
2999        b.arg(k_row)
3000            .arg(v_row)
3001            .arg(kc)
3002            .arg(vc)
3003            .arg(t0_dev)
3004            .arg(&kdk)
3005            .arg(&kdv)
3006            .arg(&ktb)
3007            .arg(&vtb);
3008        unsafe {
3009            b.launch(cfg)?;
3010        }
3011        Ok(())
3012    }
3013
3014    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
3015    pub fn pack_tok_p(
3016        &self,
3017        tok: &CudaSlice<u32>,
3018        p: &CudaSlice<f32>,
3019        out: &mut CudaSlice<u32>,
3020        slot: usize,
3021    ) -> Result<(), Box<dyn std::error::Error>> {
3022        let f = self.func("pack_tok_p");
3023        let sl = slot as i32;
3024        let cfg = LaunchConfig {
3025            grid_dim: (1, 1, 1),
3026            block_dim: (32, 1, 1),
3027            shared_mem_bytes: 0,
3028        };
3029        let __s_b = self.gpu.stream();
3030        let mut b = __s_b.launch_builder(&f);
3031        b.arg(tok).arg(p).arg(out).arg(&sl);
3032        unsafe {
3033            b.launch(cfg)?;
3034        }
3035        Ok(())
3036    }
3037    pub fn tok_map_u32(
3038        &self,
3039        tok: &mut CudaSlice<u32>,
3040        map: &CudaSlice<u32>,
3041    ) -> Result<(), Box<dyn std::error::Error>> {
3042        let f = self.func("tok_map_u32");
3043        let cfg = LaunchConfig {
3044            grid_dim: (1, 1, 1),
3045            block_dim: (32, 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(tok).arg(map);
3051        unsafe {
3052            b.launch(cfg)?;
3053        }
3054        Ok(())
3055    }
3056
3057    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
3058    #[allow(clippy::too_many_arguments)]
3059    pub fn spec_assemble_verify(
3060        &self,
3061        tokp: &CudaSlice<u32>,
3062        pend: &CudaSlice<u32>,
3063        d2t: Option<&CudaSlice<u32>>,
3064        vtok: &mut CudaSlice<u32>,
3065        brk: &mut CudaSlice<u32>,
3066        p_min: f32,
3067        k: usize,
3068        pmin0: bool,
3069    ) -> Result<(), Box<dyn std::error::Error>> {
3070        let f = self.func("spec_assemble_verify");
3071        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
3072        let cfg = LaunchConfig {
3073            grid_dim: (1, 1, 1),
3074            block_dim: (32, 1, 1),
3075            shared_mem_bytes: 0,
3076        };
3077        let __s_b = self.gpu.stream();
3078        let mut b = __s_b.launch_builder(&f);
3079        match d2t {
3080            Some(m) => {
3081                b.arg(tokp)
3082                    .arg(pend)
3083                    .arg(m)
3084                    .arg(vtok)
3085                    .arg(brk)
3086                    .arg(&p_min)
3087                    .arg(&ki)
3088                    .arg(&pm);
3089                unsafe {
3090                    b.launch(cfg)?;
3091                }
3092            }
3093            None => {
3094                let null: u64 = 0;
3095                b.arg(tokp)
3096                    .arg(pend)
3097                    .arg(&null)
3098                    .arg(vtok)
3099                    .arg(brk)
3100                    .arg(&p_min)
3101                    .arg(&ki)
3102                    .arg(&pm);
3103                unsafe {
3104                    b.launch(cfg)?;
3105                }
3106            }
3107        }
3108        Ok(())
3109    }
3110
3111    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
3112    #[allow(clippy::too_many_arguments)]
3113    pub fn ssm_conv_ring_rebuild_dc(
3114        &self,
3115        qkv_tm: &CudaSlice<f32>,
3116        ring_old: &CudaSlice<f32>,
3117        conv_state: &mut CudaSlice<f32>,
3118        conv_dim: usize,
3119        acc: &CudaSlice<u32>,
3120        base: usize,
3121        t_v: usize,
3122        d_conv: usize,
3123    ) -> Result<(), Box<dyn std::error::Error>> {
3124        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
3125        let n = conv_dim * (d_conv - 1);
3126        let cfg = LaunchConfig::for_num_elems(n as u32);
3127        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
3128        let __s_b = self.gpu.stream();
3129        let mut b = __s_b.launch_builder(&f);
3130        b.arg(qkv_tm)
3131            .arg(ring_old)
3132            .arg(conv_state)
3133            .arg(&cd)
3134            .arg(acc)
3135            .arg(&b0)
3136            .arg(&tv)
3137            .arg(&dc);
3138        unsafe {
3139            b.launch(cfg)?;
3140        }
3141        Ok(())
3142    }
3143    #[allow(clippy::too_many_arguments)]
3144    pub fn gdn_scan_s128_dc(
3145        &self,
3146        q: &CudaSlice<f32>,
3147        k: &CudaSlice<f32>,
3148        v: &CudaSlice<f32>,
3149        g: &CudaSlice<f32>,
3150        beta: &CudaSlice<f32>,
3151        state_in: &CudaSlice<f32>,
3152        state_out: &mut CudaSlice<f32>,
3153        o: &mut CudaSlice<f32>,
3154        n_head: usize,
3155        acc: &CudaSlice<u32>,
3156        base: usize,
3157        t_v: usize,
3158        scale: f32,
3159    ) -> Result<(), Box<dyn std::error::Error>> {
3160        let f = self.func("gdn_scan_s128_dc");
3161        const S_V: u32 = 128;
3162        const WARP: u32 = 32;
3163        const COLS_PER_BLOCK: u32 = 4;
3164        let cfg = LaunchConfig {
3165            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
3166            block_dim: (WARP, COLS_PER_BLOCK, 1),
3167            shared_mem_bytes: 0,
3168        };
3169        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
3170        let __s_b = self.gpu.stream();
3171        let mut b = __s_b.launch_builder(&f);
3172        b.arg(q)
3173            .arg(k)
3174            .arg(v)
3175            .arg(g)
3176            .arg(beta)
3177            .arg(state_in)
3178            .arg(state_out)
3179            .arg(o)
3180            .arg(&h)
3181            .arg(acc)
3182            .arg(&b0)
3183            .arg(&tv)
3184            .arg(&scale);
3185        unsafe {
3186            b.launch(cfg)?;
3187        }
3188        Ok(())
3189    }
3190
3191    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
3192    pub fn spec_rollback_kv(
3193        &self,
3194        len_ptrs: &CudaSlice<u64>,
3195        saved: &CudaSlice<i32>,
3196        acc: &CudaSlice<u32>,
3197        base: usize,
3198        n_layer: usize,
3199    ) -> Result<(), Box<dyn std::error::Error>> {
3200        let f = self.func("spec_rollback_kv");
3201        let (b, nl) = (base as i32, n_layer as i32);
3202        let cfg = LaunchConfig {
3203            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
3204            block_dim: (64, 1, 1),
3205            shared_mem_bytes: 0,
3206        };
3207        let __s_bl = self.gpu.stream();
3208        let mut bl = __s_bl.launch_builder(&f);
3209        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
3210        unsafe {
3211            bl.launch(cfg)?;
3212        }
3213        Ok(())
3214    }
3215
3216    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
3217    pub fn spec_fork_valid(
3218        &self,
3219        acc: &CudaSlice<u32>,
3220        optimistic_pending: u32,
3221        valid: &mut CudaSlice<u32>,
3222    ) -> Result<(), Box<dyn std::error::Error>> {
3223        let f = self.func("spec_fork_valid");
3224        let cfg = LaunchConfig {
3225            grid_dim: (1, 1, 1),
3226            block_dim: (1, 1, 1),
3227            shared_mem_bytes: 0,
3228        };
3229        let __s_bl = self.gpu.stream();
3230        let mut bl = __s_bl.launch_builder(&f);
3231        bl.arg(acc).arg(&optimistic_pending).arg(valid);
3232        unsafe {
3233            bl.launch(cfg)?;
3234        }
3235        Ok(())
3236    }
3237
3238    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
3239    pub fn spec_fork_reconcile_kv(
3240        &self,
3241        len_ptrs: &CudaSlice<u64>,
3242        saved: &CudaSlice<i32>,
3243        acc: &CudaSlice<u32>,
3244        valid: &CudaSlice<u32>,
3245        base: usize,
3246        n_layer: usize,
3247    ) -> Result<(), Box<dyn std::error::Error>> {
3248        let f = self.func("spec_fork_reconcile_kv");
3249        let (b, nl) = (base as i32, n_layer as i32);
3250        let cfg = LaunchConfig {
3251            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
3252            block_dim: (64, 1, 1),
3253            shared_mem_bytes: 0,
3254        };
3255        let __s_bl = self.gpu.stream();
3256        let mut bl = __s_bl.launch_builder(&f);
3257        bl.arg(len_ptrs)
3258            .arg(saved)
3259            .arg(acc)
3260            .arg(valid)
3261            .arg(&b)
3262            .arg(&nl);
3263        unsafe {
3264            bl.launch(cfg)?;
3265        }
3266        Ok(())
3267    }
3268
3269    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
3270    pub fn spec_fork_restore_f32(
3271        &self,
3272        snapshot: &CudaSlice<f32>,
3273        state: &mut CudaSlice<f32>,
3274        valid: &CudaSlice<u32>,
3275    ) -> Result<(), Box<dyn std::error::Error>> {
3276        assert_eq!(
3277            snapshot.len(),
3278            state.len(),
3279            "fork recurrent snapshot shape mismatch"
3280        );
3281        let f = self.func("spec_fork_restore_f32");
3282        let n = state.len() as i32;
3283        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
3284        let cfg = LaunchConfig {
3285            grid_dim: (blocks, 1, 1),
3286            block_dim: (256, 1, 1),
3287            shared_mem_bytes: 0,
3288        };
3289        let __s_bl = self.gpu.stream();
3290        let mut bl = __s_bl.launch_builder(&f);
3291        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
3292        unsafe {
3293            bl.launch(cfg)?;
3294        }
3295        Ok(())
3296    }
3297
3298    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
3299    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
3300    pub fn spec_seed_gather(
3301        &self,
3302        vx: &CudaSlice<f32>,
3303        fill_prev: &CudaSlice<f32>,
3304        acc: &CudaSlice<u32>,
3305        h_seed: &mut CudaSlice<f32>,
3306        base: usize,
3307        n_embd: usize,
3308    ) -> Result<(), Box<dyn std::error::Error>> {
3309        let f = self.func("spec_seed_gather");
3310        let (b, ne) = (base as i32, n_embd as i32);
3311        let cfg = LaunchConfig {
3312            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
3313            block_dim: (256, 1, 1),
3314            shared_mem_bytes: 0,
3315        };
3316        let __s_bl = self.gpu.stream();
3317        let mut bl = __s_bl.launch_builder(&f);
3318        bl.arg(vx)
3319            .arg(fill_prev)
3320            .arg(acc)
3321            .arg(h_seed)
3322            .arg(&b)
3323            .arg(&ne);
3324        unsafe {
3325            bl.launch(cfg)?;
3326        }
3327        Ok(())
3328    }
3329
3330    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
3331    pub fn spec_accept_greedy(
3332        &self,
3333        preds: &CudaSlice<u32>,
3334        draft: &CudaSlice<u32>,
3335        last_pred: u32,
3336        base: usize,
3337        k_round: usize,
3338        out: &mut CudaSlice<u32>,
3339    ) -> Result<(), Box<dyn std::error::Error>> {
3340        let f = self.func("spec_accept_greedy");
3341        let (b, k) = (base as i32, k_round as i32);
3342        let cfg = LaunchConfig {
3343            grid_dim: (1, 1, 1),
3344            block_dim: (32, 1, 1),
3345            shared_mem_bytes: 0,
3346        };
3347        let __s_bl = self.gpu.stream();
3348        let mut bl = __s_bl.launch_builder(&f);
3349        bl.arg(preds)
3350            .arg(draft)
3351            .arg(&last_pred)
3352            .arg(&b)
3353            .arg(&k)
3354            .arg(out);
3355        unsafe {
3356            bl.launch(cfg)?;
3357        }
3358        Ok(())
3359    }
3360
3361    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
3362    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
3363    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
3364
3365    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
3366    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
3367    pub fn gumbel_perturb(
3368        &self,
3369        x: &CudaSlice<f32>,
3370        y: &mut CudaSlice<f32>,
3371        n: usize,
3372        seed: u64,
3373        stream_pos: u32,
3374        temp: f32,
3375    ) -> Result<(), Box<dyn std::error::Error>> {
3376        let f = self.func("gumbel_perturb_f32");
3377        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3378        let cfg = LaunchConfig {
3379            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3380            block_dim: (256, 1, 1),
3381            shared_mem_bytes: 0,
3382        };
3383        let __s_b = self.gpu.stream();
3384        let mut b = __s_b.launch_builder(&f);
3385        b.arg(x)
3386            .arg(&mut *y)
3387            .arg(&ni)
3388            .arg(&slo)
3389            .arg(&shi)
3390            .arg(&stream_pos)
3391            .arg(&temp);
3392        unsafe {
3393            b.launch(cfg)?;
3394        }
3395        Ok(())
3396    }
3397
3398    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
3399    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
3400    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
3401    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
3402    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
3403    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
3404    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
3405    pub fn mask_logits_col(
3406        &self,
3407        logits: &mut CudaSlice<f32>,
3408        mask: &CudaSlice<u32>,
3409        col: usize,
3410        n: usize,
3411        mask_words: usize,
3412    ) -> Result<(), Box<dyn std::error::Error>> {
3413        let f = self.func("mask_logits_f32");
3414        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
3415        let cfg = LaunchConfig {
3416            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
3417            block_dim: (256, 1, 1),
3418            shared_mem_bytes: 0,
3419        };
3420        let __s_b = self.gpu.stream();
3421        let mut b = __s_b.launch_builder(&f);
3422        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
3423        unsafe {
3424            b.launch(cfg)?;
3425        }
3426        Ok(())
3427    }
3428
3429    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
3430    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
3431    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
3432    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
3433    /// (the lane index is the in-row position; `col` only moves the input pointer). That
3434    /// pointer-invariance IS the serving isolation contract for sampled rows.
3435    pub fn gumbel_perturb_col(
3436        &self,
3437        x: &CudaSlice<f32>,
3438        col: usize,
3439        y: &mut CudaSlice<f32>,
3440        n: usize,
3441        seed: u64,
3442        stream_pos: u32,
3443        temp: f32,
3444    ) -> Result<(), Box<dyn std::error::Error>> {
3445        let f = self.func("gumbel_perturb_f32");
3446        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3447        let col_view = x.slice(col * n..(col + 1) * n);
3448        let cfg = LaunchConfig {
3449            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3450            block_dim: (256, 1, 1),
3451            shared_mem_bytes: 0,
3452        };
3453        let __s_b = self.gpu.stream();
3454        let mut b = __s_b.launch_builder(&f);
3455        b.arg(&col_view)
3456            .arg(&mut *y)
3457            .arg(&ni)
3458            .arg(&slo)
3459            .arg(&shi)
3460            .arg(&stream_pos)
3461            .arg(&temp);
3462        unsafe {
3463            b.launch(cfg)?;
3464        }
3465        Ok(())
3466    }
3467
3468    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
3469    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
3470    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
3471    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
3472    /// the serving isolation contract for sampled rows).
3473    #[allow(clippy::too_many_arguments)]
3474    pub fn gumbel_perturb_filtered_col(
3475        &self,
3476        x: &CudaSlice<f32>,
3477        col: usize,
3478        y: &mut CudaSlice<f32>,
3479        n: usize,
3480        seed: u64,
3481        stream_pos: u32,
3482        temp: f32,
3483        stat_max: &CudaSlice<f32>,
3484        stat_th: &CudaSlice<f32>,
3485        stat_idx: usize,
3486    ) -> Result<(), Box<dyn std::error::Error>> {
3487        let f = self.func("gumbel_perturb_filtered_col_f32");
3488        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3489        let (ci, si) = (col as i32, stat_idx as i32);
3490        let cfg = LaunchConfig {
3491            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3492            block_dim: (256, 1, 1),
3493            shared_mem_bytes: 0,
3494        };
3495        let __s_b = self.gpu.stream();
3496        let mut b = __s_b.launch_builder(&f);
3497        b.arg(x)
3498            .arg(&ci)
3499            .arg(&mut *y)
3500            .arg(&ni)
3501            .arg(&slo)
3502            .arg(&shi)
3503            .arg(&stream_pos)
3504            .arg(&temp)
3505            .arg(stat_max)
3506            .arg(stat_th)
3507            .arg(&si);
3508        unsafe {
3509            b.launch(cfg)?;
3510        }
3511        Ok(())
3512    }
3513
3514    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
3515    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
3516    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
3517    /// reads it (counter is data, not state — graph-replay-safe).
3518    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
3519        let f = self.func("memra_sctr_inc");
3520        let cfg = LaunchConfig {
3521            grid_dim: (1, 1, 1),
3522            block_dim: (1, 1, 1),
3523            shared_mem_bytes: 0,
3524        };
3525        let __s_b = self.gpu.stream();
3526        let mut b = __s_b.launch_builder(&f);
3527        b.arg(&mut *ctr);
3528        unsafe {
3529            b.launch(cfg)?;
3530        }
3531        Ok(())
3532    }
3533
3534    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3535    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3536    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3537    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3538    pub fn gumbel_perturb_ctr(
3539        &self,
3540        x: &CudaSlice<f32>,
3541        y: &mut CudaSlice<f32>,
3542        n: usize,
3543        seed: u64,
3544        ctr: &CudaSlice<u32>,
3545        temp: f32,
3546    ) -> Result<(), Box<dyn std::error::Error>> {
3547        let f = self.func("gumbel_perturb_ctr_f32");
3548        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3549        let cfg = LaunchConfig {
3550            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3551            block_dim: (256, 1, 1),
3552            shared_mem_bytes: 0,
3553        };
3554        let __s_b = self.gpu.stream();
3555        let mut b = __s_b.launch_builder(&f);
3556        b.arg(x)
3557            .arg(&mut *y)
3558            .arg(&ni)
3559            .arg(&slo)
3560            .arg(&shi)
3561            .arg(ctr)
3562            .arg(&temp);
3563        unsafe {
3564            b.launch(cfg)?;
3565        }
3566        Ok(())
3567    }
3568
3569    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3570    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3571    /// (smallest-index tie-break — matches the argmax-gate contract).
3572    pub fn softmax_gather(
3573        &self,
3574        x: &CudaSlice<f32>,
3575        row_stride: usize,
3576        ids: &CudaSlice<u32>,
3577        rows: &CudaSlice<i32>,
3578        out: &mut CudaSlice<f32>,
3579        n: usize,
3580        npair: usize,
3581        temp: f32,
3582    ) -> Result<(), Box<dyn std::error::Error>> {
3583        let f = self.func("softmax_gather_f32");
3584        let (ni, rs) = (n as i32, row_stride as i64);
3585        let np = npair as i32;
3586        let cfg = LaunchConfig {
3587            grid_dim: (npair as u32, 1, 1),
3588            block_dim: (256, 1, 1),
3589            shared_mem_bytes: 0,
3590        };
3591        let __s_b = self.gpu.stream();
3592        let mut b = __s_b.launch_builder(&f);
3593        b.arg(x)
3594            .arg(&rs)
3595            .arg(ids)
3596            .arg(rows)
3597            .arg(&mut *out)
3598            .arg(&ni)
3599            .arg(&np)
3600            .arg(&temp);
3601        unsafe {
3602            b.launch(cfg)?;
3603        }
3604        Ok(())
3605    }
3606
3607    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3608    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3609    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3610    pub fn residual_sample(
3611        &self,
3612        p: &CudaSlice<f32>,
3613        q: Option<&CudaSlice<f32>>,
3614        n: usize,
3615        temp: f32,
3616        seed: u64,
3617        stream_pos: u32,
3618        out_tok: &mut CudaSlice<u32>,
3619    ) -> Result<(), Box<dyn std::error::Error>> {
3620        let f = self.func("residual_sample_f32");
3621        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3622        let nth = 1024u32;
3623        let cfg = LaunchConfig {
3624            grid_dim: (1, 1, 1),
3625            block_dim: (nth, 1, 1),
3626            shared_mem_bytes: 0,
3627        };
3628        let has_q: i32 = q.is_some() as i32;
3629        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3630        let __s_b = self.gpu.stream();
3631        let mut b = __s_b.launch_builder(&f);
3632        b.arg(p)
3633            .arg(qbuf)
3634            .arg(&has_q)
3635            .arg(&ni)
3636            .arg(&temp)
3637            .arg(&slo)
3638            .arg(&shi)
3639            .arg(&stream_pos)
3640            .arg(&mut *out_tok);
3641        unsafe {
3642            b.launch(cfg)?;
3643        }
3644        Ok(())
3645    }
3646
3647    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3648    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3649    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3650    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3651    pub fn with_moe_cache<R>(
3652        &self,
3653        max_block_bytes: usize,
3654        f: impl FnOnce(
3655            &mut crate::moe_cache::MoeSlotCache,
3656            &Engine,
3657        ) -> Result<R, Box<dyn std::error::Error>>,
3658    ) -> Result<R, Box<dyn std::error::Error>> {
3659        let mut guard = self.moe_cache.lock().unwrap();
3660        if guard.is_none() {
3661            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3662        }
3663        let cache = guard.as_mut().unwrap();
3664        f(cache, self)
3665    }
3666
3667    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3668    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3669    pub fn freeze_moe_cache(&self) {
3670        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3671            cache.freeze();
3672        }
3673    }
3674
3675    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3676    /// Never constructs a cache.
3677    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3678        self.moe_cache
3679            .lock()
3680            .unwrap()
3681            .as_ref()
3682            .map(crate::moe_cache::MoeSlotCache::export_residency)
3683    }
3684
3685    pub(crate) fn moe_cache_frozen(&self) -> bool {
3686        self.moe_cache
3687            .lock()
3688            .unwrap()
3689            .as_ref()
3690            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3691    }
3692
3693    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3694    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3695    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3696    /// while leaving the profiling warmup's established batched behavior untouched.
3697    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3698    /// tokenwise arm anyway.)
3699    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3700        crate::cpu_experts::configured()
3701            && self.moe_cache_frozen()
3702            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3703    }
3704
3705    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3706    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3707        assert!(
3708            self.moe_cache.lock().unwrap().is_none(),
3709            "MoE cache layout configured after cache construction"
3710        );
3711        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3712    }
3713
3714    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3715        self.moe_cache_layout.lock().unwrap().clone()
3716    }
3717
3718    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3719    pub fn moe_cache_enabled() -> bool {
3720        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3721    }
3722
3723    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3724    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3725    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3726        let guard = self.moe_cache.lock().unwrap();
3727        guard
3728            .as_ref()
3729            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3730    }
3731
3732    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3733    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3734    /// callers compare a before/after snapshot around a decode window.
3735    pub fn cpu_expert_stats(
3736        &self,
3737    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3738        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3739    }
3740
3741    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3742    /// the backend tail that resident-GPU expert work did not hide.
3743    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3744        crate::cpu_experts::predictor_stats()
3745    }
3746
3747    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3748        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3749    }
3750
3751    /// CPU-routed expert selections grouped by how many of their three projections were already
3752    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3753    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3754        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3755    }
3756
3757    /// Positioned-read proof-backend counters:
3758    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3759    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3760        let guard = self.moe_cache.lock().unwrap();
3761        guard
3762            .as_ref()
3763            .and_then(|cache| cache.pread_stats())
3764            .map(|stats| {
3765                (
3766                    stats.reads,
3767                    stats.bytes,
3768                    stats.read_errors,
3769                    stats.short_reads,
3770                    stats.fallbacks,
3771                    stats.buffer_waits,
3772                    stats.ring_full,
3773                )
3774            })
3775    }
3776
3777    /// Spill configuration values that warned and substituted their documented defaults.
3778    pub fn spill_config_fallbacks(&self) -> u64 {
3779        crate::spill_pread::config_fallbacks()
3780    }
3781
3782    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3783    pub fn moe_cache_reset_counters(&self) {
3784        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3785            c.reset_counters();
3786        }
3787    }
3788
3789    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3790        Ok(self.gpu.stream().clone_htod(v)?)
3791    }
3792
3793    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3794    /// past the final q4_0 block through their aligned window — the bytes never reach a
3795    /// result (funnelshift discards them) but must be mapped memory.
3796    pub fn htod_bytes_padded(
3797        &self,
3798        v: &[u8],
3799        pad: usize,
3800    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3801        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3802        {
3803            let mut view = d.slice_mut(0..v.len());
3804            self.gpu.stream().memcpy_htod(v, &mut view)?;
3805        }
3806        Ok(d)
3807    }
3808
3809    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3810    pub fn copy_into(
3811        &self,
3812        dst: &mut CudaSlice<f32>,
3813        off: usize,
3814        src: &CudaSlice<f32>,
3815        len: usize,
3816    ) -> Result<(), Box<dyn std::error::Error>> {
3817        let mut view = dst.slice_mut(off..off + len);
3818        self.gpu
3819            .stream()
3820            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3821        Ok(())
3822    }
3823
3824    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3825    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3826    pub fn copy_u8_into(
3827        &self,
3828        dst: &mut CudaSlice<u8>,
3829        off: usize,
3830        src: &CudaSlice<u8>,
3831        len: usize,
3832    ) -> Result<(), Box<dyn std::error::Error>> {
3833        let mut view = dst.slice_mut(off..off + len);
3834        self.gpu
3835            .stream()
3836            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3837        Ok(())
3838    }
3839
3840    /// D2D byte-range copy with explicit source and destination offsets.
3841    pub fn copy_u8_range_into(
3842        &self,
3843        dst: &mut CudaSlice<u8>,
3844        dst_off: usize,
3845        src: &CudaSlice<u8>,
3846        src_off: usize,
3847        len: usize,
3848    ) -> Result<(), Box<dyn std::error::Error>> {
3849        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3850        self.gpu
3851            .stream()
3852            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3853        Ok(())
3854    }
3855
3856    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3857    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3858    /// keeping the audited attention range contiguous without changing its absolute start.
3859    pub fn prepare_kv_append(
3860        &self,
3861        kv: &mut crate::cache::KvLayer,
3862        retain_from: usize,
3863        append_rows: usize,
3864    ) -> Result<usize, Box<dyn std::error::Error>> {
3865        let Some(plan) = kv
3866            .ring
3867            .as_ref()
3868            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3869            .transpose()?
3870        else {
3871            return Ok(kv.len);
3872        };
3873        match plan {
3874            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3875            crate::cache::KvRingAppend::Rebase {
3876                src_row,
3877                keep_rows,
3878                new_base,
3879                write_row,
3880            } => {
3881                if keep_rows > 0 {
3882                    let k_len = keep_rows * kv.k_tok_bytes;
3883                    let v_len = keep_rows * kv.v_tok_bytes;
3884                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3885                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3886                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3887                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3888                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3889                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3890                }
3891                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3892                Ok(write_row)
3893            }
3894        }
3895    }
3896
3897    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3898    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3899    pub fn htod_u8_into(
3900        &self,
3901        dst: &mut CudaSlice<u8>,
3902        off: usize,
3903        src: &[u8],
3904    ) -> Result<(), Box<dyn std::error::Error>> {
3905        let mut view = dst.slice_mut(off..off + src.len());
3906        self.gpu.stream().memcpy_htod(src, &mut view)?;
3907        Ok(())
3908    }
3909
3910    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3911        b.slice(0..len)
3912    }
3913
3914    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3915    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3916    pub fn view_u8_range<'a>(
3917        &self,
3918        b: &'a CudaSlice<u8>,
3919        start: usize,
3920        end: usize,
3921    ) -> cudarc::driver::CudaView<'a, u8> {
3922        b.slice(start..end)
3923    }
3924    pub fn view_u8<'a>(
3925        &self,
3926        b: &'a CudaSlice<u8>,
3927        len: usize,
3928    ) -> cudarc::driver::CudaView<'a, u8> {
3929        b.slice(0..len)
3930    }
3931
3932    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3933    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3934    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3935    pub fn append_kv_quantized(
3936        &self,
3937        k_row: &CudaSlice<f32>,
3938        v_row: &CudaSlice<f32>,
3939        kc: &mut CudaSlice<u8>,
3940        vc: &mut CudaSlice<u8>,
3941        t: usize,
3942        kv_dim_k: usize,
3943        kv_dim_v: usize,
3944        k_tok_bytes: usize,
3945        v_tok_bytes: usize,
3946        g: bool,
3947    ) -> Result<(), Box<dyn std::error::Error>> {
3948        let f = if g {
3949            self.func_g("append_quantize_kv_q8_0_q5_1")
3950        } else {
3951            self.func("append_quantize_kv_q8_0_q5_1")
3952        };
3953        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3954        let cfg = LaunchConfig {
3955            grid_dim: (nblk, 1, 1),
3956            block_dim: (32, 1, 1),
3957            shared_mem_bytes: 0,
3958        };
3959        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3960        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3961        let __s_b = self.gpu.stream();
3962        let mut b = __s_b.launch_builder(&f);
3963        b.arg(k_row)
3964            .arg(v_row)
3965            .arg(kc)
3966            .arg(vc)
3967            .arg(&ti)
3968            .arg(&kdk)
3969            .arg(&kdv)
3970            .arg(&ktb)
3971            .arg(&vtb);
3972        unsafe {
3973            b.launch(cfg)?;
3974        }
3975        Ok(())
3976    }
3977
3978    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3979    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3980    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3981    pub fn append_kv_quantized_dc(
3982        &self,
3983        k_row: &CudaSlice<f32>,
3984        v_row: &CudaSlice<f32>,
3985        kc: &mut CudaSlice<u8>,
3986        vc: &mut CudaSlice<u8>,
3987        t_dev: &CudaSlice<i32>,
3988        kv_dim_k: usize,
3989        kv_dim_v: usize,
3990        k_tok_bytes: usize,
3991        v_tok_bytes: usize,
3992        g: bool,
3993    ) -> Result<(), Box<dyn std::error::Error>> {
3994        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3995        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3996        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3997        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3998        if Self::pdl_on() && Self::pdl_wb_on() {
3999            use cudarc::driver::{DevicePtr, DevicePtrMut};
4000            let s = &self.gpu.stream();
4001            let (pk, _g0) = k_row.device_ptr(s);
4002            let (pv, _g1) = v_row.device_ptr(s);
4003            let (pkc, _g2) = kc.device_ptr_mut(s);
4004            let (pvc, _g3) = vc.device_ptr_mut(s);
4005            let (pt, _g4) = t_dev.device_ptr(s);
4006            let mut ps = [
4007                &pk as *const _ as *mut std::ffi::c_void,
4008                &pv as *const _ as *mut _,
4009                &pkc as *const _ as *mut _,
4010                &pvc as *const _ as *mut _,
4011                &pt as *const _ as *mut _,
4012                &kdk as *const _ as *mut _,
4013                &kdv as *const _ as *mut _,
4014                &ktb as *const _ as *mut _,
4015                &vtb as *const _ as *mut _,
4016            ];
4017            unsafe {
4018                self.launch_pdl_flash(
4019                    g,
4020                    "append_quantize_kv_q8_0_q5_1_dc",
4021                    (nblk, 1, 1),
4022                    (32, 1, 1),
4023                    0,
4024                    &mut ps,
4025                )?;
4026            }
4027            return Ok(());
4028        }
4029        let f = if g {
4030            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
4031        } else {
4032            self.func("append_quantize_kv_q8_0_q5_1_dc")
4033        };
4034        let cfg = LaunchConfig {
4035            grid_dim: (nblk, 1, 1),
4036            block_dim: (32, 1, 1),
4037            shared_mem_bytes: 0,
4038        };
4039        let __s_b = self.gpu.stream();
4040        let mut b = __s_b.launch_builder(&f);
4041        b.arg(k_row)
4042            .arg(v_row)
4043            .arg(kc)
4044            .arg(vc)
4045            .arg(t_dev)
4046            .arg(&kdk)
4047            .arg(&kdv)
4048            .arg(&ktb)
4049            .arg(&vtb);
4050        unsafe {
4051            b.launch(cfg)?;
4052        }
4053        Ok(())
4054    }
4055
4056    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
4057    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
4058    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
4059    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
4060    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
4061    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
4062    #[allow(clippy::too_many_arguments)]
4063    pub fn append_kv_quantized_rows(
4064        &self,
4065        k_rows: &CudaSlice<f32>,
4066        v_rows: &CudaSlice<f32>,
4067        kc: &mut CudaSlice<u8>,
4068        vc: &mut CudaSlice<u8>,
4069        t0: usize,
4070        t: usize,
4071        kv_dim_k: usize,
4072        kv_dim_v: usize,
4073        k_tok_bytes: usize,
4074        v_tok_bytes: usize,
4075        g: bool,
4076    ) -> Result<(), Box<dyn std::error::Error>> {
4077        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
4078            for i in 0..t {
4079                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
4080                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
4081                self.append_kv_quantized_view(
4082                    &k_row,
4083                    &v_row,
4084                    kc,
4085                    vc,
4086                    t0 + i,
4087                    kv_dim_k,
4088                    kv_dim_v,
4089                    k_tok_bytes,
4090                    v_tok_bytes,
4091                    g,
4092                )?;
4093            }
4094            return Ok(());
4095        }
4096        let f = if g {
4097            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
4098        } else {
4099            self.func("append_quantize_kv_q8_0_q5_1_rows")
4100        };
4101        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
4102        let cfg = LaunchConfig {
4103            grid_dim: (nblk, t as u32, 1),
4104            block_dim: (32, 1, 1),
4105            shared_mem_bytes: 0,
4106        };
4107        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
4108        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
4109        let __s_b = self.gpu.stream();
4110        let mut b = __s_b.launch_builder(&f);
4111        b.arg(k_rows)
4112            .arg(v_rows)
4113            .arg(kc)
4114            .arg(vc)
4115            .arg(&t0i)
4116            .arg(&kdk)
4117            .arg(&kdv)
4118            .arg(&ktb)
4119            .arg(&vtb);
4120        unsafe {
4121            b.launch(cfg)?;
4122        }
4123        Ok(())
4124    }
4125
4126    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
4127    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
4128    /// later, inside a captured graph) without a host round-trip.
4129    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
4130        let f = self.func("inc_i32");
4131        let cfg = LaunchConfig {
4132            grid_dim: (1, 1, 1),
4133            block_dim: (1, 1, 1),
4134            shared_mem_bytes: 0,
4135        };
4136        let __s_b = self.gpu.stream();
4137        let mut b = __s_b.launch_builder(&f);
4138        b.arg(p);
4139        unsafe {
4140            b.launch(cfg)?;
4141        }
4142        Ok(())
4143    }
4144
4145    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
4146    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
4147    pub fn append_kv_quantized_view(
4148        &self,
4149        k_row: &cudarc::driver::CudaView<f32>,
4150        v_row: &cudarc::driver::CudaView<f32>,
4151        kc: &mut CudaSlice<u8>,
4152        vc: &mut CudaSlice<u8>,
4153        t: usize,
4154        kv_dim_k: usize,
4155        kv_dim_v: usize,
4156        k_tok_bytes: usize,
4157        v_tok_bytes: usize,
4158        g: bool,
4159    ) -> Result<(), Box<dyn std::error::Error>> {
4160        let stream = self.gpu.stream();
4161        ensure_tensor_stream_device(k_row, &stream, "append_kv_quantized_view.k_row")?;
4162        ensure_tensor_stream_device(v_row, &stream, "append_kv_quantized_view.v_row")?;
4163        ensure_tensor_stream_device(kc, &stream, "append_kv_quantized_view.k_cache")?;
4164        ensure_tensor_stream_device(vc, &stream, "append_kv_quantized_view.v_cache")?;
4165        let f = if g {
4166            self.func_g("append_quantize_kv_q8_0_q5_1")
4167        } else {
4168            self.func("append_quantize_kv_q8_0_q5_1")
4169        };
4170        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
4171        let cfg = LaunchConfig {
4172            grid_dim: (nblk, 1, 1),
4173            block_dim: (32, 1, 1),
4174            shared_mem_bytes: 0,
4175        };
4176        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
4177        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
4178        let mut b = stream.launch_builder(&f);
4179        b.arg(k_row)
4180            .arg(v_row)
4181            .arg(kc)
4182            .arg(vc)
4183            .arg(&ti)
4184            .arg(&kdk)
4185            .arg(&kdv)
4186            .arg(&ktb)
4187            .arg(&vtb);
4188        unsafe {
4189            b.launch(cfg)?;
4190        }
4191        Ok(())
4192    }
4193
4194    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
4195    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
4196    pub fn copy_view_into(
4197        &self,
4198        dst: &mut CudaSlice<f32>,
4199        off: usize,
4200        src: &cudarc::driver::CudaView<f32>,
4201        len: usize,
4202    ) -> Result<(), Box<dyn std::error::Error>> {
4203        let mut view = dst.slice_mut(off..off + len);
4204        self.gpu
4205            .stream()
4206            .memcpy_dtod(&src.slice(0..len), &mut view)?;
4207        Ok(())
4208    }
4209
4210    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
4211    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
4212    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
4213    pub fn clone_dtod(
4214        &self,
4215        src: &CudaSlice<f32>,
4216    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4217        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
4218        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
4219        Ok(dst)
4220    }
4221
4222    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
4223    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
4224    pub fn dtod_copy_view(
4225        &self,
4226        src: &cudarc::driver::CudaView<f32>,
4227        dst: &mut CudaSlice<f32>,
4228    ) -> Result<(), Box<dyn std::error::Error>> {
4229        self.gpu.stream().memcpy_dtod(src, dst)?;
4230        Ok(())
4231    }
4232
4233    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
4234    pub fn dtod_copy_view_i8(
4235        &self,
4236        src: &cudarc::driver::CudaView<i8>,
4237        dst: &mut CudaSlice<i8>,
4238    ) -> Result<(), Box<dyn std::error::Error>> {
4239        self.gpu.stream().memcpy_dtod(src, dst)?;
4240        Ok(())
4241    }
4242
4243    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
4244    pub fn dtod_copy_into(
4245        &self,
4246        src: &CudaSlice<f32>,
4247        dst: &mut CudaSlice<f32>,
4248        offset: usize,
4249    ) -> Result<(), Box<dyn std::error::Error>> {
4250        let n = src.len();
4251        let mut dv = dst.slice_mut(offset..offset + n);
4252        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
4253        Ok(())
4254    }
4255
4256    /// Batched uniform-size D2D copy (engine-bundle slice 1, DSF-ROUNDCOST-20260820 §1.1):
4257    /// `n` disjoint regions of `words` f32 each; `table` = [src_0..src_{n-1}, dst_0..dst_{n-1}]
4258    /// raw device pointers. ONE kernel launch replaces `n` memcpy_dtod dispatches — the dspark
4259    /// round's snap/commit copy dribble (~0.9 ms/round of dispatch serialization measured).
4260    /// Bytes and stream order are identical to the memcpy sequence it replaces.
4261    pub fn copy_batch_uniform_f32(
4262        &self,
4263        table: &CudaSlice<u64>,
4264        n: usize,
4265        words: usize,
4266    ) -> Result<(), Box<dyn std::error::Error>> {
4267        if n == 0 || words == 0 {
4268            return Ok(());
4269        }
4270        debug_assert!(
4271            table.len() >= 2 * n,
4272            "pointer table must hold n srcs + n dsts"
4273        );
4274        let f = self.func("copy_batch_uniform_f32");
4275        // Enough blocks to stream a multi-MB region, few enough that (chunks x n) stays a
4276        // sane grid: 512K-word ssm regions get 48 grid-striding blocks each.
4277        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
4278        let (ni, wi) = (n as i32, words as i32);
4279        let cfg = LaunchConfig {
4280            grid_dim: (chunks, n as u32, 1),
4281            block_dim: (256, 1, 1),
4282            shared_mem_bytes: 0,
4283        };
4284        let __s = self.gpu.stream();
4285        let mut b = __s.launch_builder(&f);
4286        b.arg(table).arg(&ni).arg(&wi);
4287        unsafe {
4288            b.launch(cfg)?;
4289        }
4290        Ok(())
4291    }
4292
4293    /// H2D refresh of an EXISTING u64 pointer table IN PLACE (stable pointer — the batched
4294    /// state-copy tables are refreshed per round because the GDN ssm handles ping-pong).
4295    pub fn htod_u64_into(
4296        &self,
4297        v: &[u64],
4298        dst: &mut CudaSlice<u64>,
4299    ) -> Result<(), Box<dyn std::error::Error>> {
4300        let mut view = dst.slice_mut(0..v.len());
4301        self.gpu.stream().memcpy_htod(v, &mut view)?;
4302        Ok(())
4303    }
4304
4305    /// Indirect-source copy (engine-bundle slice 3): the src ADDRESS is loaded from a
4306    /// device pointer-table entry at run time, so a captured graph follows the gdn
4307    /// ping-pong through the same table its scan kernels read — a baked memcpy node
4308    /// would keep the capture-time physical buffer. `dst_off`/`words` in f32 elements.
4309    pub fn copy_indirect_src_f32(
4310        &self,
4311        src_entry: &cudarc::driver::CudaView<u64>,
4312        dst: &mut CudaSlice<f32>,
4313        dst_off: usize,
4314        words: usize,
4315    ) -> Result<(), Box<dyn std::error::Error>> {
4316        let f = self.func("copy_indirect_src_f32");
4317        let chunks = (words / 4).max(1).div_ceil(256).min(48) as u32;
4318        let wi = words as i32;
4319        let cfg = LaunchConfig {
4320            grid_dim: (chunks, 1, 1),
4321            block_dim: (256, 1, 1),
4322            shared_mem_bytes: 0,
4323        };
4324        let mut dv = dst.slice_mut(dst_off..dst_off + words);
4325        let __s = self.gpu.stream();
4326        let mut b = __s.launch_builder(&f);
4327        b.arg(src_entry).arg(&mut dv).arg(&wi);
4328        unsafe {
4329            b.launch(cfg)?;
4330        }
4331        Ok(())
4332    }
4333
4334    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
4335    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
4336        self.alloc_uninit::<i8>(n)
4337    }
4338
4339    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
4340    pub fn qmatvec(
4341        &self,
4342        w: &CudaSlice<u8>,
4343        x: &CudaSlice<f32>,
4344        m: usize,
4345        in_f: usize,
4346        out_f: usize,
4347        qtype: i32,
4348        row_bytes: usize,
4349    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4350        let f = self.func("qmatvec_f32");
4351        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4352        let cfg = LaunchConfig {
4353            grid_dim: (out_f as u32, m as u32, 1),
4354            block_dim: (256, 1, 1),
4355            shared_mem_bytes: 0,
4356        };
4357        let (inf, outf, mi, qt, rb) =
4358            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4359        let __s_b = self.gpu.stream();
4360        let mut b = __s_b.launch_builder(&f);
4361        b.arg(w)
4362            .arg(x)
4363            .arg(&mut y)
4364            .arg(&inf)
4365            .arg(&outf)
4366            .arg(&mi)
4367            .arg(&qt)
4368            .arg(&rb);
4369        unsafe {
4370            b.launch(cfg)?;
4371        }
4372        Ok(y)
4373    }
4374
4375    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
4376    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
4377        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
4378        self.keep_if_capturing(&s);
4379        Ok(s)
4380    }
4381
4382    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
4383    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
4384    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
4385    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
4386        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
4387        self.keep_if_capturing(&s);
4388        Ok(s)
4389    }
4390
4391    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
4392    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
4393    pub fn memset_zeros_view(
4394        &self,
4395        dst: &mut cudarc::driver::CudaViewMut<f32>,
4396    ) -> Result<(), Box<dyn std::error::Error>> {
4397        self.gpu.stream().memset_zeros(dst)?;
4398        Ok(())
4399    }
4400
4401    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
4402    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
4403    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
4404    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
4405    /// stream would require an event).
4406    pub fn stage_expert(
4407        &self,
4408        host_bytes: &[u8],
4409        scratch: &mut CudaSlice<u8>,
4410        off: usize,
4411    ) -> Result<(), Box<dyn std::error::Error>> {
4412        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
4413        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
4414        Ok(())
4415    }
4416
4417    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
4418    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
4419    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
4420    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
4421    /// One CTA per token row, 256 threads (one per expert).
4422    pub fn moe_router_topk(
4423        &self,
4424        logits: &CudaSlice<f32>,
4425        t: usize,
4426        n_expert: usize,
4427        n_used: usize,
4428    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4429        let f = self.func("moe_router_topk_f32");
4430        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
4431        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
4432        let cfg = LaunchConfig {
4433            grid_dim: (t as u32, 1, 1),
4434            block_dim: (n_expert as u32, 1, 1),
4435            shared_mem_bytes: 0,
4436        };
4437        let (ne, nu) = (n_expert as i32, n_used as i32);
4438        let __s_b = self.gpu.stream();
4439        let mut b = __s_b.launch_builder(&f);
4440        b.arg(logits)
4441            .arg(&mut sel_idx)
4442            .arg(&mut sel_w)
4443            .arg(&ne)
4444            .arg(&nu);
4445        unsafe {
4446            b.launch(cfg)?;
4447        }
4448        Ok((sel_idx, sel_w))
4449    }
4450
4451    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
4452    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
4453    pub fn moe_router_topk_scaled(
4454        &self,
4455        logits: &CudaSlice<f32>,
4456        t: usize,
4457        n_expert: usize,
4458        n_used: usize,
4459        ex_scale: &CudaSlice<f32>,
4460    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4461        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
4462        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
4463        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
4464        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
4465        let f = self.func("moe_router_topk_scaled_f32");
4466        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
4467        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
4468        let cfg = LaunchConfig {
4469            grid_dim: (t as u32, 1, 1),
4470            block_dim: (n_expert as u32, 1, 1),
4471            shared_mem_bytes: 0,
4472        };
4473        let (ne, nu) = (n_expert as i32, n_used as i32);
4474        let __s_b = self.gpu.stream();
4475        let mut b = __s_b.launch_builder(&f);
4476        b.arg(logits)
4477            .arg(&mut sel_idx)
4478            .arg(&mut sel_w)
4479            .arg(&ne)
4480            .arg(&nu)
4481            .arg(ex_scale);
4482        unsafe {
4483            b.launch(cfg)?;
4484        }
4485        Ok((sel_idx, sel_w))
4486    }
4487
4488    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
4489    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
4490    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
4491    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
4492    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
4493    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
4494    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
4495    pub fn moe_router_topk_host(
4496        &self,
4497        logits: &CudaSlice<f32>,
4498        t: usize,
4499        n_expert: usize,
4500        n_used: usize,
4501    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4502        let f = self.func("moe_router_topk_f32");
4503        let n = t * n_used;
4504        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
4505        let mut sel_w = self.alloc_uninit::<f32>(n)?;
4506        let cfg = LaunchConfig {
4507            grid_dim: (t as u32, 1, 1),
4508            block_dim: (n_expert as u32, 1, 1),
4509            shared_mem_bytes: 0,
4510        };
4511        let (ne, nu) = (n_expert as i32, n_used as i32);
4512        let __s_b = self.gpu.stream();
4513        let mut b = __s_b.launch_builder(&f);
4514        b.arg(logits)
4515            .arg(&mut sel_idx)
4516            .arg(&mut sel_w)
4517            .arg(&ne)
4518            .arg(&nu);
4519        unsafe {
4520            b.launch(cfg)?;
4521        }
4522        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
4523        let bytes = n * 8;
4524        let mut guard = self.router_stage.lock().unwrap();
4525        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4526            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4527        }
4528        let stage = guard.as_mut().unwrap();
4529        let (si, sw) = unsafe {
4530            (
4531                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4532                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4533            )
4534        };
4535        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
4536        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
4537        self.gpu.stream().synchronize()?; // ONE sync for both
4538        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4539    }
4540
4541    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
4542    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
4543    /// original expert ids before top-k. Exact key ties choose the smaller original id.
4544    #[allow(clippy::too_many_arguments)]
4545    pub fn moe_router_sigmoid_topk(
4546        &self,
4547        logits: &CudaSlice<f32>,
4548        t: usize,
4549        n_expert: usize,
4550        n_used: usize,
4551        active_count: usize,
4552        correction_bias: &CudaSlice<f32>,
4553        active: &CudaSlice<u8>,
4554        scaling_factor: f32,
4555        route_norm: bool,
4556    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
4557        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
4558        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
4559            return Err(format!(
4560                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
4561            )
4562            .into());
4563        }
4564        if logits.len() < t * n_expert
4565            || correction_bias.len() != n_expert
4566            || active.len() != n_expert
4567        {
4568            return Err(format!(
4569                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
4570                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
4571            ).into());
4572        }
4573        let f = if crate::sig_expf_dev_on() && crate::topk_fast_on() {
4574            // Latency twin of the dexp arm (identical outputs): barrier-lean top-k
4575            // over the dexp scoring class. Composes the two doors it rides.
4576            self.func("moe_router_sigmoid_topk_f32_dexp_fast")
4577        } else if crate::sig_expf_dev_on() {
4578            self.func("moe_router_sigmoid_topk_f32_dexp")
4579        } else if crate::topk_fast_on() {
4580            self.func("moe_router_sigmoid_topk_f32_fast")
4581        } else {
4582            self.func("moe_router_sigmoid_topk_f32")
4583        };
4584        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
4585        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
4586        let threads = n_expert.div_ceil(32) * 32;
4587        let cfg = LaunchConfig {
4588            grid_dim: (t as u32, 1, 1),
4589            block_dim: (threads as u32, 1, 1),
4590            shared_mem_bytes: 0,
4591        };
4592        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
4593        let __s_b = self.gpu.stream();
4594        let mut b = __s_b.launch_builder(&f);
4595        b.arg(logits)
4596            .arg(correction_bias)
4597            .arg(active)
4598            .arg(&mut sel_idx)
4599            .arg(&mut sel_w)
4600            .arg(&ne)
4601            .arg(&nu)
4602            .arg(&scaling_factor)
4603            .arg(&rn);
4604        unsafe {
4605            b.launch(cfg)?;
4606        }
4607        Ok((sel_idx, sel_w))
4608    }
4609
4610    /// `moe_router_sigmoid_topk` writing into caller-owned buffers (alloc-free: child graphs
4611    /// cannot contain mem nodes, so the token-graph e-sections pre-own every output).
4612    #[allow(clippy::too_many_arguments)]
4613    /// Ring a doorbell flag at a RAW device address (see `memra_ring_flag`): one store of
4614    /// `value`, fenced. Used by a peer rank to signal join readiness into root memory, where
4615    /// the model engine can wait on it with a same-device stream memop.
4616    pub fn ring_flag_raw(&self, ptr: u64, value: u32) -> Result<(), Box<dyn std::error::Error>> {
4617        if ptr == 0 {
4618            return Err("ring_flag_raw: unarmed flag".into());
4619        }
4620        let f = self.func("memra_ring_flag");
4621        let cfg = LaunchConfig {
4622            grid_dim: (1, 1, 1),
4623            block_dim: (32, 1, 1),
4624            shared_mem_bytes: 0,
4625        };
4626        let __s_b = self.gpu.stream();
4627        let mut b = __s_b.launch_builder(&f);
4628        b.arg(&ptr).arg(&value);
4629        unsafe {
4630            b.launch(cfg)?;
4631        }
4632        Ok(())
4633    }
4634
4635    /// One-launch mirror of a routed selection (`sel` int32 + `route_w` f32) — see
4636    /// `moe_sel_w_mirror`. Replaces the two tiny D2D copies the rank pull used to issue.
4637    pub fn moe_sel_w_mirror(
4638        &self,
4639        sel_src: &CudaSlice<i32>,
4640        w_src: &CudaSlice<f32>,
4641        sel_dst: &mut CudaSlice<i32>,
4642        w_dst: &mut CudaSlice<f32>,
4643        n: usize,
4644    ) -> Result<(), Box<dyn std::error::Error>> {
4645        if n == 0
4646            || n > 32
4647            || sel_src.len() < n
4648            || w_src.len() < n
4649            || sel_dst.len() < n
4650            || w_dst.len() < n
4651        {
4652            return Err(format!("moe_sel_w_mirror geometry n={n}").into());
4653        }
4654        let f = self.func("moe_sel_w_mirror");
4655        let cfg = LaunchConfig {
4656            grid_dim: (1, 1, 1),
4657            block_dim: (32, 1, 1),
4658            shared_mem_bytes: 0,
4659        };
4660        let ni = n as i32;
4661        let __s_b = self.gpu.stream();
4662        let mut b = __s_b.launch_builder(&f);
4663        b.arg(sel_src).arg(w_src).arg(sel_dst).arg(w_dst).arg(&ni);
4664        unsafe {
4665            b.launch(cfg)?;
4666        }
4667        Ok(())
4668    }
4669
4670    pub fn moe_router_sigmoid_topk_into(
4671        &self,
4672        logits: &CudaSlice<f32>,
4673        t: usize,
4674        n_expert: usize,
4675        n_used: usize,
4676        active_count: usize,
4677        correction_bias: &CudaSlice<f32>,
4678        active: &CudaSlice<u8>,
4679        scaling_factor: f32,
4680        route_norm: bool,
4681        sel_idx: &mut CudaSlice<i32>,
4682        sel_w: &mut CudaSlice<f32>,
4683    ) -> Result<(), Box<dyn std::error::Error>> {
4684        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
4685        if n_expert == 0
4686            || n_expert > 1024
4687            || n_used == 0
4688            || n_used > 32 // the kernels' shared pick cache (s_pick_w) is sized 32
4689            || n_used > n_expert
4690            || logits.len() < t * n_expert
4691            || correction_bias.len() != n_expert
4692            || active.len() != n_expert
4693            || sel_idx.len() < t * n_used
4694            || sel_w.len() < t * n_used
4695        {
4696            return Err("sigmoid router _into geometry mismatch".into());
4697        }
4698        let f = if crate::sig_expf_dev_on() {
4699            self.func("moe_router_sigmoid_topk_f32_dexp")
4700        } else if crate::topk_fast_on() {
4701            self.func("moe_router_sigmoid_topk_f32_fast")
4702        } else {
4703            self.func("moe_router_sigmoid_topk_f32")
4704        };
4705        let threads = n_expert.div_ceil(32) * 32;
4706        let cfg = LaunchConfig {
4707            grid_dim: (t as u32, 1, 1),
4708            block_dim: (threads as u32, 1, 1),
4709            shared_mem_bytes: 0,
4710        };
4711        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
4712        let __s_b = self.gpu.stream();
4713        let mut b = __s_b.launch_builder(&f);
4714        b.arg(logits)
4715            .arg(correction_bias)
4716            .arg(active)
4717            .arg(&mut *sel_idx)
4718            .arg(&mut *sel_w)
4719            .arg(&ne)
4720            .arg(&nu)
4721            .arg(&scaling_factor)
4722            .arg(&rn);
4723        unsafe {
4724            b.launch(cfg)?;
4725        }
4726        Ok(())
4727    }
4728
4729    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
4730    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
4731    #[allow(clippy::too_many_arguments)]
4732    pub fn moe_router_sigmoid_topk_host(
4733        &self,
4734        logits: &CudaSlice<f32>,
4735        t: usize,
4736        n_expert: usize,
4737        n_used: usize,
4738        active_count: usize,
4739        correction_bias: &CudaSlice<f32>,
4740        active: &CudaSlice<u8>,
4741        scaling_factor: f32,
4742        route_norm: bool,
4743    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4744        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4745            logits,
4746            t,
4747            n_expert,
4748            n_used,
4749            active_count,
4750            correction_bias,
4751            active,
4752            scaling_factor,
4753            route_norm,
4754        )?;
4755        let n = t * n_used;
4756        let bytes = n * 8;
4757        let mut guard = self.router_stage.lock().unwrap();
4758        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4759            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4760        }
4761        let stage = guard.as_mut().unwrap();
4762        let (si, sw) = unsafe {
4763            (
4764                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4765                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4766            )
4767        };
4768        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4769        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4770        self.gpu.stream().synchronize()?;
4771        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4772    }
4773
4774    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4775    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4776    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4777    pub fn stage_expert_async(
4778        &self,
4779        host_bytes: &[u8],
4780        scratch: &mut CudaSlice<u8>,
4781        off: usize,
4782    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4783        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4784        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4785        Ok(self.copy_stream.record_event(None)?)
4786    }
4787
4788    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4789    pub fn compute_wait(
4790        &self,
4791        ev: &cudarc::driver::CudaEvent,
4792    ) -> Result<(), Box<dyn std::error::Error>> {
4793        self.gpu.stream().wait(ev)?;
4794        Ok(())
4795    }
4796
4797    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4798    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4799    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4800    /// CudaView base+offset pointer is honored by the launch arg.
4801    pub fn qmatvec_view(
4802        &self,
4803        w: &CudaSlice<u8>,
4804        range: std::ops::Range<usize>,
4805        x: &cudarc::driver::CudaView<f32>,
4806        m: usize,
4807        in_f: usize,
4808        out_f: usize,
4809        qtype: i32,
4810        row_bytes: usize,
4811    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4812        let f = self.func("qmatvec_f32");
4813        let wv = w.slice(range); // CudaView<u8>, offset honored
4814        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4815        let cfg = LaunchConfig {
4816            grid_dim: (out_f as u32, m as u32, 1),
4817            block_dim: (256, 1, 1),
4818            shared_mem_bytes: 0,
4819        };
4820        let (inf, outf, mi, qt, rb) =
4821            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4822        let __s_b = self.gpu.stream();
4823        let mut b = __s_b.launch_builder(&f);
4824        b.arg(&wv)
4825            .arg(x)
4826            .arg(&mut y)
4827            .arg(&inf)
4828            .arg(&outf)
4829            .arg(&mi)
4830            .arg(&qt)
4831            .arg(&rb);
4832        unsafe {
4833            b.launch(cfg)?;
4834        }
4835        Ok(y)
4836    }
4837
4838    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4839    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4840    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4841    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4842    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4843    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4844    #[allow(clippy::too_many_arguments)]
4845    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4846    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4847    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4848    pub fn moe_gate_up_silu8_q8(
4849        &self,
4850        gp: WPtr8,
4851        up: WPtr8,
4852        aq: &CudaSlice<i8>,
4853        ad: &CudaSlice<f32>,
4854        in_f: usize,
4855        n_ff: usize,
4856        n_used: usize,
4857        qt_g: i32,
4858        qt_u: i32,
4859        rb_g: usize,
4860        rb_u: usize,
4861    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4862        let f = self.func("moe_gate_up_silu8_q8");
4863        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4864        let cfg = LaunchConfig {
4865            grid_dim: (n_ff as u32, n_used as u32, 1),
4866            block_dim: (32, 1, 1),
4867            shared_mem_bytes: 0,
4868        };
4869        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4870        let __s_b = self.gpu.stream();
4871        let mut b = __s_b.launch_builder(&f);
4872        b.arg(&gp)
4873            .arg(&up)
4874            .arg(aq)
4875            .arg(ad)
4876            .arg(&mut act)
4877            .arg(&inf)
4878            .arg(&nff)
4879            .arg(&qt_g)
4880            .arg(&qt_u)
4881            .arg(&rbg)
4882            .arg(&rbu);
4883        unsafe {
4884            b.launch(cfg)?;
4885        }
4886        Ok(act)
4887    }
4888
4889    #[allow(clippy::too_many_arguments)]
4890    pub fn moe_down8_fma_q8(
4891        &self,
4892        dp: WPtr8,
4893        w: F32x8,
4894        aq2: &CudaSlice<i8>,
4895        ad2: &CudaSlice<f32>,
4896        dst: &mut cudarc::driver::CudaViewMut<f32>,
4897        in_f: usize,
4898        out_f: usize,
4899        n_used: usize,
4900        qt: i32,
4901        rb: usize,
4902    ) -> Result<(), Box<dyn std::error::Error>> {
4903        let f = self.func("moe_down8_fma_q8");
4904        let cfg = LaunchConfig {
4905            grid_dim: (out_f as u32, 1, 1),
4906            block_dim: (32, 1, 1),
4907            shared_mem_bytes: 0,
4908        };
4909        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4910        let __s_b = self.gpu.stream();
4911        let mut b = __s_b.launch_builder(&f);
4912        b.arg(&dp)
4913            .arg(&w)
4914            .arg(aq2)
4915            .arg(ad2)
4916            .arg(dst)
4917            .arg(&inf)
4918            .arg(&outf)
4919            .arg(&nu)
4920            .arg(&qt)
4921            .arg(&rbi);
4922        unsafe {
4923            b.launch(cfg)?;
4924        }
4925        Ok(())
4926    }
4927
4928    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4929    pub fn qmatvec_expert_q8(
4930        &self,
4931        w: &CudaSlice<u8>,
4932        range: std::ops::Range<usize>,
4933        aq: &CudaSlice<i8>,
4934        ad: &CudaSlice<f32>,
4935        m: usize,
4936        in_f: usize,
4937        out_f: usize,
4938        qtype: i32,
4939        row_bytes: usize,
4940    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4941        let f = self.func("qmatvec_expert_q8");
4942        let wv = w.slice(range);
4943        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4944        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4945        let cfg = LaunchConfig {
4946            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4947            block_dim: (32, ROWS, 1),
4948            shared_mem_bytes: 0,
4949        };
4950        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4951        let __s_b = self.gpu.stream();
4952        let mut b = __s_b.launch_builder(&f);
4953        b.arg(&wv)
4954            .arg(aq)
4955            .arg(ad)
4956            .arg(&mut y)
4957            .arg(&inf)
4958            .arg(&outf)
4959            .arg(&mi)
4960            .arg(&qtype)
4961            .arg(&rbi);
4962        unsafe {
4963            b.launch(cfg)?;
4964        }
4965        Ok(y)
4966    }
4967
4968    pub fn moe_gate_up_silu8(
4969        &self,
4970        gp: WPtr8,
4971        up: WPtr8,
4972        x: &cudarc::driver::CudaView<f32>,
4973        in_f: usize,
4974        n_ff: usize,
4975        n_used: usize,
4976        qt_g: i32,
4977        qt_u: i32,
4978        rb_g: usize,
4979        rb_u: usize,
4980    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4981        let f = self.func("moe_gate_up_silu8_f32");
4982        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4983        let cfg = LaunchConfig {
4984            grid_dim: (n_ff as u32, n_used as u32, 1),
4985            block_dim: (256, 1, 1),
4986            shared_mem_bytes: 0,
4987        };
4988        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4989        let __s_b = self.gpu.stream();
4990        let mut b = __s_b.launch_builder(&f);
4991        b.arg(&gp)
4992            .arg(&up)
4993            .arg(x)
4994            .arg(&mut act)
4995            .arg(&inf)
4996            .arg(&nff)
4997            .arg(&qt_g)
4998            .arg(&qt_u)
4999            .arg(&rbg)
5000            .arg(&rbu);
5001        unsafe {
5002            b.launch(cfg)?;
5003        }
5004        Ok(act)
5005    }
5006
5007    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
5008    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
5009    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
5010    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
5011    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
5012    #[allow(clippy::too_many_arguments)]
5013    pub fn moe_down8_fma_into(
5014        &self,
5015        dp: WPtr8,
5016        w: F32x8,
5017        act: &CudaSlice<f32>,
5018        dst: &mut cudarc::driver::CudaViewMut<f32>,
5019        in_f: usize,
5020        out_f: usize,
5021        n_used: usize,
5022        qt: i32,
5023        rb: usize,
5024    ) -> Result<(), Box<dyn std::error::Error>> {
5025        let f = self.func("moe_down8_fma_f32");
5026        let cfg = LaunchConfig {
5027            grid_dim: (out_f as u32, 1, 1),
5028            block_dim: (256, 1, 1),
5029            shared_mem_bytes: 0,
5030        };
5031        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
5032        let __s_b = self.gpu.stream();
5033        let mut b = __s_b.launch_builder(&f);
5034        b.arg(&dp)
5035            .arg(&w)
5036            .arg(act)
5037            .arg(dst)
5038            .arg(&inf)
5039            .arg(&outf)
5040            .arg(&nu)
5041            .arg(&qt)
5042            .arg(&rbv);
5043        unsafe {
5044            b.launch(cfg)?;
5045        }
5046        Ok(())
5047    }
5048
5049    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
5050    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
5051    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
5052    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
5053    #[allow(clippy::too_many_arguments)]
5054    /// dp4a q8 twin of the _dev pair (resident-experts arc).
5055    ///
5056    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
5057    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
5058    /// down's FMA chain stays slot-ordered serial). Seams:
5059    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
5060    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
5061    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
5062    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
5063    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
5064    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
5065    ///                       decode on 35B/rtx6000) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
5066    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
5067    ///                       only) | w8h2 (h2 x slot-parallel)
5068    #[allow(clippy::too_many_arguments)]
5069    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
5070    #[allow(clippy::too_many_arguments)]
5071    pub fn moe_pairs_matvec_q8(
5072        &self,
5073        table: &CudaSlice<u64>,
5074        proj: i32,
5075        pair_tok: &CudaSlice<i32>,
5076        pair_ex: &CudaSlice<i32>,
5077        aq: &CudaSlice<i8>,
5078        ad: &CudaSlice<f32>,
5079        in_f: usize,
5080        out_f: usize,
5081        n_expert: usize,
5082        n_pairs: usize,
5083        qtype: i32,
5084        row_bytes: usize,
5085    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5086        let f = self.func("moe_pairs_matvec_q8");
5087        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
5088        const ROWS: u32 = 4;
5089        let cfg = LaunchConfig {
5090            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
5091            block_dim: (32, ROWS, 1),
5092            shared_mem_bytes: 0,
5093        };
5094        let (inf, outf, ne, np, rbi) = (
5095            in_f as i32,
5096            out_f as i32,
5097            n_expert as i32,
5098            n_pairs as i32,
5099            row_bytes as i64,
5100        );
5101        let __s_b = self.gpu.stream();
5102        let mut b = __s_b.launch_builder(&f);
5103        b.arg(table)
5104            .arg(&proj)
5105            .arg(pair_tok)
5106            .arg(pair_ex)
5107            .arg(aq)
5108            .arg(ad)
5109            .arg(&mut y)
5110            .arg(&inf)
5111            .arg(&outf)
5112            .arg(&ne)
5113            .arg(&np)
5114            .arg(&qtype)
5115            .arg(&rbi);
5116        unsafe {
5117            b.launch(cfg)?;
5118        }
5119        Ok(y)
5120    }
5121
5122    /// Expert-major pair matvec (weight-reuse across each expert's token group).
5123    #[allow(clippy::too_many_arguments)]
5124    pub fn moe_pairs_matvec_q8_em(
5125        &self,
5126        table: &CudaSlice<u64>,
5127        proj: i32,
5128        ex_ids: &CudaSlice<i32>,
5129        ex_off: &CudaSlice<i32>,
5130        ex_pairs: &CudaSlice<i32>,
5131        pair_tok: &CudaSlice<i32>,
5132        aq: &CudaSlice<i8>,
5133        ad: &CudaSlice<f32>,
5134        in_f: usize,
5135        out_f: usize,
5136        n_expert: usize,
5137        n_active: usize,
5138        n_pairs: usize,
5139        qtype: i32,
5140        row_bytes: usize,
5141    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5142        let f = self.func("moe_pairs_matvec_q8_em");
5143        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
5144        const ROWS: u32 = 4;
5145        let cfg = LaunchConfig {
5146            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
5147            block_dim: (32, ROWS, 1),
5148            shared_mem_bytes: 0,
5149        };
5150        let (inf, outf, ne, na, rbi) = (
5151            in_f as i32,
5152            out_f as i32,
5153            n_expert as i32,
5154            n_active as i32,
5155            row_bytes as i64,
5156        );
5157        let __s_b = self.gpu.stream();
5158        let mut b = __s_b.launch_builder(&f);
5159        b.arg(table)
5160            .arg(&proj)
5161            .arg(ex_ids)
5162            .arg(ex_off)
5163            .arg(ex_pairs)
5164            .arg(pair_tok)
5165            .arg(aq)
5166            .arg(ad)
5167            .arg(&mut y)
5168            .arg(&inf)
5169            .arg(&outf)
5170            .arg(&ne)
5171            .arg(&na)
5172            .arg(&qtype)
5173            .arg(&rbi);
5174        unsafe {
5175            b.launch(cfg)?;
5176        }
5177        Ok(y)
5178    }
5179
5180    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
5181    // weight group once per (row,group) then dp4a's across the expert's token group.
5182    #[allow(clippy::too_many_arguments)]
5183    pub fn moe_pairs_matvec_q8_dec(
5184        &self,
5185        table: &CudaSlice<u64>,
5186        proj: i32,
5187        ex_ids: &CudaSlice<i32>,
5188        ex_off: &CudaSlice<i32>,
5189        ex_pairs: &CudaSlice<i32>,
5190        pair_tok: &CudaSlice<i32>,
5191        aq: &CudaSlice<i8>,
5192        ad: &CudaSlice<f32>,
5193        in_f: usize,
5194        out_f: usize,
5195        n_expert: usize,
5196        n_active: usize,
5197        n_pairs: usize,
5198        qtype: i32,
5199        row_bytes: usize,
5200    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5201        let f = self.func("moe_pairs_matvec_q8_dec");
5202        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
5203        const ROWS: u32 = 4;
5204        let cfg = LaunchConfig {
5205            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
5206            block_dim: (32, ROWS, 1),
5207            shared_mem_bytes: 0,
5208        };
5209        let (inf, outf, ne, na, rbi) = (
5210            in_f as i32,
5211            out_f as i32,
5212            n_expert as i32,
5213            n_active as i32,
5214            row_bytes as i64,
5215        );
5216        let __s_b = self.gpu.stream();
5217        let mut b = __s_b.launch_builder(&f);
5218        b.arg(table)
5219            .arg(&proj)
5220            .arg(ex_ids)
5221            .arg(ex_off)
5222            .arg(ex_pairs)
5223            .arg(pair_tok)
5224            .arg(aq)
5225            .arg(ad)
5226            .arg(&mut y)
5227            .arg(&inf)
5228            .arg(&outf)
5229            .arg(&ne)
5230            .arg(&na)
5231            .arg(&qtype)
5232            .arg(&rbi);
5233        unsafe {
5234            b.launch(cfg)?;
5235        }
5236        Ok(y)
5237    }
5238
5239    pub fn moe_pairs_gelu_mul(
5240        &self,
5241        gate: &CudaSlice<f32>,
5242        up: &CudaSlice<f32>,
5243        n: usize,
5244    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5245        let f = self.func("moe_pairs_gelu_mul");
5246        let mut act = self.alloc_uninit::<f32>(n)?;
5247        let cfg = LaunchConfig::for_num_elems(n as u32);
5248        let nl = n as i64;
5249        let __s_b = self.gpu.stream();
5250        let mut b = __s_b.launch_builder(&f);
5251        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
5252        unsafe {
5253            b.launch(cfg)?;
5254        }
5255        Ok(act)
5256    }
5257
5258    pub fn moe_pairs_silu_mul(
5259        &self,
5260        gate: &CudaSlice<f32>,
5261        up: &CudaSlice<f32>,
5262        n: usize,
5263    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5264        let f = self.func("moe_pairs_silu_mul");
5265        let mut act = self.alloc_uninit::<f32>(n)?;
5266        let cfg = LaunchConfig::for_num_elems(n as u32);
5267        let nl = n as i64;
5268        let __s_b = self.gpu.stream();
5269        let mut b = __s_b.launch_builder(&f);
5270        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
5271        unsafe {
5272            b.launch(cfg)?;
5273        }
5274        Ok(act)
5275    }
5276
5277    #[allow(clippy::too_many_arguments)]
5278    pub fn moe_pairs_scatter(
5279        &self,
5280        y_down: &CudaSlice<f32>,
5281        pair_w: &CudaSlice<f32>,
5282        tok_pair_off: &CudaSlice<i32>,
5283        tok_pair_ids: &CudaSlice<i32>,
5284        moe_out: &mut CudaSlice<f32>,
5285        t: usize,
5286        n_embd: usize,
5287    ) -> Result<(), Box<dyn std::error::Error>> {
5288        let f = self.func("moe_pairs_scatter");
5289        let cfg = LaunchConfig {
5290            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
5291            block_dim: (256, 1, 1),
5292            shared_mem_bytes: 0,
5293        };
5294        let ne = n_embd as i32;
5295        let __s_b = self.gpu.stream();
5296        let mut b = __s_b.launch_builder(&f);
5297        b.arg(y_down)
5298            .arg(pair_w)
5299            .arg(tok_pair_off)
5300            .arg(tok_pair_ids)
5301            .arg(moe_out)
5302            .arg(&ne);
5303        unsafe {
5304            b.launch(cfg)?;
5305        }
5306        Ok(())
5307    }
5308
5309    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
5310    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
5311    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
5312    #[allow(clippy::too_many_arguments)]
5313    pub fn moe_gate_up_gelu8_dev_q8(
5314        &self,
5315        table: &CudaSlice<u64>,
5316        sel: &cudarc::driver::CudaView<i32>,
5317        aq: &CudaSlice<i8>,
5318        ad: &CudaSlice<f32>,
5319        in_f: usize,
5320        n_ff: usize,
5321        n_used: usize,
5322        n_expert: usize,
5323        qt_g: i32,
5324        qt_u: i32,
5325        rb_g: usize,
5326        rb_u: usize,
5327    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5328        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5329        let (inf, nff, ne, rbg, rbu) = (
5330            in_f as i32,
5331            n_ff as i32,
5332            n_expert as i32,
5333            rb_g as i64,
5334            rb_u as i64,
5335        );
5336        let f = self.func("moe_gate_up_gelu8_dev_q8");
5337        let cfg = LaunchConfig {
5338            grid_dim: (n_ff as u32, n_used as u32, 1),
5339            block_dim: (32, 1, 1),
5340            shared_mem_bytes: 0,
5341        };
5342        let __s_b = self.gpu.stream();
5343        let mut b = __s_b.launch_builder(&f);
5344        b.arg(table)
5345            .arg(sel)
5346            .arg(aq)
5347            .arg(ad)
5348            .arg(&mut act)
5349            .arg(&inf)
5350            .arg(&nff)
5351            .arg(&ne)
5352            .arg(&qt_g)
5353            .arg(&qt_u)
5354            .arg(&rbg)
5355            .arg(&rbu);
5356        unsafe {
5357            b.launch(cfg)?;
5358        }
5359        Ok(act)
5360    }
5361
5362    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
5363    #[allow(clippy::too_many_arguments)]
5364    pub fn moe_gate_up_gelu8_dev_q8_rows(
5365        &self,
5366        table: &CudaSlice<u64>,
5367        sel: &CudaSlice<i32>,
5368        aq: &CudaSlice<i8>,
5369        ad: &CudaSlice<f32>,
5370        t: usize,
5371        in_f: usize,
5372        n_ff: usize,
5373        n_used: usize,
5374        n_expert: usize,
5375        qt_g: i32,
5376        qt_u: i32,
5377        rb_g: usize,
5378        rb_u: usize,
5379    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5380        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5381        let (inf, nff, ne, rbg, rbu, nu) = (
5382            in_f as i32,
5383            n_ff as i32,
5384            n_expert as i32,
5385            rb_g as i64,
5386            rb_u as i64,
5387            n_used as i32,
5388        );
5389        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
5390        let cfg = LaunchConfig {
5391            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5392            block_dim: (32, 1, 1),
5393            shared_mem_bytes: 0,
5394        };
5395        let __s_b = self.gpu.stream();
5396        let mut b = __s_b.launch_builder(&f);
5397        b.arg(table)
5398            .arg(sel)
5399            .arg(aq)
5400            .arg(ad)
5401            .arg(&mut act)
5402            .arg(&inf)
5403            .arg(&nff)
5404            .arg(&ne)
5405            .arg(&qt_g)
5406            .arg(&qt_u)
5407            .arg(&rbg)
5408            .arg(&rbu)
5409            .arg(&nu);
5410        unsafe {
5411            b.launch(cfg)?;
5412        }
5413        Ok(act)
5414    }
5415
5416    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
5417    #[allow(clippy::too_many_arguments)]
5418    pub fn moe_gate_up_gelu8_dev_q8_csr(
5419        &self,
5420        table: &CudaSlice<u64>,
5421        sel: &CudaSlice<i32>,
5422        aq: &CudaSlice<i8>,
5423        ad: &CudaSlice<f32>,
5424        n_pairs: usize,
5425        in_f: usize,
5426        n_ff: usize,
5427        n_used: usize,
5428        n_expert: usize,
5429        qt_g: i32,
5430        qt_u: i32,
5431        rb_g: usize,
5432        rb_u: usize,
5433    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5434        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5435        let (inf, nff, ne, rbg, rbu, nu, npi) = (
5436            in_f as i32,
5437            n_ff as i32,
5438            n_expert as i32,
5439            rb_g as i64,
5440            rb_u as i64,
5441            n_used as i32,
5442            n_pairs as i32,
5443        );
5444        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
5445        let cfg = LaunchConfig {
5446            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5447            block_dim: (32, 1, 1),
5448            shared_mem_bytes: 0,
5449        };
5450        let __s_b = self.gpu.stream();
5451        let mut b = __s_b.launch_builder(&f);
5452        b.arg(table)
5453            .arg(sel)
5454            .arg(aq)
5455            .arg(ad)
5456            .arg(&mut act)
5457            .arg(&inf)
5458            .arg(&nff)
5459            .arg(&ne)
5460            .arg(&qt_g)
5461            .arg(&qt_u)
5462            .arg(&rbg)
5463            .arg(&rbu)
5464            .arg(&nu)
5465            .arg(&npi);
5466        unsafe {
5467            b.launch(cfg)?;
5468        }
5469        Ok(act)
5470    }
5471
5472    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
5473    #[allow(clippy::too_many_arguments)]
5474    pub fn moe_down8_fma_dev_q8_rows_g(
5475        &self,
5476        table: &CudaSlice<u64>,
5477        sel: &CudaSlice<i32>,
5478        w: &CudaSlice<f32>,
5479        aq2: &CudaSlice<i8>,
5480        ad2: &CudaSlice<f32>,
5481        dst: &mut CudaSlice<f32>,
5482        t: usize,
5483        in_f: usize,
5484        out_f: usize,
5485        n_used: usize,
5486        n_expert: usize,
5487        qt: i32,
5488        rb: usize,
5489    ) -> Result<(), Box<dyn std::error::Error>> {
5490        let (inf, outf, nu, ne, rbi) = (
5491            in_f as i32,
5492            out_f as i32,
5493            n_used as i32,
5494            n_expert as i32,
5495            rb as i64,
5496        );
5497        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
5498        // eight warps, then replay the original slot-ordered FMA chain. Every
5499        // other shape retains the generic one-warp rows kernel.
5500        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
5501        let f = self.func(if step_b1_w8 {
5502            "moe_down8_fma_dev_q8_rows_w8"
5503        } else {
5504            "moe_down8_fma_dev_q8_rows_g"
5505        });
5506        let cfg = LaunchConfig {
5507            grid_dim: (out_f as u32, 1, t as u32),
5508            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
5509            shared_mem_bytes: 0,
5510        };
5511        let __s_b = self.gpu.stream();
5512        let mut b = __s_b.launch_builder(&f);
5513        b.arg(table)
5514            .arg(sel)
5515            .arg(w)
5516            .arg(aq2)
5517            .arg(ad2)
5518            .arg(dst)
5519            .arg(&inf)
5520            .arg(&outf)
5521            .arg(&nu)
5522            .arg(&ne)
5523            .arg(&qt)
5524            .arg(&rbi);
5525        unsafe {
5526            b.launch(cfg)?;
5527        }
5528        Ok(())
5529    }
5530
5531    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
5532    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
5533    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
5534    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
5535        let (out_f, in_f) = (2048usize, 2816usize);
5536        let nblk = in_f / 32;
5537        let mut seed = 0x9E3779B97F4A7C15u64;
5538        let mut rng = move || {
5539            seed = seed
5540                .wrapping_mul(6364136223846793005)
5541                .wrapping_add(1442695040888963407);
5542            (seed >> 33) as u8
5543        };
5544        let mut w = vec![0u8; out_f * nblk * 18];
5545        for b in w.iter_mut() {
5546            *b = rng();
5547        }
5548        for r in 0..out_f {
5549            for g in 0..nblk {
5550                let off = (r * nblk + g) * 18;
5551                w[off] = 0x00;
5552                w[off + 1] = 0x2C; // sane half d
5553            }
5554        }
5555        let qplane = out_f * nblk * 16;
5556        let mut wrp = vec![0u8; w.len()];
5557        for r in 0..out_f {
5558            for g in 0..nblk {
5559                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
5560                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
5561                    .copy_from_slice(&src[0..2]);
5562                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
5563            }
5564        }
5565        let w_d = self.htod_bytes(&w)?;
5566        let wrp_d = self.htod_bytes(&wrp)?;
5567        let mut aq = vec![0i8; m * in_f];
5568        for v in aq.iter_mut() {
5569            *v = rng() as i8;
5570        }
5571        let aq_d = self.htod_i8(&aq)?;
5572        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
5573        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
5574        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
5575        const RPB: u32 = 4;
5576        let cfg = LaunchConfig {
5577            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
5578            block_dim: (32, RPB, 1),
5579            shared_mem_bytes: 0,
5580        };
5581        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
5582        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
5583        let fb = self.func("qmatvec_q4_0_mmvq_b4");
5584        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
5585        {
5586            let __s_b = self.gpu.stream();
5587            let mut b = __s_b.launch_builder(&fb);
5588            b.arg(&w_d)
5589                .arg(&aq_d)
5590                .arg(&ad_d)
5591                .arg(&mut y0)
5592                .arg(&inf)
5593                .arg(&outf)
5594                .arg(&mi)
5595                .arg(&rb);
5596            unsafe {
5597                b.launch(cfg)?;
5598            }
5599            let __s_b = self.gpu.stream();
5600            let mut b = __s_b.launch_builder(&fr);
5601            b.arg(&wrp_d)
5602                .arg(&aq_d)
5603                .arg(&ad_d)
5604                .arg(&mut y1)
5605                .arg(&inf)
5606                .arg(&outf)
5607                .arg(&mi)
5608                .arg(&qp);
5609            unsafe {
5610                b.launch(cfg)?;
5611            }
5612        }
5613        self.gpu.stream().synchronize()?;
5614        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
5615        let nd = h0
5616            .iter()
5617            .zip(&h1)
5618            .filter(|(a, b)| a.to_bits() != b.to_bits())
5619            .count();
5620        if nd != 0 {
5621            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
5622        }
5623        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
5624            self.gpu.stream().synchronize()?;
5625            let t0 = std::time::Instant::now();
5626            for _ in 0..500 {
5627                if rp {
5628                    let __s_b = self.gpu.stream();
5629                    let mut b = __s_b.launch_builder(&fr);
5630                    b.arg(&wrp_d)
5631                        .arg(&aq_d)
5632                        .arg(&ad_d)
5633                        .arg(&mut y1)
5634                        .arg(&inf)
5635                        .arg(&outf)
5636                        .arg(&mi)
5637                        .arg(&qp);
5638                    unsafe {
5639                        b.launch(cfg)?;
5640                    }
5641                } else {
5642                    let __s_b = self.gpu.stream();
5643                    let mut b = __s_b.launch_builder(&fb);
5644                    b.arg(&w_d)
5645                        .arg(&aq_d)
5646                        .arg(&ad_d)
5647                        .arg(&mut y0)
5648                        .arg(&inf)
5649                        .arg(&outf)
5650                        .arg(&mi)
5651                        .arg(&rb);
5652                    unsafe {
5653                        b.launch(cfg)?;
5654                    }
5655                }
5656            }
5657            self.gpu.stream().synchronize()?;
5658            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
5659        };
5660        let _ = time(false)?;
5661        let _ = time(true)?; // warm
5662        Ok((time(false)?, time(true)?))
5663    }
5664
5665    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
5666    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
5667    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
5668    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
5669    pub fn build_q4_rp4(
5670        &self,
5671        t: &mut crate::model::GpuTensor,
5672    ) -> Result<(), Box<dyn std::error::Error>> {
5673        use crate::model::GpuTensor;
5674        let GpuTensor::Quant {
5675            bytes,
5676            qtype,
5677            row_bytes,
5678            ne,
5679            rp4,
5680            ..
5681        } = t
5682        else {
5683            return Ok(());
5684        };
5685        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
5686            return Ok(());
5687        }
5688        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5689        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
5690            return Ok(());
5691        }
5692        let nblk = in_f / 32;
5693        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
5694        let f = self.func("q4_0_split_rp_build");
5695        let n = (out_f * nblk) as i32;
5696        let cfg = LaunchConfig {
5697            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5698            block_dim: (256, 1, 1),
5699            shared_mem_bytes: 0,
5700        };
5701        let (of, nb) = (out_f as i32, nblk as i32);
5702        let _ = n;
5703        let __s_b = self.gpu.stream();
5704        let mut b = __s_b.launch_builder(&f);
5705        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5706        unsafe {
5707            b.launch(cfg)?;
5708        }
5709        *rp4 = Some(dst);
5710        Ok(())
5711    }
5712
5713    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
5714    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
5715    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
5716    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5717    pub fn build_q8_rp4(
5718        &self,
5719        t: &mut crate::model::GpuTensor,
5720    ) -> Result<(), Box<dyn std::error::Error>> {
5721        use crate::model::GpuTensor;
5722        let GpuTensor::Quant {
5723            bytes,
5724            qtype,
5725            row_bytes,
5726            ne,
5727            rp4,
5728            ..
5729        } = t
5730        else {
5731            return Ok(());
5732        };
5733        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
5734            return Ok(());
5735        }
5736        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5737        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5738            return Ok(());
5739        }
5740        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5741        Ok(())
5742    }
5743
5744    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5745    /// mirror without a GpuTensor (same kernel the loader path above uses).
5746    pub fn build_q8_rp4_raw(
5747        &self,
5748        bytes: &CudaSlice<u8>,
5749        in_f: usize,
5750        out_f: usize,
5751    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5752        assert!(in_f % 32 == 0);
5753        let nblk = in_f / 32;
5754        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5755        let f = self.func("q8_0_split_rp_build");
5756        let cfg = LaunchConfig {
5757            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5758            block_dim: (256, 1, 1),
5759            shared_mem_bytes: 0,
5760        };
5761        let (of, nb) = (out_f as i32, nblk as i32);
5762        let __s_b = self.gpu.stream();
5763        let mut b = __s_b.launch_builder(&f);
5764        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5765        unsafe {
5766            b.launch(cfg)?;
5767        }
5768        Ok(dst)
5769    }
5770
5771    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5772    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5773    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5774    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5775    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5776    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5777    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5778    pub fn build_q4k_rp4(
5779        &self,
5780        t: &mut crate::model::GpuTensor,
5781    ) -> Result<(), Box<dyn std::error::Error>> {
5782        use crate::model::GpuTensor;
5783        let GpuTensor::Quant {
5784            bytes,
5785            qtype,
5786            row_bytes,
5787            ne,
5788            rp4,
5789            ..
5790        } = t
5791        else {
5792            return Ok(());
5793        };
5794        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5795            return Ok(());
5796        }
5797        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5798        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5799            return Ok(());
5800        }
5801        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5802        Ok(())
5803    }
5804
5805    pub fn build_q6k_rp4(
5806        &self,
5807        t: &mut crate::model::GpuTensor,
5808    ) -> Result<(), Box<dyn std::error::Error>> {
5809        use crate::model::GpuTensor;
5810        let GpuTensor::Quant {
5811            bytes,
5812            qtype,
5813            row_bytes,
5814            ne,
5815            rp4,
5816            ..
5817        } = t
5818        else {
5819            return Ok(());
5820        };
5821        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5822            return Ok(());
5823        }
5824        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5825        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5826            return Ok(());
5827        }
5828        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5829        Ok(())
5830    }
5831
5832    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5833    pub fn build_kq_rp4_raw(
5834        &self,
5835        bytes: &CudaSlice<u8>,
5836        in_f: usize,
5837        out_f: usize,
5838        qtype: i32,
5839    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5840        assert!(in_f % 256 == 0);
5841        let nsbk = in_f / 256;
5842        let (sb_bytes, kname) = match qtype {
5843            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5844            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5845            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5846        };
5847        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5848        let f = self.func(kname);
5849        let cfg = LaunchConfig {
5850            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5851            block_dim: (256, 1, 1),
5852            shared_mem_bytes: 0,
5853        };
5854        let (of, nb) = (out_f as i32, nsbk as i32);
5855        let __s_b = self.gpu.stream();
5856        let mut b = __s_b.launch_builder(&f);
5857        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5858        unsafe {
5859            b.launch(cfg)?;
5860        }
5861        Ok(dst)
5862    }
5863
5864    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5865    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5866    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5867    pub fn kqrp_enabled() -> bool {
5868        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5869        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5870            Ok("0") => false,
5871            Ok(_) => true,
5872            Err(_) => cfg!(memra_hopper_mma),
5873        })
5874    }
5875
5876    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5877    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5878    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5879    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5880    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5881    pub fn build_q4_rp_swap(
5882        &self,
5883        t: &mut crate::model::GpuTensor,
5884    ) -> Result<bool, Box<dyn std::error::Error>> {
5885        use crate::model::GpuTensor;
5886        // QTYPE GUARD IN THE SWAP ITSELF (gemma4 NVFP4mix prefill-NaN, 2026-08-17):
5887        // `rp4` is a SHARED field — the Q8RP walk parks Q8_0 MIRRORS there, and this
5888        // fn used to `take()` whatever it found. On a Q8_0-carrying gemma4-dense trunk
5889        // the swap hijacked those mirrors: `bytes` became split-plane in place, the
5890        // m<=16 `_rp` dispatch read them correctly (masking the corruption from every
5891        // decode pin), and every GGUF-layout prefill consumer (MMQ + GEMM) read the
5892        // fp16 d-plane as weights -> layer-0 NaN, <pad>-spam serving. Only a tensor
5893        // this fn's OWN builder serves may ever be swapped; everything else refuses
5894        // here, regardless of walk ordering.
5895        if !matches!(t, GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0) {
5896            return Ok(false);
5897        }
5898        self.build_q4_rp4(t)?;
5899        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5900        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5901            return Ok(false);
5902        };
5903        match rp4.take() {
5904            Some(split) => {
5905                *bytes = split; // the GGUF-layout buffer drops here
5906                *rp = true;
5907                Ok(true)
5908            }
5909            None => Ok(false),
5910        }
5911    }
5912
5913    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5914    pub fn q4rp_enabled() -> bool {
5915        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5916        *ON.get_or_init(|| {
5917            std::env::var("MEMRA_Q4RP")
5918                .map(|v| v != "0")
5919                .unwrap_or(true)
5920        })
5921    }
5922
5923    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5924    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5925    pub fn copy_rows_strided(
5926        &self,
5927        src: &CudaSlice<f32>,
5928        dst: &mut CudaSlice<f32>,
5929        row_elems: usize,
5930        n_rows: usize,
5931        src_stride: usize,
5932        src_off: usize,
5933    ) -> Result<(), Box<dyn std::error::Error>> {
5934        let f = self.func("copy_rows_strided_f32");
5935        let cfg = LaunchConfig {
5936            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5937            block_dim: (256, 1, 1),
5938            shared_mem_bytes: 0,
5939        };
5940        let (re, nr) = (row_elems as i32, n_rows as i32);
5941        let (st, off) = (src_stride as i64, src_off as i64);
5942        let __s_b = self.gpu.stream();
5943        let mut b = __s_b.launch_builder(&f);
5944        b.arg(src)
5945            .arg(&mut *dst)
5946            .arg(&re)
5947            .arg(&nr)
5948            .arg(&st)
5949            .arg(&off);
5950        unsafe {
5951            b.launch(cfg)?;
5952        }
5953        Ok(())
5954    }
5955
5956    /// Place dense `[row][row_elems]` source rows into one column range of a strided destination.
5957    ///
5958    /// This is a byte-preserving layout operation. It exists so multi-GPU collectives can move
5959    /// one dense shard per rank and reconstruct the canonical token-major matrix without issuing
5960    /// one peer copy per token.
5961    pub fn place_rows_strided(
5962        &self,
5963        src: &CudaSlice<f32>,
5964        dst: &mut CudaSlice<f32>,
5965        row_elems: usize,
5966        n_rows: usize,
5967        dst_stride: usize,
5968        dst_off: usize,
5969    ) -> Result<(), Box<dyn std::error::Error>> {
5970        if row_elems == 0 || n_rows == 0 {
5971            return Err("strided row placement requires nonzero rows and row width".into());
5972        }
5973        let src_len = n_rows
5974            .checked_mul(row_elems)
5975            .ok_or("strided row placement source size overflow")?;
5976        let dst_len = n_rows
5977            .checked_sub(1)
5978            .and_then(|rows| rows.checked_mul(dst_stride))
5979            .and_then(|base| base.checked_add(dst_off))
5980            .and_then(|base| base.checked_add(row_elems))
5981            .ok_or("strided row placement destination size overflow")?;
5982        let row_end = dst_off
5983            .checked_add(row_elems)
5984            .ok_or("strided row placement row size overflow")?;
5985        if src.len() < src_len || dst.len() < dst_len || row_end > dst_stride {
5986            return Err(format!(
5987                "strided row placement geometry mismatch: src={} need_src={src_len} \
5988                 dst={} need_dst={dst_len} row_elems={row_elems} rows={n_rows} \
5989                 dst_stride={dst_stride} dst_off={dst_off}",
5990                src.len(),
5991                dst.len(),
5992            )
5993            .into());
5994        }
5995        if row_elems > i32::MAX as usize || n_rows > i32::MAX as usize {
5996            return Err("strided row placement exceeds CUDA kernel geometry".into());
5997        }
5998        let f = self.func("place_rows_strided_f32");
5999        let cfg = LaunchConfig {
6000            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
6001            block_dim: (256, 1, 1),
6002            shared_mem_bytes: 0,
6003        };
6004        let (re, nr) = (row_elems as i32, n_rows as i32);
6005        let (st, off) = (dst_stride as i64, dst_off as i64);
6006        let __s_b = self.gpu.stream();
6007        let mut b = __s_b.launch_builder(&f);
6008        b.arg(src)
6009            .arg(&mut *dst)
6010            .arg(&re)
6011            .arg(&nr)
6012            .arg(&st)
6013            .arg(&off);
6014        unsafe {
6015            b.launch(cfg)?;
6016        }
6017        Ok(())
6018    }
6019
6020    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
6021    pub fn u32_set_k(
6022        &self,
6023        dst: &mut CudaSlice<u32>,
6024        v: u32,
6025        idx: usize,
6026    ) -> Result<(), Box<dyn std::error::Error>> {
6027        let f = self.func("u32_set_k");
6028        let cfg = LaunchConfig {
6029            grid_dim: (1, 1, 1),
6030            block_dim: (1, 1, 1),
6031            shared_mem_bytes: 0,
6032        };
6033        let ii = idx as i32;
6034        let __s_b = self.gpu.stream();
6035        let mut b = __s_b.launch_builder(&f);
6036        b.arg(dst).arg(&v).arg(&ii);
6037        unsafe {
6038            b.launch(cfg)?;
6039        }
6040        Ok(())
6041    }
6042
6043    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
6044    pub fn i32_add_k(
6045        &self,
6046        d: &mut CudaSlice<i32>,
6047        v: i32,
6048    ) -> Result<(), Box<dyn std::error::Error>> {
6049        let f = self.func("i32_add_k");
6050        let cfg = LaunchConfig {
6051            grid_dim: (1, 1, 1),
6052            block_dim: (32, 1, 1),
6053            shared_mem_bytes: 0,
6054        };
6055        let __s_b = self.gpu.stream();
6056        let mut b = __s_b.launch_builder(&f);
6057        b.arg(d).arg(&v);
6058        unsafe {
6059            b.launch(cfg)?;
6060        }
6061        Ok(())
6062    }
6063
6064    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
6065    pub fn i32_iota_from(
6066        &self,
6067        ctr: &CudaSlice<i32>,
6068        dst: &mut CudaSlice<i32>,
6069        n: usize,
6070    ) -> Result<(), Box<dyn std::error::Error>> {
6071        let f = self.func("i32_iota_from");
6072        let cfg = LaunchConfig::for_num_elems(n as u32);
6073        let ni = n as i32;
6074        let __s_b = self.gpu.stream();
6075        let mut b = __s_b.launch_builder(&f);
6076        b.arg(ctr).arg(dst).arg(&ni);
6077        unsafe {
6078            b.launch(cfg)?;
6079        }
6080        Ok(())
6081    }
6082
6083    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
6084    pub fn u32_map_k(
6085        &self,
6086        buf: &mut CudaSlice<u32>,
6087        map: &CudaSlice<u32>,
6088        idx: usize,
6089    ) -> Result<(), Box<dyn std::error::Error>> {
6090        let f = self.func("u32_map_k");
6091        let cfg = LaunchConfig {
6092            grid_dim: (1, 1, 1),
6093            block_dim: (1, 1, 1),
6094            shared_mem_bytes: 0,
6095        };
6096        let ii = idx as i32;
6097        let __s_b = self.gpu.stream();
6098        let mut b = __s_b.launch_builder(&f);
6099        b.arg(buf).arg(map).arg(&ii);
6100        unsafe {
6101            b.launch(cfg)?;
6102        }
6103        Ok(())
6104    }
6105
6106    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
6107    #[allow(clippy::too_many_arguments)]
6108    pub fn u32_pack2(
6109        &self,
6110        a: &CudaSlice<u32>,
6111        off_a: usize,
6112        n1: usize,
6113        b_in: &CudaSlice<u32>,
6114        n2: usize,
6115        out: &mut CudaSlice<u32>,
6116    ) -> Result<(), Box<dyn std::error::Error>> {
6117        let f = self.func("u32_pack2");
6118        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
6119        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
6120        let __s_b = self.gpu.stream();
6121        let mut b = __s_b.launch_builder(&f);
6122        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
6123        unsafe {
6124            b.launch(cfg)?;
6125        }
6126        Ok(())
6127    }
6128
6129    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
6130    pub fn moe_w_exscale(
6131        &self,
6132        w: &mut CudaSlice<f32>,
6133        sel: &CudaSlice<i32>,
6134        s: &CudaSlice<f32>,
6135        n: usize,
6136    ) -> Result<(), Box<dyn std::error::Error>> {
6137        let f = self.func("moe_w_exscale");
6138        let cfg = LaunchConfig::for_num_elems(n as u32);
6139        let ni = n as i32;
6140        let __s_b = self.gpu.stream();
6141        let mut b = __s_b.launch_builder(&f);
6142        b.arg(w).arg(sel).arg(s).arg(&ni);
6143        unsafe {
6144            b.launch(cfg)?;
6145        }
6146        Ok(())
6147    }
6148
6149    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
6150    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
6151    pub fn moe_w_scale_by_expert(
6152        &self,
6153        w: &mut CudaSlice<f32>,
6154        sel: &CudaSlice<i32>,
6155        macros: &CudaSlice<f32>,
6156        n_expert: usize,
6157        n: usize,
6158    ) -> Result<(), Box<dyn std::error::Error>> {
6159        let f = self.func("moe_w_scale_by_expert");
6160        let cfg = LaunchConfig {
6161            grid_dim: (n.div_ceil(64) as u32, 1, 1),
6162            block_dim: (64, 1, 1),
6163            shared_mem_bytes: 0,
6164        };
6165        let (ne, nn) = (n_expert as i32, n as i32);
6166        let __s_b = self.gpu.stream();
6167        let mut b = __s_b.launch_builder(&f);
6168        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
6169        unsafe {
6170            b.launch(cfg)?;
6171        }
6172        Ok(())
6173    }
6174
6175    pub fn moe_gate_up_silu8_dev_q8(
6176        &self,
6177        table: &CudaSlice<u64>,
6178        sel: &cudarc::driver::CudaView<i32>,
6179        aq: &CudaSlice<i8>,
6180        ad: &CudaSlice<f32>,
6181        in_f: usize,
6182        n_ff: usize,
6183        n_used: usize,
6184        n_expert: usize,
6185        qt_g: i32,
6186        qt_u: i32,
6187        rb_g: usize,
6188        rb_u: usize,
6189        macros: &CudaSlice<f32>,
6190    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6191        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
6192        let (mode, wpb) = GU.get_or_init(|| {
6193            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
6194            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
6195                .ok()
6196                .and_then(|v| v.parse().ok())
6197                .unwrap_or(4u32)
6198                .clamp(1, 16);
6199            (mode, wpb)
6200        });
6201        let (mode, wpb) = (mode.as_str(), *wpb);
6202        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
6203        let (inf, nff, ne, rbg, rbu) = (
6204            in_f as i32,
6205            n_ff as i32,
6206            n_expert as i32,
6207            rb_g as i64,
6208            rb_u as i64,
6209        );
6210        let (f, cfg) = match mode {
6211            "1" | "2" | "4" => {
6212                let rpw: u32 = mode.parse().unwrap();
6213                let f = self.func(match rpw {
6214                    1 => "moe_gate_up_silu8_dev_q8_r1",
6215                    2 => "moe_gate_up_silu8_dev_q8_r2",
6216                    _ => "moe_gate_up_silu8_dev_q8_r4",
6217                });
6218                let rows_per_block = (rpw * wpb) as usize;
6219                let gx = n_ff.div_ceil(rows_per_block) as u32;
6220                (
6221                    f,
6222                    LaunchConfig {
6223                        grid_dim: (gx, n_used as u32, 1),
6224                        block_dim: (32, wpb, 1),
6225                        shared_mem_bytes: 0,
6226                    },
6227                )
6228            }
6229            "j8" if n_used <= 32 => (
6230                self.func("moe_gate_up_silu8_dev_q8_j8"),
6231                LaunchConfig {
6232                    grid_dim: (n_ff as u32, 1, 1),
6233                    block_dim: (32, n_used as u32, 1),
6234                    shared_mem_bytes: 0,
6235                },
6236            ),
6237            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
6238            "vsm2" => {
6239                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
6240                let sh = (rb_g + rb_u) as u32;
6241                use cudarc::driver::sys::CUfunction_attribute_enum as A;
6242                f.set_attribute(
6243                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
6244                    sh as i32,
6245                )?;
6246                (
6247                    f,
6248                    LaunchConfig {
6249                        grid_dim: (n_ff as u32, n_used as u32, 1),
6250                        block_dim: (32, 1, 1),
6251                        shared_mem_bytes: sh,
6252                    },
6253                )
6254            }
6255            "vsm" => {
6256                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
6257                let sh = (rb_g + rb_u) as u32;
6258                use cudarc::driver::sys::CUfunction_attribute_enum as A;
6259                f.set_attribute(
6260                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
6261                    sh as i32,
6262                )?;
6263                (
6264                    f,
6265                    LaunchConfig {
6266                        grid_dim: (n_ff as u32, n_used as u32, 1),
6267                        block_dim: (32, 1, 1),
6268                        shared_mem_bytes: sh,
6269                    },
6270                )
6271            }
6272            "sg" => (
6273                self.func("moe_gate_up_silu8_dev_q8_sg"),
6274                LaunchConfig {
6275                    grid_dim: (n_ff as u32, n_used as u32, 1),
6276                    block_dim: (32, 1, 1),
6277                    shared_mem_bytes: 0,
6278                },
6279            ),
6280            "j8sg" if n_used <= 32 => (
6281                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
6282                LaunchConfig {
6283                    grid_dim: (n_ff as u32, 1, 1),
6284                    block_dim: (32, n_used as u32, 1),
6285                    shared_mem_bytes: 0,
6286                },
6287            ),
6288            "u64" if in_f == 2048 => (
6289                self.func("moe_gate_up_silu8_dev_q8_u64"),
6290                LaunchConfig {
6291                    grid_dim: (n_ff as u32, n_used as u32, 1),
6292                    block_dim: (32, 1, 1),
6293                    shared_mem_bytes: 0,
6294                },
6295            ),
6296            "gs4" if in_f == 2048 => (
6297                self.func("moe_gate_up_silu8_dev_q8_gs4"),
6298                LaunchConfig {
6299                    grid_dim: (n_ff as u32, n_used as u32, 1),
6300                    block_dim: (32, 4, 1),
6301                    shared_mem_bytes: 0,
6302                },
6303            ),
6304            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
6305            "v" | "" => (
6306                self.func("moe_gate_up_silu8_dev_q8_v"),
6307                LaunchConfig {
6308                    grid_dim: (n_ff as u32, n_used as u32, 1),
6309                    block_dim: (32, 1, 1),
6310                    shared_mem_bytes: 0,
6311                },
6312            ),
6313            "s2" => (
6314                self.func("moe_gate_up_silu8_dev_q8_s2"),
6315                LaunchConfig {
6316                    grid_dim: (n_ff as u32, n_used as u32, 1),
6317                    block_dim: (32, 2, 1),
6318                    shared_mem_bytes: 0,
6319                },
6320            ),
6321            "s2z" => {
6322                let rz = wpb.min(16); // s2z smem tile is [16][2]
6323                (
6324                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
6325                    LaunchConfig {
6326                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
6327                        block_dim: (32, 2, rz),
6328                        shared_mem_bytes: 0,
6329                    },
6330                )
6331            }
6332            _ => (
6333                self.func("moe_gate_up_silu8_dev_q8"),
6334                LaunchConfig {
6335                    grid_dim: (n_ff as u32, n_used as u32, 1),
6336                    block_dim: (32, 1, 1),
6337                    shared_mem_bytes: 0,
6338                },
6339            ),
6340        };
6341        let __s_b = self.gpu.stream();
6342        let mut b = __s_b.launch_builder(&f);
6343        b.arg(table)
6344            .arg(sel)
6345            .arg(aq)
6346            .arg(ad)
6347            .arg(&mut act)
6348            .arg(&inf)
6349            .arg(&nff)
6350            .arg(&ne)
6351            .arg(&qt_g)
6352            .arg(&qt_u)
6353            .arg(&rbg)
6354            .arg(&rbu)
6355            .arg(macros);
6356        unsafe {
6357            b.launch(cfg)?;
6358        }
6359        Ok(act)
6360    }
6361
6362    #[allow(clippy::too_many_arguments)]
6363    pub fn moe_down8_fma_dev_q8(
6364        &self,
6365        table: &CudaSlice<u64>,
6366        sel: &cudarc::driver::CudaView<i32>,
6367        w: &cudarc::driver::CudaView<f32>,
6368        aq2: &CudaSlice<i8>,
6369        ad2: &CudaSlice<f32>,
6370        dst: &mut cudarc::driver::CudaViewMut<f32>,
6371        in_f: usize,
6372        out_f: usize,
6373        n_used: usize,
6374        n_expert: usize,
6375        qt: i32,
6376        rb: usize,
6377    ) -> Result<(), Box<dyn std::error::Error>> {
6378        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
6379        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
6380        let (inf, outf, nu, ne, rbi) = (
6381            in_f as i32,
6382            out_f as i32,
6383            n_used as i32,
6384            n_expert as i32,
6385            rb as i64,
6386        );
6387        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
6388        // the h2 twins are nsb==16 (in_f==512) shape-gated.
6389        let (f, cfg) = match mode.as_str() {
6390            m @ ("1" | "2" | "4") if n_used <= 8 => {
6391                let rpw: usize = m.parse().unwrap();
6392                let f = self.func(match rpw {
6393                    1 => "moe_down8_fma_dev_q8_w8r1",
6394                    2 => "moe_down8_fma_dev_q8_w8r2",
6395                    _ => "moe_down8_fma_dev_q8_w8r4",
6396                });
6397                (
6398                    f,
6399                    LaunchConfig {
6400                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
6401                        block_dim: (32, n_used as u32, 1),
6402                        shared_mem_bytes: 0,
6403                    },
6404                )
6405            }
6406            "h2" if in_f == 512 => (
6407                self.func("moe_down8_fma_dev_q8_h2"),
6408                LaunchConfig {
6409                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6410                    block_dim: (32, 1, 1),
6411                    shared_mem_bytes: 0,
6412                },
6413            ),
6414            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
6415            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
6416            "" if in_f == 704 && n_used <= 8 => (
6417                self.func("moe_down8_fma_dev_q8_w8r2"),
6418                LaunchConfig {
6419                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6420                    block_dim: (32, n_used as u32, 1),
6421                    shared_mem_bytes: 0,
6422                },
6423            ),
6424            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
6425            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
6426            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
6427            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
6428                self.func("moe_down8_fma_dev_q8_w8h2v"),
6429                LaunchConfig {
6430                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6431                    block_dim: (32, n_used as u32, 1),
6432                    shared_mem_bytes: 0,
6433                },
6434            ),
6435            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
6436                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
6437                LaunchConfig {
6438                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
6439                    block_dim: (32, n_used as u32, 1),
6440                    shared_mem_bytes: 0,
6441                },
6442            ),
6443            "w8h2r2" if in_f == 512 && n_used <= 8 => (
6444                self.func("moe_down8_fma_dev_q8_w8h2r2"),
6445                LaunchConfig {
6446                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
6447                    block_dim: (32, n_used as u32, 1),
6448                    shared_mem_bytes: 0,
6449                },
6450            ),
6451            "w8h2" if in_f == 512 && n_used <= 8 => (
6452                self.func("moe_down8_fma_dev_q8_w8h2"),
6453                LaunchConfig {
6454                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6455                    block_dim: (32, n_used as u32, 1),
6456                    shared_mem_bytes: 0,
6457                },
6458            ),
6459            _ => (
6460                self.func("moe_down8_fma_dev_q8"),
6461                LaunchConfig {
6462                    grid_dim: (out_f as u32, 1, 1),
6463                    block_dim: (32, 1, 1),
6464                    shared_mem_bytes: 0,
6465                },
6466            ),
6467        };
6468        let __s_b = self.gpu.stream();
6469        let mut b = __s_b.launch_builder(&f);
6470        b.arg(table)
6471            .arg(sel)
6472            .arg(w)
6473            .arg(aq2)
6474            .arg(ad2)
6475            .arg(dst)
6476            .arg(&inf)
6477            .arg(&outf)
6478            .arg(&nu)
6479            .arg(&ne)
6480            .arg(&qt)
6481            .arg(&rbi);
6482        unsafe {
6483            b.launch(cfg)?;
6484        }
6485        Ok(())
6486    }
6487
6488    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
6489    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
6490    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
6491    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
6492    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
6493    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
6494    #[allow(clippy::too_many_arguments)]
6495    pub fn moe_gate_up_silu8_dev_q8_rows(
6496        &self,
6497        table: &CudaSlice<u64>,
6498        sel: &CudaSlice<i32>,
6499        aq: &CudaSlice<i8>,
6500        ad: &CudaSlice<f32>,
6501        t: usize,
6502        in_f: usize,
6503        n_ff: usize,
6504        n_used: usize,
6505        n_expert: usize,
6506        qt_g: i32,
6507        qt_u: i32,
6508        rb_g: usize,
6509        rb_u: usize,
6510        macros: &CudaSlice<f32>,
6511    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6512        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
6513        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
6514        let cfg = LaunchConfig {
6515            grid_dim: (n_ff as u32, n_used as u32, t as u32),
6516            block_dim: (32, 1, 1),
6517            shared_mem_bytes: 0,
6518        };
6519        let (inf, nff, ne, nu, rbg, rbu) = (
6520            in_f as i32,
6521            n_ff as i32,
6522            n_expert as i32,
6523            n_used as i32,
6524            rb_g as i64,
6525            rb_u as i64,
6526        );
6527        let __s_b = self.gpu.stream();
6528        let mut b = __s_b.launch_builder(&f);
6529        b.arg(table)
6530            .arg(sel)
6531            .arg(aq)
6532            .arg(ad)
6533            .arg(&mut act)
6534            .arg(&inf)
6535            .arg(&nff)
6536            .arg(&ne)
6537            .arg(&qt_g)
6538            .arg(&qt_u)
6539            .arg(&rbg)
6540            .arg(&rbu)
6541            .arg(&nu)
6542            .arg(macros);
6543        unsafe {
6544            b.launch(cfg)?;
6545        }
6546        Ok(act)
6547    }
6548
6549    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
6550    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
6551    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
6552    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
6553    #[allow(clippy::too_many_arguments)]
6554    pub fn moe_down8_fma_dev_q8_rows(
6555        &self,
6556        table: &CudaSlice<u64>,
6557        sel: &CudaSlice<i32>,
6558        w: &CudaSlice<f32>,
6559        aq2: &CudaSlice<i8>,
6560        ad2: &CudaSlice<f32>,
6561        dst: &mut CudaSlice<f32>,
6562        t: usize,
6563        in_f: usize,
6564        out_f: usize,
6565        n_used: usize,
6566        n_expert: usize,
6567        qt: i32,
6568        rb: usize,
6569    ) -> Result<(), Box<dyn std::error::Error>> {
6570        assert!(
6571            in_f == 512 && n_used <= 8,
6572            "down rows twin is w8h2v shape-gated"
6573        );
6574        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
6575        let cfg = LaunchConfig {
6576            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
6577            block_dim: (32, n_used as u32, 1),
6578            shared_mem_bytes: 0,
6579        };
6580        let (inf, outf, nu, ne, rbi) = (
6581            in_f as i32,
6582            out_f as i32,
6583            n_used as i32,
6584            n_expert as i32,
6585            rb as i64,
6586        );
6587        let __s_b = self.gpu.stream();
6588        let mut b = __s_b.launch_builder(&f);
6589        b.arg(table)
6590            .arg(sel)
6591            .arg(w)
6592            .arg(aq2)
6593            .arg(ad2)
6594            .arg(dst)
6595            .arg(&inf)
6596            .arg(&outf)
6597            .arg(&nu)
6598            .arg(&ne)
6599            .arg(&qt)
6600            .arg(&rbi);
6601        unsafe {
6602            b.launch(cfg)?;
6603        }
6604        Ok(())
6605    }
6606
6607    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
6608    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
6609    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
6610    #[allow(clippy::too_many_arguments)]
6611    pub fn moe_gate_up_silu8_dev_q8_csr(
6612        &self,
6613        table: &CudaSlice<u64>,
6614        sel: &CudaSlice<i32>,
6615        aq: &CudaSlice<i8>,
6616        ad: &CudaSlice<f32>,
6617        n_pairs: usize,
6618        in_f: usize,
6619        n_ff: usize,
6620        n_used: usize,
6621        n_expert: usize,
6622        qt_g: i32,
6623        qt_u: i32,
6624        rb_g: usize,
6625        rb_u: usize,
6626    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6627        // NVFP4 experts take the NVFP4-specialized owner-scan twin (lane/moebatch-q35moe);
6628        // host gate guarantees qt_g == qt_u within a supported class.
6629        let f = if qt_g == crate::QT_NVFP4 {
6630            self.func("moe_gate_up_silu8_dev_q8_csr_nvfp4")
6631        } else {
6632            self.func("moe_gate_up_silu8_dev_q8_csr_iq4")
6633        };
6634        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
6635        let cfg = LaunchConfig {
6636            grid_dim: (n_ff as u32, n_pairs as u32, 1),
6637            block_dim: (32, 1, 1),
6638            shared_mem_bytes: 0,
6639        };
6640        let (inf, nff, ne, nu, npi, rbg, rbu) = (
6641            in_f as i32,
6642            n_ff as i32,
6643            n_expert as i32,
6644            n_used as i32,
6645            n_pairs as i32,
6646            rb_g as i64,
6647            rb_u as i64,
6648        );
6649        let __s_b = self.gpu.stream();
6650        let mut b = __s_b.launch_builder(&f);
6651        b.arg(table)
6652            .arg(sel)
6653            .arg(aq)
6654            .arg(ad)
6655            .arg(&mut act)
6656            .arg(&inf)
6657            .arg(&nff)
6658            .arg(&ne)
6659            .arg(&qt_g)
6660            .arg(&qt_u)
6661            .arg(&rbg)
6662            .arg(&rbu)
6663            .arg(&nu)
6664            .arg(&npi);
6665        unsafe {
6666            b.launch(cfg)?;
6667        }
6668        Ok(act)
6669    }
6670
6671    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
6672    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
6673    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
6674    #[allow(clippy::too_many_arguments)]
6675    pub fn moe_down8_fma_dev_q8_variant(
6676        &self,
6677        variant: &str,
6678        table: &CudaSlice<u64>,
6679        sel: &cudarc::driver::CudaView<i32>,
6680        w: &cudarc::driver::CudaView<f32>,
6681        aq2: &CudaSlice<i8>,
6682        ad2: &CudaSlice<f32>,
6683        dst: &mut cudarc::driver::CudaViewMut<f32>,
6684        in_f: usize,
6685        out_f: usize,
6686        n_used: usize,
6687        n_expert: usize,
6688        qt: i32,
6689        rb: usize,
6690    ) -> Result<(), Box<dyn std::error::Error>> {
6691        let (inf, outf, nu, ne, rbi) = (
6692            in_f as i32,
6693            out_f as i32,
6694            n_used as i32,
6695            n_expert as i32,
6696            rb as i64,
6697        );
6698        let (f, cfg) = match variant {
6699            "w8h2" | "w8h2v" => (
6700                self.func(if variant == "w8h2" {
6701                    "moe_down8_fma_dev_q8_w8h2"
6702                } else {
6703                    "moe_down8_fma_dev_q8_w8h2v"
6704                }),
6705                LaunchConfig {
6706                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
6707                    block_dim: (32, n_used as u32, 1),
6708                    shared_mem_bytes: 0,
6709                },
6710            ),
6711            "w8h2r2" | "w8h2r2v" => (
6712                self.func(if variant == "w8h2r2" {
6713                    "moe_down8_fma_dev_q8_w8h2r2"
6714                } else {
6715                    "moe_down8_fma_dev_q8_w8h2r2v"
6716                }),
6717                LaunchConfig {
6718                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
6719                    block_dim: (32, n_used as u32, 1),
6720                    shared_mem_bytes: 0,
6721                },
6722            ),
6723            _ => (
6724                self.func("moe_down8_fma_dev_q8"),
6725                LaunchConfig {
6726                    grid_dim: (out_f as u32, 1, 1),
6727                    block_dim: (32, 1, 1),
6728                    shared_mem_bytes: 0,
6729                },
6730            ),
6731        };
6732        let __s_b = self.gpu.stream();
6733        let mut b = __s_b.launch_builder(&f);
6734        b.arg(table)
6735            .arg(sel)
6736            .arg(w)
6737            .arg(aq2)
6738            .arg(ad2)
6739            .arg(dst)
6740            .arg(&inf)
6741            .arg(&outf)
6742            .arg(&nu)
6743            .arg(&ne)
6744            .arg(&qt)
6745            .arg(&rbi);
6746        unsafe {
6747            b.launch(cfg)?;
6748        }
6749        Ok(())
6750    }
6751
6752    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
6753    #[allow(clippy::too_many_arguments)]
6754    pub fn moe_gate_up_silu8_dev_q8_variant(
6755        &self,
6756        variant: &str,
6757        table: &CudaSlice<u64>,
6758        sel: &cudarc::driver::CudaView<i32>,
6759        aq: &CudaSlice<i8>,
6760        ad: &CudaSlice<f32>,
6761        in_f: usize,
6762        n_ff: usize,
6763        n_used: usize,
6764        n_expert: usize,
6765        qt_g: i32,
6766        qt_u: i32,
6767        rb_g: usize,
6768        rb_u: usize,
6769    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6770        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
6771        let (inf, nff, ne, rbg, rbu) = (
6772            in_f as i32,
6773            n_ff as i32,
6774            n_expert as i32,
6775            rb_g as i64,
6776            rb_u as i64,
6777        );
6778        let f = self.func(if variant == "v" {
6779            "moe_gate_up_silu8_dev_q8_v"
6780        } else {
6781            "moe_gate_up_silu8_dev_q8"
6782        });
6783        let cfg = LaunchConfig {
6784            grid_dim: (n_ff as u32, n_used as u32, 1),
6785            block_dim: (32, 1, 1),
6786            shared_mem_bytes: 0,
6787        };
6788        let __s_b = self.gpu.stream();
6789        let mut b = __s_b.launch_builder(&f);
6790        b.arg(table)
6791            .arg(sel)
6792            .arg(aq)
6793            .arg(ad)
6794            .arg(&mut act)
6795            .arg(&inf)
6796            .arg(&nff)
6797            .arg(&ne)
6798            .arg(&qt_g)
6799            .arg(&qt_u)
6800            .arg(&rbg)
6801            .arg(&rbu);
6802        unsafe {
6803            b.launch(cfg)?;
6804        }
6805        Ok(act)
6806    }
6807
6808    pub fn moe_gate_up_silu8_dev(
6809        &self,
6810        table: &CudaSlice<u64>,
6811        sel: &cudarc::driver::CudaView<i32>,
6812        x: &cudarc::driver::CudaView<f32>,
6813        in_f: usize,
6814        n_ff: usize,
6815        n_used: usize,
6816        n_expert: usize,
6817        qt_g: i32,
6818        qt_u: i32,
6819        rb_g: usize,
6820        rb_u: usize,
6821        macros: &CudaSlice<f32>,
6822    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6823        let f = self.func("moe_gate_up_silu8_dev");
6824        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6825        let cfg = LaunchConfig {
6826            grid_dim: (n_ff as u32, n_used as u32, 1),
6827            block_dim: (256, 1, 1),
6828            shared_mem_bytes: 0,
6829        };
6830        let (inf, nff, ne, rbg, rbu) = (
6831            in_f as i32,
6832            n_ff as i32,
6833            n_expert as i32,
6834            rb_g as i64,
6835            rb_u as i64,
6836        );
6837        let __s_b = self.gpu.stream();
6838        let mut b = __s_b.launch_builder(&f);
6839        b.arg(table)
6840            .arg(sel)
6841            .arg(x)
6842            .arg(&mut act)
6843            .arg(&inf)
6844            .arg(&nff)
6845            .arg(&ne)
6846            .arg(&qt_g)
6847            .arg(&qt_u)
6848            .arg(&rbg)
6849            .arg(&rbu)
6850            .arg(macros);
6851        unsafe {
6852            b.launch(cfg)?;
6853        }
6854        Ok(act)
6855    }
6856
6857    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6858    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6859    #[allow(clippy::too_many_arguments)]
6860    pub fn moe_down8_fma_dev(
6861        &self,
6862        table: &CudaSlice<u64>,
6863        sel: &cudarc::driver::CudaView<i32>,
6864        w: &cudarc::driver::CudaView<f32>,
6865        act: &CudaSlice<f32>,
6866        dst: &mut cudarc::driver::CudaViewMut<f32>,
6867        in_f: usize,
6868        out_f: usize,
6869        n_used: usize,
6870        n_expert: usize,
6871        qt: i32,
6872        rb: usize,
6873    ) -> Result<(), Box<dyn std::error::Error>> {
6874        let f = self.func("moe_down8_fma_dev");
6875        let cfg = LaunchConfig {
6876            grid_dim: (out_f as u32, 1, 1),
6877            block_dim: (256, 1, 1),
6878            shared_mem_bytes: 0,
6879        };
6880        let (inf, outf, nu, ne, rbv) = (
6881            in_f as i32,
6882            out_f as i32,
6883            n_used as i32,
6884            n_expert as i32,
6885            rb as i64,
6886        );
6887        let __s_b = self.gpu.stream();
6888        let mut b = __s_b.launch_builder(&f);
6889        b.arg(table)
6890            .arg(sel)
6891            .arg(w)
6892            .arg(act)
6893            .arg(dst)
6894            .arg(&inf)
6895            .arg(&outf)
6896            .arg(&nu)
6897            .arg(&ne)
6898            .arg(&qt)
6899            .arg(&rbv);
6900        unsafe {
6901            b.launch(cfg)?;
6902        }
6903        Ok(())
6904    }
6905
6906    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6907    pub fn axpy_into(
6908        &self,
6909        src: &CudaSlice<f32>,
6910        alpha: f32,
6911        dst: &mut cudarc::driver::CudaViewMut<f32>,
6912        n: usize,
6913    ) -> Result<(), Box<dyn std::error::Error>> {
6914        let f = self.func("axpy_f32");
6915        let cfg = LaunchConfig::for_num_elems(n as u32);
6916        let (a, ni) = (alpha, n as i32);
6917        let __s_b = self.gpu.stream();
6918        let mut b = __s_b.launch_builder(&f);
6919        b.arg(src).arg(dst).arg(&a).arg(&ni);
6920        unsafe {
6921            b.launch(cfg)?;
6922        }
6923        Ok(())
6924    }
6925
6926    /// Host-oracle twin of `axpy_into` with separate RN multiply and add.
6927    pub fn axpy_host_into(
6928        &self,
6929        src: &cudarc::driver::CudaView<'_, f32>,
6930        alpha: f32,
6931        dst: &mut cudarc::driver::CudaViewMut<f32>,
6932        n: usize,
6933    ) -> Result<(), Box<dyn std::error::Error>> {
6934        let f = self.func("axpy_host_f32");
6935        let cfg = LaunchConfig::for_num_elems(n as u32);
6936        let (a, ni) = (alpha, n as i32);
6937        let __s_b = self.gpu.stream();
6938        let mut b = __s_b.launch_builder(&f);
6939        b.arg(src).arg(dst).arg(&a).arg(&ni);
6940        unsafe {
6941            b.launch(cfg)?;
6942        }
6943        Ok(())
6944    }
6945
6946    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6947    pub fn add_scaled_rows(
6948        &self,
6949        src: &CudaSlice<f32>,
6950        scale: &CudaSlice<f32>,
6951        dst: &mut CudaSlice<f32>,
6952        ncols: usize,
6953        nrows: usize,
6954    ) -> Result<(), Box<dyn std::error::Error>> {
6955        let f = self.func("add_scaled_rows_f32");
6956        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6957        let (nc, nr) = (ncols as i32, nrows as i32);
6958        let __s_b = self.gpu.stream();
6959        let mut b = __s_b.launch_builder(&f);
6960        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6961        unsafe {
6962            b.launch(cfg)?;
6963        }
6964        Ok(())
6965    }
6966
6967    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6968
6969    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6970    pub fn gather_rows(
6971        &self,
6972        src: &CudaSlice<f32>,
6973        idx: &CudaSlice<i32>,
6974        dst: &mut CudaSlice<f32>,
6975        ncols: usize,
6976        m_e: usize,
6977    ) -> Result<(), Box<dyn std::error::Error>> {
6978        let f = self.func("gather_rows_f32");
6979        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6980        let (nc, me) = (ncols as i32, m_e as i32);
6981        let __s_b = self.gpu.stream();
6982        let mut b = __s_b.launch_builder(&f);
6983        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6984        unsafe {
6985            b.launch(cfg)?;
6986        }
6987        Ok(())
6988    }
6989
6990    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6991    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6992    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6993    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6994    pub fn scatter_slot(
6995        &self,
6996        src: &CudaSlice<f32>,
6997        tok_idx: &CudaSlice<i32>,
6998        slot_idx: &CudaSlice<i32>,
6999        weight: &CudaSlice<f32>,
7000        dst: &mut CudaSlice<f32>,
7001        wbuf: &mut CudaSlice<f32>,
7002        ncols: usize,
7003        n_used: usize,
7004        m_e: usize,
7005    ) -> Result<(), Box<dyn std::error::Error>> {
7006        let f = self.func("scatter_add_slot_f32");
7007        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
7008        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
7009        let __s_b = self.gpu.stream();
7010        let mut b = __s_b.launch_builder(&f);
7011        b.arg(src)
7012            .arg(tok_idx)
7013            .arg(slot_idx)
7014            .arg(weight)
7015            .arg(dst)
7016            .arg(wbuf)
7017            .arg(&nc)
7018            .arg(&nu)
7019            .arg(&me);
7020        unsafe {
7021            b.launch(cfg)?;
7022        }
7023        Ok(())
7024    }
7025
7026    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
7027    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
7028    /// Uses FMA for bit-identity with the sequential axpy path.
7029    pub fn reduce_slots(
7030        &self,
7031        slots: &CudaSlice<f32>,
7032        wbuf: &CudaSlice<f32>,
7033        dst: &mut CudaSlice<f32>,
7034        ncols: usize,
7035        n_used: usize,
7036        t: usize,
7037    ) -> Result<(), Box<dyn std::error::Error>> {
7038        let f = self.func("reduce_slots_f32");
7039        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
7040        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
7041        let __s_b = self.gpu.stream();
7042        let mut b = __s_b.launch_builder(&f);
7043        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
7044        unsafe {
7045            b.launch(cfg)?;
7046        }
7047        Ok(())
7048    }
7049
7050    /// Canonical slot-order reduction with separately rounded multiply and add.
7051    ///
7052    /// This is the one-launch twin of repeated `axpy_host_into` calls. It preserves the official
7053    /// Step host-oracle arithmetic while allowing owner outputs to remain device-resident.
7054    pub fn reduce_slots_host(
7055        &self,
7056        slots: &CudaSlice<f32>,
7057        wbuf: &CudaSlice<f32>,
7058        dst: &mut CudaSlice<f32>,
7059        ncols: usize,
7060        n_used: usize,
7061        t: usize,
7062    ) -> Result<(), Box<dyn std::error::Error>> {
7063        let f = self.func("reduce_slots_host_f32");
7064        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
7065        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
7066        let __s_b = self.gpu.stream();
7067        let mut b = __s_b.launch_builder(&f);
7068        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
7069        unsafe {
7070            b.launch(cfg)?;
7071        }
7072        Ok(())
7073    }
7074
7075    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
7076    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
7077    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
7078    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
7079    /// GPU time, ~half of it redundant re-quantization of the same row.
7080    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
7081    pub fn quantize_q8_1_view(
7082        &self,
7083        x: &cudarc::driver::CudaView<f32>,
7084        m: usize,
7085        in_f: usize,
7086    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7087        let f = self.func("quantize_q8_1");
7088        let nblk = in_f / 32;
7089        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
7090        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
7091        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
7092        let (inf, mi) = (in_f as i32, m as i32);
7093        let __s_b = self.gpu.stream();
7094        let mut b = __s_b.launch_builder(&f);
7095        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
7096        unsafe {
7097            b.launch(cfg)?;
7098        }
7099        Ok((q, d))
7100    }
7101
7102    pub fn quantize_q8_1(
7103        &self,
7104        x: &CudaSlice<f32>,
7105        m: usize,
7106        in_f: usize,
7107    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7108        let nblk = in_f / 32;
7109        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
7110        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
7111        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
7112        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
7113        let (inf, mi) = (in_f as i32, m as i32);
7114        if Self::pdl_on() && Self::pdl_wb_on() {
7115            {
7116                use cudarc::driver::{DevicePtr, DevicePtrMut};
7117                let s = &self.gpu.stream();
7118                let (px, _g0) = x.device_ptr(s);
7119                let (pq, _g1) = q.device_ptr_mut(s);
7120                let (pd, _g2) = d.device_ptr_mut(s);
7121                let mut ps = [
7122                    &px as *const _ as *mut std::ffi::c_void,
7123                    &pq as *const _ as *mut _,
7124                    &pd as *const _ as *mut _,
7125                    &inf as *const _ as *mut _,
7126                    &mi as *const _ as *mut _,
7127                ];
7128                unsafe {
7129                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
7130                }
7131            }
7132            return Ok((q, d));
7133        }
7134        let f = self.func("quantize_q8_1");
7135        let __s_b = self.gpu.stream();
7136        let mut b = __s_b.launch_builder(&f);
7137        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
7138        unsafe {
7139            b.launch(cfg)?;
7140        }
7141        Ok((q, d))
7142    }
7143
7144    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
7145    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
7146    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
7147    pub fn quantize_fp4_act(
7148        &self,
7149        x: &CudaSlice<f32>,
7150        m: usize,
7151        in_f: usize,
7152    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
7153        let f = self.func("quantize_fp4_act");
7154        let nb16 = in_f / 16;
7155        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
7156        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
7157        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
7158        let (inf, mi) = (in_f as i32, m as i32);
7159        let __s_b = self.gpu.stream();
7160        let mut b = __s_b.launch_builder(&f);
7161        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
7162        unsafe {
7163            b.launch(cfg)?;
7164        }
7165        Ok((aq4, ad4))
7166    }
7167
7168    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
7169    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
7170    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
7171    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
7172    pub fn qmatvec_gemm_nvfp4_fp4(
7173        &self,
7174        bytes: &CudaSlice<u8>,
7175        x: &CudaSlice<f32>,
7176        m: usize,
7177        in_f: usize,
7178        out_f: usize,
7179        row_bytes: usize,
7180        scale: f32,
7181    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7182        assert!(
7183            in_f % 64 == 0,
7184            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
7185        );
7186        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
7187        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
7188        if scale != 1.0 {
7189            self.scale_inplace(&mut y, scale, m * out_f)?;
7190        }
7191        Ok(y)
7192    }
7193
7194    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
7195    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
7196    fn fp4_gemm_launch(
7197        &self,
7198        bytes: &CudaSlice<u8>,
7199        aq4: &CudaSlice<u32>,
7200        ad4: &CudaSlice<u8>,
7201        m: usize,
7202        in_f: usize,
7203        out_f: usize,
7204        row_bytes: usize,
7205    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7206        let f = self.func("qmatvec_gemm_nvfp4_fp4");
7207        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
7208        const BM: u32 = 64;
7209        const BN: u32 = 256;
7210        let cfg = LaunchConfig {
7211            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
7212            block_dim: (32, 4, 1),
7213            shared_mem_bytes: 0,
7214        };
7215        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7216        let __s_b = self.gpu.stream();
7217        let mut b = __s_b.launch_builder(&f);
7218        b.arg(bytes)
7219            .arg(aq4)
7220            .arg(ad4)
7221            .arg(&mut y)
7222            .arg(&inf)
7223            .arg(&outf)
7224            .arg(&mi)
7225            .arg(&rb);
7226        unsafe {
7227            b.launch(cfg)?;
7228        }
7229        Ok(y)
7230    }
7231
7232    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
7233    pub fn qmatvec_gemm_nvfp4_fp4_raw(
7234        &self,
7235        bytes: &CudaSlice<u8>,
7236        x: &CudaSlice<f32>,
7237        m: usize,
7238        in_f: usize,
7239        out_f: usize,
7240        row_bytes: usize,
7241    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7242        assert!(
7243            in_f % 64 == 0,
7244            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
7245        );
7246        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
7247        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
7248    }
7249
7250    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
7251    pub fn qmatvec_q8_0_fast(
7252        &self,
7253        w: &CudaSlice<u8>,
7254        x: &CudaSlice<f32>,
7255        m: usize,
7256        in_f: usize,
7257        out_f: usize,
7258        row_bytes: usize,
7259    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7260        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7261        let f = self.func("qmatvec_q8_0_dp4a");
7262        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
7263        let cfg = LaunchConfig {
7264            grid_dim: (out_f as u32, m as u32, 1),
7265            block_dim: (128, 1, 1),
7266            shared_mem_bytes: 0,
7267        };
7268        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7269        let __s_b = self.gpu.stream();
7270        let mut b = __s_b.launch_builder(&f);
7271        b.arg(w)
7272            .arg(&aq)
7273            .arg(&ad)
7274            .arg(&mut y)
7275            .arg(&inf)
7276            .arg(&outf)
7277            .arg(&mi)
7278            .arg(&rb);
7279        unsafe {
7280            b.launch(cfg)?;
7281        }
7282        Ok(y)
7283    }
7284
7285    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
7286    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
7287    pub fn qmatvec_q4_K_fast(
7288        &self,
7289        w: &CudaSlice<u8>,
7290        x: &CudaSlice<f32>,
7291        m: usize,
7292        in_f: usize,
7293        out_f: usize,
7294        row_bytes: usize,
7295    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7296        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7297        let f = self.func("qmatvec_q4_K_dp4a");
7298        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
7299        let cfg = LaunchConfig {
7300            grid_dim: (out_f as u32, m as u32, 1),
7301            block_dim: (128, 1, 1),
7302            shared_mem_bytes: 0,
7303        };
7304        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7305        let __s_b = self.gpu.stream();
7306        let mut b = __s_b.launch_builder(&f);
7307        b.arg(w)
7308            .arg(&aq)
7309            .arg(&ad)
7310            .arg(&mut y)
7311            .arg(&inf)
7312            .arg(&outf)
7313            .arg(&mi)
7314            .arg(&rb);
7315        unsafe {
7316            b.launch(cfg)?;
7317        }
7318        Ok(y)
7319    }
7320
7321    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
7322    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
7323    pub fn qmatvec_q6_K_fast(
7324        &self,
7325        w: &CudaSlice<u8>,
7326        x: &CudaSlice<f32>,
7327        m: usize,
7328        in_f: usize,
7329        out_f: usize,
7330        row_bytes: usize,
7331    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7332        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7333        let f = self.func("qmatvec_q6_K_dp4a");
7334        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
7335        let cfg = LaunchConfig {
7336            grid_dim: (out_f as u32, m as u32, 1),
7337            block_dim: (128, 1, 1),
7338            shared_mem_bytes: 0,
7339        };
7340        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7341        let __s_b = self.gpu.stream();
7342        let mut b = __s_b.launch_builder(&f);
7343        b.arg(w)
7344            .arg(&aq)
7345            .arg(&ad)
7346            .arg(&mut y)
7347            .arg(&inf)
7348            .arg(&outf)
7349            .arg(&mi)
7350            .arg(&rb);
7351        unsafe {
7352            b.launch(cfg)?;
7353        }
7354        Ok(y)
7355    }
7356
7357    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
7358    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
7359    pub fn qmatvec_q5_K_fast(
7360        &self,
7361        w: &CudaSlice<u8>,
7362        x: &CudaSlice<f32>,
7363        m: usize,
7364        in_f: usize,
7365        out_f: usize,
7366        row_bytes: usize,
7367    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7368        self.qmatvec_dp4a_named(
7369            "qmatvec_q5_K_dp4a",
7370            &w.slice(0..w.len()),
7371            x,
7372            m,
7373            in_f,
7374            out_f,
7375            row_bytes,
7376        )
7377    }
7378    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
7379    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
7380    pub fn qmatvec_q3_K_fast(
7381        &self,
7382        w: &CudaSlice<u8>,
7383        x: &CudaSlice<f32>,
7384        m: usize,
7385        in_f: usize,
7386        out_f: usize,
7387        row_bytes: usize,
7388    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7389        self.qmatvec_dp4a_named(
7390            "qmatvec_q3_K_dp4a",
7391            &w.slice(0..w.len()),
7392            x,
7393            m,
7394            in_f,
7395            out_f,
7396            row_bytes,
7397        )
7398    }
7399    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
7400    pub fn qmatvec_nvfp4_fast_rp(
7401        &self,
7402        w: &CudaSlice<u8>,
7403        x: &CudaSlice<f32>,
7404        m: usize,
7405        in_f: usize,
7406        out_f: usize,
7407        row_bytes: usize,
7408    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7409        assert!(
7410            in_f % 64 == 0,
7411            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
7412        );
7413        self.qmatvec_dp4a_named(
7414            "qmatvec_nvfp4_dp4a_rp",
7415            &w.slice(0..w.len()),
7416            x,
7417            m,
7418            in_f,
7419            out_f,
7420            row_bytes,
7421        )
7422    }
7423    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
7424    pub fn qmatvec_nvfp4_fast(
7425        &self,
7426        w: &cudarc::driver::CudaView<'_, u8>,
7427        x: &CudaSlice<f32>,
7428        m: usize,
7429        in_f: usize,
7430        out_f: usize,
7431        row_bytes: usize,
7432    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7433        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
7434        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
7435        assert!(
7436            in_f % 64 == 0,
7437            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
7438        );
7439        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
7440    }
7441    /// v2-layout twin of `qmatvec_nvfp4_fast` for the slot-major expert banks
7442    /// (MEMRA_NVFP4_BANK_V2) — bit-identical per row, coalesced reads.
7443    pub fn qmatvec_nvfp4_fast_v2(
7444        &self,
7445        w: &cudarc::driver::CudaView<'_, u8>,
7446        x: &CudaSlice<f32>,
7447        m: usize,
7448        in_f: usize,
7449        out_f: usize,
7450        row_bytes: usize,
7451    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7452        assert!(
7453            in_f % 64 == 0,
7454            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
7455        );
7456        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_v2", w, x, m, in_f, out_f, row_bytes)
7457    }
7458    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
7459    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
7460    pub fn qmatvec_iq4_XS_fast(
7461        &self,
7462        w: &CudaSlice<u8>,
7463        x: &CudaSlice<f32>,
7464        m: usize,
7465        in_f: usize,
7466        out_f: usize,
7467        row_bytes: usize,
7468    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7469        self.qmatvec_dp4a_named(
7470            "qmatvec_iq4_XS_dp4a",
7471            &w.slice(0..w.len()),
7472            x,
7473            m,
7474            in_f,
7475            out_f,
7476            row_bytes,
7477        )
7478    }
7479
7480    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
7481    fn qmatvec_dp4a_named(
7482        &self,
7483        name: &str,
7484        w: &cudarc::driver::CudaView<'_, u8>,
7485        x: &CudaSlice<f32>,
7486        m: usize,
7487        in_f: usize,
7488        out_f: usize,
7489        row_bytes: usize,
7490    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7491        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
7492        let f = self.func(name);
7493        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
7494        let cfg = LaunchConfig {
7495            grid_dim: (out_f as u32, m as u32, 1),
7496            block_dim: (128, 1, 1),
7497            shared_mem_bytes: 0,
7498        };
7499        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7500        let __s_b = self.gpu.stream();
7501        let mut b = __s_b.launch_builder(&f);
7502        b.arg(w)
7503            .arg(&aq)
7504            .arg(&ad)
7505            .arg(&mut y)
7506            .arg(&inf)
7507            .arg(&outf)
7508            .arg(&mi)
7509            .arg(&rb);
7510        unsafe {
7511            b.launch(cfg)?;
7512        }
7513        Ok(y)
7514    }
7515
7516    /// NVFP4 dp4a matvec over PRE-QUANTIZED q8_1 activations, writing a caller-provided output.
7517    /// Same kernel and math as `qmatvec_nvfp4_fast` (which quantizes internally and allocates
7518    /// its output); this entry exists so a routed-expert program can quantize one activation
7519    /// ONCE and reuse it across every expert's gate/up, feed `silu_mul_scaled_q8_1`'s q8_1
7520    /// straight into down, and keep persistent output workspaces — zero per-expert allocations.
7521    #[allow(clippy::too_many_arguments)]
7522    pub fn qmatvec_nvfp4_fast_prequant_into(
7523        &self,
7524        w: &CudaSlice<u8>,
7525        aq: &CudaSlice<i8>,
7526        ad: &CudaSlice<f32>,
7527        y: &mut CudaSlice<f32>,
7528        m: usize,
7529        in_f: usize,
7530        out_f: usize,
7531        row_bytes: usize,
7532    ) -> Result<(), Box<dyn std::error::Error>> {
7533        assert!(
7534            in_f % 64 == 0,
7535            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
7536        );
7537        if y.len() < m * out_f {
7538            return Err(format!(
7539                "NVFP4 prequant output {} is shorter than {m}x{out_f}",
7540                y.len()
7541            )
7542            .into());
7543        }
7544        let f = self.func("qmatvec_nvfp4_dp4a");
7545        let cfg = LaunchConfig {
7546            grid_dim: (out_f as u32, m as u32, 1),
7547            block_dim: (128, 1, 1),
7548            shared_mem_bytes: 0,
7549        };
7550        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
7551        let __s_b = self.gpu.stream();
7552        let mut b = __s_b.launch_builder(&f);
7553        b.arg(w)
7554            .arg(aq)
7555            .arg(ad)
7556            .arg(y)
7557            .arg(&inf)
7558            .arg(&outf)
7559            .arg(&mi)
7560            .arg(&rb);
7561        unsafe {
7562            b.launch(cfg)?;
7563        }
7564        Ok(())
7565    }
7566
7567    /// Fused QKV F32 matvec (one launch for all three rank-local projections; see the kernel
7568    /// doc for the numeric-class note). Requires `in_f % 4 == 0`.
7569    #[allow(clippy::too_many_arguments)]
7570    pub fn matvec_f32_qkv_into(
7571        &self,
7572        wq: &CudaSlice<f32>,
7573        wk: &CudaSlice<f32>,
7574        wv: &CudaSlice<f32>,
7575        wg: &CudaSlice<f32>,
7576        x: &CudaSlice<f32>,
7577        yq: &mut CudaSlice<f32>,
7578        yk: &mut CudaSlice<f32>,
7579        yv: &mut CudaSlice<f32>,
7580        yg: &mut CudaSlice<f32>,
7581        in_f: usize,
7582        out_q: usize,
7583        out_kv: usize,
7584        out_g: usize,
7585    ) -> Result<(), Box<dyn std::error::Error>> {
7586        if in_f % 4 != 0
7587            || wq.len() != out_q * in_f
7588            || wk.len() != out_kv * in_f
7589            || wv.len() != out_kv * in_f
7590            || wg.len() < out_g * in_f
7591            || x.len() < in_f
7592            || yq.len() < out_q
7593            || yk.len() < out_kv
7594            || yv.len() < out_kv
7595            || (out_g > 0 && yg.len() < out_g)
7596        {
7597            return Err(format!(
7598                "fused QKV geometry in={in_f} out_q={out_q} out_kv={out_kv} out_g={out_g} \
7599                 wq={} wk={} wv={} wg={}",
7600                wq.len(),
7601                wk.len(),
7602                wv.len(),
7603                wg.len()
7604            )
7605            .into());
7606        }
7607        let f = self.func("matvec_f32_qkv");
7608        let cfg = LaunchConfig {
7609            grid_dim: ((out_q + 2 * out_kv + out_g) as u32, 1, 1),
7610            block_dim: (128, 1, 1),
7611            shared_mem_bytes: 0,
7612        };
7613        let (inf, oq, okv, og) = (in_f as i32, out_q as i32, out_kv as i32, out_g as i32);
7614        let __s_b = self.gpu.stream();
7615        let mut b = __s_b.launch_builder(&f);
7616        b.arg(wq)
7617            .arg(wk)
7618            .arg(wv)
7619            .arg(wg)
7620            .arg(x)
7621            .arg(yq)
7622            .arg(yk)
7623            .arg(yv)
7624            .arg(yg)
7625            .arg(&inf)
7626            .arg(&oq)
7627            .arg(&okv)
7628            .arg(&og);
7629        unsafe {
7630            b.launch(cfg)?;
7631        }
7632        Ok(())
7633    }
7634
7635    /// Selected-experts batched twin of `qmatvec_nvfp4_fast_prequant_into`: one launch covers
7636    /// every selected expert, weights indexed `sel[t] * expert_stride` into a contiguous
7637    /// per-rank bank, activations advancing `act_row_stride`/`ad_row_stride` elements per
7638    /// selection (0 for a shared input). Per (expert, row) bit-identical to the per-expert
7639    /// kernel — the batching only removes host launch latency.
7640    #[allow(clippy::too_many_arguments)]
7641    /// FUSION #2a: gate+up sweeps in one launch (v2 banks only; identical geometry both
7642    /// banks, caller-guarded). Per-row bit-identical to two qmatvec_nvfp4_sel_into calls.
7643    #[allow(clippy::too_many_arguments)]
7644    pub fn qmatvec_nvfp4_sel_gu_into(
7645        &self,
7646        gate_bank: &CudaSlice<u8>,
7647        up_bank: &CudaSlice<u8>,
7648        sel: &CudaSlice<i32>,
7649        aq: &CudaSlice<i8>,
7650        ad: &CudaSlice<f32>,
7651        yg: &mut CudaSlice<f32>,
7652        yu: &mut CudaSlice<f32>,
7653        n_sel: usize,
7654        in_f: usize,
7655        out_f: usize,
7656        row_bytes: usize,
7657        expert_stride: usize,
7658    ) -> Result<(), Box<dyn std::error::Error>> {
7659        assert!(in_f % 64 == 0, "NVFP4 dp4a requires in_f % 64 == 0");
7660        if yg.len() < n_sel * out_f || yu.len() < n_sel * out_f || sel.len() < n_sel {
7661            return Err("NVFP4 gu sel geometry".into());
7662        }
7663        // MEMRA_SEL_GU_RPW=2|4: multirow twin (activation group read once, reused across
7664        // RPW rows' gate+up dots) — bit-identical per row, one block per RPW rows.
7665        static RPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
7666        let rpw = *RPW.get_or_init(|| {
7667            std::env::var("MEMRA_SEL_GU_RPW")
7668                .ok()
7669                .and_then(|v| v.parse().ok())
7670                .filter(|r| *r == 2 || *r == 4)
7671                .unwrap_or(1)
7672        });
7673        let rpw = if out_f % rpw == 0 { rpw } else { 1 };
7674        // MEMRA_SEL_GU_WPR=1: warp-per-row (NUMERIC-CLASS — per-row reduction order changes;
7675        // acceptance is the argmax gate + battery, the QKV_FUSED/BF16_MMV class).
7676        static WPR: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7677        let wpr = *WPR.get_or_init(|| std::env::var("MEMRA_SEL_GU_WPR").as_deref() == Ok("1"));
7678        let f = self.func(match (wpr, rpw) {
7679            (true, _) => "qmatvec_nvfp4_dp4a_sel_v2_gu_wpr",
7680            (_, 4) => "qmatvec_nvfp4_dp4a_sel_v2_gu_r4",
7681            (_, 2) => "qmatvec_nvfp4_dp4a_sel_v2_gu_r2",
7682            _ => "qmatvec_nvfp4_dp4a_sel_v2_gu",
7683        });
7684        let cfg = LaunchConfig {
7685            grid_dim: if wpr {
7686                (((2 * out_f) as u32).div_ceil(4), n_sel as u32, 1)
7687            } else if rpw == 1 {
7688                ((2 * out_f) as u32, n_sel as u32, 1)
7689            } else {
7690                ((out_f / rpw) as u32, n_sel as u32, 1)
7691            },
7692            block_dim: if wpr { (32, 4, 1) } else { (128, 1, 1) },
7693            shared_mem_bytes: 0,
7694        };
7695        let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
7696        let (rb, es) = (row_bytes as i64, expert_stride as i64);
7697        let (ars, adrs) = (0i64, 0i64);
7698        let __s_b = self.gpu.stream();
7699        let mut b = __s_b.launch_builder(&f);
7700        b.arg(gate_bank)
7701            .arg(up_bank)
7702            .arg(sel)
7703            .arg(aq)
7704            .arg(ad)
7705            .arg(yg)
7706            .arg(yu)
7707            .arg(&inf)
7708            .arg(&outf)
7709            .arg(&ns)
7710            .arg(&rb)
7711            .arg(&es)
7712            .arg(&ars)
7713            .arg(&adrs);
7714        unsafe {
7715            b.launch(cfg)?;
7716        }
7717        Ok(())
7718    }
7719
7720    /// MEMRA_SEL_DOWN8=1: the DOWN sweep and the route-weight combine in ONE launch
7721    /// (`qmatvec_nvfp4_dp4a_sel_v2_down8`, the q8 `down8 w8` occupancy arm ported to the
7722    /// NVFP4 banks). Block = (32, n_sel): one warp per slot instead of one warp per
7723    /// (row, slot), and the n_sel x out_f partial buffer disappears. Bit-identical to
7724    /// `qmatvec_nvfp4_sel_into` + `axpy_rows_seq_md_into` — same dot program, same reduce
7725    /// tree, same slot-ordered chain. Requires the v2 banks and nsb <= 32 (the fit-block
7726    /// class the reduce identity is argued at).
7727    #[allow(clippy::too_many_arguments)]
7728    pub fn qmatvec_nvfp4_sel_down8_into(
7729        &self,
7730        bank: &CudaSlice<u8>,
7731        sel: &CudaSlice<i32>,
7732        aq: &CudaSlice<i8>,
7733        ad: &CudaSlice<f32>,
7734        route_w: &CudaSlice<f32>,
7735        md: &CudaSlice<f32>,
7736        dst: &mut CudaSlice<f32>,
7737        n_sel: usize,
7738        in_f: usize,
7739        out_f: usize,
7740        row_bytes: usize,
7741        expert_stride: usize,
7742        act_row_stride: usize,
7743        ad_row_stride: usize,
7744    ) -> Result<(), Box<dyn std::error::Error>> {
7745        if in_f % 64 != 0
7746            || n_sel == 0
7747            || n_sel > 8
7748            || (in_f >> 5) > 32
7749            || dst.len() < out_f
7750            || sel.len() < n_sel
7751            || route_w.len() < n_sel
7752        {
7753            return Err(format!(
7754                "NVFP4 sel down8 geometry in_f={in_f} out_f={out_f} n_sel={n_sel} dst={}",
7755                dst.len()
7756            )
7757            .into());
7758        }
7759        if !crate::tp::nvfp4_bank_v2_on() {
7760            return Err("NVFP4 sel down8 requires the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into());
7761        }
7762        let f = self.func("qmatvec_nvfp4_dp4a_sel_v2_down8");
7763        let cfg = LaunchConfig {
7764            grid_dim: (out_f as u32, 1, 1),
7765            block_dim: (32, n_sel as u32, 1),
7766            shared_mem_bytes: 0,
7767        };
7768        let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
7769        let (rb, es) = (row_bytes as i64, expert_stride as i64);
7770        let (ars, adrs) = (act_row_stride as i64, ad_row_stride as i64);
7771        let __s_b = self.gpu.stream();
7772        let mut b = __s_b.launch_builder(&f);
7773        b.arg(bank)
7774            .arg(sel)
7775            .arg(aq)
7776            .arg(ad)
7777            .arg(route_w)
7778            .arg(md)
7779            .arg(dst)
7780            .arg(&inf)
7781            .arg(&outf)
7782            .arg(&ns)
7783            .arg(&rb)
7784            .arg(&es)
7785            .arg(&ars)
7786            .arg(&adrs);
7787        unsafe {
7788            b.launch(cfg)?;
7789        }
7790        Ok(())
7791    }
7792
7793    /// T-ROW twin of the down8 fusion (spec verify + batched serving MoE): one block per
7794    /// (output row, token row) = the exact t=1 down8 program per token — bit-identical
7795    /// per row to its own down8/axpy pair at any t.
7796    #[allow(clippy::too_many_arguments)]
7797    pub fn qmatvec_nvfp4_sel_down8_rows_into(
7798        &self,
7799        bank: &CudaSlice<u8>,
7800        sel: &CudaSlice<i32>,
7801        aq: &CudaSlice<i8>,
7802        ad: &CudaSlice<f32>,
7803        route_w: &CudaSlice<f32>,
7804        md: &CudaSlice<f32>,
7805        dst: &mut CudaSlice<f32>,
7806        t: usize,
7807        n_sel_col: usize,
7808        in_f: usize,
7809        out_f: usize,
7810        row_bytes: usize,
7811        expert_stride: usize,
7812        act_row_stride: usize,
7813        ad_row_stride: usize,
7814    ) -> Result<(), Box<dyn std::error::Error>> {
7815        let n_sel = t * n_sel_col;
7816        if in_f % 64 != 0
7817            || n_sel_col == 0
7818            || n_sel_col > 8
7819            || t == 0
7820            || t > 64
7821            || (in_f >> 5) > 32
7822            || dst.len() < t * out_f
7823            || sel.len() < n_sel
7824            || route_w.len() < n_sel
7825        {
7826            return Err(format!(
7827                "NVFP4 sel down8 rows geometry in_f={in_f} out_f={out_f} t={t} dst={}",
7828                dst.len()
7829            )
7830            .into());
7831        }
7832        if !crate::tp::nvfp4_bank_v2_on() {
7833            return Err(
7834                "NVFP4 sel down8 rows requires the v2 banks (MEMRA_NVFP4_BANK_V2=1)".into(),
7835            );
7836        }
7837        let f = self.func("qmatvec_nvfp4_dp4a_sel_v2_down8_rows");
7838        let cfg = LaunchConfig {
7839            grid_dim: (out_f as u32, t as u32, 1),
7840            block_dim: (32, n_sel_col as u32, 1),
7841            shared_mem_bytes: 0,
7842        };
7843        let (inf, outf, nsc) = (in_f as i32, out_f as i32, n_sel_col as i32);
7844        let (rb, es) = (row_bytes as i64, expert_stride as i64);
7845        let (ars, adrs) = (act_row_stride as i64, ad_row_stride as i64);
7846        let __s_b = self.gpu.stream();
7847        let mut b = __s_b.launch_builder(&f);
7848        b.arg(bank)
7849            .arg(sel)
7850            .arg(aq)
7851            .arg(ad)
7852            .arg(route_w)
7853            .arg(md)
7854            .arg(dst)
7855            .arg(&inf)
7856            .arg(&outf)
7857            .arg(&nsc)
7858            .arg(&rb)
7859            .arg(&es)
7860            .arg(&ars)
7861            .arg(&adrs);
7862        unsafe {
7863            b.launch(cfg)?;
7864        }
7865        Ok(())
7866    }
7867
7868    /// EP2 owner-guarded gate+up sweep: full-width rows, pairs whose expert this rank
7869    /// does not own exit immediately. Per-pair dot == the _sel_v2 gu body.
7870    #[allow(clippy::too_many_arguments)]
7871    pub fn qmatvec_nvfp4_sel_gu_ep_into(
7872        &self,
7873        gate_bank: &CudaSlice<u8>,
7874        up_bank: &CudaSlice<u8>,
7875        sel: &CudaSlice<i32>,
7876        aq: &CudaSlice<i8>,
7877        ad: &CudaSlice<f32>,
7878        yg: &mut CudaSlice<f32>,
7879        yu: &mut CudaSlice<f32>,
7880        n_sel: usize,
7881        in_f: usize,
7882        out_f: usize,
7883        row_bytes: usize,
7884        expert_stride: usize,
7885        owner: usize,
7886    ) -> Result<(), Box<dyn std::error::Error>> {
7887        assert!(in_f % 64 == 0, "NVFP4 dp4a requires in_f % 64 == 0");
7888        if yg.len() < n_sel * out_f || yu.len() < n_sel * out_f || sel.len() < n_sel {
7889            return Err("NVFP4 gu ep geometry".into());
7890        }
7891        let f = self.func("qmatvec_nvfp4_dp4a_sel_v2_gu_ep");
7892        let cfg = LaunchConfig {
7893            grid_dim: ((2 * out_f) as u32, n_sel as u32, 1),
7894            block_dim: (128, 1, 1),
7895            shared_mem_bytes: 0,
7896        };
7897        let (inf, outf, ns, own) = (in_f as i32, out_f as i32, n_sel as i32, owner as i32);
7898        let (rb, es) = (row_bytes as i64, expert_stride as i64);
7899        let (ars, adrs) = (0i64, 0i64);
7900        let __s_b = self.gpu.stream();
7901        let mut b = __s_b.launch_builder(&f);
7902        b.arg(gate_bank)
7903            .arg(up_bank)
7904            .arg(sel)
7905            .arg(aq)
7906            .arg(ad)
7907            .arg(yg)
7908            .arg(yu)
7909            .arg(&inf)
7910            .arg(&outf)
7911            .arg(&ns)
7912            .arg(&rb)
7913            .arg(&es)
7914            .arg(&ars)
7915            .arg(&adrs)
7916            .arg(&own);
7917        unsafe {
7918            b.launch(cfg)?;
7919        }
7920        Ok(())
7921    }
7922
7923    /// EP2 owner-guarded SwiGLU (q8_1 emission), clamped or plain by `limit`.
7924    #[allow(clippy::too_many_arguments)]
7925    pub fn silu_mul_scaled_q8_1_sel_ep_into(
7926        &self,
7927        gate: &CudaSlice<f32>,
7928        up: &CudaSlice<f32>,
7929        gmac: &CudaSlice<f32>,
7930        umac: &CudaSlice<f32>,
7931        sel: &CudaSlice<i32>,
7932        limit: Option<f32>,
7933        out_q: &mut CudaSlice<i8>,
7934        out_d: &mut CudaSlice<f32>,
7935        n_per: usize,
7936        n_sel: usize,
7937        owner: usize,
7938    ) -> Result<(), Box<dyn std::error::Error>> {
7939        if n_per % 32 != 0 || out_q.len() < n_sel * n_per || out_d.len() < n_sel * n_per / 32 {
7940            return Err("NVFP4 silu ep geometry".into());
7941        }
7942        let f = self.func("silu_mul_scaled_q8_1_sel_ep");
7943        let warps = n_sel * n_per / 32;
7944        let cfg = LaunchConfig {
7945            grid_dim: ((warps as u32).div_ceil(4), 1, 1),
7946            block_dim: (128, 1, 1),
7947            shared_mem_bytes: 0,
7948        };
7949        let (np, ns, own) = (n_per as i32, n_sel as i32, owner as i32);
7950        let (lim, has) = match limit {
7951            Some(l) => (l, 1i32),
7952            None => (0.0f32, 0i32),
7953        };
7954        let __s_b = self.gpu.stream();
7955        let mut b = __s_b.launch_builder(&f);
7956        b.arg(gate)
7957            .arg(up)
7958            .arg(gmac)
7959            .arg(umac)
7960            .arg(sel)
7961            .arg(&lim)
7962            .arg(&has)
7963            .arg(out_q)
7964            .arg(out_d)
7965            .arg(&np)
7966            .arg(&ns)
7967            .arg(&own);
7968        unsafe {
7969            b.launch(cfg)?;
7970        }
7971        Ok(())
7972    }
7973
7974    /// EP2 owner-guarded down + owned-slot combine in one launch (block `(32, n_sel)`).
7975    #[allow(clippy::too_many_arguments)]
7976    pub fn qmatvec_nvfp4_sel_down8_ep_into(
7977        &self,
7978        bank: &CudaSlice<u8>,
7979        sel: &CudaSlice<i32>,
7980        aq: &CudaSlice<i8>,
7981        ad: &CudaSlice<f32>,
7982        route_w: &CudaSlice<f32>,
7983        md: &CudaSlice<f32>,
7984        dst: &mut CudaSlice<f32>,
7985        n_sel: usize,
7986        in_f: usize,
7987        out_f: usize,
7988        row_bytes: usize,
7989        expert_stride: usize,
7990        act_row_stride: usize,
7991        ad_row_stride: usize,
7992        owner: usize,
7993    ) -> Result<(), Box<dyn std::error::Error>> {
7994        if in_f % 64 != 0 || n_sel == 0 || n_sel > 8 || (in_f >> 5) > 64 || dst.len() < out_f {
7995            return Err("NVFP4 down8 ep geometry".into());
7996        }
7997        let f = self.func("qmatvec_nvfp4_dp4a_sel_v2_down8_ep");
7998        let cfg = LaunchConfig {
7999            grid_dim: (out_f as u32, 1, 1),
8000            block_dim: (32, n_sel as u32, 1),
8001            shared_mem_bytes: 0,
8002        };
8003        let (inf, outf, ns, own) = (in_f as i32, out_f as i32, n_sel as i32, owner as i32);
8004        let (rb, es) = (row_bytes as i64, expert_stride as i64);
8005        let (ars, adrs) = (act_row_stride as i64, ad_row_stride as i64);
8006        let __s_b = self.gpu.stream();
8007        let mut b = __s_b.launch_builder(&f);
8008        b.arg(bank)
8009            .arg(sel)
8010            .arg(aq)
8011            .arg(ad)
8012            .arg(route_w)
8013            .arg(md)
8014            .arg(dst)
8015            .arg(&inf)
8016            .arg(&outf)
8017            .arg(&ns)
8018            .arg(&rb)
8019            .arg(&es)
8020            .arg(&ars)
8021            .arg(&adrs)
8022            .arg(&own);
8023        unsafe {
8024            b.launch(cfg)?;
8025        }
8026        Ok(())
8027    }
8028
8029    pub fn qmatvec_nvfp4_sel_into(
8030        &self,
8031        bank: &CudaSlice<u8>,
8032        sel: &CudaSlice<i32>,
8033        aq: &CudaSlice<i8>,
8034        ad: &CudaSlice<f32>,
8035        y: &mut CudaSlice<f32>,
8036        n_sel: usize,
8037        in_f: usize,
8038        out_f: usize,
8039        row_bytes: usize,
8040        expert_stride: usize,
8041        act_row_stride: usize,
8042        ad_row_stride: usize,
8043    ) -> Result<(), Box<dyn std::error::Error>> {
8044        assert!(
8045            in_f % 64 == 0,
8046            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
8047        );
8048        if y.len() < n_sel * out_f || sel.len() < n_sel {
8049            return Err(format!(
8050                "NVFP4 sel output {} / sel {} shorter than {n_sel}x{out_f}",
8051                y.len(),
8052                sel.len()
8053            )
8054            .into());
8055        }
8056        // MEMRA_SEL_MR=1: 4-concurrent-row-groups twin — per row bit-identical (same 128-thread
8057        // striding + reduction). MEASURED SLOWER on the 188-SM card (40.8 vs 42.9 tok/s e2e,
8058        // 2026-08-21: 512-thread blocks trade occupancy for launch-tail savings and lose; the
8059        // sequential-rows variant was flat). Default stays the single-row form.
8060        // MEMRA_SEL_STREAM=1: 16-rows-per-block streaming twin with next-row register
8061        // prefetch (bit-identical per row; one group per thread, so in_f <= 4096 only).
8062        static MR: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
8063        let mode = *MR.get_or_init(|| {
8064            if crate::tp::nvfp4_bank_v2_on() {
8065                3
8066            } else if std::env::var("MEMRA_SEL_STREAM").as_deref() == Ok("1") {
8067                2
8068            } else if std::env::var("MEMRA_SEL_MR").as_deref() == Ok("1") {
8069                1
8070            } else {
8071                0
8072            }
8073        });
8074        let mode = if mode == 2 && in_f > 4096 { 0 } else { mode };
8075        // v2s streaming twin (MEMRA_SEL_V2S=1 on top of the v2 bank): 8 contiguous rows per
8076        // block with next-row int4 prefetch; needs 16B-aligned rows (gate/up 2304B yes, down
8077        // 360B no -> single-row v2) and one slot per thread (in_f <= 4096).
8078        static V2S: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8079        let v2s = mode == 3
8080            && *V2S.get_or_init(|| std::env::var("MEMRA_SEL_V2S").as_deref() == Ok("1"))
8081            && row_bytes % 16 == 0
8082            && in_f <= 4096;
8083        let f = match (mode, v2s) {
8084            (3, true) => self.func("qmatvec_nvfp4_dp4a_sel_v2s"),
8085            (3, false) => self.func("qmatvec_nvfp4_dp4a_sel_v2"),
8086            (2, _) => self.func("qmatvec_nvfp4_dp4a_sel_stream"),
8087            (1, _) => self.func("qmatvec_nvfp4_dp4a_sel_mr4"),
8088            _ => self.func("qmatvec_nvfp4_dp4a_sel"),
8089        };
8090        // Thread-fit block for narrow rows (the DOWN sweep: in_f=640 -> nsb=20 slots left
8091        // 108 of 128 threads idle AND thread-capped resident blocks). blockDim >= nsb keeps
8092        // thread g on slot g; the dropped threads contributed exact 0.0 partials to the
8093        // reduce, so the result bits are unchanged. Applies to the single-row forms only.
8094        let nsb = in_f >> 5;
8095        let fit_block: u32 = if (mode == 0 || mode == 3) && !v2s && nsb <= 32 {
8096            32
8097        } else if mode == 1 {
8098            512
8099        } else {
8100            128
8101        };
8102        let cfg = LaunchConfig {
8103            grid_dim: (
8104                if v2s {
8105                    (out_f as u32).div_ceil(8)
8106                } else {
8107                    match mode {
8108                        2 => (out_f as u32).div_ceil(16),
8109                        1 => (out_f as u32).div_ceil(4),
8110                        _ => out_f as u32,
8111                    }
8112                },
8113                n_sel as u32,
8114                1,
8115            ),
8116            block_dim: (fit_block, 1, 1),
8117            shared_mem_bytes: 0,
8118        };
8119        let (inf, outf, ns) = (in_f as i32, out_f as i32, n_sel as i32);
8120        let (rb, es, ars, adrs) = (
8121            row_bytes as i64,
8122            expert_stride as i64,
8123            act_row_stride as i64,
8124            ad_row_stride as i64,
8125        );
8126        let __s_b = self.gpu.stream();
8127        let mut b = __s_b.launch_builder(&f);
8128        b.arg(bank)
8129            .arg(sel)
8130            .arg(aq)
8131            .arg(ad)
8132            .arg(y)
8133            .arg(&inf)
8134            .arg(&outf)
8135            .arg(&ns)
8136            .arg(&rb)
8137            .arg(&es)
8138            .arg(&ars)
8139            .arg(&adrs);
8140        unsafe {
8141            b.launch(cfg)?;
8142        }
8143        Ok(())
8144    }
8145
8146    /// Selected-experts batched twin of `silu_mul_scaled_q8_1`: [n_sel, n_per] rows, macros
8147    /// from device arrays indexed via sel. Per expert row bit-identical to the scalar kernel.
8148    /// `limit` = the step35 routed SwiGLU clamp (min(silu, limit) * clamp(up, +-limit)); None
8149    /// takes the plain SiLU kernel.
8150    #[allow(clippy::too_many_arguments)]
8151    pub fn silu_mul_scaled_q8_1_sel_into(
8152        &self,
8153        gate: &CudaSlice<f32>,
8154        up: &CudaSlice<f32>,
8155        gmac: &CudaSlice<f32>,
8156        umac: &CudaSlice<f32>,
8157        sel: &CudaSlice<i32>,
8158        limit: Option<f32>,
8159        out_q: &mut CudaSlice<i8>,
8160        out_d: &mut CudaSlice<f32>,
8161        n_per: usize,
8162        n_sel: usize,
8163    ) -> Result<(), Box<dyn std::error::Error>> {
8164        let n = n_per * n_sel;
8165        if n_per % 32 != 0 || out_q.len() < n || out_d.len() < n / 32 {
8166            return Err(format!(
8167                "silu sel geometry n_per={n_per} n_sel={n_sel} q={} d={}",
8168                out_q.len(),
8169                out_d.len()
8170            )
8171            .into());
8172        }
8173        if let Some(limit) = limit {
8174            if limit <= 1e-6 {
8175                return Err(format!(
8176                    "silu sel clamp limit {limit} is at or below the 1e-6 eps gate"
8177                )
8178                .into());
8179            }
8180            let f = self.func("silu_mul_scaled_q8_1_sel_clamp");
8181            let cfg = LaunchConfig::for_num_elems(n as u32);
8182            let (np, ns) = (n_per as i32, n_sel as i32);
8183            let __s_b = self.gpu.stream();
8184            let mut b = __s_b.launch_builder(&f);
8185            b.arg(gate)
8186                .arg(up)
8187                .arg(gmac)
8188                .arg(umac)
8189                .arg(sel)
8190                .arg(&limit)
8191                .arg(out_q)
8192                .arg(out_d)
8193                .arg(&np)
8194                .arg(&ns);
8195            unsafe {
8196                b.launch(cfg)?;
8197            }
8198            return Ok(());
8199        }
8200        let f = self.func("silu_mul_scaled_q8_1_sel");
8201        let cfg = LaunchConfig::for_num_elems(n as u32);
8202        let (np, ns) = (n_per as i32, n_sel as i32);
8203        let __s_b = self.gpu.stream();
8204        let mut b = __s_b.launch_builder(&f);
8205        b.arg(gate)
8206            .arg(up)
8207            .arg(gmac)
8208            .arg(umac)
8209            .arg(sel)
8210            .arg(out_q)
8211            .arg(out_d)
8212            .arg(&np)
8213            .arg(&ns);
8214        unsafe {
8215            b.launch(cfg)?;
8216        }
8217        Ok(())
8218    }
8219
8220    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8221        Ok(self.gpu.stream().clone_htod(v)?)
8222    }
8223    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
8224        Ok(self.gpu.stream().clone_htod(v)?)
8225    }
8226    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
8227    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
8228        Ok(self.gpu.stream().clone_htod(v)?)
8229    }
8230    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
8231        Ok(self.gpu.stream().clone_htod(v)?)
8232    }
8233    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
8234    pub fn dtoh_view(
8235        &self,
8236        d: &cudarc::driver::CudaView<f32>,
8237    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8238        let v = self.gpu.stream().clone_dtoh(d)?;
8239        self.gpu.stream().synchronize()?;
8240        Ok(v)
8241    }
8242    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
8243        let v = self.gpu.stream().clone_dtoh(d)?;
8244        self.gpu.stream().synchronize()?;
8245        Ok(v)
8246    }
8247    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
8248    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
8249    /// issuing them together avoids a second stream synchronization in every trunk layer.
8250    pub fn dtoh_pair(
8251        &self,
8252        a: &CudaSlice<f32>,
8253        b: &CudaSlice<f32>,
8254    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
8255        let av = self.gpu.stream().clone_dtoh(a)?;
8256        let bv = self.gpu.stream().clone_dtoh(b)?;
8257        self.gpu.stream().synchronize()?;
8258        Ok((av, bv))
8259    }
8260    /// View-scoped twin of `dtoh_pair` for reusable capacity buffers whose inactive tail must not
8261    /// cross a shape-sensitive host boundary.
8262    pub fn dtoh_pair_views(
8263        &self,
8264        a: &cudarc::driver::CudaView<f32>,
8265        b: &cudarc::driver::CudaView<f32>,
8266    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
8267        let av = self.gpu.stream().clone_dtoh(a)?;
8268        let bv = self.gpu.stream().clone_dtoh(b)?;
8269        self.gpu.stream().synchronize()?;
8270        Ok((av, bv))
8271    }
8272    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
8273    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
8274        let v = self.gpu.stream().clone_dtoh(d)?;
8275        self.gpu.stream().synchronize()?;
8276        Ok(v)
8277    }
8278    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
8279    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
8280        let v = self.gpu.stream().clone_dtoh(d)?;
8281        self.gpu.stream().synchronize()?;
8282        Ok(v)
8283    }
8284    pub fn dtoh_u8_view(
8285        &self,
8286        d: &cudarc::driver::CudaView<u8>,
8287    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
8288        let v = self.gpu.stream().clone_dtoh(d)?;
8289        self.gpu.stream().synchronize()?;
8290        Ok(v)
8291    }
8292    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8293        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
8294        self.keep_if_capturing(&s);
8295        Ok(s)
8296    }
8297
8298    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
8299    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
8300    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
8301    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
8302    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
8303    /// back (or kept resident for graph replay). Returns the device token buffer.
8304    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
8305    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
8306    pub fn prob_of_token_device(
8307        &self,
8308        logits: &CudaSlice<f32>,
8309        tok: &CudaSlice<u32>,
8310        n_vocab: usize,
8311    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8312        let nb = ARGMAX_NB;
8313        let mut part = self.alloc_uninit::<f32>(nb)?;
8314        let mut p = self.alloc_uninit::<f32>(1)?;
8315        let f1 = self.func("prob_of_token_partial_f32");
8316        let cfg1 = LaunchConfig {
8317            grid_dim: (nb as u32, 1, 1),
8318            block_dim: (256, 1, 1),
8319            shared_mem_bytes: 0,
8320        };
8321        let nv = n_vocab as i32;
8322        let __s_b1 = self.gpu.stream();
8323        let mut b1 = __s_b1.launch_builder(&f1);
8324        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
8325        unsafe {
8326            b1.launch(cfg1)?;
8327        }
8328        let f2 = self.func("prob_of_token_final_f32");
8329        let cfg2 = LaunchConfig {
8330            grid_dim: (1, 1, 1),
8331            block_dim: (256, 1, 1),
8332            shared_mem_bytes: 0,
8333        };
8334        let nbi = nb as i32;
8335        let __s_b2 = self.gpu.stream();
8336        let mut b2 = __s_b2.launch_builder(&f2);
8337        b2.arg(&part).arg(&mut p).arg(&nbi);
8338        unsafe {
8339            b2.launch(cfg2)?;
8340        }
8341        Ok(p)
8342    }
8343
8344    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
8345    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
8346    /// where the host reads the p-min confidence between replays. Same kernels, same math.
8347    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
8348    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
8349    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
8350    pub fn prob_of_token_device_col(
8351        &self,
8352        logits: &CudaSlice<f32>,
8353        tok_all: &CudaSlice<u32>,
8354        tok_idx: usize,
8355        p_out: &mut CudaSlice<f32>,
8356        p_idx: usize,
8357        n_vocab: usize,
8358    ) -> Result<(), Box<dyn std::error::Error>> {
8359        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
8360        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
8361        let nb = ARGMAX_NB;
8362        let mut part = self.alloc_uninit::<f32>(nb)?;
8363        let f1 = self.func("prob_of_token_partial_f32");
8364        let cfg1 = LaunchConfig {
8365            grid_dim: (nb as u32, 1, 1),
8366            block_dim: (256, 1, 1),
8367            shared_mem_bytes: 0,
8368        };
8369        let nv = n_vocab as i32;
8370        let __s_b1 = self.gpu.stream();
8371        let mut b1 = __s_b1.launch_builder(&f1);
8372        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
8373        unsafe {
8374            b1.launch(cfg1)?;
8375        }
8376        let f2 = self.func("prob_of_token_final_f32");
8377        let cfg2 = LaunchConfig {
8378            grid_dim: (1, 1, 1),
8379            block_dim: (256, 1, 1),
8380            shared_mem_bytes: 0,
8381        };
8382        let nbi = nb as i32;
8383        let __s_b2 = self.gpu.stream();
8384        let mut b2 = __s_b2.launch_builder(&f2);
8385        b2.arg(&part).arg(&mut p_v).arg(&nbi);
8386        unsafe {
8387            b2.launch(cfg2)?;
8388        }
8389        Ok(())
8390    }
8391
8392    pub fn prob_of_token_device_into(
8393        &self,
8394        logits: &CudaSlice<f32>,
8395        tok: &CudaSlice<u32>,
8396        p_out: &mut CudaSlice<f32>,
8397        n_vocab: usize,
8398    ) -> Result<(), Box<dyn std::error::Error>> {
8399        let nb = ARGMAX_NB;
8400        let mut part = self.alloc_uninit::<f32>(nb)?;
8401        let f1 = self.func("prob_of_token_partial_f32");
8402        let cfg1 = LaunchConfig {
8403            grid_dim: (nb as u32, 1, 1),
8404            block_dim: (256, 1, 1),
8405            shared_mem_bytes: 0,
8406        };
8407        let nv = n_vocab as i32;
8408        let __s_b1 = self.gpu.stream();
8409        let mut b1 = __s_b1.launch_builder(&f1);
8410        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
8411        unsafe {
8412            b1.launch(cfg1)?;
8413        }
8414        let f2 = self.func("prob_of_token_final_f32");
8415        let cfg2 = LaunchConfig {
8416            grid_dim: (1, 1, 1),
8417            block_dim: (256, 1, 1),
8418            shared_mem_bytes: 0,
8419        };
8420        let nbi = nb as i32;
8421        let __s_b2 = self.gpu.stream();
8422        let mut b2 = __s_b2.launch_builder(&f2);
8423        b2.arg(&part).arg(p_out).arg(&nbi);
8424        unsafe {
8425            b2.launch(cfg2)?;
8426        }
8427        Ok(())
8428    }
8429
8430    /// Token-graph chunk loop: hist[idx] = *tok; idx += 1 — device-indexed history append
8431    /// (graph-constant params, device-varying index). Capture-safe.
8432    pub fn u32_hist_append(
8433        &self,
8434        tok: &CudaSlice<u32>,
8435        hist: &mut CudaSlice<u32>,
8436        idx: &mut CudaSlice<i32>,
8437    ) -> Result<(), Box<dyn std::error::Error>> {
8438        let f = self.func("u32_hist_append");
8439        let cfg = LaunchConfig {
8440            grid_dim: (1, 1, 1),
8441            block_dim: (32, 1, 1),
8442            shared_mem_bytes: 0,
8443        };
8444        let __s_b = self.gpu.stream();
8445        let mut b = __s_b.launch_builder(&f);
8446        b.arg(tok).arg(&mut *hist).arg(&mut *idx);
8447        unsafe {
8448            b.launch(cfg)?;
8449        }
8450        Ok(())
8451    }
8452
8453    pub fn argmax_token_device(
8454        &self,
8455        logits: &CudaSlice<f32>,
8456        n_vocab: usize,
8457    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
8458        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
8459        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
8460        Ok(tok)
8461    }
8462    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
8463    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
8464    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
8465    /// pointer is baked once and the token id never round-trips to host inside steady state. The
8466    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
8467    /// captured passes bake fixed addresses.
8468    pub fn argmax_token_device_into(
8469        &self,
8470        logits: &CudaSlice<f32>,
8471        tok: &mut CudaSlice<u32>,
8472        n_vocab: usize,
8473    ) -> Result<(), Box<dyn std::error::Error>> {
8474        let nb = ARGMAX_NB;
8475        let f1 = self.func("argmax_partial_f32");
8476        let f2 = self.func("argmax_final_f32");
8477        let mut guard = self.argmax_partials.lock().unwrap();
8478        if guard.is_none() {
8479            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
8480            // buffers carry no cudarc events (illegal inside capture).
8481            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
8482            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
8483            *guard = Some((pv, pi));
8484        }
8485        let (part_v, part_i) = guard.as_mut().unwrap();
8486        let nv = n_vocab as i32;
8487        let nbi = nb as i32;
8488        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
8489        let cfg1 = LaunchConfig {
8490            grid_dim: (nb as u32, 1, 1),
8491            block_dim: (256, 1, 1),
8492            shared_mem_bytes: 0,
8493        };
8494        let __s_b1 = self.gpu.stream();
8495        let mut b1 = __s_b1.launch_builder(&f1);
8496        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
8497        unsafe {
8498            b1.launch(cfg1)?;
8499        }
8500        // pass 2: one block reduces NB partials -> token_out[0].
8501        let cfg2 = LaunchConfig {
8502            grid_dim: (1, 1, 1),
8503            block_dim: (256, 1, 1),
8504            shared_mem_bytes: 0,
8505        };
8506        let __s_b2 = self.gpu.stream();
8507        let mut b2 = __s_b2.launch_builder(&f2);
8508        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
8509        unsafe {
8510            b2.launch(cfg2)?;
8511        }
8512        Ok(())
8513    }
8514    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
8515    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
8516    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
8517    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
8518    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
8519    pub fn argmax_token_device_col(
8520        &self,
8521        logits: &CudaSlice<f32>,
8522        col: usize,
8523        n_vocab: usize,
8524        toks: &mut CudaSlice<u32>,
8525        out_idx: usize,
8526    ) -> Result<(), Box<dyn std::error::Error>> {
8527        let nb = ARGMAX_NB;
8528        let f1 = self.func("argmax_partial_f32");
8529        let f2 = self.func("argmax_final_f32");
8530        let mut guard = self.argmax_partials.lock().unwrap();
8531        if guard.is_none() {
8532            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
8533            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
8534            *guard = Some((pv, pi));
8535        }
8536        let (part_v, part_i) = guard.as_mut().unwrap();
8537        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
8538        let nv = n_vocab as i32;
8539        let nbi = nb as i32;
8540        let cfg1 = LaunchConfig {
8541            grid_dim: (nb as u32, 1, 1),
8542            block_dim: (256, 1, 1),
8543            shared_mem_bytes: 0,
8544        };
8545        let __s_b1 = self.gpu.stream();
8546        let mut b1 = __s_b1.launch_builder(&f1);
8547        b1.arg(&col_view)
8548            .arg(&mut *part_v)
8549            .arg(&mut *part_i)
8550            .arg(&nv);
8551        unsafe {
8552            b1.launch(cfg1)?;
8553        }
8554        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
8555        let cfg2 = LaunchConfig {
8556            grid_dim: (1, 1, 1),
8557            block_dim: (256, 1, 1),
8558            shared_mem_bytes: 0,
8559        };
8560        let __s_b2 = self.gpu.stream();
8561        let mut b2 = __s_b2.launch_builder(&f2);
8562        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
8563        unsafe {
8564            b2.launch(cfg2)?;
8565        }
8566        Ok(())
8567    }
8568    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
8569    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
8570        Ok(self.gpu.stream().clone_htod(v)?)
8571    }
8572    pub fn dtoh_u64(&self, d: &CudaSlice<u64>) -> Result<Vec<u64>, Box<dyn std::error::Error>> {
8573        let v = self.gpu.stream().clone_dtoh(d)?;
8574        self.gpu.stream().synchronize()?;
8575        Ok(v)
8576    }
8577
8578    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
8579        let v = self.gpu.stream().clone_dtoh(d)?;
8580        self.gpu.stream().synchronize()?;
8581        Ok(v)
8582    }
8583    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
8584    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
8585    /// contents change every step, the address must not, so a captured graph can read it).
8586    pub fn htod_u32_into(
8587        &self,
8588        dst: &mut CudaSlice<u32>,
8589        src: &[u32],
8590    ) -> Result<(), Box<dyn std::error::Error>> {
8591        let mut view = dst.slice_mut(0..src.len());
8592        self.gpu.stream().memcpy_htod(src, &mut view)?;
8593        Ok(())
8594    }
8595
8596    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
8597    /// table without changing the device address its reconcile kernel consumes.
8598    pub fn htod_i32_into(
8599        &self,
8600        dst: &mut CudaSlice<i32>,
8601        src: &[i32],
8602    ) -> Result<(), Box<dyn std::error::Error>> {
8603        let mut view = dst.slice_mut(0..src.len());
8604        self.gpu.stream().memcpy_htod(src, &mut view)?;
8605        Ok(())
8606    }
8607
8608    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
8609        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
8610        self.keep_if_capturing(&s);
8611        Ok(s)
8612    }
8613    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
8614    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
8615    pub fn embed_gather_device_into(
8616        &self,
8617        embd: &CudaSlice<u8>,
8618        token_d: &CudaSlice<u32>,
8619        x_out: &mut CudaSlice<f32>,
8620        n_embd: usize,
8621        qtype: i32,
8622        row_bytes: usize,
8623    ) -> Result<(), Box<dyn std::error::Error>> {
8624        let f = self.func("embed_gather_u32");
8625        let cfg = LaunchConfig {
8626            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
8627            block_dim: (256, 1, 1),
8628            shared_mem_bytes: 0,
8629        };
8630        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
8631        let __s_b = self.gpu.stream();
8632        let mut b = __s_b.launch_builder(&f);
8633        b.arg(embd)
8634            .arg(token_d)
8635            .arg(x_out)
8636            .arg(&ne)
8637            .arg(&qt)
8638            .arg(&rb);
8639        unsafe {
8640            b.launch(cfg)?;
8641        }
8642        Ok(())
8643    }
8644    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
8645    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
8646        let v = self.gpu.stream().clone_dtoh(d)?;
8647        self.gpu.stream().synchronize()?;
8648        Ok(v[0])
8649    }
8650    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
8651    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
8652    /// the counter value after the throwaway capture warmups corrupt it.
8653    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
8654    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
8655    /// copy (fine at stream-idle boundaries, poison mid-round).
8656    pub fn i32_set_k(
8657        &self,
8658        dst: &mut CudaSlice<i32>,
8659        v: i32,
8660    ) -> Result<(), Box<dyn std::error::Error>> {
8661        let f = self.func("i32_set_k");
8662        let cfg = LaunchConfig {
8663            grid_dim: (1, 1, 1),
8664            block_dim: (1, 1, 1),
8665            shared_mem_bytes: 0,
8666        };
8667        let idx = 0i32;
8668        let __s_b = self.gpu.stream();
8669        let mut b = __s_b.launch_builder(&f);
8670        b.arg(dst).arg(&v).arg(&idx);
8671        unsafe {
8672            b.launch(cfg)?;
8673        }
8674        Ok(())
8675    }
8676
8677    pub fn set_i32_one(
8678        &self,
8679        d: &mut CudaSlice<i32>,
8680        v: i32,
8681    ) -> Result<(), Box<dyn std::error::Error>> {
8682        self.gpu.stream().memcpy_htod(&[v], d)?;
8683        Ok(())
8684    }
8685    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
8686    /// during priming / capture-state restore.
8687    pub fn set_u32_one(
8688        &self,
8689        d: &mut CudaSlice<u32>,
8690        v: u32,
8691    ) -> Result<(), Box<dyn std::error::Error>> {
8692        self.gpu.stream().memcpy_htod(&[v], d)?;
8693        Ok(())
8694    }
8695    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
8696    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
8697        let v = self.gpu.stream().clone_dtoh(d)?;
8698        self.gpu.stream().synchronize()?;
8699        Ok(v[0])
8700    }
8701    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
8702    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
8703        Ok(self.gpu.stream().clone_htod(bytes)?)
8704    }
8705    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
8706    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
8707    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
8708    pub fn embed_gather_device(
8709        &self,
8710        embd: &CudaSlice<u8>,
8711        token_d: &CudaSlice<u32>,
8712        n_embd: usize,
8713        qtype: i32,
8714        row_bytes: usize,
8715    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8716        let f = self.func("embed_gather_u32");
8717        let mut x = self.alloc_uninit::<f32>(n_embd)?;
8718        let cfg = LaunchConfig {
8719            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
8720            block_dim: (256, 1, 1),
8721            shared_mem_bytes: 0,
8722        };
8723        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
8724        let __s_b = self.gpu.stream();
8725        let mut b = __s_b.launch_builder(&f);
8726        b.arg(embd)
8727            .arg(token_d)
8728            .arg(&mut x)
8729            .arg(&ne)
8730            .arg(&qt)
8731            .arg(&rb);
8732        unsafe {
8733            b.launch(cfg)?;
8734        }
8735        Ok(x)
8736    }
8737
8738    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
8739    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
8740    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
8741    pub fn embed_gather_device_t(
8742        &self,
8743        embd: &CudaSlice<u8>,
8744        tokens: &[u32],
8745        n_embd: usize,
8746        qtype: i32,
8747        row_bytes: usize,
8748    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8749        let t = tokens.len();
8750        let tok_d = self.gpu.stream().clone_htod(tokens)?;
8751        let f = self.func("embed_gather_u32_t");
8752        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
8753        let cfg = LaunchConfig {
8754            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
8755            block_dim: (256, 1, 1),
8756            shared_mem_bytes: 0,
8757        };
8758        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
8759        let __s_b = self.gpu.stream();
8760        let mut b = __s_b.launch_builder(&f);
8761        b.arg(embd)
8762            .arg(&tok_d)
8763            .arg(&mut x)
8764            .arg(&ne)
8765            .arg(&qt)
8766            .arg(&rb)
8767            .arg(&ti);
8768        unsafe {
8769            b.launch(cfg)?;
8770        }
8771        Ok(x)
8772    }
8773
8774    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
8775    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
8776    /// as embed_gather_device_t — bit-identical rows.
8777    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
8778    pub fn embed_gather_device_tv(
8779        &self,
8780        embd: &CudaSlice<u8>,
8781        tok_v: &cudarc::driver::CudaView<u32>,
8782        t: usize,
8783        n_embd: usize,
8784        qtype: i32,
8785        row_bytes: usize,
8786    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8787        let f = self.func("embed_gather_u32_t");
8788        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
8789        let cfg = LaunchConfig {
8790            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
8791            block_dim: (256, 1, 1),
8792            shared_mem_bytes: 0,
8793        };
8794        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
8795        let __s_b = self.gpu.stream();
8796        let mut b = __s_b.launch_builder(&f);
8797        b.arg(embd)
8798            .arg(tok_v)
8799            .arg(&mut x)
8800            .arg(&ne)
8801            .arg(&qt)
8802            .arg(&rb)
8803            .arg(&ti);
8804        unsafe {
8805            b.launch(cfg)?;
8806        }
8807        Ok(x)
8808    }
8809
8810    pub fn embed_gather_device_td(
8811        &self,
8812        embd: &CudaSlice<u8>,
8813        tok_d: &CudaSlice<u32>,
8814        t: usize,
8815        n_embd: usize,
8816        qtype: i32,
8817        row_bytes: usize,
8818    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8819        let f = self.func("embed_gather_u32_t");
8820        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
8821        let cfg = LaunchConfig {
8822            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
8823            block_dim: (256, 1, 1),
8824            shared_mem_bytes: 0,
8825        };
8826        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
8827        let __s_b = self.gpu.stream();
8828        let mut b = __s_b.launch_builder(&f);
8829        b.arg(embd)
8830            .arg(tok_d)
8831            .arg(&mut x)
8832            .arg(&ne)
8833            .arg(&qt)
8834            .arg(&rb)
8835            .arg(&ti);
8836        unsafe {
8837            b.launch(cfg)?;
8838        }
8839        Ok(x)
8840    }
8841
8842    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
8843    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
8844    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
8845    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
8846    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
8847    #[inline]
8848    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
8849    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
8850        if self
8851            .capture_keep_on
8852            .load(std::sync::atomic::Ordering::Relaxed)
8853        {
8854            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
8855        }
8856    }
8857
8858    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
8859        &self,
8860        n: usize,
8861    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
8862        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
8863        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
8864        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
8865        // not cover engine-internal buffers). Debug-only: massive launch overhead.
8866        {
8867            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8868            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
8869                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
8870                use cudarc::driver::DevicePtrMut;
8871                let n_bytes = s.len() * std::mem::size_of::<T>();
8872                let stream = self.gpu.stream();
8873                let (p_, _g) = s.device_ptr_mut(&stream);
8874                unsafe {
8875                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
8876                        .result()?;
8877                }
8878            }
8879        }
8880        self.keep_if_capturing(&s);
8881        Ok(s)
8882    }
8883
8884    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
8885    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
8886    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
8887    /// consumers alloc through this (m=1 decode arms).
8888    pub fn uninit_q8_pair(
8889        &self,
8890        n: usize,
8891    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8892        Ok((
8893            self.alloc_uninit::<i8>(n)?,
8894            self.alloc_uninit::<f32>(n / 32)?,
8895        ))
8896    }
8897
8898    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
8899        self.alloc_uninit::<f32>(n)
8900    }
8901
8902    /// i8 uninitialized scratch (same contract as `uninit`).
8903    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
8904        self.alloc_uninit::<i8>(n)
8905    }
8906
8907    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
8908    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
8909    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
8910    #[allow(clippy::too_many_arguments)]
8911    pub fn rms_norm3(
8912        &self,
8913        x: &CudaSlice<f32>,
8914        w0: &CudaSlice<f32>,
8915        w1: &CudaSlice<f32>,
8916        w2: &CudaSlice<f32>,
8917        d0: &mut CudaSlice<f32>,
8918        d1: &mut CudaSlice<f32>,
8919        d2: &mut CudaSlice<f32>,
8920        ncols: usize,
8921        nrows: usize,
8922        eps: f32,
8923    ) -> Result<(), Box<dyn std::error::Error>> {
8924        let f = self.func("rms_norm3_f32");
8925        let cfg = LaunchConfig {
8926            grid_dim: (nrows as u32, 1, 1),
8927            block_dim: (rms_block(), 1, 1),
8928            shared_mem_bytes: 0,
8929        };
8930        let (nc, e) = (ncols as i32, eps);
8931        let __s_b = self.gpu.stream();
8932        let mut b = __s_b.launch_builder(&f);
8933        b.arg(x)
8934            .arg(w0)
8935            .arg(w1)
8936            .arg(w2)
8937            .arg(d0)
8938            .arg(d1)
8939            .arg(d2)
8940            .arg(&nc)
8941            .arg(&e);
8942        unsafe {
8943            b.launch(cfg)?;
8944        }
8945        Ok(())
8946    }
8947
8948    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
8949    #[allow(clippy::too_many_arguments)]
8950    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
8951    /// piggybacks on the same conditions.
8952    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
8953        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8954        *WARP_ON.get_or_init(|| {
8955            std::env::var("MEMRA_QKVNORM_W")
8956                .map(|v| v != "0")
8957                .unwrap_or(true)
8958        }) && ncols % 4 == 0
8959            && rows >= 64
8960    }
8961
8962    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
8963    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
8964    #[allow(clippy::too_many_arguments)]
8965    pub fn rms_norm_qkv_w4b(
8966        &self,
8967        q: &CudaSlice<f32>,
8968        k: &CudaSlice<f32>,
8969        v: &CudaSlice<f32>,
8970        wq: &CudaSlice<f32>,
8971        wk: &CudaSlice<f32>,
8972        wv: &CudaSlice<f32>,
8973        dq: &mut CudaSlice<f32>,
8974        dk: &mut CudaSlice<f32>,
8975        dv: &mut CudaSlice<f32>,
8976        dvb: &mut CudaSlice<u8>,
8977        ncols: usize,
8978        rq: usize,
8979        rk: usize,
8980        eps: f32,
8981        vf16: bool,
8982    ) -> Result<(), Box<dyn std::error::Error>> {
8983        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
8984        let f = self.func("rms_norm_qkv_w4b_f32");
8985        let rows = (rq + 2 * rk) as u32;
8986        let cfg = LaunchConfig {
8987            grid_dim: (rows.div_ceil(8), 1, 1),
8988            block_dim: (256, 1, 1),
8989            shared_mem_bytes: 0,
8990        };
8991        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
8992        let vf = vf16 as i32;
8993        let __s_b = self.gpu.stream();
8994        let mut b = __s_b.launch_builder(&f);
8995        b.arg(q)
8996            .arg(k)
8997            .arg(v)
8998            .arg(wq)
8999            .arg(wk)
9000            .arg(wv)
9001            .arg(dq)
9002            .arg(dk)
9003            .arg(dv)
9004            .arg(&mut *dvb)
9005            .arg(&nc)
9006            .arg(&rqi)
9007            .arg(&rki)
9008            .arg(&rvi)
9009            .arg(&e)
9010            .arg(&vf);
9011        unsafe {
9012            b.launch(cfg)?;
9013        }
9014        Ok(())
9015    }
9016
9017    pub fn rms_norm_qkv(
9018        &self,
9019        q: &CudaSlice<f32>,
9020        k: &CudaSlice<f32>,
9021        v: &CudaSlice<f32>,
9022        wq: &CudaSlice<f32>,
9023        wk: &CudaSlice<f32>,
9024        wv: &CudaSlice<f32>,
9025        dq: &mut CudaSlice<f32>,
9026        dk: &mut CudaSlice<f32>,
9027        dv: &mut CudaSlice<f32>,
9028        ncols: usize,
9029        rq: usize,
9030        rk: usize,
9031        eps: f32,
9032    ) -> Result<(), Box<dyn std::error::Error>> {
9033        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
9034        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
9035        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
9036        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
9037        let warp_on = *WARP_ON.get_or_init(|| {
9038            std::env::var("MEMRA_QKVNORM_W")
9039                .map(|v| v != "0")
9040                .unwrap_or(true)
9041        });
9042        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
9043        // replay numerics are untouched on every model; only prefill depth takes the new config.
9044        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
9045            let f = self.func("rms_norm_qkv_w4_f32");
9046            let rows = (rq + 2 * rk) as u32;
9047            let cfg = LaunchConfig {
9048                grid_dim: (rows.div_ceil(8), 1, 1),
9049                block_dim: (256, 1, 1),
9050                shared_mem_bytes: 0,
9051            };
9052            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
9053            let __s_b = self.gpu.stream();
9054            let mut b = __s_b.launch_builder(&f);
9055            b.arg(q)
9056                .arg(k)
9057                .arg(v)
9058                .arg(wq)
9059                .arg(wk)
9060                .arg(wv)
9061                .arg(dq)
9062                .arg(dk)
9063                .arg(dv)
9064                .arg(&nc)
9065                .arg(&rqi)
9066                .arg(&rki)
9067                .arg(&rvi)
9068                .arg(&e);
9069            unsafe {
9070                b.launch(cfg)?;
9071            }
9072            return Ok(());
9073        }
9074        let f = self.func("rms_norm_qkv_f32");
9075        let grid = (rq + 2 * rk) as u32;
9076        let cfg = LaunchConfig {
9077            grid_dim: (grid, 1, 1),
9078            block_dim: (rms_block(), 1, 1),
9079            shared_mem_bytes: 0,
9080        };
9081        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
9082        let __s_b = self.gpu.stream();
9083        let mut b = __s_b.launch_builder(&f);
9084        b.arg(q)
9085            .arg(k)
9086            .arg(v)
9087            .arg(wq)
9088            .arg(wk)
9089            .arg(wv)
9090            .arg(dq)
9091            .arg(dk)
9092            .arg(dv)
9093            .arg(&nc)
9094            .arg(&rqi)
9095            .arg(&rki)
9096            .arg(&e);
9097        unsafe {
9098            b.launch(cfg)?;
9099        }
9100        Ok(())
9101    }
9102
9103    /// gemma4 fused pair of rms_norms over two different inputs (same width).
9104    #[allow(clippy::too_many_arguments)]
9105    pub fn rms_norm2x(
9106        &self,
9107        a: &CudaSlice<f32>,
9108        bb: &CudaSlice<f32>,
9109        wa: &CudaSlice<f32>,
9110        wb: &CudaSlice<f32>,
9111        da: &mut CudaSlice<f32>,
9112        db: &mut CudaSlice<f32>,
9113        ncols: usize,
9114        nrows: usize,
9115        eps: f32,
9116    ) -> Result<(), Box<dyn std::error::Error>> {
9117        let f = self.func("rms_norm2x_f32");
9118        let cfg = LaunchConfig {
9119            grid_dim: (2 * nrows as u32, 1, 1),
9120            block_dim: (rms_block(), 1, 1),
9121            shared_mem_bytes: 0,
9122        };
9123        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9124        let __s_b = self.gpu.stream();
9125        let mut b = __s_b.launch_builder(&f);
9126        b.arg(a)
9127            .arg(bb)
9128            .arg(wa)
9129            .arg(wb)
9130            .arg(da)
9131            .arg(db)
9132            .arg(&nc)
9133            .arg(&nr)
9134            .arg(&e);
9135        unsafe {
9136            b.launch(cfg)?;
9137        }
9138        Ok(())
9139    }
9140
9141    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
9142    pub fn softcap(
9143        &self,
9144        y: &mut CudaSlice<f32>,
9145        cap: f32,
9146        n: usize,
9147    ) -> Result<(), Box<dyn std::error::Error>> {
9148        let f = self.func("softcap_f32");
9149        let cfg = LaunchConfig::for_num_elems(n as u32);
9150        let ni = n as i32;
9151        let __s_b = self.gpu.stream();
9152        let mut b = __s_b.launch_builder(&f);
9153        b.arg(y).arg(&cap).arg(&ni);
9154        unsafe {
9155            b.launch(cfg)?;
9156        }
9157        Ok(())
9158    }
9159
9160    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
9161    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
9162    pub fn mask_ids_rows(
9163        &self,
9164        y: &mut CudaSlice<f32>,
9165        ids: &CudaSlice<i32>,
9166        n_ids: usize,
9167        n_vocab: usize,
9168        t: usize,
9169    ) -> Result<(), Box<dyn std::error::Error>> {
9170        let f = self.func("mask_ids_rows_f32");
9171        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
9172        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
9173        let __s_b = self.gpu.stream();
9174        let mut b = __s_b.launch_builder(&f);
9175        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
9176        unsafe {
9177            b.launch(cfg)?;
9178        }
9179        Ok(())
9180    }
9181
9182    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
9183    #[allow(clippy::too_many_arguments)]
9184    pub fn add_scale_rms_norm(
9185        &self,
9186        a: &CudaSlice<f32>,
9187        b_in: &CudaSlice<f32>,
9188        c: f32,
9189        w: &CudaSlice<f32>,
9190        res: &mut CudaSlice<f32>,
9191        dst: &mut CudaSlice<f32>,
9192        ncols: usize,
9193        nrows: usize,
9194        eps: f32,
9195    ) -> Result<(), Box<dyn std::error::Error>> {
9196        let f = self.func("add_scale_rms_norm_f32");
9197        let cfg = LaunchConfig {
9198            grid_dim: (nrows as u32, 1, 1),
9199            block_dim: (rms_block(), 1, 1),
9200            shared_mem_bytes: 0,
9201        };
9202        let (nc, e2) = (ncols as i32, eps);
9203        let __s_b = self.gpu.stream();
9204        let mut b = __s_b.launch_builder(&f);
9205        b.arg(a)
9206            .arg(b_in)
9207            .arg(&c)
9208            .arg(w)
9209            .arg(res)
9210            .arg(dst)
9211            .arg(&nc)
9212            .arg(&e2);
9213        unsafe {
9214            b.launch(cfg)?;
9215        }
9216        Ok(())
9217    }
9218
9219    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
9220    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
9221    #[allow(clippy::too_many_arguments)]
9222    pub fn add_scale_rms_norm_q8_1(
9223        &self,
9224        a: &CudaSlice<f32>,
9225        b_in: &CudaSlice<f32>,
9226        c: f32,
9227        w: &CudaSlice<f32>,
9228        res: &mut CudaSlice<f32>,
9229        ncols: usize,
9230        nrows: usize,
9231        eps: f32,
9232    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9233        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9234        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9235        let (nc, e2) = (ncols as i32, eps);
9236        if Self::pdl_on() && Self::pdl_wb_on() {
9237            {
9238                use cudarc::driver::{DevicePtr, DevicePtrMut};
9239                let s = &self.gpu.stream();
9240                let (pa, _g0) = a.device_ptr(s);
9241                let (pb, _g1) = b_in.device_ptr(s);
9242                let (pw, _g2) = w.device_ptr(s);
9243                let (pr, _g3) = res.device_ptr_mut(s);
9244                let (pq, _g4) = out_q.device_ptr_mut(s);
9245                let (pd, _g5) = out_d.device_ptr_mut(s);
9246                let mut ps = [
9247                    &pa as *const _ as *mut std::ffi::c_void,
9248                    &pb as *const _ as *mut _,
9249                    &c as *const _ as *mut _,
9250                    &pw as *const _ as *mut _,
9251                    &pr as *const _ as *mut _,
9252                    &pq as *const _ as *mut _,
9253                    &pd as *const _ as *mut _,
9254                    &nc as *const _ as *mut _,
9255                    &e2 as *const _ as *mut _,
9256                ];
9257                unsafe {
9258                    self.launch_pdl(
9259                        "add_scale_rms_norm_q8_1",
9260                        (nrows as u32, 1, 1),
9261                        (rms_block(), 1, 1),
9262                        &mut ps,
9263                    )?;
9264                }
9265            }
9266            return Ok((out_q, out_d));
9267        }
9268        let f = self.func("add_scale_rms_norm_q8_1");
9269        let cfg = LaunchConfig {
9270            grid_dim: (nrows as u32, 1, 1),
9271            block_dim: (rms_block(), 1, 1),
9272            shared_mem_bytes: 0,
9273        };
9274        let __s_b = self.gpu.stream();
9275        let mut b = __s_b.launch_builder(&f);
9276        b.arg(a)
9277            .arg(b_in)
9278            .arg(&c)
9279            .arg(w)
9280            .arg(res)
9281            .arg(&mut out_q)
9282            .arg(&mut out_d)
9283            .arg(&nc)
9284            .arg(&e2);
9285        unsafe {
9286            b.launch(cfg)?;
9287        }
9288        Ok((out_q, out_d))
9289    }
9290
9291    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
9292    #[allow(clippy::too_many_arguments)]
9293    pub fn add_scale_rms_norm_q8_1_into(
9294        &self,
9295        a: &CudaSlice<f32>,
9296        b_in: &CudaSlice<f32>,
9297        c: f32,
9298        w: &CudaSlice<f32>,
9299        res: &mut CudaSlice<f32>,
9300        ncols: usize,
9301        nrows: usize,
9302        eps: f32,
9303        out_q: &mut CudaSlice<i8>,
9304        out_d: &mut CudaSlice<f32>,
9305    ) -> Result<(), Box<dyn std::error::Error>> {
9306        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
9307        let (nc, e2) = (ncols as i32, eps);
9308        if Self::pdl_on() && Self::pdl_wb_on() {
9309            use cudarc::driver::{DevicePtr, DevicePtrMut};
9310            let s = &self.gpu.stream();
9311            let (pa, _g0) = a.device_ptr(s);
9312            let (pb, _g1) = b_in.device_ptr(s);
9313            let (pw, _g2) = w.device_ptr(s);
9314            let (pr, _g3) = res.device_ptr_mut(s);
9315            let (pq, _g4) = out_q.device_ptr_mut(s);
9316            let (pd, _g5) = out_d.device_ptr_mut(s);
9317            let mut ps = [
9318                &pa as *const _ as *mut std::ffi::c_void,
9319                &pb as *const _ as *mut _,
9320                &c as *const _ as *mut _,
9321                &pw as *const _ as *mut _,
9322                &pr as *const _ as *mut _,
9323                &pq as *const _ as *mut _,
9324                &pd as *const _ as *mut _,
9325                &nc as *const _ as *mut _,
9326                &e2 as *const _ as *mut _,
9327            ];
9328            unsafe {
9329                self.launch_pdl(
9330                    "add_scale_rms_norm_q8_1",
9331                    (nrows as u32, 1, 1),
9332                    (rms_block(), 1, 1),
9333                    &mut ps,
9334                )?;
9335            }
9336            return Ok(());
9337        }
9338        let f = self.func("add_scale_rms_norm_q8_1");
9339        let cfg = LaunchConfig {
9340            grid_dim: (nrows as u32, 1, 1),
9341            block_dim: (rms_block(), 1, 1),
9342            shared_mem_bytes: 0,
9343        };
9344        let __s_b = self.gpu.stream();
9345        let mut b = __s_b.launch_builder(&f);
9346        b.arg(a)
9347            .arg(b_in)
9348            .arg(&c)
9349            .arg(w)
9350            .arg(res)
9351            .arg(&mut *out_q)
9352            .arg(&mut *out_d)
9353            .arg(&nc)
9354            .arg(&e2);
9355        unsafe {
9356            b.launch(cfg)?;
9357        }
9358        Ok(())
9359    }
9360
9361    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
9362    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
9363    #[allow(clippy::too_many_arguments)]
9364    pub fn rms_pre_add_scale_rms_norm_q8_1(
9365        &self,
9366        a: &CudaSlice<f32>,
9367        wa: &CudaSlice<f32>,
9368        b_in: &CudaSlice<f32>,
9369        c: f32,
9370        w: &CudaSlice<f32>,
9371        res: &mut CudaSlice<f32>,
9372        ncols: usize,
9373        nrows: usize,
9374        eps: f32,
9375    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9376        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9377        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9378        let (nc, e2) = (ncols as i32, eps);
9379        if Self::pdl_on() {
9380            {
9381                use cudarc::driver::{DevicePtr, DevicePtrMut};
9382                let s = &self.gpu.stream();
9383                let (pa, _g0) = a.device_ptr(s);
9384                let (pwa, _g1) = wa.device_ptr(s);
9385                let (pb, _g2) = b_in.device_ptr(s);
9386                let (pw, _g3) = w.device_ptr(s);
9387                let (pr, _g4) = res.device_ptr_mut(s);
9388                let (pq, _g5) = out_q.device_ptr_mut(s);
9389                let (pd, _g6) = out_d.device_ptr_mut(s);
9390                let mut ps = [
9391                    &pa as *const _ as *mut std::ffi::c_void,
9392                    &pwa as *const _ as *mut _,
9393                    &pb as *const _ as *mut _,
9394                    &c as *const _ as *mut _,
9395                    &pw as *const _ as *mut _,
9396                    &pr as *const _ as *mut _,
9397                    &pq as *const _ as *mut _,
9398                    &pd as *const _ as *mut _,
9399                    &nc as *const _ as *mut _,
9400                    &e2 as *const _ as *mut _,
9401                ];
9402                unsafe {
9403                    self.launch_pdl(
9404                        "rms_pre_add_scale_rms_norm_q8_1",
9405                        (nrows as u32, 1, 1),
9406                        (rms_block(), 1, 1),
9407                        &mut ps,
9408                    )?;
9409                }
9410            }
9411            return Ok((out_q, out_d));
9412        }
9413        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
9414        let cfg = LaunchConfig {
9415            grid_dim: (nrows as u32, 1, 1),
9416            block_dim: (rms_block(), 1, 1),
9417            shared_mem_bytes: 0,
9418        };
9419        let __s_b = self.gpu.stream();
9420        let mut b = __s_b.launch_builder(&f);
9421        b.arg(a)
9422            .arg(wa)
9423            .arg(b_in)
9424            .arg(&c)
9425            .arg(w)
9426            .arg(res)
9427            .arg(&mut out_q)
9428            .arg(&mut out_d)
9429            .arg(&nc)
9430            .arg(&e2);
9431        unsafe {
9432            b.launch(cfg)?;
9433        }
9434        Ok((out_q, out_d))
9435    }
9436
9437    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
9438    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
9439    pub fn gelu_tanh_mul_q8_1(
9440        &self,
9441        gate: &CudaSlice<f32>,
9442        up: &cudarc::driver::CudaView<f32>,
9443        act: &mut CudaSlice<f32>,
9444        ncols: usize,
9445        nrows: usize,
9446    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9447        debug_assert!(ncols % 128 == 0);
9448        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9449        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9450        let nc = ncols as i32;
9451        if Self::pdl_on() {
9452            {
9453                use cudarc::driver::{DevicePtr, DevicePtrMut};
9454                let s = &self.gpu.stream();
9455                let (pg, _g0) = gate.device_ptr(s);
9456                let (pu, _g1) = up.device_ptr(s);
9457                let (pact, _g2) = act.device_ptr_mut(s);
9458                let (pq, _g3) = out_q.device_ptr_mut(s);
9459                let (pd, _g4) = out_d.device_ptr_mut(s);
9460                let mut ps = [
9461                    &pg as *const _ as *mut std::ffi::c_void,
9462                    &pu as *const _ as *mut _,
9463                    &pact as *const _ as *mut _,
9464                    &pq as *const _ as *mut _,
9465                    &pd as *const _ as *mut _,
9466                    &nc as *const _ as *mut _,
9467                ];
9468                unsafe {
9469                    self.launch_pdl(
9470                        "gelu_tanh_mul_q8_1",
9471                        (nrows as u32, 1, 1),
9472                        (rms_block(), 1, 1),
9473                        &mut ps,
9474                    )?;
9475                }
9476            }
9477            return Ok((out_q, out_d));
9478        }
9479        let f = self.func("gelu_tanh_mul_q8_1");
9480        let cfg = LaunchConfig {
9481            grid_dim: (nrows as u32, 1, 1),
9482            block_dim: (rms_block(), 1, 1),
9483            shared_mem_bytes: 0,
9484        };
9485        let __s_b = self.gpu.stream();
9486        let mut b = __s_b.launch_builder(&f);
9487        b.arg(gate)
9488            .arg(up)
9489            .arg(act)
9490            .arg(&mut out_q)
9491            .arg(&mut out_d)
9492            .arg(&nc);
9493        unsafe {
9494            b.launch(cfg)?;
9495        }
9496        Ok((out_q, out_d))
9497    }
9498
9499    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
9500    #[allow(clippy::too_many_arguments)]
9501    pub fn gelu_tanh_mul_q8_1_into(
9502        &self,
9503        gate: &CudaSlice<f32>,
9504        up: &cudarc::driver::CudaView<f32>,
9505        act: &mut CudaSlice<f32>,
9506        ncols: usize,
9507        nrows: usize,
9508        out_q: &mut CudaSlice<i8>,
9509        out_d: &mut CudaSlice<f32>,
9510    ) -> Result<(), Box<dyn std::error::Error>> {
9511        debug_assert!(ncols % 128 == 0);
9512        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
9513        let nc = ncols as i32;
9514        if Self::pdl_on() {
9515            use cudarc::driver::{DevicePtr, DevicePtrMut};
9516            let s = &self.gpu.stream();
9517            let (pg, _g0) = gate.device_ptr(s);
9518            let (pu, _g1) = up.device_ptr(s);
9519            let (pact, _g2) = act.device_ptr_mut(s);
9520            let (pq, _g3) = out_q.device_ptr_mut(s);
9521            let (pd, _g4) = out_d.device_ptr_mut(s);
9522            let mut ps = [
9523                &pg as *const _ as *mut std::ffi::c_void,
9524                &pu as *const _ as *mut _,
9525                &pact as *const _ as *mut _,
9526                &pq as *const _ as *mut _,
9527                &pd as *const _ as *mut _,
9528                &nc as *const _ as *mut _,
9529            ];
9530            unsafe {
9531                self.launch_pdl(
9532                    "gelu_tanh_mul_q8_1",
9533                    (nrows as u32, 1, 1),
9534                    (rms_block(), 1, 1),
9535                    &mut ps,
9536                )?;
9537            }
9538            return Ok(());
9539        }
9540        let f = self.func("gelu_tanh_mul_q8_1");
9541        let cfg = LaunchConfig {
9542            grid_dim: (nrows as u32, 1, 1),
9543            block_dim: (rms_block(), 1, 1),
9544            shared_mem_bytes: 0,
9545        };
9546        let __s_b = self.gpu.stream();
9547        let mut b = __s_b.launch_builder(&f);
9548        b.arg(gate)
9549            .arg(up)
9550            .arg(&mut *act)
9551            .arg(&mut *out_q)
9552            .arg(&mut *out_d)
9553            .arg(&nc);
9554        unsafe {
9555            b.launch(cfg)?;
9556        }
9557        Ok(())
9558    }
9559
9560    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
9561    #[allow(clippy::too_many_arguments)]
9562    pub fn add_rms_norm3_q8z(
9563        &self,
9564        a: &CudaSlice<f32>,
9565        b_in: &CudaSlice<f32>,
9566        w0: &CudaSlice<f32>,
9567        w1: &CudaSlice<f32>,
9568        w2: &CudaSlice<f32>,
9569        res: &mut CudaSlice<f32>,
9570        out1: &mut CudaSlice<f32>,
9571        ncols: usize,
9572        nrows: usize,
9573        eps: f32,
9574    ) -> Result<
9575        (
9576            (CudaSlice<i8>, CudaSlice<f32>),
9577            (CudaSlice<i8>, CudaSlice<f32>),
9578        ),
9579        Box<dyn std::error::Error>,
9580    > {
9581        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
9582        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9583        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
9584        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9585        let f = self.func("add_rms_norm3_q8z_f32");
9586        let cfg = LaunchConfig {
9587            grid_dim: (nrows as u32, 1, 1),
9588            block_dim: (rms_block(), 1, 1),
9589            shared_mem_bytes: 0,
9590        };
9591        let (nc, e2) = (ncols as i32, eps);
9592        let __s_b = self.gpu.stream();
9593        let mut b = __s_b.launch_builder(&f);
9594        b.arg(a)
9595            .arg(b_in)
9596            .arg(w0)
9597            .arg(w1)
9598            .arg(w2)
9599            .arg(res)
9600            .arg(&mut q0)
9601            .arg(&mut d0)
9602            .arg(out1)
9603            .arg(&mut q2)
9604            .arg(&mut d2)
9605            .arg(&nc)
9606            .arg(&e2);
9607        unsafe {
9608            b.launch(cfg)?;
9609        }
9610        Ok(((q0, d0), (q2, d2)))
9611    }
9612
9613    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
9614    #[allow(clippy::too_many_arguments)]
9615    pub fn add_rms_norm3(
9616        &self,
9617        a: &CudaSlice<f32>,
9618        b_in: &CudaSlice<f32>,
9619        w0: &CudaSlice<f32>,
9620        w1: &CudaSlice<f32>,
9621        w2: &CudaSlice<f32>,
9622        res: &mut CudaSlice<f32>,
9623        d0: &mut CudaSlice<f32>,
9624        d1: &mut CudaSlice<f32>,
9625        d2: &mut CudaSlice<f32>,
9626        ncols: usize,
9627        nrows: usize,
9628        eps: f32,
9629    ) -> Result<(), Box<dyn std::error::Error>> {
9630        let f = self.func("add_rms_norm3_f32");
9631        let cfg = LaunchConfig {
9632            grid_dim: (nrows as u32, 1, 1),
9633            block_dim: (rms_block(), 1, 1),
9634            shared_mem_bytes: 0,
9635        };
9636        let (nc, e2) = (ncols as i32, eps);
9637        let __s_b = self.gpu.stream();
9638        let mut b = __s_b.launch_builder(&f);
9639        b.arg(a)
9640            .arg(b_in)
9641            .arg(w0)
9642            .arg(w1)
9643            .arg(w2)
9644            .arg(res)
9645            .arg(d0)
9646            .arg(d1)
9647            .arg(d2)
9648            .arg(&nc)
9649            .arg(&e2);
9650        unsafe {
9651            b.launch(cfg)?;
9652        }
9653        Ok(())
9654    }
9655
9656    /// dst = (a + b) * c (residual add + layer scale, one launch).
9657    pub fn add_scale(
9658        &self,
9659        a: &CudaSlice<f32>,
9660        b_in: &CudaSlice<f32>,
9661        c: f32,
9662        dst: &mut CudaSlice<f32>,
9663        n: usize,
9664    ) -> Result<(), Box<dyn std::error::Error>> {
9665        let f = self.func("add_scale_f32");
9666        let cfg = LaunchConfig::for_num_elems(n as u32);
9667        let ni = n as i32;
9668        let __s_b = self.gpu.stream();
9669        let mut b = __s_b.launch_builder(&f);
9670        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
9671        unsafe {
9672            b.launch(cfg)?;
9673        }
9674        Ok(())
9675    }
9676
9677    /// Vision-tower LayerNorm (with bias) over [nrows, ncols] — lane/vision.
9678    pub fn layer_norm_bias(
9679        &self,
9680        x: &CudaSlice<f32>,
9681        w: &CudaSlice<f32>,
9682        b: &CudaSlice<f32>,
9683        dst: &mut CudaSlice<f32>,
9684        ncols: usize,
9685        nrows: usize,
9686        eps: f32,
9687    ) -> Result<(), Box<dyn std::error::Error>> {
9688        let f = self.func("layer_norm_bias_f32");
9689        let (nc, e) = (ncols as i32, eps);
9690        let cfg = LaunchConfig {
9691            grid_dim: (nrows as u32, 1, 1),
9692            block_dim: (256, 1, 1),
9693            shared_mem_bytes: 0,
9694        };
9695        let __s_b = self.gpu.stream();
9696        let mut lb = __s_b.launch_builder(&f);
9697        lb.arg(x).arg(w).arg(b).arg(&mut *dst).arg(&nc).arg(&e);
9698        unsafe {
9699            lb.launch(cfg)?;
9700        }
9701        Ok(())
9702    }
9703
9704    /// gelu_pytorch_tanh elementwise (vision tower MLP activation).
9705    pub fn gelu_tanh(
9706        &self,
9707        x: &CudaSlice<f32>,
9708        dst: &mut CudaSlice<f32>,
9709        n: usize,
9710    ) -> Result<(), Box<dyn std::error::Error>> {
9711        let f = self.func("gelu_tanh_f32");
9712        let ni = n as i64;
9713        let cfg = LaunchConfig {
9714            grid_dim: (n.div_ceil(256) as u32, 1, 1),
9715            block_dim: (256, 1, 1),
9716            shared_mem_bytes: 0,
9717        };
9718        let __s_b = self.gpu.stream();
9719        let mut lb = __s_b.launch_builder(&f);
9720        lb.arg(x).arg(&mut *dst).arg(&ni);
9721        unsafe {
9722            lb.launch(cfg)?;
9723        }
9724        Ok(())
9725    }
9726
9727    /// In-place row softmax over [nrows, ncols] (bidirectional vision attention).
9728    pub fn row_softmax(
9729        &self,
9730        x: &mut CudaSlice<f32>,
9731        ncols: usize,
9732        nrows: usize,
9733    ) -> Result<(), Box<dyn std::error::Error>> {
9734        let f = self.func("row_softmax_f32");
9735        let nc = ncols as i32;
9736        let cfg = LaunchConfig {
9737            grid_dim: (nrows as u32, 1, 1),
9738            block_dim: (256, 1, 1),
9739            shared_mem_bytes: 0,
9740        };
9741        let __s_b = self.gpu.stream();
9742        let mut lb = __s_b.launch_builder(&f);
9743        lb.arg(&mut *x).arg(&nc);
9744        unsafe {
9745            lb.launch(cfg)?;
9746        }
9747        Ok(())
9748    }
9749
9750    pub fn rms_norm(
9751        &self,
9752        x: &CudaSlice<f32>,
9753        w: &CudaSlice<f32>,
9754        dst: &mut CudaSlice<f32>,
9755        ncols: usize,
9756        nrows: usize,
9757        eps: f32,
9758    ) -> Result<(), Box<dyn std::error::Error>> {
9759        let (nc, e) = (ncols as i32, eps);
9760        let kname = if Self::norm_ilp_on() {
9761            "rms_norm_f32_v2"
9762        } else {
9763            "rms_norm_f32"
9764        };
9765        if Self::pdl_on() && Self::pdl_wb_on() {
9766            use cudarc::driver::{DevicePtr, DevicePtrMut};
9767            let s = &self.gpu.stream();
9768            let (px, _g0) = x.device_ptr(s);
9769            let (pw, _g1) = w.device_ptr(s);
9770            let (pd, _g2) = dst.device_ptr_mut(s);
9771            let mut ps = [
9772                &px as *const _ as *mut std::ffi::c_void,
9773                &pw as *const _ as *mut _,
9774                &pd as *const _ as *mut _,
9775                &nc as *const _ as *mut _,
9776                &e as *const _ as *mut _,
9777            ];
9778            unsafe {
9779                self.launch_pdl(kname, (nrows as u32, 1, 1), (rms_block(), 1, 1), &mut ps)?;
9780            }
9781            return Ok(());
9782        }
9783        let f = self.func(kname);
9784        let cfg = LaunchConfig {
9785            grid_dim: (nrows as u32, 1, 1),
9786            block_dim: (rms_block(), 1, 1),
9787            shared_mem_bytes: 0,
9788        };
9789        let __s_b = self.gpu.stream();
9790        let mut b = __s_b.launch_builder(&f);
9791        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
9792        unsafe {
9793            b.launch(cfg)?;
9794        }
9795        Ok(())
9796    }
9797
9798    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
9799    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
9800    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
9801    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
9802    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
9803    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
9804    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
9805    pub fn rms_norm_decode(
9806        &self,
9807        x: &CudaSlice<f32>,
9808        w: &CudaSlice<f32>,
9809        dst: &mut CudaSlice<f32>,
9810        ncols: usize,
9811        nrows: usize,
9812        eps: f32,
9813    ) -> Result<(), Box<dyn std::error::Error>> {
9814        let f = self.func(if Self::norm_ilp_on() {
9815            "rms_norm_f32_v2"
9816        } else {
9817            "rms_norm_f32"
9818        });
9819        let cfg = LaunchConfig {
9820            grid_dim: (nrows as u32, 1, 1),
9821            block_dim: (1024, 1, 1),
9822            shared_mem_bytes: 0,
9823        };
9824        let (nc, e) = (ncols as i32, eps);
9825        let __s_b = self.gpu.stream();
9826        let mut b = __s_b.launch_builder(&f);
9827        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
9828        unsafe {
9829            b.launch(cfg)?;
9830        }
9831        Ok(())
9832    }
9833
9834    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
9835    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
9836    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
9837    pub fn rms_norm_q8_1(
9838        &self,
9839        x: &CudaSlice<f32>,
9840        w: &CudaSlice<f32>,
9841        ncols: usize,
9842        nrows: usize,
9843        eps: f32,
9844    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9845        let nblk = ncols / 32;
9846        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
9847        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
9848        let (nc, e) = (ncols as i32, eps);
9849        if Self::pdl_on() {
9850            {
9851                use cudarc::driver::{DevicePtr, DevicePtrMut};
9852                let s = &self.gpu.stream();
9853                let (px, _g0) = x.device_ptr(s);
9854                let (pw, _g1) = w.device_ptr(s);
9855                let (pq, _g2) = q.device_ptr_mut(s);
9856                let (pd, _g3) = d.device_ptr_mut(s);
9857                let mut ps = [
9858                    &px as *const _ as *mut std::ffi::c_void,
9859                    &pw as *const _ as *mut _,
9860                    &pq as *const _ as *mut _,
9861                    &pd as *const _ as *mut _,
9862                    &nc as *const _ as *mut _,
9863                    &e as *const _ as *mut _,
9864                ];
9865                unsafe {
9866                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
9867                }
9868            }
9869            return Ok((q, d));
9870        }
9871        let f = self.func("rms_norm_q8_1");
9872        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
9873        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
9874        let cfg = LaunchConfig {
9875            grid_dim: (nrows as u32, 1, 1),
9876            block_dim: (1024, 1, 1),
9877            shared_mem_bytes: 0,
9878        };
9879        let __s_b = self.gpu.stream();
9880        let mut b = __s_b.launch_builder(&f);
9881        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
9882        unsafe {
9883            b.launch(cfg)?;
9884        }
9885        Ok((q, d))
9886    }
9887
9888    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
9889    /// PDL arm), caller-owned outputs.
9890    pub fn rms_norm_q8_1_into(
9891        &self,
9892        x: &CudaSlice<f32>,
9893        w: &CudaSlice<f32>,
9894        ncols: usize,
9895        nrows: usize,
9896        eps: f32,
9897        q: &mut CudaSlice<i8>,
9898        d: &mut CudaSlice<f32>,
9899    ) -> Result<(), Box<dyn std::error::Error>> {
9900        let nblk = ncols / 32;
9901        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
9902        let (nc, e) = (ncols as i32, eps);
9903        if Self::pdl_on() {
9904            use cudarc::driver::{DevicePtr, DevicePtrMut};
9905            let s = &self.gpu.stream();
9906            let (px, _g0) = x.device_ptr(s);
9907            let (pw, _g1) = w.device_ptr(s);
9908            let (pq, _g2) = q.device_ptr_mut(s);
9909            let (pd, _g3) = d.device_ptr_mut(s);
9910            let mut ps = [
9911                &px as *const _ as *mut std::ffi::c_void,
9912                &pw as *const _ as *mut _,
9913                &pq as *const _ as *mut _,
9914                &pd as *const _ as *mut _,
9915                &nc as *const _ as *mut _,
9916                &e as *const _ as *mut _,
9917            ];
9918            unsafe {
9919                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
9920            }
9921            return Ok(());
9922        }
9923        let f = self.func("rms_norm_q8_1");
9924        let cfg = LaunchConfig {
9925            grid_dim: (nrows as u32, 1, 1),
9926            block_dim: (1024, 1, 1),
9927            shared_mem_bytes: 0,
9928        };
9929        let __s_b = self.gpu.stream();
9930        let mut b = __s_b.launch_builder(&f);
9931        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
9932        unsafe {
9933            b.launch(cfg)?;
9934        }
9935        Ok(())
9936    }
9937
9938    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
9939    pub fn quantize_q8_1_into(
9940        &self,
9941        x: &CudaSlice<f32>,
9942        m: usize,
9943        in_f: usize,
9944        q: &mut CudaSlice<i8>,
9945        d: &mut CudaSlice<f32>,
9946    ) -> Result<(), Box<dyn std::error::Error>> {
9947        let nblk = in_f / 32;
9948        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
9949        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
9950        let (inf, mi) = (in_f as i32, m as i32);
9951        if Self::pdl_on() && Self::pdl_wb_on() {
9952            use cudarc::driver::{DevicePtr, DevicePtrMut};
9953            let s = &self.gpu.stream();
9954            let (px, _g0) = x.device_ptr(s);
9955            let (pq, _g1) = q.device_ptr_mut(s);
9956            let (pd, _g2) = d.device_ptr_mut(s);
9957            let mut ps = [
9958                &px as *const _ as *mut std::ffi::c_void,
9959                &pq as *const _ as *mut _,
9960                &pd as *const _ as *mut _,
9961                &inf as *const _ as *mut _,
9962                &mi as *const _ as *mut _,
9963            ];
9964            unsafe {
9965                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
9966            }
9967            return Ok(());
9968        }
9969        let f = self.func("quantize_q8_1");
9970        let __s_b = self.gpu.stream();
9971        let mut b = __s_b.launch_builder(&f);
9972        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
9973        unsafe {
9974            b.launch(cfg)?;
9975        }
9976        Ok(())
9977    }
9978
9979    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
9980    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
9981    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
9982    pub fn add_rms_norm_q8_1(
9983        &self,
9984        a: &CudaSlice<f32>,
9985        b_in: &CudaSlice<f32>,
9986        w: &CudaSlice<f32>,
9987        res: &mut CudaSlice<f32>,
9988        ncols: usize,
9989        nrows: usize,
9990        eps: f32,
9991    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9992        let nblk = ncols / 32;
9993        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
9994        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
9995        let f = self.func("add_rms_norm_q8_1");
9996        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
9997        let cfg = LaunchConfig {
9998            grid_dim: (nrows as u32, 1, 1),
9999            block_dim: (1024, 1, 1),
10000            shared_mem_bytes: 0,
10001        };
10002        let (nc, e) = (ncols as i32, eps);
10003        let __s_bld = self.gpu.stream();
10004        let mut bld = __s_bld.launch_builder(&f);
10005        bld.arg(a)
10006            .arg(b_in)
10007            .arg(w)
10008            .arg(res)
10009            .arg(&mut q)
10010            .arg(&mut d)
10011            .arg(&nc)
10012            .arg(&e);
10013        unsafe {
10014            bld.launch(cfg)?;
10015        }
10016        Ok((q, d))
10017    }
10018
10019    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
10020    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
10021    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
10022    /// O-PROJ TAIL FUSION M2: mixed = a0+a1 composed in-register, then the VERBATIM
10023    /// add_rms_norm program. Raw UVA pointers for the join partials (persistent ws rows).
10024    #[allow(clippy::too_many_arguments)]
10025    pub fn join_add_rms_norm_raw(
10026        &self,
10027        a0_raw: u64,
10028        a1_raw: u64,
10029        x: &CudaSlice<f32>,
10030        w: &CudaSlice<f32>,
10031        res: &mut CudaSlice<f32>,
10032        dst: &mut CudaSlice<f32>,
10033        ncols: usize,
10034        eps: f32,
10035    ) -> Result<(), Box<dyn std::error::Error>> {
10036        if a0_raw == 0 || a1_raw == 0 || x.len() < ncols || res.len() < ncols || dst.len() < ncols {
10037            return Err("join_add_rms_norm geometry".into());
10038        }
10039        let f = self.func("join_add_rms_norm_f32");
10040        let cfg = LaunchConfig {
10041            grid_dim: (1, 1, 1),
10042            block_dim: (rms_block(), 1, 1),
10043            shared_mem_bytes: 0,
10044        };
10045        let (nc, e) = (ncols as i32, eps);
10046        let __s_b = self.gpu.stream();
10047        let mut b = __s_b.launch_builder(&f);
10048        b.arg(&a0_raw)
10049            .arg(&a1_raw)
10050            .arg(x)
10051            .arg(w)
10052            .arg(&mut *res)
10053            .arg(&mut *dst)
10054            .arg(&nc)
10055            .arg(&e);
10056        unsafe {
10057            b.launch(cfg)?;
10058        }
10059        Ok(())
10060    }
10061
10062    pub fn add_rms_norm(
10063        &self,
10064        a: &CudaSlice<f32>,
10065        b: &CudaSlice<f32>,
10066        w: &CudaSlice<f32>,
10067        res: &mut CudaSlice<f32>,
10068        dst: &mut CudaSlice<f32>,
10069        ncols: usize,
10070        nrows: usize,
10071        eps: f32,
10072    ) -> Result<(), Box<dyn std::error::Error>> {
10073        let (nc, e) = (ncols as i32, eps);
10074        let kname = if Self::norm_ilp_on() {
10075            "add_rms_norm_f32_v2"
10076        } else {
10077            "add_rms_norm_f32"
10078        };
10079        if Self::pdl_on() && Self::pdl_wb_on() {
10080            use cudarc::driver::{DevicePtr, DevicePtrMut};
10081            let s = &self.gpu.stream();
10082            let (pa, _g0) = a.device_ptr(s);
10083            let (pb, _g1) = b.device_ptr(s);
10084            let (pw, _g2) = w.device_ptr(s);
10085            let (pr, _g3) = res.device_ptr_mut(s);
10086            let (pd, _g4) = dst.device_ptr_mut(s);
10087            let mut ps = [
10088                &pa as *const _ as *mut std::ffi::c_void,
10089                &pb as *const _ as *mut _,
10090                &pw as *const _ as *mut _,
10091                &pr as *const _ as *mut _,
10092                &pd as *const _ as *mut _,
10093                &nc as *const _ as *mut _,
10094                &e as *const _ as *mut _,
10095            ];
10096            unsafe {
10097                self.launch_pdl(kname, (nrows as u32, 1, 1), (rms_block(), 1, 1), &mut ps)?;
10098            }
10099            return Ok(());
10100        }
10101        let f = self.func(kname);
10102        let cfg = LaunchConfig {
10103            grid_dim: (nrows as u32, 1, 1),
10104            block_dim: (rms_block(), 1, 1),
10105            shared_mem_bytes: 0,
10106        };
10107        let __s_b2 = self.gpu.stream();
10108        let mut b2 = __s_b2.launch_builder(&f);
10109        b2.arg(a)
10110            .arg(b)
10111            .arg(w)
10112            .arg(&mut *res)
10113            .arg(&mut *dst)
10114            .arg(&nc)
10115            .arg(&e);
10116        unsafe {
10117            b2.launch(cfg)?;
10118        }
10119        Ok(())
10120    }
10121
10122    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
10123    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
10124    #[allow(clippy::too_many_arguments)]
10125    pub fn rms_pre_add_rms_norm(
10126        &self,
10127        a: &CudaSlice<f32>,
10128        wa: &CudaSlice<f32>,
10129        b: &CudaSlice<f32>,
10130        w: &CudaSlice<f32>,
10131        res: &mut CudaSlice<f32>,
10132        dst: &mut CudaSlice<f32>,
10133        ncols: usize,
10134        nrows: usize,
10135        eps: f32,
10136    ) -> Result<(), Box<dyn std::error::Error>> {
10137        let f = self.func("rms_pre_add_rms_norm_f32");
10138        let cfg = LaunchConfig {
10139            grid_dim: (nrows as u32, 1, 1),
10140            block_dim: (rms_block(), 1, 1),
10141            shared_mem_bytes: 0,
10142        };
10143        let (nc, e) = (ncols as i32, eps);
10144        let __s_b2 = self.gpu.stream();
10145        let mut b2 = __s_b2.launch_builder(&f);
10146        b2.arg(a)
10147            .arg(wa)
10148            .arg(b)
10149            .arg(w)
10150            .arg(&mut *res)
10151            .arg(&mut *dst)
10152            .arg(&nc)
10153            .arg(&e);
10154        unsafe {
10155            b2.launch(cfg)?;
10156        }
10157        Ok(())
10158    }
10159
10160    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
10161    #[allow(clippy::too_many_arguments)]
10162    pub fn rms_pre_add_rms_norm_q8z(
10163        &self,
10164        a: &CudaSlice<f32>,
10165        wa: &CudaSlice<f32>,
10166        b: &CudaSlice<f32>,
10167        w: &CudaSlice<f32>,
10168        res: &mut CudaSlice<f32>,
10169        dst: &mut CudaSlice<f32>,
10170        ncols: usize,
10171        nrows: usize,
10172        eps: f32,
10173    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10174        debug_assert!(ncols % 128 == 0);
10175        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
10176        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
10177        let (nc, e) = (ncols as i32, eps);
10178        if Self::pdl_on() {
10179            {
10180                use cudarc::driver::{DevicePtr, DevicePtrMut};
10181                let s = &self.gpu.stream();
10182                let (pa, _g0) = a.device_ptr(s);
10183                let (pwa, _g1) = wa.device_ptr(s);
10184                let (pb, _g2) = b.device_ptr(s);
10185                let (pw, _g3) = w.device_ptr(s);
10186                let (pr, _g4) = res.device_ptr_mut(s);
10187                let (pdst, _g5) = dst.device_ptr_mut(s);
10188                let (pq, _g6) = out_q.device_ptr_mut(s);
10189                let (pd, _g7) = out_d.device_ptr_mut(s);
10190                let mut ps = [
10191                    &pa as *const _ as *mut std::ffi::c_void,
10192                    &pwa as *const _ as *mut _,
10193                    &pb as *const _ as *mut _,
10194                    &pw as *const _ as *mut _,
10195                    &pr as *const _ as *mut _,
10196                    &pdst as *const _ as *mut _,
10197                    &pq as *const _ as *mut _,
10198                    &pd as *const _ as *mut _,
10199                    &nc as *const _ as *mut _,
10200                    &e as *const _ as *mut _,
10201                ];
10202                unsafe {
10203                    self.launch_pdl(
10204                        "rms_pre_add_rms_norm_q8z_f32",
10205                        (nrows as u32, 1, 1),
10206                        (rms_block(), 1, 1),
10207                        &mut ps,
10208                    )?;
10209                }
10210            }
10211            return Ok((out_q, out_d));
10212        }
10213        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
10214        let cfg = LaunchConfig {
10215            grid_dim: (nrows as u32, 1, 1),
10216            block_dim: (rms_block(), 1, 1),
10217            shared_mem_bytes: 0,
10218        };
10219        let __s_b2 = self.gpu.stream();
10220        let mut b2 = __s_b2.launch_builder(&f);
10221        b2.arg(a)
10222            .arg(wa)
10223            .arg(b)
10224            .arg(w)
10225            .arg(&mut *res)
10226            .arg(&mut *dst)
10227            .arg(&mut out_q)
10228            .arg(&mut out_d)
10229            .arg(&nc)
10230            .arg(&e);
10231        unsafe {
10232            b2.launch(cfg)?;
10233        }
10234        Ok((out_q, out_d))
10235    }
10236
10237    /// Slot-fed twin of `rms_pre_add_rms_norm_q8z` (gemma4 pn-fold, slotted/graph arm):
10238    /// identical kernel, caller-owned outputs, PLAIN launch only — the dc_slotted capture
10239    /// body must stay attribute-free (the fused2_into precedent).
10240    #[allow(clippy::too_many_arguments)]
10241    pub fn rms_pre_add_rms_norm_q8z_into(
10242        &self,
10243        a: &CudaSlice<f32>,
10244        wa: &CudaSlice<f32>,
10245        b: &CudaSlice<f32>,
10246        w: &CudaSlice<f32>,
10247        res: &mut CudaSlice<f32>,
10248        dst: &mut CudaSlice<f32>,
10249        ncols: usize,
10250        nrows: usize,
10251        eps: f32,
10252        out_q: &mut CudaSlice<i8>,
10253        out_d: &mut CudaSlice<f32>,
10254    ) -> Result<(), Box<dyn std::error::Error>> {
10255        debug_assert!(ncols % 128 == 0);
10256        let (nc, e) = (ncols as i32, eps);
10257        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
10258        let cfg = LaunchConfig {
10259            grid_dim: (nrows as u32, 1, 1),
10260            block_dim: (rms_block(), 1, 1),
10261            shared_mem_bytes: 0,
10262        };
10263        let __s_b = self.gpu.stream();
10264        let mut b2 = __s_b.launch_builder(&f);
10265        b2.arg(a)
10266            .arg(wa)
10267            .arg(b)
10268            .arg(w)
10269            .arg(&mut *res)
10270            .arg(&mut *dst)
10271            .arg(&mut *out_q)
10272            .arg(&mut *out_d)
10273            .arg(&nc)
10274            .arg(&e);
10275        unsafe {
10276            b2.launch(cfg)?;
10277        }
10278        Ok(())
10279    }
10280
10281    /// Slot-fed twin of `rms_pre_add_scale_rms_norm_q8_1` (gemma4 pn-fold exit, slotted
10282    /// arm): identical kernel, caller-owned outputs, PLAIN launch (capture-safe).
10283    #[allow(clippy::too_many_arguments)]
10284    pub fn rms_pre_add_scale_rms_norm_q8_1_into(
10285        &self,
10286        a: &CudaSlice<f32>,
10287        wa: &CudaSlice<f32>,
10288        b_in: &CudaSlice<f32>,
10289        c: f32,
10290        w: &CudaSlice<f32>,
10291        res: &mut CudaSlice<f32>,
10292        ncols: usize,
10293        nrows: usize,
10294        eps: f32,
10295        out_q: &mut CudaSlice<i8>,
10296        out_d: &mut CudaSlice<f32>,
10297    ) -> Result<(), Box<dyn std::error::Error>> {
10298        debug_assert!(ncols % 128 == 0);
10299        let (nc, e2) = (ncols as i32, eps);
10300        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
10301        let cfg = LaunchConfig {
10302            grid_dim: (nrows as u32, 1, 1),
10303            block_dim: (rms_block(), 1, 1),
10304            shared_mem_bytes: 0,
10305        };
10306        let __s_b = self.gpu.stream();
10307        let mut b2 = __s_b.launch_builder(&f);
10308        b2.arg(a)
10309            .arg(wa)
10310            .arg(b_in)
10311            .arg(&c)
10312            .arg(w)
10313            .arg(&mut *res)
10314            .arg(&mut *out_q)
10315            .arg(&mut *out_d)
10316            .arg(&nc)
10317            .arg(&e2);
10318        unsafe {
10319            b2.launch(cfg)?;
10320        }
10321        Ok(())
10322    }
10323
10324    /// gemma4 pn-fold seam (GAP-DIAGNOSIS verdict 7, the E4B glue backport): the dense
10325    /// decode/verify/slotted trio folds post_attn_norm into the tail entry
10326    /// (rms_pre_add_rms_norm[_q8z]) and post_ffw_norm into the residual exit
10327    /// (rms_pre_add_scale_rms_norm_q8_1). BITS-CHANGING vs the two-launch chain (the
10328    /// single-phase reduction's expansion rounding — E4B receipts); every arm moves
10329    /// together so decode == verify == graph parity holds BY CONSTRUCTION within either
10330    /// seam value. MEMRA_G4_PNFOLD=0 restores the unfused chain everywhere.
10331    pub fn g4_pnfold_on() -> bool {
10332        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10333        *ON.get_or_init(|| {
10334            std::env::var("MEMRA_G4_PNFOLD")
10335                .map(|v| v != "0")
10336                .unwrap_or(true)
10337        })
10338    }
10339
10340    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
10341    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
10342    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
10343    pub fn build_q4_out_concat3(
10344        &self,
10345        w0: &crate::model::GpuTensor,
10346        w1: &crate::model::GpuTensor,
10347        w2: &crate::model::GpuTensor,
10348    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
10349        use crate::model::GpuTensor;
10350        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
10351            match w {
10352                GpuTensor::Quant {
10353                    qtype,
10354                    row_bytes,
10355                    rp,
10356                    ..
10357                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
10358                _ => None,
10359            }
10360        };
10361        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
10362        else {
10363            return Ok(None);
10364        };
10365        if rb0 != rb1
10366            || rb0 != rb2
10367            || w0.in_features() != w1.in_features()
10368            || w0.in_features() != w2.in_features()
10369        {
10370            return Ok(None);
10371        }
10372        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
10373            match w {
10374                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
10375                _ => unreachable!(),
10376            }
10377        }
10378        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
10379        let total = rb0 * (o0 + o1 + o2);
10380        let mut cat = self.alloc_u8(total)?;
10381        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
10382        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
10383        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
10384        Ok(Some(GpuTensor::Quant {
10385            bytes: cat,
10386            qtype: QT_Q4_0,
10387            row_bytes: rb0,
10388            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
10389            scale: 1.0,
10390            rp: false,
10391            #[cfg(memra_cutlass)]
10392            cutlass: None,
10393            fp8: None,
10394            blk: None,
10395            rp4: None,
10396            f16: None,
10397        }))
10398    }
10399
10400    /// FULL-WIDTH-ROPE CONTRACT for the fused rms_norm+qkv+rope kernels
10401    /// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up).
10402    ///
10403    /// `rms_norm_qkv_rope_f32` / `_cat_f32` (`cu/kernels.cu`) and
10404    /// `rms_norm_qkv_rope_append_body` (`cu/flash_attn.cu`) take NO `n_dims`/`n_rot` argument.
10405    /// They compute `int half = ncols / 2` and rotate the FULL head width by construction — the
10406    /// standalone `rope_neox*` kernels take `n_dims` and early-return above it, these do not.
10407    ///
10408    /// Every call site today is a gemma-4 arm, where `n_rot == head_dim`, so nothing is wrong
10409    /// now. But this is the n_rot bug ONE FUSION away: fuse a partial-rotary arch onto these
10410    /// kernels (qwen3.5 = 64 rotary dims of a 256-wide head; step35 full-attn = 64 of 128) and
10411    /// 192 dims that must pass through unrotated get rotated silently — no error, no NaN, just a
10412    /// wrong model. The n_rot lane already paid for that class once, in the config derivation.
10413    ///
10414    /// So the fusions now take the layer's DERIVED rope width and refuse anything but full
10415    /// width. A future partial-rotary caller fails at its first launch with the geometry named
10416    /// instead of serving quietly wrong logits.
10417    fn full_width_rope_only(
10418        kernel: &str,
10419        n_rot: usize,
10420        head_dim: usize,
10421    ) -> Result<(), Box<dyn std::error::Error>> {
10422        if n_rot == head_dim {
10423            return Ok(());
10424        }
10425        Err(format!(
10426            "{kernel}: PARTIAL ROTARY REFUSED — n_rot {n_rot} != head_dim {head_dim}. This fused \
10427             rms_norm+qkv+rope kernel carries no n_dims parameter and rotates the full head \
10428             width (half = ncols/2), so it would rotate dims {n_rot}..{head_dim} that must pass \
10429             through unrotated. Use the split path (rms_norm_qkv + rope_neox/rope_neox2 with \
10430             n_dims={n_rot}), or add an n_dims early-return to the kernel and widen this guard."
10431        )
10432        .into())
10433    }
10434
10435    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
10436    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
10437    /// ([`Engine::full_width_rope_only`]).
10438    #[allow(clippy::too_many_arguments)]
10439    pub fn rms_norm_qkv_rope_cat(
10440        &self,
10441        qkv: &CudaSlice<f32>,
10442        wq: &CudaSlice<f32>,
10443        wk: &CudaSlice<f32>,
10444        wv: &CudaSlice<f32>,
10445        q: &mut CudaSlice<f32>,
10446        k: &mut CudaSlice<f32>,
10447        v: &mut CudaSlice<f32>,
10448        head_dim: usize,
10449        n_rot: usize,
10450        rq: usize,
10451        rk: usize,
10452        pos: &CudaSlice<i32>,
10453        nh_q: usize,
10454        nh_k: usize,
10455        base: f32,
10456        freq_scale: f32,
10457        ff: Option<&CudaSlice<f32>>,
10458        eps: f32,
10459    ) -> Result<(), Box<dyn std::error::Error>> {
10460        Self::full_width_rope_only("rms_norm_qkv_rope_cat", n_rot, head_dim)?;
10461        let rows = rq + rk + rk;
10462        let theta_scale = base.powf(-2.0 / head_dim as f32);
10463        let (nc, rqi, rki, nhq, nhk) = (
10464            head_dim as i32,
10465            rq as i32,
10466            rk as i32,
10467            nh_q as i32,
10468            nh_k as i32,
10469        );
10470        if Self::pdl_on() {
10471            use cudarc::driver::{DevicePtr, DevicePtrMut};
10472            let s = &self.gpu.stream();
10473            let (pqkv, _g0) = qkv.device_ptr(s);
10474            let (pwq, _g1) = wq.device_ptr(s);
10475            let (pwk, _g2) = wk.device_ptr(s);
10476            let (pwv, _g3) = wv.device_ptr(s);
10477            let (pq, _g4) = q.device_ptr_mut(s);
10478            let (pk, _g5) = k.device_ptr_mut(s);
10479            let (pv, _g6) = v.device_ptr_mut(s);
10480            let (ppos, _g7) = pos.device_ptr(s);
10481            let (pff, _g8) = match ff {
10482                Some(t) => {
10483                    let (p, g) = t.device_ptr(s);
10484                    (p, Some(g))
10485                }
10486                None => (0, None),
10487            };
10488            let mut ps = [
10489                &pqkv as *const _ as *mut std::ffi::c_void,
10490                &pwq as *const _ as *mut _,
10491                &pwk as *const _ as *mut _,
10492                &pwv as *const _ as *mut _,
10493                &pq as *const _ as *mut _,
10494                &pk as *const _ as *mut _,
10495                &pv as *const _ as *mut _,
10496                &nc as *const _ as *mut _,
10497                &rqi as *const _ as *mut _,
10498                &rki as *const _ as *mut _,
10499                &ppos as *const _ as *mut _,
10500                &nhq as *const _ as *mut _,
10501                &nhk as *const _ as *mut _,
10502                &theta_scale as *const _ as *mut _,
10503                &freq_scale as *const _ as *mut _,
10504                &pff as *const _ as *mut _,
10505                &eps as *const _ as *mut _,
10506            ];
10507            unsafe {
10508                self.launch_pdl(
10509                    "rms_norm_qkv_rope_cat_f32",
10510                    (rows as u32, 1, 1),
10511                    (rms_block(), 1, 1),
10512                    &mut ps,
10513                )?;
10514            }
10515            return Ok(());
10516        }
10517        let f = self.func("rms_norm_qkv_rope_cat_f32");
10518        let cfg = LaunchConfig {
10519            grid_dim: (rows as u32, 1, 1),
10520            block_dim: (rms_block(), 1, 1),
10521            shared_mem_bytes: 0,
10522        };
10523        let __s_b = self.gpu.stream();
10524        let mut b = __s_b.launch_builder(&f);
10525        match ff {
10526            Some(t) => {
10527                b.arg(qkv)
10528                    .arg(wq)
10529                    .arg(wk)
10530                    .arg(wv)
10531                    .arg(&mut *q)
10532                    .arg(&mut *k)
10533                    .arg(&mut *v)
10534                    .arg(&nc)
10535                    .arg(&rqi)
10536                    .arg(&rki)
10537                    .arg(pos)
10538                    .arg(&nhq)
10539                    .arg(&nhk)
10540                    .arg(&theta_scale)
10541                    .arg(&freq_scale)
10542                    .arg(t)
10543                    .arg(&eps);
10544                unsafe {
10545                    b.launch(cfg)?;
10546                }
10547            }
10548            None => {
10549                let null: u64 = 0;
10550                b.arg(qkv)
10551                    .arg(wq)
10552                    .arg(wk)
10553                    .arg(wv)
10554                    .arg(&mut *q)
10555                    .arg(&mut *k)
10556                    .arg(&mut *v)
10557                    .arg(&nc)
10558                    .arg(&rqi)
10559                    .arg(&rki)
10560                    .arg(pos)
10561                    .arg(&nhq)
10562                    .arg(&nhk)
10563                    .arg(&theta_scale)
10564                    .arg(&freq_scale)
10565                    .arg(&null)
10566                    .arg(&eps);
10567                unsafe {
10568                    b.launch(cfg)?;
10569                }
10570            }
10571        }
10572        Ok(())
10573    }
10574
10575    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
10576    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
10577    /// ([`Engine::full_width_rope_only`]).
10578    #[allow(clippy::too_many_arguments)]
10579    pub fn rms_norm_qkv_rope(
10580        &self,
10581        q0: &CudaSlice<f32>,
10582        k0: &CudaSlice<f32>,
10583        v0: &CudaSlice<f32>,
10584        wq: &CudaSlice<f32>,
10585        wk: &CudaSlice<f32>,
10586        wv: &CudaSlice<f32>,
10587        q: &mut CudaSlice<f32>,
10588        k: &mut CudaSlice<f32>,
10589        v: &mut CudaSlice<f32>,
10590        head_dim: usize,
10591        n_rot: usize,
10592        rq: usize,
10593        rk: usize,
10594        pos: &CudaSlice<i32>,
10595        nh_q: usize,
10596        nh_k: usize,
10597        base: f32,
10598        freq_scale: f32,
10599        ff: Option<&CudaSlice<f32>>,
10600        eps: f32,
10601    ) -> Result<(), Box<dyn std::error::Error>> {
10602        Self::full_width_rope_only("rms_norm_qkv_rope", n_rot, head_dim)?;
10603        let f = self.func("rms_norm_qkv_rope_f32");
10604        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
10605        let cfg = LaunchConfig {
10606            grid_dim: (rows as u32, 1, 1),
10607            block_dim: (rms_block(), 1, 1),
10608            shared_mem_bytes: 0,
10609        };
10610        let theta_scale = base.powf(-2.0 / head_dim as f32);
10611        let (nc, rqi, rki, nhq, nhk) = (
10612            head_dim as i32,
10613            rq as i32,
10614            rk as i32,
10615            nh_q as i32,
10616            nh_k as i32,
10617        );
10618        let __s_b = self.gpu.stream();
10619        let mut b = __s_b.launch_builder(&f);
10620        match ff {
10621            Some(t) => {
10622                b.arg(q0)
10623                    .arg(k0)
10624                    .arg(v0)
10625                    .arg(wq)
10626                    .arg(wk)
10627                    .arg(wv)
10628                    .arg(&mut *q)
10629                    .arg(&mut *k)
10630                    .arg(&mut *v)
10631                    .arg(&nc)
10632                    .arg(&rqi)
10633                    .arg(&rki)
10634                    .arg(pos)
10635                    .arg(&nhq)
10636                    .arg(&nhk)
10637                    .arg(&theta_scale)
10638                    .arg(&freq_scale)
10639                    .arg(t)
10640                    .arg(&eps);
10641                unsafe {
10642                    b.launch(cfg)?;
10643                }
10644            }
10645            None => {
10646                let null: u64 = 0;
10647                b.arg(q0)
10648                    .arg(k0)
10649                    .arg(v0)
10650                    .arg(wq)
10651                    .arg(wk)
10652                    .arg(wv)
10653                    .arg(&mut *q)
10654                    .arg(&mut *k)
10655                    .arg(&mut *v)
10656                    .arg(&nc)
10657                    .arg(&rqi)
10658                    .arg(&rki)
10659                    .arg(pos)
10660                    .arg(&nhq)
10661                    .arg(&nhk)
10662                    .arg(&theta_scale)
10663                    .arg(&freq_scale)
10664                    .arg(&null)
10665                    .arg(&eps);
10666                unsafe {
10667                    b.launch(cfg)?;
10668                }
10669            }
10670        }
10671        Ok(())
10672    }
10673
10674    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
10675    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
10676    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
10677    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
10678    /// ([`Engine::full_width_rope_only`]).
10679    #[allow(clippy::too_many_arguments)]
10680    pub fn rms_norm_qkv_rope_append_dc(
10681        &self,
10682        q0: &CudaSlice<f32>,
10683        k0: &CudaSlice<f32>,
10684        v0: &CudaSlice<f32>,
10685        wq: &CudaSlice<f32>,
10686        wk: &CudaSlice<f32>,
10687        wv: &CudaSlice<f32>,
10688        q: &mut CudaSlice<f32>,
10689        k: &mut CudaSlice<f32>,
10690        v: &mut CudaSlice<f32>,
10691        head_dim: usize,
10692        n_rot: usize,
10693        rq: usize,
10694        rk: usize,
10695        pos: &CudaSlice<i32>,
10696        nh_q: usize,
10697        nh_k: usize,
10698        base: f32,
10699        freq_scale: f32,
10700        ff: Option<&CudaSlice<f32>>,
10701        eps: f32,
10702        kc: &mut CudaSlice<u8>,
10703        vc: &mut CudaSlice<u8>,
10704        t_dev: &CudaSlice<i32>,
10705        k_tok_bytes: usize,
10706        v_tok_bytes: usize,
10707        g: bool,
10708    ) -> Result<(), Box<dyn std::error::Error>> {
10709        Self::full_width_rope_only("rms_norm_qkv_rope_append_dc", n_rot, head_dim)?;
10710        let rows = rq + rk + rk;
10711        let theta_scale = base.powf(-2.0 / head_dim as f32);
10712        let (nc, rqi, rki, nhq, nhk) = (
10713            head_dim as i32,
10714            rq as i32,
10715            rk as i32,
10716            nh_q as i32,
10717            nh_k as i32,
10718        );
10719        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10720        if Self::pdl_on() && Self::pdl_wb_on() {
10721            use cudarc::driver::{DevicePtr, DevicePtrMut};
10722            let s = &self.gpu.stream();
10723            let (p0, _a0) = q0.device_ptr(s);
10724            let (p1, _a1) = k0.device_ptr(s);
10725            let (p2, _a2) = v0.device_ptr(s);
10726            let (pwq, _a3) = wq.device_ptr(s);
10727            let (pwk, _a4) = wk.device_ptr(s);
10728            let (pwv, _a5) = wv.device_ptr(s);
10729            let (pq, _a6) = q.device_ptr_mut(s);
10730            let (pk, _a7) = k.device_ptr_mut(s);
10731            let (pv, _a8) = v.device_ptr_mut(s);
10732            let (pp, _a9) = pos.device_ptr(s);
10733            let pff: u64 = match ff {
10734                Some(t) => {
10735                    let (p, _gg) = t.device_ptr(s);
10736                    p as u64
10737                }
10738                None => 0,
10739            };
10740            let (pkc, _a10) = kc.device_ptr_mut(s);
10741            let (pvc, _a11) = vc.device_ptr_mut(s);
10742            let (pt, _a12) = t_dev.device_ptr(s);
10743            let mut ps = [
10744                &p0 as *const _ as *mut std::ffi::c_void,
10745                &p1 as *const _ as *mut _,
10746                &p2 as *const _ as *mut _,
10747                &pwq as *const _ as *mut _,
10748                &pwk as *const _ as *mut _,
10749                &pwv as *const _ as *mut _,
10750                &pq as *const _ as *mut _,
10751                &pk as *const _ as *mut _,
10752                &pv as *const _ as *mut _,
10753                &nc as *const _ as *mut _,
10754                &rqi as *const _ as *mut _,
10755                &rki as *const _ as *mut _,
10756                &pp as *const _ as *mut _,
10757                &nhq as *const _ as *mut _,
10758                &nhk as *const _ as *mut _,
10759                &theta_scale as *const _ as *mut _,
10760                &freq_scale as *const _ as *mut _,
10761                &pff as *const _ as *mut _,
10762                &eps as *const _ as *mut _,
10763                &pkc as *const _ as *mut _,
10764                &pvc as *const _ as *mut _,
10765                &pt as *const _ as *mut _,
10766                &ktb as *const _ as *mut _,
10767                &vtb as *const _ as *mut _,
10768            ];
10769            unsafe {
10770                self.launch_pdl_flash(
10771                    g,
10772                    "rms_norm_qkv_rope_append_dc_f32",
10773                    (rows as u32, 1, 1),
10774                    (rms_block(), 1, 1),
10775                    0,
10776                    &mut ps,
10777                )?;
10778            }
10779            return Ok(());
10780        }
10781        let f = if g {
10782            self.func_g("rms_norm_qkv_rope_append_dc_f32")
10783        } else {
10784            self.func("rms_norm_qkv_rope_append_dc_f32")
10785        };
10786        let cfg = LaunchConfig {
10787            grid_dim: (rows as u32, 1, 1),
10788            block_dim: (rms_block(), 1, 1),
10789            shared_mem_bytes: 0,
10790        };
10791        let __s_b = self.gpu.stream();
10792        let mut b = __s_b.launch_builder(&f);
10793        match ff {
10794            Some(t) => {
10795                b.arg(q0)
10796                    .arg(k0)
10797                    .arg(v0)
10798                    .arg(wq)
10799                    .arg(wk)
10800                    .arg(wv)
10801                    .arg(&mut *q)
10802                    .arg(&mut *k)
10803                    .arg(&mut *v)
10804                    .arg(&nc)
10805                    .arg(&rqi)
10806                    .arg(&rki)
10807                    .arg(pos)
10808                    .arg(&nhq)
10809                    .arg(&nhk)
10810                    .arg(&theta_scale)
10811                    .arg(&freq_scale)
10812                    .arg(t)
10813                    .arg(&eps)
10814                    .arg(&mut *kc)
10815                    .arg(&mut *vc)
10816                    .arg(t_dev)
10817                    .arg(&ktb)
10818                    .arg(&vtb);
10819                unsafe {
10820                    b.launch(cfg)?;
10821                }
10822            }
10823            None => {
10824                let null: u64 = 0;
10825                b.arg(q0)
10826                    .arg(k0)
10827                    .arg(v0)
10828                    .arg(wq)
10829                    .arg(wk)
10830                    .arg(wv)
10831                    .arg(&mut *q)
10832                    .arg(&mut *k)
10833                    .arg(&mut *v)
10834                    .arg(&nc)
10835                    .arg(&rqi)
10836                    .arg(&rki)
10837                    .arg(pos)
10838                    .arg(&nhq)
10839                    .arg(&nhk)
10840                    .arg(&theta_scale)
10841                    .arg(&freq_scale)
10842                    .arg(&null)
10843                    .arg(&eps)
10844                    .arg(&mut *kc)
10845                    .arg(&mut *vc)
10846                    .arg(t_dev)
10847                    .arg(&ktb)
10848                    .arg(&vtb);
10849                unsafe {
10850                    b.launch(cfg)?;
10851                }
10852            }
10853        }
10854        Ok(())
10855    }
10856
10857    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
10858    /// Host-len twin of `rms_norm_qkv_rope_append_dc` (zoo-fusion arc): the eager decode
10859    /// arm tracks kv length on host (`kvl.len`), so the append slot rides the launch arg
10860    /// instead of the device counter. Kernel body is SHARED with the _dc entry (one
10861    /// inlined body — bit-identical to the rms_norm_qkv_rope + append pair it replaces,
10862    /// same law as the dc fold). `n_rot` is the layer's derived rotary width and MUST equal
10863    /// `head_dim` ([`Engine::full_width_rope_only`]).
10864    #[allow(clippy::too_many_arguments)]
10865    pub fn rms_norm_qkv_rope_append(
10866        &self,
10867        q0: &CudaSlice<f32>,
10868        k0: &CudaSlice<f32>,
10869        v0: &CudaSlice<f32>,
10870        wq: &CudaSlice<f32>,
10871        wk: &CudaSlice<f32>,
10872        wv: &CudaSlice<f32>,
10873        q: &mut CudaSlice<f32>,
10874        k: &mut CudaSlice<f32>,
10875        v: &mut CudaSlice<f32>,
10876        head_dim: usize,
10877        n_rot: usize,
10878        rq: usize,
10879        rk: usize,
10880        pos: &CudaSlice<i32>,
10881        nh_q: usize,
10882        nh_k: usize,
10883        base: f32,
10884        freq_scale: f32,
10885        ff: Option<&CudaSlice<f32>>,
10886        eps: f32,
10887        kc: &mut CudaSlice<u8>,
10888        vc: &mut CudaSlice<u8>,
10889        t: usize,
10890        k_tok_bytes: usize,
10891        v_tok_bytes: usize,
10892        g: bool,
10893    ) -> Result<(), Box<dyn std::error::Error>> {
10894        Self::full_width_rope_only("rms_norm_qkv_rope_append", n_rot, head_dim)?;
10895        let rows = rq + rk + rk;
10896        let theta_scale = base.powf(-2.0 / head_dim as f32);
10897        let (nc, rqi, rki, nhq, nhk) = (
10898            head_dim as i32,
10899            rq as i32,
10900            rk as i32,
10901            nh_q as i32,
10902            nh_k as i32,
10903        );
10904        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
10905        let ti = t as i32;
10906        if Self::pdl_on() && Self::pdl_wb_on() {
10907            use cudarc::driver::{DevicePtr, DevicePtrMut};
10908            let s = &self.gpu.stream();
10909            let (p0, _a0) = q0.device_ptr(s);
10910            let (p1, _a1) = k0.device_ptr(s);
10911            let (p2, _a2) = v0.device_ptr(s);
10912            let (pwq, _a3) = wq.device_ptr(s);
10913            let (pwk, _a4) = wk.device_ptr(s);
10914            let (pwv, _a5) = wv.device_ptr(s);
10915            let (pq, _a6) = q.device_ptr_mut(s);
10916            let (pk, _a7) = k.device_ptr_mut(s);
10917            let (pv, _a8) = v.device_ptr_mut(s);
10918            let (pp, _a9) = pos.device_ptr(s);
10919            let pff: u64 = match ff {
10920                Some(t) => {
10921                    let (p, _gg) = t.device_ptr(s);
10922                    p as u64
10923                }
10924                None => 0,
10925            };
10926            let (pkc, _a10) = kc.device_ptr_mut(s);
10927            let (pvc, _a11) = vc.device_ptr_mut(s);
10928            let mut ps = [
10929                &p0 as *const _ as *mut std::ffi::c_void,
10930                &p1 as *const _ as *mut _,
10931                &p2 as *const _ as *mut _,
10932                &pwq as *const _ as *mut _,
10933                &pwk as *const _ as *mut _,
10934                &pwv as *const _ as *mut _,
10935                &pq as *const _ as *mut _,
10936                &pk as *const _ as *mut _,
10937                &pv as *const _ as *mut _,
10938                &nc as *const _ as *mut _,
10939                &rqi as *const _ as *mut _,
10940                &rki as *const _ as *mut _,
10941                &pp as *const _ as *mut _,
10942                &nhq as *const _ as *mut _,
10943                &nhk as *const _ as *mut _,
10944                &theta_scale as *const _ as *mut _,
10945                &freq_scale as *const _ as *mut _,
10946                &pff as *const _ as *mut _,
10947                &eps as *const _ as *mut _,
10948                &pkc as *const _ as *mut _,
10949                &pvc as *const _ as *mut _,
10950                &ti as *const _ as *mut _,
10951                &ktb as *const _ as *mut _,
10952                &vtb as *const _ as *mut _,
10953            ];
10954            unsafe {
10955                self.launch_pdl_flash(
10956                    g,
10957                    "rms_norm_qkv_rope_append_f32",
10958                    (rows as u32, 1, 1),
10959                    (rms_block(), 1, 1),
10960                    0,
10961                    &mut ps,
10962                )?;
10963            }
10964            return Ok(());
10965        }
10966        let f = if g {
10967            self.func_g("rms_norm_qkv_rope_append_f32")
10968        } else {
10969            self.func("rms_norm_qkv_rope_append_f32")
10970        };
10971        let cfg = LaunchConfig {
10972            grid_dim: (rows as u32, 1, 1),
10973            block_dim: (rms_block(), 1, 1),
10974            shared_mem_bytes: 0,
10975        };
10976        let __s_b = self.gpu.stream();
10977        let mut b = __s_b.launch_builder(&f);
10978        let null: u64 = 0;
10979        b.arg(q0)
10980            .arg(k0)
10981            .arg(v0)
10982            .arg(wq)
10983            .arg(wk)
10984            .arg(wv)
10985            .arg(&mut *q)
10986            .arg(&mut *k)
10987            .arg(&mut *v)
10988            .arg(&nc)
10989            .arg(&rqi)
10990            .arg(&rki)
10991            .arg(pos)
10992            .arg(&nhq)
10993            .arg(&nhk)
10994            .arg(&theta_scale)
10995            .arg(&freq_scale);
10996        match ff {
10997            Some(t) => {
10998                b.arg(t);
10999            }
11000            None => {
11001                b.arg(&null);
11002            }
11003        }
11004        b.arg(&eps)
11005            .arg(&mut *kc)
11006            .arg(&mut *vc)
11007            .arg(&ti)
11008            .arg(&ktb)
11009            .arg(&vtb);
11010        unsafe {
11011            b.launch(cfg)?;
11012        }
11013        Ok(())
11014    }
11015
11016    pub fn add_q8_1(
11017        &self,
11018        a: &CudaSlice<f32>,
11019        b: &CudaSlice<f32>,
11020        res: &mut CudaSlice<f32>,
11021        ncols: usize,
11022        nrows: usize,
11023    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11024        debug_assert!(ncols % 128 == 0);
11025        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
11026        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
11027        let f = self.func("add_q8_1_f32");
11028        let cfg = LaunchConfig {
11029            grid_dim: (nrows as u32, 1, 1),
11030            block_dim: (rms_block(), 1, 1),
11031            shared_mem_bytes: 0,
11032        };
11033        let nc = ncols as i32;
11034        let __s_b2 = self.gpu.stream();
11035        let mut b2 = __s_b2.launch_builder(&f);
11036        b2.arg(a)
11037            .arg(b)
11038            .arg(&mut *res)
11039            .arg(&mut out_q)
11040            .arg(&mut out_d)
11041            .arg(&nc);
11042        unsafe {
11043            b2.launch(cfg)?;
11044        }
11045        Ok((out_q, out_d))
11046    }
11047
11048    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
11049    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
11050    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
11051    pub fn rms_pre_add_q8_1(
11052        &self,
11053        a: &CudaSlice<f32>,
11054        wa: &CudaSlice<f32>,
11055        b: &CudaSlice<f32>,
11056        res: &mut CudaSlice<f32>,
11057        ncols: usize,
11058        nrows: usize,
11059        eps: f32,
11060    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11061        debug_assert!(ncols % 128 == 0);
11062        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
11063        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
11064        let f = self.func("rms_pre_add_q8_1_f32");
11065        let cfg = LaunchConfig {
11066            grid_dim: (nrows as u32, 1, 1),
11067            block_dim: (rms_block(), 1, 1),
11068            shared_mem_bytes: 0,
11069        };
11070        let (nc, ep) = (ncols as i32, eps);
11071        let __s_b2 = self.gpu.stream();
11072        let mut b2 = __s_b2.launch_builder(&f);
11073        b2.arg(a)
11074            .arg(wa)
11075            .arg(b)
11076            .arg(&mut *res)
11077            .arg(&mut out_q)
11078            .arg(&mut out_d)
11079            .arg(&nc)
11080            .arg(&ep);
11081        unsafe {
11082            b2.launch(cfg)?;
11083        }
11084        Ok((out_q, out_d))
11085    }
11086
11087    /// L2 norm per row (head_dim), no weight.
11088    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
11089    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
11090    pub fn l2_v2_on(ncols: usize) -> bool {
11091        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
11092    }
11093
11094    pub fn l2_norm_pp(
11095        &self,
11096        x: &CudaSlice<f32>,
11097        dst: &mut CudaSlice<f32>,
11098        dst16: Option<&mut CudaSlice<u8>>,
11099        ncols: usize,
11100        nrows: usize,
11101        eps: f32,
11102    ) -> Result<(), Box<dyn std::error::Error>> {
11103        if Self::l2_v2_on(ncols) {
11104            let f = self.func("l2_norm_pp_v2_f32");
11105            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
11106            let cfg = LaunchConfig {
11107                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
11108                block_dim: (256, 1, 1),
11109                shared_mem_bytes: 0,
11110            };
11111            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
11112            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
11113            let d16: u64 = match dst16 {
11114                Some(d) => self.addr_u8(d),
11115                None => 0,
11116            };
11117            let __s_b = self.gpu.stream();
11118            let mut b = __s_b.launch_builder(&f);
11119            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
11120            unsafe {
11121                b.launch(cfg)?;
11122            }
11123            return Ok(());
11124        }
11125        self.l2_norm(x, dst, ncols, nrows, eps)
11126    }
11127
11128    pub fn l2_norm(
11129        &self,
11130        x: &CudaSlice<f32>,
11131        dst: &mut CudaSlice<f32>,
11132        ncols: usize,
11133        nrows: usize,
11134        eps: f32,
11135    ) -> Result<(), Box<dyn std::error::Error>> {
11136        let f = self.func("l2_norm_f32");
11137        let cfg = LaunchConfig {
11138            grid_dim: (nrows as u32, 1, 1),
11139            block_dim: (256, 1, 1),
11140            shared_mem_bytes: 0,
11141        };
11142        let (nc, e) = (ncols as i32, eps);
11143        let __s_b = self.gpu.stream();
11144        let mut b = __s_b.launch_builder(&f);
11145        b.arg(x).arg(dst).arg(&nc).arg(&e);
11146        unsafe {
11147            b.launch(cfg)?;
11148        }
11149        Ok(())
11150    }
11151
11152    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
11153    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
11154    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
11155    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
11156    /// propagate through gdn_scan and flip argmax on marginal logits.
11157    pub fn l2_norm_decode(
11158        &self,
11159        x: &CudaSlice<f32>,
11160        dst: &mut CudaSlice<f32>,
11161        ncols: usize,
11162        nrows: usize,
11163        eps: f32,
11164    ) -> Result<(), Box<dyn std::error::Error>> {
11165        let f = self.func("l2_norm_f32");
11166        let cfg = LaunchConfig {
11167            grid_dim: (nrows as u32, 1, 1),
11168            block_dim: (32, 1, 1),
11169            shared_mem_bytes: 0,
11170        };
11171        let (nc, e) = (ncols as i32, eps);
11172        let __s_b = self.gpu.stream();
11173        let mut b = __s_b.launch_builder(&f);
11174        b.arg(x).arg(dst).arg(&nc).arg(&e);
11175        unsafe {
11176            b.launch(cfg)?;
11177        }
11178        Ok(())
11179    }
11180
11181    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
11182    pub fn rope_neox(
11183        &self,
11184        x: &mut CudaSlice<f32>,
11185        pos: &CudaSlice<i32>,
11186        head_dim: usize,
11187        n_dims: usize,
11188        n_heads: usize,
11189        n_tokens: usize,
11190        freq_base: f32,
11191        freq_scale: f32,
11192    ) -> Result<(), Box<dyn std::error::Error>> {
11193        let f = self.func("rope_neox_f32");
11194        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
11195        let grid = (n_heads * n_tokens) as u32;
11196        let cfg = LaunchConfig {
11197            grid_dim: (grid, 1, 1),
11198            block_dim: ((head_dim / 2) as u32, 1, 1),
11199            shared_mem_bytes: 0,
11200        };
11201        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
11202        let __s_b = self.gpu.stream();
11203        let mut b = __s_b.launch_builder(&f);
11204        b.arg(x)
11205            .arg(pos)
11206            .arg(&hd)
11207            .arg(&nd)
11208            .arg(&nh)
11209            .arg(&theta_scale)
11210            .arg(&freq_scale);
11211        unsafe {
11212            b.launch(cfg)?;
11213        }
11214        Ok(())
11215    }
11216
11217    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
11218    pub fn rope_neox_ff(
11219        &self,
11220        x: &mut CudaSlice<f32>,
11221        pos: &CudaSlice<i32>,
11222        head_dim: usize,
11223        n_dims: usize,
11224        n_heads: usize,
11225        n_tokens: usize,
11226        freq_base: f32,
11227        freq_scale: f32,
11228        ff: &CudaSlice<f32>,
11229    ) -> Result<(), Box<dyn std::error::Error>> {
11230        let f = self.func("rope_neox_ff_f32");
11231        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
11232        let grid = (n_heads * n_tokens) as u32;
11233        let cfg = LaunchConfig {
11234            grid_dim: (grid, 1, 1),
11235            block_dim: ((head_dim / 2) as u32, 1, 1),
11236            shared_mem_bytes: 0,
11237        };
11238        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
11239        let __s_b = self.gpu.stream();
11240        let mut b = __s_b.launch_builder(&f);
11241        b.arg(x)
11242            .arg(pos)
11243            .arg(&hd)
11244            .arg(&nd)
11245            .arg(&nh)
11246            .arg(&theta_scale)
11247            .arg(&freq_scale)
11248            .arg(ff);
11249        unsafe {
11250            b.launch(cfg)?;
11251        }
11252        Ok(())
11253    }
11254
11255    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
11256    #[allow(clippy::too_many_arguments)]
11257    pub fn rope_neox2(
11258        &self,
11259        q: &mut CudaSlice<f32>,
11260        k: &mut CudaSlice<f32>,
11261        pos: &CudaSlice<i32>,
11262        head_dim: usize,
11263        n_dims: usize,
11264        nh_q: usize,
11265        nh_k: usize,
11266        n_tokens: usize,
11267        freq_base: f32,
11268        freq_scale: f32,
11269        ff: Option<&CudaSlice<f32>>,
11270    ) -> Result<(), Box<dyn std::error::Error>> {
11271        let f = self.func("rope_neox2_f32");
11272        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
11273        let grid = ((nh_q + nh_k) * n_tokens) as u32;
11274        let cfg = LaunchConfig {
11275            grid_dim: (grid, 1, 1),
11276            block_dim: ((head_dim / 2) as u32, 1, 1),
11277            shared_mem_bytes: 0,
11278        };
11279        let (hd, nd, nq, nk, nt) = (
11280            head_dim as i32,
11281            n_dims as i32,
11282            nh_q as i32,
11283            nh_k as i32,
11284            n_tokens as i32,
11285        );
11286        let __s_b = self.gpu.stream();
11287        let mut b = __s_b.launch_builder(&f);
11288        b.arg(q)
11289            .arg(k)
11290            .arg(pos)
11291            .arg(&hd)
11292            .arg(&nd)
11293            .arg(&nq)
11294            .arg(&nk)
11295            .arg(&nt)
11296            .arg(&theta_scale)
11297            .arg(&freq_scale);
11298        match ff {
11299            Some(ffv) => {
11300                b.arg(ffv);
11301                unsafe {
11302                    b.launch(cfg)?;
11303                }
11304            }
11305            None => {
11306                let null: u64 = 0;
11307                b.arg(&null);
11308                unsafe {
11309                    b.launch(cfg)?;
11310                }
11311            }
11312        }
11313        Ok(())
11314    }
11315
11316    /// gemma4 R1: dst = GELU_tanh(gate) * up.
11317    pub fn gelu_tanh_mul(
11318        &self,
11319        gate: &CudaSlice<f32>,
11320        up: &CudaSlice<f32>,
11321        dst: &mut CudaSlice<f32>,
11322        n: usize,
11323    ) -> Result<(), Box<dyn std::error::Error>> {
11324        let f = self.func("gelu_tanh_mul_f32");
11325        let cfg = LaunchConfig::for_num_elems(n as u32);
11326        let ni = n as i32;
11327        let __s_b = self.gpu.stream();
11328        let mut b = __s_b.launch_builder(&f);
11329        b.arg(gate).arg(up).arg(dst).arg(&ni);
11330        unsafe {
11331            b.launch(cfg)?;
11332        }
11333        Ok(())
11334    }
11335
11336    pub fn silu_mul(
11337        &self,
11338        gate: &CudaSlice<f32>,
11339        up: &CudaSlice<f32>,
11340        dst: &mut CudaSlice<f32>,
11341        n: usize,
11342    ) -> Result<(), Box<dyn std::error::Error>> {
11343        let f = self.func("silu_mul_f32");
11344        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
11345        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
11346        let ni = n as i32;
11347        let __s_b = self.gpu.stream();
11348        let mut b = __s_b.launch_builder(&f);
11349        b.arg(gate).arg(up).arg(dst).arg(&ni);
11350        unsafe {
11351            b.launch(cfg)?;
11352        }
11353        Ok(())
11354    }
11355
11356    /// SwiGLU twin using Memra's host-matching expf transcription.
11357    pub fn silu_mul_host_expf(
11358        &self,
11359        gate: &CudaSlice<f32>,
11360        up: &CudaSlice<f32>,
11361        dst: &mut CudaSlice<f32>,
11362        n: usize,
11363    ) -> Result<(), Box<dyn std::error::Error>> {
11364        let f = self.func("silu_mul_host_expf_f32");
11365        let cfg = LaunchConfig::for_num_elems(n as u32);
11366        let ni = n as i32;
11367        let __s_b = self.gpu.stream();
11368        let mut b = __s_b.launch_builder(&f);
11369        b.arg(gate).arg(up).arg(dst).arg(&ni);
11370        unsafe {
11371            b.launch(cfg)?;
11372        }
11373        Ok(())
11374    }
11375
11376    /// Step routed-expert clamp twin using Memra's host-matching expf transcription.
11377    pub fn silu_clamped_mul_host_expf(
11378        &self,
11379        gate: &CudaSlice<f32>,
11380        up: &CudaSlice<f32>,
11381        limit: f32,
11382        dst: &mut CudaSlice<f32>,
11383        n: usize,
11384    ) -> Result<(), Box<dyn std::error::Error>> {
11385        if !limit.is_finite() || limit <= 0.0 {
11386            return Err(
11387                format!("Step routed-expert clamp limit must be positive, got {limit}").into(),
11388            );
11389        }
11390        let f = self.func("silu_clamped_mul_host_expf_f32");
11391        let cfg = LaunchConfig::for_num_elems(n as u32);
11392        let ni = n as i32;
11393        let __s_b = self.gpu.stream();
11394        let mut b = __s_b.launch_builder(&f);
11395        b.arg(gate).arg(up).arg(&limit).arg(dst).arg(&ni);
11396        unsafe {
11397            b.launch(cfg)?;
11398        }
11399        Ok(())
11400    }
11401
11402    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
11403    /// for the down projection — kills the standalone convert pass. Bit-identical class.
11404    pub fn silu_mul_f16out(
11405        &self,
11406        gate: &CudaSlice<f32>,
11407        up: &CudaSlice<f32>,
11408        dst: &mut CudaSlice<f32>,
11409        dst16: &mut CudaSlice<u8>,
11410        n: usize,
11411    ) -> Result<(), Box<dyn std::error::Error>> {
11412        let f = self.func("silu_mul_f16out_f32");
11413        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
11414        let ni = n as i32;
11415        let __s_b = self.gpu.stream();
11416        let mut b = __s_b.launch_builder(&f);
11417        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
11418        unsafe {
11419            b.launch(cfg)?;
11420        }
11421        Ok(())
11422    }
11423
11424    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
11425    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
11426    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
11427    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
11428    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
11429    /// launches per dense FFN layer (the gate+up post-matmul scales).
11430    pub fn silu_mul_scaled(
11431        &self,
11432        gate: &CudaSlice<f32>,
11433        up: &CudaSlice<f32>,
11434        gs: f32,
11435        us: f32,
11436        dst: &mut CudaSlice<f32>,
11437        n: usize,
11438    ) -> Result<(), Box<dyn std::error::Error>> {
11439        let f = self.func("silu_mul_scaled_f32");
11440        let cfg = LaunchConfig::for_num_elems(n as u32);
11441        let ni = n as i32;
11442        let (gsf, usf) = (gs, us);
11443        let __s_b = self.gpu.stream();
11444        let mut b = __s_b.launch_builder(&f);
11445        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
11446        unsafe {
11447            b.launch(cfg)?;
11448        }
11449        Ok(())
11450    }
11451
11452    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
11453    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
11454    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
11455    #[allow(clippy::too_many_arguments)]
11456    pub fn swigluoai_mul_scaled(
11457        &self,
11458        gate: &CudaSlice<f32>,
11459        up: &CudaSlice<f32>,
11460        gs: f32,
11461        us: f32,
11462        alpha: f32,
11463        limit: f32,
11464        dst: &mut CudaSlice<f32>,
11465        n: usize,
11466    ) -> Result<(), Box<dyn std::error::Error>> {
11467        let f = self.func("swigluoai_mul_scaled_f32");
11468        let cfg = LaunchConfig::for_num_elems(n as u32);
11469        let ni = n as i32;
11470        let __s_b = self.gpu.stream();
11471        let mut b = __s_b.launch_builder(&f);
11472        b.arg(gate)
11473            .arg(up)
11474            .arg(&gs)
11475            .arg(&us)
11476            .arg(&alpha)
11477            .arg(&limit)
11478            .arg(dst)
11479            .arg(&ni);
11480        unsafe {
11481            b.launch(cfg)?;
11482        }
11483        Ok(())
11484    }
11485
11486    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
11487    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
11488    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
11489    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
11490    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
11491    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
11492    /// n must be a multiple of 32 (n_ff always is).
11493    pub fn silu_mul_scaled_q8_1(
11494        &self,
11495        gate: &CudaSlice<f32>,
11496        up: &CudaSlice<f32>,
11497        gs: f32,
11498        us: f32,
11499        n: usize,
11500    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11501        let f = self.func("silu_mul_scaled_q8_1");
11502        let nblk = n / 32;
11503        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
11504        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
11505        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
11506        let cfg = LaunchConfig::for_num_elems(n as u32);
11507        let (gsf, usf, ni) = (gs, us, n as i32);
11508        let __s_b = self.gpu.stream();
11509        let mut b = __s_b.launch_builder(&f);
11510        b.arg(gate)
11511            .arg(up)
11512            .arg(&gsf)
11513            .arg(&usf)
11514            .arg(&mut aq)
11515            .arg(&mut ad)
11516            .arg(&ni);
11517        unsafe {
11518            b.launch(cfg)?;
11519        }
11520        Ok((aq, ad))
11521    }
11522
11523    pub fn add(
11524        &self,
11525        a: &CudaSlice<f32>,
11526        b_in: &CudaSlice<f32>,
11527        dst: &mut CudaSlice<f32>,
11528        n: usize,
11529    ) -> Result<(), Box<dyn std::error::Error>> {
11530        let f = self.func("add_f32");
11531        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
11532        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
11533        let ni = n as i32;
11534        let __s_bld = self.gpu.stream();
11535        let mut bld = __s_bld.launch_builder(&f);
11536        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
11537        unsafe {
11538            bld.launch(cfg)?;
11539        }
11540        Ok(())
11541    }
11542
11543    pub fn mul(
11544        &self,
11545        a: &CudaSlice<f32>,
11546        b_in: &CudaSlice<f32>,
11547        dst: &mut CudaSlice<f32>,
11548        n: usize,
11549    ) -> Result<(), Box<dyn std::error::Error>> {
11550        let f = self.func("mul_f32");
11551        let cfg = LaunchConfig::for_num_elems(n as u32);
11552        let ni = n as i32;
11553        let __s_bld = self.gpu.stream();
11554        let mut bld = __s_bld.launch_builder(&f);
11555        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
11556        unsafe {
11557            bld.launch(cfg)?;
11558        }
11559        Ok(())
11560    }
11561
11562    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
11563    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
11564    pub fn matmul(
11565        &self,
11566        w: &crate::model::GpuTensor,
11567        x: &CudaSlice<f32>,
11568        m: usize,
11569    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11570        use crate::model::GpuTensor;
11571        let in_f = w.in_features();
11572        let out_f = w.out_features();
11573        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
11574        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
11575        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
11576        // gives nothing). Quantize the activation once here then call the GEMM.
11577        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
11578        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
11579        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
11580        #[allow(non_snake_case)]
11581        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
11582        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
11583        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
11584            usize::MAX
11585        } else {
11586            16usize
11587        };
11588
11589        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
11590        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
11591        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
11592        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
11593        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
11594        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
11595        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
11596        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
11597        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
11598        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
11599        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
11600        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
11601        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
11602        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
11603        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
11604        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
11605        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
11606        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
11607        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
11608        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
11609        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
11610        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
11611        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
11612        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
11613        if m >= GEMM_M_THRESHOLD {
11614            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
11615                return Ok(y);
11616            }
11617            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
11618            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
11619            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
11620            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
11621            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
11622            // tile defaults differently by operand source.
11623            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
11624                return Ok(y);
11625            }
11626            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
11627            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
11628            if let Some(y) = self.try_f16_gemm(w, x, m)? {
11629                return Ok(y);
11630            }
11631        }
11632        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
11633        // m threshold the rest of this method uses:
11634        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
11635        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
11636        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
11637        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
11638        //     across every tier by construction with no batched twin needed.
11639        //
11640        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
11641        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
11642        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
11643        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
11644        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
11645        // arms is what makes sure it never gets there.
11646        if let GpuTensor::Quant { qtype, .. } = w {
11647            if *qtype == QT_F8_E4M3_BLK {
11648                if m >= GEMM_M_THRESHOLD {
11649                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
11650                        return Ok(y);
11651                    }
11652                }
11653                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11654                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
11655                    return Ok(y);
11656                }
11657            }
11658        }
11659        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
11660            return self.qmatvec_mmq(w, x, m);
11661        }
11662        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
11663            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11664            return self.qmatvec_gemm(w, &aq, &ad, m);
11665        }
11666        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
11667        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
11668        if m >= GEMM_M_THRESHOLD {
11669            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
11670                return Ok(y);
11671            }
11672        }
11673        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
11674        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
11675        // to Stage-A f32-dequant (the correctness oracle path).
11676        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
11677        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
11678        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
11679        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
11680        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
11681        if m == 1 && fast {
11682            if let GpuTensor::Quant {
11683                bytes,
11684                qtype,
11685                row_bytes,
11686                rp,
11687                rp4,
11688                scale,
11689                ..
11690            } = w
11691            {
11692                if self.mmvq_supports(*qtype) {
11693                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
11694                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
11695                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
11696                    let (bytes, rp) = match rp4 {
11697                        Some(m4) => (m4, true),
11698                        None => (bytes, *rp),
11699                    };
11700                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11701                    return self.qmatvec_mmvq(
11702                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
11703                    );
11704                }
11705            }
11706        }
11707        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
11708        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
11709        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
11710        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
11711        // block below. MEMRA_NO_BATCHED -> per-m path.
11712        //
11713        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
11714        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
11715        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
11716        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
11717        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
11718        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
11719        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
11720        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
11721        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
11722        if (2..=16).contains(&m)
11723            && fast
11724            && std::env::var("MEMRA_NO_BATCHED").is_err()
11725            && (m <= 4 || Self::b8_enabled())
11726        {
11727            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
11728            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
11729            // is present (rp4) — the mirror pick below then routes to the _rp family.
11730            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
11731            // because the native e4m3 row layout is already aligned and needs no mirror.
11732            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
11733            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
11734            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
11735            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
11736            let m_ok = m <= 8
11737                || matches!(w, GpuTensor::Quant { qtype, .. }
11738                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
11739                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
11740            if m_ok {
11741                if let GpuTensor::Quant {
11742                    bytes,
11743                    qtype,
11744                    row_bytes,
11745                    rp,
11746                    rp4,
11747                    ..
11748                } = w
11749                {
11750                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
11751                        let (bytes, rp) = match rp4 {
11752                            Some(m4) => (m4, true),
11753                            None => (bytes, *rp),
11754                        };
11755                        let mcols = Self::batched_mcols(m);
11756                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11757                        let mut y = self.qmatvec_mmvq_batched(
11758                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
11759                        )?;
11760                        if let GpuTensor::Quant { scale, .. } = w {
11761                            if *scale != 1.0 {
11762                                self.scale_inplace(&mut y, *scale, m * out_f)?;
11763                            }
11764                        }
11765                        return Ok(y);
11766                    }
11767                }
11768            }
11769        }
11770        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
11771        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
11772        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
11773        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
11774        // for this dtype, so the generic match below must never see it under `fast`.
11775        if fast {
11776            if let GpuTensor::Quant {
11777                bytes,
11778                qtype,
11779                row_bytes,
11780                scale,
11781                ..
11782            } = w
11783            {
11784                if *qtype == QT_F8_E4M3 {
11785                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11786                    return self.qmatvec_mmvq(
11787                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
11788                    );
11789                }
11790            }
11791        }
11792        let mut y = match w {
11793            GpuTensor::Quant {
11794                bytes,
11795                qtype,
11796                row_bytes,
11797                ..
11798            } if fast && *qtype == QT_Q8_0 => {
11799                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11800            }
11801            GpuTensor::Quant {
11802                bytes,
11803                qtype,
11804                row_bytes,
11805                ..
11806            } if fast && *qtype == QT_Q4_K => {
11807                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11808            }
11809            GpuTensor::Quant {
11810                bytes,
11811                qtype,
11812                row_bytes,
11813                ..
11814            } if fast && *qtype == QT_Q6_K => {
11815                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11816            }
11817            GpuTensor::Quant {
11818                bytes,
11819                qtype,
11820                row_bytes,
11821                ..
11822            } if fast && *qtype == QT_Q5_K => {
11823                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11824            }
11825            GpuTensor::Quant {
11826                bytes,
11827                qtype,
11828                row_bytes,
11829                ..
11830            } if fast && *qtype == QT_Q3_K => {
11831                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11832            }
11833            GpuTensor::Quant {
11834                bytes,
11835                qtype,
11836                row_bytes,
11837                rp,
11838                ..
11839            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
11840                if *rp {
11841                    "qmatvec_nvfp4_dp4a_rp"
11842                } else {
11843                    "qmatvec_nvfp4_dp4a"
11844                },
11845                &bytes.slice(0..bytes.len()),
11846                x,
11847                m,
11848                in_f,
11849                out_f,
11850                *row_bytes,
11851            )?,
11852            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
11853            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
11854            // anomaly (research/kat-anomaly-20260802/).
11855            GpuTensor::Quant {
11856                bytes,
11857                qtype,
11858                row_bytes,
11859                ..
11860            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
11861                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
11862            }
11863            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
11864            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
11865            // without first writing the matching kernel, or func() will panic
11866            // "kernel ... not in any fatbin".
11867            GpuTensor::Quant {
11868                bytes,
11869                qtype,
11870                row_bytes,
11871                rp,
11872                ..
11873            } =>
11874            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
11875            // deq(row,j) form cannot address the planes; same value/product order).
11876            {
11877                self.qmatvec(
11878                    bytes,
11879                    x,
11880                    m,
11881                    in_f,
11882                    out_f,
11883                    if *rp && *qtype == QT_NVFP4 {
11884                        QT_NVFP4_RP
11885                    } else {
11886                        *qtype
11887                    },
11888                    *row_bytes,
11889                )?
11890            }
11891            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
11892            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
11893            // cuBLASLt f32 GEMV as the Float arm.
11894            GpuTensor::FloatBf16 { data, .. } => {
11895                self.linear_bf16_chunked(x, data, m, in_f, out_f, false, None)?
11896            }
11897        };
11898        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
11899        if let GpuTensor::Quant { scale, .. } = w {
11900            if *scale != 1.0 {
11901                self.scale_inplace(&mut y, *scale, m * out_f)?;
11902            }
11903        }
11904        Ok(y)
11905    }
11906
11907    /// True when `MEMRA_FAST=0`, i.e. the Stage-A f32 oracle is the requested arithmetic and every
11908    /// `matmul_pre` call will take the raw-f32 escape rather than the q8_1 pair.
11909    ///
11910    /// WHY THIS EXISTS AS ITS OWN PREDICATE: `uses_q8_1_fast` needs a weight, but the callers that
11911    /// have to DECIDE WHETHER TO MATERIALIZE an f32 activation sit one level above any weight — the
11912    /// gemma-4 decode trunk emits a q8_1 pair per layer and hands it down, so the f32 has to be
11913    /// produced (or not) by the loop that owns the residual. Cached in a OnceLock like every other
11914    /// `*_on()` flag here: this is read once per layer per token on the decode path, and the daily
11915    /// path must not pay an env lookup for a flag that is off.
11916    pub fn stage_a_raw_needed() -> bool {
11917        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11918        *ON.get_or_init(|| std::env::var("MEMRA_FAST").as_deref() == Ok("0"))
11919    }
11920
11921    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
11922    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
11923    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
11924        use crate::model::GpuTensor;
11925        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
11926            return false;
11927        }
11928        match w {
11929            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
11930            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
11931            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
11932            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
11933            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
11934            // block class has no fused twin yet, so each of its projections takes its own launch.
11935            GpuTensor::Quant { qtype, .. } => {
11936                matches!(
11937                    *qtype,
11938                    QT_Q8_0
11939                        | QT_Q4_K
11940                        | QT_Q6_K
11941                        | QT_Q5_K
11942                        | QT_Q3_K
11943                        | QT_NVFP4
11944                        | QT_F8_E4M3
11945                        | QT_F8_E4M3_BLK
11946                        | QT_Q4_0
11947                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
11948            }
11949            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
11950        }
11951    }
11952
11953    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
11954    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
11955    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
11956    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
11957    pub fn matmul_pre(
11958        &self,
11959        w: &crate::model::GpuTensor,
11960        aq: &CudaSlice<i8>,
11961        ad: &CudaSlice<f32>,
11962        x_fallback: &CudaSlice<f32>,
11963        m: usize,
11964    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
11965        use crate::model::GpuTensor;
11966        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
11967        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
11968        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
11969        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
11970        // rc=30013 dig, 2026-07-31).
11971        let x_raw_ok = x_fallback.len() >= m * w.in_features();
11972        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
11973        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
11974        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
11975            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
11976                return Ok(y);
11977            }
11978            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
11979            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
11980            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
11981                return Ok(y);
11982            }
11983            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
11984            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
11985                return Ok(y);
11986            }
11987        }
11988        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
11989        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
11990        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
11991        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
11992        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
11993        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
11994            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
11995                return Ok(y);
11996            }
11997        }
11998        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
11999            return Ok(y);
12000        }
12001        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
12002        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
12003        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
12004        // aq/ad.
12005        if m >= 16
12006            && w.out_features() >= 128
12007            && self.mmq_supports(w)
12008            && !self.verify_exact_on()
12009            && x_raw_ok
12010        {
12011            return self.qmatvec_mmq(w, x_fallback, m);
12012        }
12013        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
12014        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
12015        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
12016            if let Some(y) =
12017                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
12018            {
12019                return Ok(y);
12020            }
12021        }
12022        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
12023        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
12024        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
12025            return self.qmatvec_gemm(w, aq, ad, m);
12026        }
12027        // THE SEVENTH ARM. Every raw-f32 arm above is guarded by `x_raw_ok`; this one — the
12028        // Stage-A / Float escape — was not, and it is the ONLY one `MEMRA_FAST=0` opens. So the
12029        // 2026-07-31 E4B rc=30013 fix (add the length guard, keep empty-fallback callers off the
12030        // raw-f32 arms) protected the six arms the FAST path can reach and missed the one the
12031        // ORACLE path reaches. Consequence before this guard: the gemma-4 decode arms, which pass
12032        // `e.zeros(0)` because their trunk emits only a q8_1 pair and never materializes an f32
12033        // attn-normed activation, fell through to Stage-A `qmatvec_f32` (cu/qmatvec.cu:5442),
12034        // which reads `m * in_f` floats out of a 0-byte allocation ->
12035        // CUDA_ERROR_ILLEGAL_ADDRESS at layer 0 of the first decode token. The fault is STICKY:
12036        // it poisons the context, so every LATER request in that process fails with an unrelated
12037        // message ("cache alloc failed: ...") and the true cause appears exactly once, in the
12038        // first failure. That is what made `MEMRA_FAST=0` — the reference named in
12039        // `tools/argmax-margin-gate.sh`'s own header — return an opaque HTTP 500 on the gemma
12040        // dense artifact and left the arm with no working truth instrument.
12041        //
12042        // Refuse loudly instead of reading out of bounds. A named error at the true call site is
12043        // strictly better than an illegal address surfacing later at an unrelated sync point, and
12044        // an oracle that cannot run must say so rather than corrupt the context it runs in.
12045        if !self.uses_q8_1_fast(w) {
12046            if !x_raw_ok {
12047                return Err(format!(
12048                    "matmul_pre: q8_1-fast is off for this weight but x_fallback holds {} f32 \
12049                     (need m*in_f = {}*{} = {}). This call site pre-quantized its activation and \
12050                     dropped the f32, so there is nothing to fall back to — pass the real f32 \
12051                     activation (see Engine::rms_norm_decode, which is bit-identical to \
12052                     rms_norm_q8_1's reduction) or keep the weight on the q8_1 path.",
12053                    x_fallback.len(),
12054                    m,
12055                    w.in_features(),
12056                    m * w.in_features()
12057                )
12058                .into());
12059            }
12060            return self.matmul(w, x_fallback, m);
12061        }
12062        let in_f = w.in_features();
12063        let out_f = w.out_features();
12064        let (bytes, qtype, row_bytes, scale, rp) = match w {
12065            GpuTensor::Quant {
12066                bytes,
12067                qtype,
12068                row_bytes,
12069                scale,
12070                rp,
12071                ..
12072            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12073            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
12074        };
12075        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
12076        // the dp4a/oracle tails below keep the raw GGUF bytes.
12077        let (mbytes, mrp) = match w {
12078            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12079            _ => (bytes, rp),
12080        };
12081        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
12082        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
12083        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
12084        if m == 1 && self.mmvq_supports(qtype) {
12085            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
12086        }
12087        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
12088        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
12089        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
12090        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
12091        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
12092        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
12093        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
12094        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
12095        // m=5..8 on the old per-m path (b8-tier-only seam).
12096        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
12097        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
12098        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
12099        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
12100            && std::env::var("MEMRA_NO_BATCHED").is_err()
12101            && (m <= 4 || Self::b8_enabled())
12102            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
12103            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
12104            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
12105            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
12106                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
12107        {
12108            let mcols = Self::batched_mcols(m);
12109            return self.qmatvec_mmvq_batched(
12110                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
12111            );
12112        }
12113        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
12114        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
12115        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
12116        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
12117        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
12118        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
12119            let (b2, r2) = if qtype == QT_Q4_0 {
12120                (mbytes, mrp)
12121            } else {
12122                (bytes, rp)
12123            };
12124            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
12125        }
12126        let name = match qtype {
12127            QT_Q8_0 => "qmatvec_q8_0_dp4a",
12128            QT_Q4_K => "qmatvec_q4_K_dp4a",
12129            QT_Q6_K => "qmatvec_q6_K_dp4a",
12130            QT_Q5_K => "qmatvec_q5_K_dp4a",
12131            QT_Q3_K => "qmatvec_q3_K_dp4a",
12132            QT_NVFP4 => {
12133                if rp {
12134                    "qmatvec_nvfp4_dp4a_rp"
12135                } else {
12136                    "qmatvec_nvfp4_dp4a"
12137                }
12138            }
12139            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
12140            _ => unreachable!(),
12141        };
12142        let f = self.func(name);
12143        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
12144        let cfg = LaunchConfig {
12145            grid_dim: (out_f as u32, m as u32, 1),
12146            block_dim: (128, 1, 1),
12147            shared_mem_bytes: 0,
12148        };
12149        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12150        let __s_b = self.gpu.stream();
12151        let mut b = __s_b.launch_builder(&f);
12152        b.arg(bytes)
12153            .arg(aq)
12154            .arg(ad)
12155            .arg(&mut y)
12156            .arg(&inf)
12157            .arg(&outf)
12158            .arg(&mi)
12159            .arg(&rb);
12160        unsafe {
12161            b.launch(cfg)?;
12162        }
12163        if scale != 1.0 {
12164            self.scale_inplace(&mut y, scale, m * out_f)?;
12165        }
12166        Ok(y)
12167    }
12168
12169    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
12170    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
12171    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
12172    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
12173    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
12174    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
12175    /// reduce as m=1); this method just forces that path unconditionally.
12176    pub fn matmul_decode_exact(
12177        &self,
12178        w: &crate::model::GpuTensor,
12179        x: &CudaSlice<f32>,
12180        m: usize,
12181    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12182        use crate::model::GpuTensor;
12183        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
12184        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
12185        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
12186        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
12187        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
12188        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
12189        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
12190        if let GpuTensor::Float { data, .. } = w {
12191            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
12192        }
12193        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
12194        // float linear (same n-independent reduction contract as the Float arm above).
12195        if let GpuTensor::FloatBf16 { data, .. } = w {
12196            let (in_f, out_f) = (w.in_features(), w.out_features());
12197            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true, None);
12198        }
12199        if !self.uses_q8_1_fast(w) {
12200            return self.matmul(w, x, m);
12201        }
12202        let in_f = w.in_features();
12203        let out_f = w.out_features();
12204        let (bytes, qtype, row_bytes, scale, rp) = match w {
12205            GpuTensor::Quant {
12206                bytes,
12207                qtype,
12208                row_bytes,
12209                scale,
12210                rp,
12211                ..
12212            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12213            _ => return self.matmul(w, x, m),
12214        };
12215        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
12216        // which does its own mirror pick).
12217        let (bytes, rp) = match w {
12218            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12219            _ => (bytes, rp),
12220        };
12221        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12222        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
12223        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
12224        // (token,row) by construction, which is exactly what this method exists to guarantee.
12225        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
12226            return Ok(y);
12227        }
12228        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
12229        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
12230        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
12231        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
12232        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
12233        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
12234        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
12235        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
12236        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
12237            && std::env::var("MEMRA_NO_BATCHED").is_err()
12238            && (m <= 4 || Self::b8_enabled())
12239            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
12240            // no mirror precondition, `rp` selects the layout only.
12241            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
12242                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
12243        {
12244            let mcols = Self::batched_mcols(m);
12245            return self.qmatvec_mmvq_batched(
12246                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
12247            );
12248        }
12249        if self.mmvq_supports(qtype) {
12250            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
12251            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
12252            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
12253        }
12254        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
12255        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
12256        self.matmul_pre(w, &aq, &ad, x, m)
12257    }
12258
12259    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
12260    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
12261    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
12262    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
12263    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
12264    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
12265    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
12266    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
12267    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
12268    pub fn matmul_decode_exact_pre(
12269        &self,
12270        w: &crate::model::GpuTensor,
12271        aq: &CudaSlice<i8>,
12272        ad: &CudaSlice<f32>,
12273        m: usize,
12274    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12275        use crate::model::GpuTensor;
12276        debug_assert!(
12277            self.uses_q8_1_fast(w),
12278            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
12279        );
12280        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
12281        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
12282            return Ok(y);
12283        }
12284        let in_f = w.in_features();
12285        let out_f = w.out_features();
12286        let (bytes, qtype, row_bytes, scale, rp) = match w {
12287            GpuTensor::Quant {
12288                bytes,
12289                qtype,
12290                row_bytes,
12291                scale,
12292                rp,
12293                ..
12294            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12295            _ => {
12296                return Err(
12297                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
12298                );
12299            }
12300        };
12301        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
12302        let (bytes, rp) = match w {
12303            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12304            _ => (bytes, rp),
12305        };
12306        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
12307        if (2..=16).contains(&m)
12308            && self.batched_supports(qtype)
12309            && self.mmvq_supports(qtype)
12310            && std::env::var("MEMRA_NO_BATCHED").is_err()
12311            && (m <= 4 || Self::b8_enabled())
12312            && (m <= 8
12313                || qtype == QT_Q4_0
12314                || qtype == QT_Q6_K
12315                || qtype == QT_F8_E4M3
12316                || qtype == QT_NVFP4
12317                || qtype == QT_Q4_K
12318                || qtype == QT_Q5_K
12319                || qtype == QT_Q8_0)
12320        {
12321            let mcols = Self::batched_mcols(m);
12322            return self.qmatvec_mmvq_batched(
12323                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
12324            );
12325        }
12326        if self.mmvq_supports(qtype) {
12327            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
12328        }
12329        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
12330        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
12331        let x0 = self.zeros(0)?;
12332        self.matmul_pre(w, aq, ad, &x0, m)
12333    }
12334
12335    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
12336    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
12337    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
12338    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
12339    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
12340    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
12341    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
12342    /// per-tensor path.
12343    pub fn matmul_decode_exact_dual_pre(
12344        &self,
12345        w0: &crate::model::GpuTensor,
12346        w1: &crate::model::GpuTensor,
12347        aq: &CudaSlice<i8>,
12348        ad: &CudaSlice<f32>,
12349        m: usize,
12350    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
12351    {
12352        use crate::model::GpuTensor;
12353        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12354        let on = *ON.get_or_init(|| {
12355            std::env::var("MEMRA_SPEC_DUAL_T")
12356                .map(|v| v != "0")
12357                .unwrap_or(true)
12358        });
12359        if !on
12360            || !(2..=7).contains(&m)
12361            || std::env::var("MEMRA_NO_BATCHED").is_ok()
12362            || !self.uses_q8_1_fast(w0)
12363            || !self.uses_q8_1_fast(w1)
12364        {
12365            return Ok(None);
12366        }
12367        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
12368        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
12369        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
12370        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
12371        if !self.mmvq_supports(QT_NVFP4) {
12372            return Ok(None);
12373        }
12374        let (in_f, out_f) = (w0.in_features(), w0.out_features());
12375        if w1.in_features() != in_f || w1.out_features() != out_f {
12376            return Ok(None);
12377        }
12378        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
12379            (
12380                GpuTensor::Quant {
12381                    bytes: b0,
12382                    qtype: q0,
12383                    row_bytes: rb0,
12384                    scale: s0,
12385                    rp: rp0,
12386                    rp4: None,
12387                    ..
12388                },
12389                GpuTensor::Quant {
12390                    bytes: b1,
12391                    qtype: q1,
12392                    row_bytes: rb1,
12393                    scale: s1,
12394                    rp: rp1,
12395                    rp4: None,
12396                    ..
12397                },
12398            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
12399                (b0, b1, *rb0, *s0, *s1, *rp0)
12400            }
12401            _ => return Ok(None),
12402        };
12403        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
12404        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
12405        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
12406        {
12407            return Ok(None);
12408        }
12409        let (y0, y1) =
12410            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
12411        Ok(Some(((y0, s0), (y1, s1))))
12412    }
12413
12414    /// GROUP-4 GDN-tuple BATCHED matvec (trunk-kernels slice C): the qwen35 linear-layer
12415    /// in-projection 4-tuple (wqkv / wqkv_gate / ssm_beta / ssm_alpha) from ONE pre-quantized
12416    /// activation in ONE launch. Blocks map to the concatenated row space; every out_f must be
12417    /// a multiple of 8 (rows_per_block) so each warp's row pair resolves to one tensor; per
12418    /// (tensor, token, row) the kernel body is `nvfp4_mmvq_batched_rp` VERBATIM with the
12419    /// tensor's macro-scale fused at the write (== the conditional scale_inplace pass,
12420    /// bit-identical) -> BIT-IDENTICAL to the four single launches. Split-plane rp NVFP4 only,
12421    /// m=2..16 (exact-width MCOLS at m=5..7 mirroring the B567 law; m>4 requires b8_enabled
12422    /// like the singles; m=9..=16 rides the b16 form — the E4 width lift that lets
12423    /// `matmul_nvfp4_fused3/4` delegate that class here, lane/orndecode2).
12424    /// None -> caller runs the four singles. MEMRA_TK_GDN_GROUP=0 rollback.
12425    pub fn matmul_decode_exact_group4_pre(
12426        &self,
12427        ws: [&crate::model::GpuTensor; 4],
12428        aq: &CudaSlice<i8>,
12429        ad: &CudaSlice<f32>,
12430        m: usize,
12431    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
12432        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12433        let on = *ON.get_or_init(|| {
12434            std::env::var("MEMRA_TK_GDN_GROUP")
12435                .map(|v| v != "0")
12436                .unwrap_or(true)
12437        });
12438        self.matmul_decode_exact_group_pre(&ws, aq, ad, m, on, "GDN group4")
12439    }
12440
12441    /// GROUP-3 twin for the qwen35 full-attention q/k/v triple (trunk-kernels slice D):
12442    /// the SAME group4 kernels with n3=0 (blocks never reach the fourth range; W3/y3 are
12443    /// never dereferenced) — per (tensor, token, row) bit-identical to the three singles
12444    /// exactly as the group4 door is to its four. MEMRA_TK_FA_GROUP=0 rollback.
12445    pub fn matmul_decode_exact_group3_pre(
12446        &self,
12447        ws: [&crate::model::GpuTensor; 3],
12448        aq: &CudaSlice<i8>,
12449        ad: &CudaSlice<f32>,
12450        m: usize,
12451    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
12452        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12453        let on = *ON.get_or_init(|| {
12454            std::env::var("MEMRA_TK_FA_GROUP")
12455                .map(|v| v != "0")
12456                .unwrap_or(true)
12457        });
12458        self.matmul_decode_exact_group_pre(&ws, aq, ad, m, on, "FA group3")
12459    }
12460
12461    /// Shared core of the group3/group4 doors: eligibility mirror of the singles' batched
12462    /// dispatch, then ONE `qmatvec_nvfp4_mmvq_group4_b*_rp` launch over the concatenated
12463    /// row space (3-tensor callers ride n3=0). Returns one output per input tensor.
12464    fn matmul_decode_exact_group_pre(
12465        &self,
12466        ws: &[&crate::model::GpuTensor],
12467        aq: &CudaSlice<i8>,
12468        ad: &CudaSlice<f32>,
12469        m: usize,
12470        on: bool,
12471        tag: &'static str,
12472    ) -> Result<Option<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
12473        use crate::model::GpuTensor;
12474        if !on
12475            || !(2..=16).contains(&m)
12476            || std::env::var("MEMRA_NO_BATCHED").is_ok()
12477            || (m > 4 && !Self::b8_enabled())
12478            || !self.mmvq_supports(QT_NVFP4)
12479            || !self.batched_supports(QT_NVFP4)
12480        {
12481            return Ok(None);
12482        }
12483        let in_f = ws[0].in_features();
12484        let mut parts: Vec<(&CudaSlice<u8>, usize, f32)> = Vec::with_capacity(4);
12485        for w in ws {
12486            if !self.uses_q8_1_fast(w) || w.in_features() != in_f {
12487                return Ok(None);
12488            }
12489            match w {
12490                GpuTensor::Quant {
12491                    bytes,
12492                    qtype,
12493                    scale,
12494                    rp: true,
12495                    rp4: None,
12496                    ..
12497                } if *qtype == QT_NVFP4 && w.out_features() % 8 == 0 => {
12498                    parts.push((bytes, w.out_features(), *scale));
12499                }
12500                _ => return Ok(None),
12501            }
12502        }
12503        // MCOLS tier mirrors the singles: batched_mcols + the B567 exact-width law at m=5..7.
12504        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12505        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
12506        let mcols = if (5..=7).contains(&m) && b567 {
12507            m
12508        } else {
12509            Self::batched_mcols(m)
12510        };
12511        let kname: &'static str = match mcols {
12512            2 => "qmatvec_nvfp4_mmvq_group4_b2_rp",
12513            4 => "qmatvec_nvfp4_mmvq_group4_b4_rp",
12514            5 => "qmatvec_nvfp4_mmvq_group4_b5_rp",
12515            6 => "qmatvec_nvfp4_mmvq_group4_b6_rp",
12516            7 => "qmatvec_nvfp4_mmvq_group4_b7_rp",
12517            8 => "qmatvec_nvfp4_mmvq_group4_b8_rp",
12518            16 => "qmatvec_nvfp4_mmvq_group4_b16_rp",
12519            _ => return Ok(None),
12520        };
12521        // Engagement receipt PER DOOR (dead-arm lesson): one shared Once here suppressed
12522        // the second door's print on the slice-D battery — key the once-set by tag.
12523        if std::env::var("MEMRA_DEBUG").is_ok() {
12524            use std::sync::Mutex;
12525            static SEEN: Mutex<Vec<&'static str>> = Mutex::new(Vec::new());
12526            let mut seen = SEEN.lock().unwrap();
12527            if !seen.contains(&tag) {
12528                seen.push(tag);
12529                eprintln!("[memra] {tag} batched ENGAGED (m={m})");
12530            }
12531        }
12532        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12533        let rows_per_block = ROWS_PER_BLOCK * 2; // WROWS=2 in the group kernel
12534        let total: usize = parts.iter().map(|p| p.1).sum();
12535        let three = parts.len() == 3;
12536        let mut y0 = self.alloc_uninit::<f32>(m * parts[0].1)?;
12537        let mut y1 = self.alloc_uninit::<f32>(m * parts[1].1)?;
12538        let mut y2 = self.alloc_uninit::<f32>(m * parts[2].1)?;
12539        // 3-tensor callers: n3=0 means no block ever resolves to the fourth range — W3/y3
12540        // are never dereferenced; a 1-element dummy keeps the launch ABI without aliasing y0.
12541        let mut y3 = self.alloc_uninit::<f32>(if three { 1 } else { m * parts[3].1 })?;
12542        let cfg = LaunchConfig {
12543            grid_dim: ((total as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
12544            block_dim: (32, ROWS_PER_BLOCK, 1),
12545            shared_mem_bytes: 0,
12546        };
12547        let (inf, mi) = (in_f as i32, m as i32);
12548        let (n0, n1, n2) = (parts[0].1 as i32, parts[1].1 as i32, parts[2].1 as i32);
12549        let n3 = if three { 0i32 } else { parts[3].1 as i32 };
12550        let (s0, s1, s2) = (parts[0].2, parts[1].2, parts[2].2);
12551        let s3 = if three { 1.0f32 } else { parts[3].2 };
12552        let w3 = if three { parts[0].0 } else { parts[3].0 };
12553        let f = self.func(kname);
12554        let __s_b = self.gpu.stream();
12555        let mut b = __s_b.launch_builder(&f);
12556        b.arg(parts[0].0)
12557            .arg(parts[1].0)
12558            .arg(parts[2].0)
12559            .arg(w3)
12560            .arg(aq)
12561            .arg(ad)
12562            .arg(&mut y0)
12563            .arg(&mut y1)
12564            .arg(&mut y2)
12565            .arg(&mut y3)
12566            .arg(&inf)
12567            .arg(&n0)
12568            .arg(&n1)
12569            .arg(&n2)
12570            .arg(&n3)
12571            .arg(&mi)
12572            .arg(&s0)
12573            .arg(&s1)
12574            .arg(&s2)
12575            .arg(&s3);
12576        unsafe {
12577            b.launch(cfg)?;
12578        }
12579        Ok(Some(if three {
12580            vec![y0, y1, y2]
12581        } else {
12582            vec![y0, y1, y2, y3]
12583        }))
12584    }
12585
12586    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
12587    /// launch computes both FFN projections of a verify batch — same activation, same shape,
12588    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
12589    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
12590    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
12591    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
12592    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
12593    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
12594    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
12595    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
12596    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
12597    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
12598    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
12599    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
12600    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
12601    pub fn matmul_decode_exact_dual(
12602        &self,
12603        w0: &crate::model::GpuTensor,
12604        w1: &crate::model::GpuTensor,
12605        x: &CudaSlice<f32>,
12606        m: usize,
12607    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12608        use crate::model::GpuTensor;
12609        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12610        let on = *ON.get_or_init(|| {
12611            std::env::var("MEMRA_SPEC_DUAL_T")
12612                .map(|v| v != "0")
12613                .unwrap_or(true)
12614        });
12615        if !on
12616            || !(2..=4).contains(&m)
12617            || std::env::var("MEMRA_NO_BATCHED").is_ok()
12618            || !self.uses_q8_1_fast(w0)
12619            || !self.uses_q8_1_fast(w1)
12620        {
12621            return Ok(None);
12622        }
12623        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
12624        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
12625        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
12626        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
12627        if !self.mmvq_supports(QT_NVFP4) {
12628            return Ok(None);
12629        }
12630        let (in_f, out_f) = (w0.in_features(), w0.out_features());
12631        if w1.in_features() != in_f || w1.out_features() != out_f {
12632            return Ok(None);
12633        }
12634        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
12635            (
12636                GpuTensor::Quant {
12637                    bytes: b0,
12638                    qtype: q0,
12639                    row_bytes: rb0,
12640                    scale: s0,
12641                    rp: rp0,
12642                    rp4: None,
12643                    ..
12644                },
12645                GpuTensor::Quant {
12646                    bytes: b1,
12647                    qtype: q1,
12648                    row_bytes: rb1,
12649                    scale: s1,
12650                    rp: rp1,
12651                    rp4: None,
12652                    ..
12653                },
12654            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
12655                (b0, b1, *rb0, *s0, *s1, *rp0)
12656            }
12657            _ => return Ok(None),
12658        };
12659        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
12660        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
12661        if std::env::var("MEMRA_DEBUG").is_ok() {
12662            static ONCE: std::sync::Once = std::sync::Once::new();
12663            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
12664        }
12665        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12666        let (y0, y1) =
12667            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
12668        let mut y0 = y0;
12669        let mut y1 = y1;
12670        if s0 != 1.0 {
12671            self.scale_inplace(&mut y0, s0, m * out_f)?;
12672        }
12673        if s1 != 1.0 {
12674            self.scale_inplace(&mut y1, s1, m * out_f)?;
12675        }
12676        Ok(Some((y0, y1)))
12677    }
12678
12679    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
12680    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
12681    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
12682    /// twins (both buffers must be the repacked layout).
12683    #[allow(clippy::too_many_arguments)]
12684    pub fn qmatvec_batched_dual_raw(
12685        &self,
12686        b0: &CudaSlice<u8>,
12687        b1: &CudaSlice<u8>,
12688        aq: &CudaSlice<i8>,
12689        ad: &CudaSlice<f32>,
12690        m: usize,
12691        in_f: usize,
12692        out_f: usize,
12693        row_bytes: usize,
12694        rp: bool,
12695    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12696        const ROWS_PER_BLOCK: u32 = 4;
12697        let mcols = Self::batched_mcols(m);
12698        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
12699        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
12700        let tiny_rp1 = rp
12701            && mcols == 4
12702            && out_f <= 128
12703            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
12704        let (name, rows_per_block) = if tiny_rp1 {
12705            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
12706        } else {
12707            match (mcols, rp, m) {
12708                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
12709                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
12710                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
12711                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
12712                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
12713                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
12714                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
12715                _ => {
12716                    return Err(
12717                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
12718                    );
12719                }
12720            }
12721        };
12722        let f = self.func(name);
12723        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
12724        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
12725        let cfg = LaunchConfig {
12726            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
12727            block_dim: (32, ROWS_PER_BLOCK, 1),
12728            shared_mem_bytes: 0,
12729        };
12730        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12731        let __s_b = self.gpu.stream();
12732        let mut b = __s_b.launch_builder(&f);
12733        b.arg(b0)
12734            .arg(b1)
12735            .arg(aq)
12736            .arg(ad)
12737            .arg(&mut y0)
12738            .arg(&mut y1)
12739            .arg(&inf)
12740            .arg(&outf)
12741            .arg(&mi)
12742            .arg(&rb);
12743        unsafe {
12744            b.launch(cfg)?;
12745        }
12746        Ok((y0, y1))
12747    }
12748
12749    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
12750    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
12751    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
12752    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
12753    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
12754    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
12755    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
12756    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
12757    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
12758    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
12759    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
12760    pub fn matmul_pre_dual_noscale(
12761        &self,
12762        w0: &crate::model::GpuTensor,
12763        w1: &crate::model::GpuTensor,
12764        aq: &CudaSlice<i8>,
12765        ad: &CudaSlice<f32>,
12766        m: usize,
12767    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
12768    {
12769        use crate::model::GpuTensor;
12770        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
12771            return Ok(None);
12772        }
12773        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
12774        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
12775        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
12776        // would mix dispatch families across the pair — the exact class `q8_fused_params`
12777        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
12778        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
12779        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
12780        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
12781        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
12782        if !self.mmvq_supports(QT_NVFP4) {
12783            return Ok(None);
12784        }
12785        let (in_f, out_f) = (w0.in_features(), w0.out_features());
12786        if w1.in_features() != in_f || w1.out_features() != out_f {
12787            return Ok(None);
12788        }
12789        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
12790        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
12791        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
12792        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
12793        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
12794        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
12795        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
12796        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
12797        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
12798        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
12799        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
12800        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
12801        let no_mirror =
12802            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
12803        if self.q8_ffn_fuse2_on()
12804            && no_mirror(w0)
12805            && no_mirror(w1)
12806            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
12807        {
12808            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
12809            return Ok(Some(((y0, 1.0), (y1, 1.0))));
12810        }
12811        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
12812        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
12813        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
12814        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
12815        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
12816        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
12817        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
12818        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
12819        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
12820        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
12821            let (y0, y1) =
12822                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
12823            return Ok(Some(((y0, p0.3), (y1, p1.3))));
12824        }
12825        let (b0, q0, rb0, s0, rp0) = match w0 {
12826            GpuTensor::Quant {
12827                bytes,
12828                qtype,
12829                row_bytes,
12830                scale,
12831                rp,
12832                ..
12833            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12834            _ => return Ok(None),
12835        };
12836        let (b1, q1, rb1, s1, rp1) = match w1 {
12837            GpuTensor::Quant {
12838                bytes,
12839                qtype,
12840                row_bytes,
12841                scale,
12842                rp,
12843                ..
12844            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12845            _ => return Ok(None),
12846        };
12847        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
12848            return Ok(None);
12849        }
12850        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12851        const RPW: u32 = 2;
12852        let rows_per_block = ROWS_PER_BLOCK * RPW;
12853        let f = self.func(if rp0 {
12854            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
12855        } else {
12856            "qmatvec_nvfp4_mmvq_dual_mr2"
12857        });
12858        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
12859        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
12860        let cfg = LaunchConfig {
12861            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
12862            block_dim: (32, ROWS_PER_BLOCK, 1),
12863            shared_mem_bytes: 0,
12864        };
12865        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
12866        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
12867        // yscale args stay 1.0 here (they exist for the single-tensor callers).
12868        let one = 1.0f32;
12869        let __s_b = self.gpu.stream();
12870        let mut b = __s_b.launch_builder(&f);
12871        b.arg(b0)
12872            .arg(b1)
12873            .arg(aq)
12874            .arg(ad)
12875            .arg(&mut y0)
12876            .arg(&mut y1)
12877            .arg(&inf)
12878            .arg(&outf)
12879            .arg(&mi)
12880            .arg(&rb)
12881            .arg(&one)
12882            .arg(&one);
12883        unsafe {
12884            b.launch(cfg)?;
12885        }
12886        Ok(Some(((y0, s0), (y1, s1))))
12887    }
12888
12889    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
12890    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
12891    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
12892    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
12893    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
12894    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
12895    /// back to the three singles.
12896    #[allow(clippy::too_many_arguments)]
12897    pub fn matmul_nvfp4_fused3(
12898        &self,
12899        w0: &crate::model::GpuTensor,
12900        w1: &crate::model::GpuTensor,
12901        w2: &crate::model::GpuTensor,
12902        aq: &CudaSlice<i8>,
12903        ad: &CudaSlice<f32>,
12904        m: usize,
12905    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12906    {
12907        use crate::model::GpuTensor;
12908        // m == 1 rides the original fused kernel; m = 2..=8 rides the BATCHED fused twin
12909        // (lane/samplat — the fused4_b8 pattern): the seg body is nvfp4_mmvq_batched_rp_sc
12910        // verbatim, weight rows read once for all m columns, bit-identical per
12911        // (tensor,row,column) to the three bN_rpsc singles. The old "at m>1 the fused
12912        // segments would re-read the weight per row" note described the grid.y=m lift,
12913        // which this twin deliberately is NOT.
12914        if !self.mmvq_supports(QT_NVFP4)
12915            || !self.uses_q8_1_fast(w0)
12916            || !self.uses_q8_1_fast(w1)
12917            || !self.uses_q8_1_fast(w2)
12918        {
12919            return Ok(None);
12920        }
12921        // m = 9..=16 (lane/orndecode2): the exact-16 tier's trio width rides the GROUP3
12922        // door — same family and bit-identity law as the fused4 delegate above.
12923        if (9..=16).contains(&m) {
12924            return Ok(
12925                match self.matmul_decode_exact_group3_pre([w0, w1, w2], aq, ad, m)? {
12926                    Some(mut ys) => {
12927                        let y2 = ys.pop().unwrap();
12928                        let y1 = ys.pop().unwrap();
12929                        let y0 = ys.pop().unwrap();
12930                        Some((y0, y1, y2))
12931                    }
12932                    None => None,
12933                },
12934            );
12935        }
12936        if !(1..=8).contains(&m) {
12937            return Ok(None);
12938        }
12939        if m > 1 {
12940            let in_f = w0.in_features();
12941            if std::env::var("MEMRA_NVFP4_FUSED3B").as_deref() == Ok("0")
12942                || !self.batched_supports(QT_NVFP4)
12943                || std::env::var("MEMRA_NO_BATCHED").is_ok()
12944                || (m > 4 && !Self::b8_enabled())
12945                || in_f % 512 != 0
12946                || in_f / 64 > 272
12947            {
12948                return Ok(None);
12949            }
12950        }
12951        let unpack = |w: &crate::model::GpuTensor| match w {
12952            GpuTensor::Quant {
12953                bytes,
12954                qtype,
12955                scale,
12956                rp,
12957                ..
12958            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
12959            _ => None,
12960        };
12961        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
12962            return Ok(None);
12963        };
12964        let in_f = w0.in_features();
12965        if w1.in_features() != in_f || w2.in_features() != in_f {
12966            return Ok(None);
12967        }
12968        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
12969        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
12970        const RPW: u32 = 2;
12971        let rows_pb = ROWS_PER_BLOCK * RPW;
12972        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
12973        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12974        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12975        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
12976        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
12977        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
12978        // only dereferenced for the launch-arg build inside this call.
12979        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
12980        if m > 1 {
12981            // batched twin has no in-kernel scale — refuse scale carriers (GGUF trunk = 1.0).
12982            if p0.1 != 1.0 || p1.1 != 1.0 || p2.1 != 1.0 {
12983                return Ok(None);
12984            }
12985            let f = self.func("qmatvec_nvfp4_mmvq_fused3_b8_rpsc");
12986            let cfg = LaunchConfig {
12987                grid_dim: (nb(o0) + nb(o1) + nb(o2), 1, 1),
12988                block_dim: (32, ROWS_PER_BLOCK, 1),
12989                shared_mem_bytes: 0,
12990            };
12991            let __s_b = self.gpu.stream();
12992            let mut b = __s_b.launch_builder(&f);
12993            b.arg(b0)
12994                .arg(b1)
12995                .arg(b2)
12996                .arg(aq)
12997                .arg(ad)
12998                .arg(&mut y0)
12999                .arg(&mut y1)
13000                .arg(&mut y2)
13001                .arg(&inf)
13002                .arg(&oi0)
13003                .arg(&oi1)
13004                .arg(&oi2)
13005                .arg(&mi);
13006            unsafe {
13007                b.launch(cfg)?;
13008            }
13009            return Ok(Some((y0, y1, y2)));
13010        }
13011        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
13012        let cfg = LaunchConfig {
13013            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
13014            block_dim: (32, ROWS_PER_BLOCK, 1),
13015            shared_mem_bytes: 0,
13016        };
13017        let __s_b = self.gpu.stream();
13018        let mut b = __s_b.launch_builder(&f);
13019        b.arg(b0)
13020            .arg(b1)
13021            .arg(b2)
13022            .arg(aq)
13023            .arg(ad)
13024            .arg(&mut y0)
13025            .arg(&mut y1)
13026            .arg(&mut y2)
13027            .arg(&inf)
13028            .arg(&oi0)
13029            .arg(&oi1)
13030            .arg(&oi2)
13031            .arg(&mi)
13032            .arg(&p0.1)
13033            .arg(&p1.1)
13034            .arg(&p2.1);
13035        unsafe {
13036            b.launch(cfg)?;
13037        }
13038        Ok(Some((y0, y1, y2)))
13039    }
13040
13041    /// fused2 twin of `matmul_nvfp4_fused3`, for MIXED-type weight groups: the gemma4
13042    /// dense NVFP4mix recipe keeps `attn_v` and `ffn_down` at Q8_0 (full-NVFP4 was
13043    /// measured garbage on this dense class), so its q/k/v trio and gate/up/down never
13044    /// satisfy an all-NVFP4 fused3 — the pairs that ARE uniformly NVFP4 (q,k and
13045    /// gate,up) fuse here instead. m==1 only, same law as fused3/fused4: per
13046    /// (tensor,row) the kernel seg body is VERBATIM, so the fusion is bit-identical to
13047    /// two separate launches. `MEMRA_NVFP4_FUSED2=0` is the rollback seam and the
13048    /// same-binary interleaved A/B arm.
13049    pub fn matmul_nvfp4_fused2(
13050        &self,
13051        w0: &crate::model::GpuTensor,
13052        w1: &crate::model::GpuTensor,
13053        aq: &CudaSlice<i8>,
13054        ad: &CudaSlice<f32>,
13055        m: usize,
13056    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
13057        use crate::model::GpuTensor;
13058        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13059        let off =
13060            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
13061        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
13062        // read serves all m rows); the fused segments would re-read the weight per row.
13063        if off
13064            || m != 1
13065            || !self.mmvq_supports(QT_NVFP4)
13066            || !self.uses_q8_1_fast(w0)
13067            || !self.uses_q8_1_fast(w1)
13068        {
13069            return Ok(None);
13070        }
13071        let unpack = |w: &crate::model::GpuTensor| match w {
13072            GpuTensor::Quant {
13073                bytes,
13074                qtype,
13075                scale,
13076                rp,
13077                ..
13078            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
13079            _ => None,
13080        };
13081        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
13082            return Ok(None);
13083        };
13084        let in_f = w0.in_features();
13085        if w1.in_features() != in_f {
13086            return Ok(None);
13087        }
13088        let (o0, o1) = (w0.out_features(), w1.out_features());
13089        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
13090        const RPW: u32 = 2;
13091        let rows_pb = ROWS_PER_BLOCK * RPW;
13092        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
13093        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
13094        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
13095        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
13096        let cfg = LaunchConfig {
13097            grid_dim: (nb(o0) + nb(o1), m as u32, 1),
13098            block_dim: (32, ROWS_PER_BLOCK, 1),
13099            shared_mem_bytes: 0,
13100        };
13101        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, m as i32);
13102        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
13103        // only dereferenced for the launch-arg build inside this call.
13104        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
13105        // PDL wave-B: the kernel carries MEMRA_PDL_ENTRY — launch overlaps the producer's
13106        // drain (quantize/norm emitting aq/ad). Same math, same order, bit-identical.
13107        if Self::pdl_on() && Self::pdl_mmvq_on() && Self::pdl_nvfp4q8_on() {
13108            {
13109                use cudarc::driver::{DevicePtr, DevicePtrMut};
13110                let s = &self.gpu.stream();
13111                let (pw0, _g0) = b0.device_ptr(s);
13112                let (pw1, _g1) = b1.device_ptr(s);
13113                let (paq, _g2) = aq.device_ptr(s);
13114                let (pad, _g3) = ad.device_ptr(s);
13115                let (py0, _g4) = y0.device_ptr_mut(s);
13116                let (py1, _g5) = y1.device_ptr_mut(s);
13117                let (s0, s1) = (p0.1, p1.1);
13118                let mut ps = [
13119                    &pw0 as *const _ as *mut std::ffi::c_void,
13120                    &pw1 as *const _ as *mut _,
13121                    &paq as *const _ as *mut _,
13122                    &pad as *const _ as *mut _,
13123                    &py0 as *const _ as *mut _,
13124                    &py1 as *const _ as *mut _,
13125                    &inf as *const _ as *mut _,
13126                    &oi0 as *const _ as *mut _,
13127                    &oi1 as *const _ as *mut _,
13128                    &mi as *const _ as *mut _,
13129                    &s0 as *const _ as *mut _,
13130                    &s1 as *const _ as *mut _,
13131                ];
13132                unsafe {
13133                    self.launch_pdl(
13134                        "qmatvec_nvfp4_mmvq_fused2_rp",
13135                        cfg.grid_dim,
13136                        cfg.block_dim,
13137                        &mut ps,
13138                    )?;
13139                }
13140            }
13141            return Ok(Some((y0, y1)));
13142        }
13143        let __s_b = self.gpu.stream();
13144        let mut b = __s_b.launch_builder(&f);
13145        b.arg(b0)
13146            .arg(b1)
13147            .arg(aq)
13148            .arg(ad)
13149            .arg(&mut y0)
13150            .arg(&mut y1)
13151            .arg(&inf)
13152            .arg(&oi0)
13153            .arg(&oi1)
13154            .arg(&mi)
13155            .arg(&p0.1)
13156            .arg(&p1.1);
13157        unsafe {
13158            b.launch(cfg)?;
13159        }
13160        Ok(Some((y0, y1)))
13161    }
13162
13163    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch to
13164    /// `matmul_nvfp4_fused2`, caller-owned outputs — the gemma4 dc_slotted graph body
13165    /// needs zero mem nodes, so the allocating wrapper can't serve it. Returns false
13166    /// (decline) on any non-NVFP4/rp pair; the caller chains or refuses.
13167    pub fn matmul_nvfp4_fused2_into(
13168        &self,
13169        w0: &crate::model::GpuTensor,
13170        w1: &crate::model::GpuTensor,
13171        aq: &CudaSlice<i8>,
13172        ad: &CudaSlice<f32>,
13173        y0: &mut CudaSlice<f32>,
13174        y1: &mut CudaSlice<f32>,
13175    ) -> Result<bool, Box<dyn std::error::Error>> {
13176        use crate::model::GpuTensor;
13177        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13178        let off =
13179            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
13180        if off
13181            || !self.mmvq_supports(QT_NVFP4)
13182            || !self.uses_q8_1_fast(w0)
13183            || !self.uses_q8_1_fast(w1)
13184        {
13185            return Ok(false);
13186        }
13187        let unpack = |w: &crate::model::GpuTensor| match w {
13188            GpuTensor::Quant {
13189                bytes,
13190                qtype,
13191                scale,
13192                rp,
13193                ..
13194            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
13195            _ => None,
13196        };
13197        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
13198            return Ok(false);
13199        };
13200        let in_f = w0.in_features();
13201        if w1.in_features() != in_f {
13202            return Ok(false);
13203        }
13204        let (o0, o1) = (w0.out_features(), w1.out_features());
13205        if y0.len() < o0 || y1.len() < o1 {
13206            return Ok(false);
13207        }
13208        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
13209        const RPW: u32 = 2;
13210        let rows_pb = ROWS_PER_BLOCK * RPW;
13211        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
13212        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
13213        let cfg = LaunchConfig {
13214            grid_dim: (nb(o0) + nb(o1), 1, 1),
13215            block_dim: (32, ROWS_PER_BLOCK, 1),
13216            shared_mem_bytes: 0,
13217        };
13218        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, 1i32);
13219        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
13220        // only dereferenced for the launch-arg build inside this call.
13221        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
13222        let __s_b = self.gpu.stream();
13223        let mut b = __s_b.launch_builder(&f);
13224        b.arg(b0)
13225            .arg(b1)
13226            .arg(aq)
13227            .arg(ad)
13228            .arg(&mut *y0)
13229            .arg(&mut *y1)
13230            .arg(&inf)
13231            .arg(&oi0)
13232            .arg(&oi1)
13233            .arg(&mi)
13234            .arg(&p0.1)
13235            .arg(&p1.1);
13236        unsafe {
13237            b.launch(cfg)?;
13238        }
13239        Ok(true)
13240    }
13241
13242    /// fused4 twin of `matmul_nvfp4_fused3`: the Linear-mixer projection quartet
13243    /// (wqkv + wqkv_gate + ssm_beta + ssm_alpha) in one launch, m==1 only. Per
13244    /// (tensor,row) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM — bit-identical
13245    /// to four separate launches (rig-native decode increment 2, RIG-NATIVE-DECODE.md).
13246    #[allow(clippy::type_complexity)]
13247    pub fn matmul_nvfp4_fused4(
13248        &self,
13249        w0: &crate::model::GpuTensor,
13250        w1: &crate::model::GpuTensor,
13251        w2: &crate::model::GpuTensor,
13252        w3: &crate::model::GpuTensor,
13253        aq: &CudaSlice<i8>,
13254        ad: &CudaSlice<f32>,
13255        m: usize,
13256    ) -> Result<
13257        Option<(
13258            CudaSlice<f32>,
13259            CudaSlice<f32>,
13260            CudaSlice<f32>,
13261            CudaSlice<f32>,
13262        )>,
13263        Box<dyn std::error::Error>,
13264    > {
13265        use crate::model::GpuTensor;
13266        // MEMRA_NVFP4_FUSED4=0: rollback seam + the same-binary interleaved A/B arm.
13267        // m == 1 rides the original fused kernel; m = 2..=8 rides the BATCHED fused twin
13268        // (lane/samplat, 2026-08-21): same quartet-in-one-launch shape, seg body =
13269        // nvfp4_mmvq_batched_rp_sc verbatim (weight rows read once for all m columns) —
13270        // bit-identical per (tensor,row,column) to the four bN_rpsc singles it replaces.
13271        // Admission mirrors the singles' batched gates below.
13272        if std::env::var("MEMRA_NVFP4_FUSED4").as_deref() == Ok("0")
13273            || !self.mmvq_supports(QT_NVFP4)
13274            || !self.uses_q8_1_fast(w0)
13275            || !self.uses_q8_1_fast(w1)
13276            || !self.uses_q8_1_fast(w2)
13277            || !self.uses_q8_1_fast(w3)
13278        {
13279            return Ok(None);
13280        }
13281        // m = 9..=16 (lane/orndecode2, the exact-16 tier's trunk): the rp-sc seg twins stop
13282        // at 8; this width class rides the GROUP4 door instead — nvfp4_mmvq_batched_rp<16,_>
13283        // body, the SAME family as the b16_rp singles the tier would otherwise launch four
13284        // times, bit-identical per (tensor,token,row) incl. the fused write-side scale.
13285        if (9..=16).contains(&m) {
13286            return Ok(
13287                match self.matmul_decode_exact_group4_pre([w0, w1, w2, w3], aq, ad, m)? {
13288                    Some(mut ys) => {
13289                        let y3 = ys.pop().unwrap();
13290                        let y2 = ys.pop().unwrap();
13291                        let y1 = ys.pop().unwrap();
13292                        let y0 = ys.pop().unwrap();
13293                        Some((y0, y1, y2, y3))
13294                    }
13295                    None => None,
13296                },
13297            );
13298        }
13299        if !(1..=8).contains(&m) {
13300            return Ok(None);
13301        }
13302        if m > 1 {
13303            // the batched-twin gates: the bN_rpsc program this must stay byte-identical to
13304            // (matmul_pre's batched arm), plus the rp-sc dispatch shape requirements.
13305            let in_f = w0.in_features();
13306            if !self.batched_supports(QT_NVFP4)
13307                || std::env::var("MEMRA_NO_BATCHED").is_ok()
13308                || (m > 4 && !Self::b8_enabled())
13309                || in_f % 512 != 0
13310                || in_f / 64 > 272
13311            {
13312                return Ok(None);
13313            }
13314        }
13315        let unpack = |w: &crate::model::GpuTensor| match w {
13316            GpuTensor::Quant {
13317                bytes,
13318                qtype,
13319                scale,
13320                rp,
13321                ..
13322            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
13323            _ => None,
13324        };
13325        let (Some(p0), Some(p1), Some(p2), Some(p3)) =
13326            (unpack(w0), unpack(w1), unpack(w2), unpack(w3))
13327        else {
13328            return Ok(None);
13329        };
13330        let in_f = w0.in_features();
13331        if w1.in_features() != in_f || w2.in_features() != in_f || w3.in_features() != in_f {
13332            return Ok(None);
13333        }
13334        let (o0, o1, o2, o3) = (
13335            w0.out_features(),
13336            w1.out_features(),
13337            w2.out_features(),
13338            w3.out_features(),
13339        );
13340        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
13341        const RPW: u32 = 2;
13342        let rows_pb = ROWS_PER_BLOCK * RPW;
13343        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
13344        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
13345        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
13346        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
13347        let mut y3 = self.alloc_uninit::<f32>(m * o3)?;
13348        let (inf, oi0, oi1, oi2, oi3, mi) = (
13349            in_f as i32,
13350            o0 as i32,
13351            o1 as i32,
13352            o2 as i32,
13353            o3 as i32,
13354            m as i32,
13355        );
13356        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
13357        // only dereferenced for the launch-arg build inside this call.
13358        let (b0, b1, b2, b3) = unsafe { (&*p0.0, &*p1.0, &*p2.0, &*p3.0) };
13359        if m > 1 {
13360            // Batched fused twin: no in-kernel scale (the bN_rpsc program has none) — refuse
13361            // scale-carrying tensors so the singles path keeps them (GGUF trunk scales are 1.0).
13362            if p0.1 != 1.0 || p1.1 != 1.0 || p2.1 != 1.0 || p3.1 != 1.0 {
13363                return Ok(None);
13364            }
13365            let f = self.func("qmatvec_nvfp4_mmvq_fused4_b8_rpsc");
13366            let cfg = LaunchConfig {
13367                grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), 1, 1),
13368                block_dim: (32, ROWS_PER_BLOCK, 1),
13369                shared_mem_bytes: 0,
13370            };
13371            let __s_b = self.gpu.stream();
13372            let mut b = __s_b.launch_builder(&f);
13373            b.arg(b0)
13374                .arg(b1)
13375                .arg(b2)
13376                .arg(b3)
13377                .arg(aq)
13378                .arg(ad)
13379                .arg(&mut y0)
13380                .arg(&mut y1)
13381                .arg(&mut y2)
13382                .arg(&mut y3)
13383                .arg(&inf)
13384                .arg(&oi0)
13385                .arg(&oi1)
13386                .arg(&oi2)
13387                .arg(&oi3)
13388                .arg(&mi);
13389            unsafe {
13390                b.launch(cfg)?;
13391            }
13392            return Ok(Some((y0, y1, y2, y3)));
13393        }
13394        let f = self.func("qmatvec_nvfp4_mmvq_fused4_rp");
13395        let cfg = LaunchConfig {
13396            grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), m as u32, 1),
13397            block_dim: (32, ROWS_PER_BLOCK, 1),
13398            shared_mem_bytes: 0,
13399        };
13400        let __s_b = self.gpu.stream();
13401        let mut b = __s_b.launch_builder(&f);
13402        b.arg(b0)
13403            .arg(b1)
13404            .arg(b2)
13405            .arg(b3)
13406            .arg(aq)
13407            .arg(ad)
13408            .arg(&mut y0)
13409            .arg(&mut y1)
13410            .arg(&mut y2)
13411            .arg(&mut y3)
13412            .arg(&inf)
13413            .arg(&oi0)
13414            .arg(&oi1)
13415            .arg(&oi2)
13416            .arg(&oi3)
13417            .arg(&mi)
13418            .arg(&p0.1)
13419            .arg(&p1.1)
13420            .arg(&p2.1)
13421            .arg(&p3.1);
13422        unsafe {
13423            b.launch(cfg)?;
13424        }
13425        Ok(Some((y0, y1, y2, y3)))
13426    }
13427
13428    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
13429    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
13430    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
13431    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
13432    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
13433    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
13434    /// back to the per-tensor path.
13435    pub fn matmul_q8_fused2(
13436        &self,
13437        w0: &crate::model::GpuTensor,
13438        w1: &crate::model::GpuTensor,
13439        aq: &CudaSlice<i8>,
13440        ad: &CudaSlice<f32>,
13441    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
13442        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
13443        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
13444        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
13445        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
13446        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
13447        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
13448            return Ok(Some(self.e4m3_fused2_core(
13449                p0.0,
13450                p1.0,
13451                aq,
13452                ad,
13453                w0.in_features(),
13454                p0.1,
13455                p1.1,
13456                p0.2,
13457                p0.3,
13458                p1.3,
13459            )?));
13460        }
13461        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
13462            return Ok(None);
13463        };
13464        Ok(Some(self.q8_fused2_core(
13465            p0.0,
13466            p1.0,
13467            aq,
13468            ad,
13469            w0.in_features(),
13470            p0.1,
13471            p1.1,
13472            p0.2,
13473        )?))
13474    }
13475
13476    #[allow(clippy::too_many_arguments)]
13477    fn q8_fused2_core(
13478        &self,
13479        b0: &CudaSlice<u8>,
13480        b1: &CudaSlice<u8>,
13481        aq: &CudaSlice<i8>,
13482        ad: &CudaSlice<f32>,
13483        in_f: usize,
13484        out0: usize,
13485        out1: usize,
13486        row_bytes: usize,
13487    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13488        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13489        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
13490        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
13491        let f = self.func("qmatvec_q8_0_mmvq_fused2");
13492        let mut y0 = self.alloc_uninit::<f32>(out0)?;
13493        let mut y1 = self.alloc_uninit::<f32>(out1)?;
13494        let cfg = LaunchConfig {
13495            grid_dim: (nb0 + nb1, 1, 1),
13496            block_dim: (32, ROWS_PER_BLOCK, 1),
13497            shared_mem_bytes: 0,
13498        };
13499        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
13500        let __s_b = self.gpu.stream();
13501        let mut b = __s_b.launch_builder(&f);
13502        b.arg(b0)
13503            .arg(b1)
13504            .arg(aq)
13505            .arg(ad)
13506            .arg(&mut y0)
13507            .arg(&mut y1)
13508            .arg(&inf)
13509            .arg(&o0)
13510            .arg(&o1)
13511            .arg(&rbl);
13512        unsafe {
13513            b.launch(cfg)?;
13514        }
13515        Ok((y0, y1))
13516    }
13517
13518    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
13519    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
13520    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
13521    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
13522    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
13523    pub fn matmul_q8_fused2_x(
13524        &self,
13525        w0: &crate::model::GpuTensor,
13526        w1: &crate::model::GpuTensor,
13527        x: &CudaSlice<f32>,
13528    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
13529        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
13530            return Ok(None);
13531        }
13532        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
13533            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
13534            return Ok(Some(self.e4m3_fused2_core(
13535                p0.0,
13536                p1.0,
13537                &aq,
13538                &ad,
13539                w0.in_features(),
13540                p0.1,
13541                p1.1,
13542                p0.2,
13543                p0.3,
13544                p1.3,
13545            )?));
13546        }
13547        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
13548            return Ok(None);
13549        };
13550        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
13551        Ok(Some(self.q8_fused2_core(
13552            p0.0,
13553            p1.0,
13554            &aq,
13555            &ad,
13556            w0.in_features(),
13557            p0.1,
13558            p1.1,
13559            p0.2,
13560        )?))
13561    }
13562
13563    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
13564    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
13565    #[allow(clippy::too_many_arguments)]
13566    pub fn qmatvec_q8_fused2_raw(
13567        &self,
13568        b0: &CudaSlice<u8>,
13569        b1: &CudaSlice<u8>,
13570        x: &CudaSlice<f32>,
13571        in_f: usize,
13572        out0: usize,
13573        out1: usize,
13574        row_bytes: usize,
13575    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13576        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13577        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
13578    }
13579
13580    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
13581    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
13582    /// (tensor,row) to three separate m=1 MMVQ launches.
13583    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
13584    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
13585    pub fn matmul_q4_fused3(
13586        &self,
13587        w0: &crate::model::GpuTensor,
13588        w1: &crate::model::GpuTensor,
13589        w2: &crate::model::GpuTensor,
13590        aq: &CudaSlice<i8>,
13591        ad: &CudaSlice<f32>,
13592    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
13593    {
13594        use crate::model::GpuTensor;
13595        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
13596            match w {
13597                GpuTensor::Quant {
13598                    qtype, row_bytes, ..
13599                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
13600                _ => None,
13601            }
13602        };
13603        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
13604            return Ok(None);
13605        };
13606        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
13607            return Ok(None);
13608        }
13609        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
13610        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
13611        // the separate matvecs (each routes its own rp).
13612        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
13613            match w {
13614                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
13615                    Some(m) => (m, true),
13616                    None => (bytes, *rp),
13617                },
13618                _ => unreachable!(),
13619            }
13620        }
13621        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
13622        if rp0 != rp1 || rp1 != rp2 {
13623            return Ok(None);
13624        }
13625        let rp = rp0;
13626        let rpb: u32 = 4;
13627        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
13628        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
13629        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
13630        let mr1 = rp && Self::q40_mr1_on();
13631        let nb = |o: usize| {
13632            if mr1 {
13633                (o as u32).div_ceil(rpb)
13634            } else {
13635                (o as u32).div_ceil(2).div_ceil(rpb)
13636            }
13637        };
13638        let grid = nb(o0) + nb(o1) + nb(o2);
13639        let mut y0 = self.alloc_uninit::<f32>(o0)?;
13640        let mut y1 = self.alloc_uninit::<f32>(o1)?;
13641        let mut y2 = self.alloc_uninit::<f32>(o2)?;
13642        let f = self.func(if mr1 {
13643            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
13644        } else if rp {
13645            "qmatvec_q4_0_mmvq_fused3_rp"
13646        } else {
13647            "qmatvec_q4_0_mmvq_fused3"
13648        });
13649        let cfg = LaunchConfig {
13650            grid_dim: (grid, 1, 1),
13651            block_dim: (32, rpb, 1),
13652            shared_mem_bytes: 0,
13653        };
13654        let inf = w0.in_features() as i32;
13655        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
13656        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
13657        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
13658        // variant may take the programmatic-serialization launch.
13659        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
13660            {
13661                use cudarc::driver::{DevicePtr, DevicePtrMut};
13662                let s = &self.gpu.stream();
13663                let (p0, _g0) = b0.device_ptr(s);
13664                let (p1, _g1) = b1.device_ptr(s);
13665                let (p2, _g2) = b2.device_ptr(s);
13666                let (paq, _g3) = aq.device_ptr(s);
13667                let (pad, _g4) = ad.device_ptr(s);
13668                let (py0, _g5) = y0.device_ptr_mut(s);
13669                let (py1, _g6) = y1.device_ptr_mut(s);
13670                let (py2, _g7) = y2.device_ptr_mut(s);
13671                let mut ps = [
13672                    &p0 as *const _ as *mut std::ffi::c_void,
13673                    &p1 as *const _ as *mut _,
13674                    &p2 as *const _ as *mut _,
13675                    &paq as *const _ as *mut _,
13676                    &pad as *const _ as *mut _,
13677                    &py0 as *const _ as *mut _,
13678                    &py1 as *const _ as *mut _,
13679                    &py2 as *const _ as *mut _,
13680                    &inf as *const _ as *mut _,
13681                    &oo0 as *const _ as *mut _,
13682                    &oo1 as *const _ as *mut _,
13683                    &oo2 as *const _ as *mut _,
13684                    &r0 as *const _ as *mut _,
13685                    &r1 as *const _ as *mut _,
13686                    &r2 as *const _ as *mut _,
13687                ];
13688                unsafe {
13689                    self.launch_pdl(
13690                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
13691                        (grid, 1, 1),
13692                        (32, rpb, 1),
13693                        &mut ps,
13694                    )?;
13695                }
13696            }
13697            return Ok(Some((y0, y1, y2)));
13698        }
13699        let __s_b = self.gpu.stream();
13700        let mut b = __s_b.launch_builder(&f);
13701        b.arg(b0)
13702            .arg(b1)
13703            .arg(b2)
13704            .arg(aq)
13705            .arg(ad)
13706            .arg(&mut y0)
13707            .arg(&mut y1)
13708            .arg(&mut y2)
13709            .arg(&inf)
13710            .arg(&oo0)
13711            .arg(&oo1)
13712            .arg(&oo2)
13713            .arg(&r0)
13714            .arg(&r1)
13715            .arg(&r2);
13716        unsafe {
13717            b.launch(cfg)?;
13718        }
13719        Ok(Some((y0, y1, y2)))
13720    }
13721
13722    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
13723    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
13724    #[allow(clippy::too_many_arguments)]
13725    pub fn matmul_q4_fused3_into(
13726        &self,
13727        w0: &crate::model::GpuTensor,
13728        w1: &crate::model::GpuTensor,
13729        w2: &crate::model::GpuTensor,
13730        aq: &CudaSlice<i8>,
13731        ad: &CudaSlice<f32>,
13732        y0: &mut CudaSlice<f32>,
13733        y1: &mut CudaSlice<f32>,
13734        y2: &mut CudaSlice<f32>,
13735    ) -> Result<bool, Box<dyn std::error::Error>> {
13736        use crate::model::GpuTensor;
13737        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
13738            match w {
13739                GpuTensor::Quant {
13740                    qtype, row_bytes, ..
13741                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
13742                _ => None,
13743            }
13744        };
13745        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
13746            return Ok(false);
13747        };
13748        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
13749            return Ok(false);
13750        }
13751        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
13752            match w {
13753                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
13754                    Some(m) => (m, true),
13755                    None => (bytes, *rp),
13756                },
13757                _ => unreachable!(),
13758            }
13759        }
13760        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
13761        if rp0 != rp1 || rp1 != rp2 {
13762            return Ok(false);
13763        }
13764        let rp = rp0;
13765        let rpb: u32 = 4;
13766        let mr1 = rp && Self::q40_mr1_on();
13767        let nb = |o: usize| {
13768            if mr1 {
13769                (o as u32).div_ceil(rpb)
13770            } else {
13771                (o as u32).div_ceil(2).div_ceil(rpb)
13772            }
13773        };
13774        let grid = nb(o0) + nb(o1) + nb(o2);
13775        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
13776        let f = self.func(if mr1 {
13777            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
13778        } else if rp {
13779            "qmatvec_q4_0_mmvq_fused3_rp"
13780        } else {
13781            "qmatvec_q4_0_mmvq_fused3"
13782        });
13783        let cfg = LaunchConfig {
13784            grid_dim: (grid, 1, 1),
13785            block_dim: (32, rpb, 1),
13786            shared_mem_bytes: 0,
13787        };
13788        let inf = w0.in_features() as i32;
13789        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
13790        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
13791        // PDL wave-A: identical to the owned twin (capture-lane parity).
13792        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
13793            use cudarc::driver::{DevicePtr, DevicePtrMut};
13794            let s = &self.gpu.stream();
13795            let (p0, _g0) = b0.device_ptr(s);
13796            let (p1, _g1) = b1.device_ptr(s);
13797            let (p2, _g2) = b2.device_ptr(s);
13798            let (paq, _g3) = aq.device_ptr(s);
13799            let (pad, _g4) = ad.device_ptr(s);
13800            let (py0, _g5) = y0.device_ptr_mut(s);
13801            let (py1, _g6) = y1.device_ptr_mut(s);
13802            let (py2, _g7) = y2.device_ptr_mut(s);
13803            let mut ps = [
13804                &p0 as *const _ as *mut std::ffi::c_void,
13805                &p1 as *const _ as *mut _,
13806                &p2 as *const _ as *mut _,
13807                &paq as *const _ as *mut _,
13808                &pad as *const _ as *mut _,
13809                &py0 as *const _ as *mut _,
13810                &py1 as *const _ as *mut _,
13811                &py2 as *const _ as *mut _,
13812                &inf as *const _ as *mut _,
13813                &oo0 as *const _ as *mut _,
13814                &oo1 as *const _ as *mut _,
13815                &oo2 as *const _ as *mut _,
13816                &r0 as *const _ as *mut _,
13817                &r1 as *const _ as *mut _,
13818                &r2 as *const _ as *mut _,
13819            ];
13820            unsafe {
13821                self.launch_pdl(
13822                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
13823                    (grid, 1, 1),
13824                    (32, rpb, 1),
13825                    &mut ps,
13826                )?;
13827            }
13828            return Ok(true);
13829        }
13830        let __s_b = self.gpu.stream();
13831        let mut b = __s_b.launch_builder(&f);
13832        b.arg(b0)
13833            .arg(b1)
13834            .arg(b2)
13835            .arg(aq)
13836            .arg(ad)
13837            .arg(&mut *y0)
13838            .arg(&mut *y1)
13839            .arg(&mut *y2)
13840            .arg(&inf)
13841            .arg(&oo0)
13842            .arg(&oo1)
13843            .arg(&oo2)
13844            .arg(&r0)
13845            .arg(&r1)
13846            .arg(&r2);
13847        unsafe {
13848            b.launch(cfg)?;
13849        }
13850        Ok(true)
13851    }
13852
13853    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
13854    pub fn matmul_q4_fused2(
13855        &self,
13856        w0: &crate::model::GpuTensor,
13857        w1: &crate::model::GpuTensor,
13858        aq: &CudaSlice<i8>,
13859        ad: &CudaSlice<f32>,
13860    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
13861        use crate::model::GpuTensor;
13862        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
13863            match w {
13864                GpuTensor::Quant {
13865                    qtype, row_bytes, ..
13866                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
13867                _ => None,
13868            }
13869        };
13870        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
13871            return Ok(None);
13872        };
13873        if w0.in_features() != w1.in_features() {
13874            return Ok(None);
13875        }
13876        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
13877        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
13878            match w {
13879                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
13880                    Some(m) => (m, true),
13881                    None => (bytes, *rp),
13882                },
13883                _ => unreachable!(),
13884            }
13885        }
13886        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
13887        if rp0 != rp1 {
13888            return Ok(None);
13889        }
13890        let rp = rp0;
13891        let rpb: u32 = 4;
13892        // mr1 twin — see matmul_q4_fused3.
13893        let mr1 = rp && Self::q40_mr1_on();
13894        let nb = |o: usize| {
13895            if mr1 {
13896                (o as u32).div_ceil(rpb)
13897            } else {
13898                (o as u32).div_ceil(2).div_ceil(rpb)
13899            }
13900        };
13901        let grid = nb(o0) + nb(o1);
13902        let mut y0 = self.alloc_uninit::<f32>(o0)?;
13903        let mut y1 = self.alloc_uninit::<f32>(o1)?;
13904        let f = self.func(if mr1 {
13905            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
13906        } else if rp {
13907            "qmatvec_q4_0_mmvq_fused2_rp"
13908        } else {
13909            "qmatvec_q4_0_mmvq_fused2"
13910        });
13911        let cfg = LaunchConfig {
13912            grid_dim: (grid, 1, 1),
13913            block_dim: (32, rpb, 1),
13914            shared_mem_bytes: 0,
13915        };
13916        let inf = w0.in_features() as i32;
13917        let (oo0, oo1) = (o0 as i32, o1 as i32);
13918        let (r0, r1) = (rb0 as i64, rb1 as i64);
13919        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
13920        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
13921            {
13922                use cudarc::driver::{DevicePtr, DevicePtrMut};
13923                let s = &self.gpu.stream();
13924                let (p0, _g0) = b0.device_ptr(s);
13925                let (p1, _g1) = b1.device_ptr(s);
13926                let (paq, _g2) = aq.device_ptr(s);
13927                let (pad, _g3) = ad.device_ptr(s);
13928                let (py0, _g4) = y0.device_ptr_mut(s);
13929                let (py1, _g5) = y1.device_ptr_mut(s);
13930                let mut ps = [
13931                    &p0 as *const _ as *mut std::ffi::c_void,
13932                    &p1 as *const _ as *mut _,
13933                    &paq as *const _ as *mut _,
13934                    &pad as *const _ as *mut _,
13935                    &py0 as *const _ as *mut _,
13936                    &py1 as *const _ as *mut _,
13937                    &inf as *const _ as *mut _,
13938                    &oo0 as *const _ as *mut _,
13939                    &oo1 as *const _ as *mut _,
13940                    &r0 as *const _ as *mut _,
13941                    &r1 as *const _ as *mut _,
13942                ];
13943                unsafe {
13944                    self.launch_pdl(
13945                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
13946                        (grid, 1, 1),
13947                        (32, rpb, 1),
13948                        &mut ps,
13949                    )?;
13950                }
13951            }
13952            return Ok(Some((y0, y1)));
13953        }
13954        let __s_b = self.gpu.stream();
13955        let mut b = __s_b.launch_builder(&f);
13956        b.arg(b0)
13957            .arg(b1)
13958            .arg(aq)
13959            .arg(ad)
13960            .arg(&mut y0)
13961            .arg(&mut y1)
13962            .arg(&inf)
13963            .arg(&oo0)
13964            .arg(&oo1)
13965            .arg(&r0)
13966            .arg(&r1);
13967        unsafe {
13968            b.launch(cfg)?;
13969        }
13970        Ok(Some((y0, y1)))
13971    }
13972
13973    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
13974    pub fn matmul_q4_fused2_into(
13975        &self,
13976        w0: &crate::model::GpuTensor,
13977        w1: &crate::model::GpuTensor,
13978        aq: &CudaSlice<i8>,
13979        ad: &CudaSlice<f32>,
13980        y0: &mut CudaSlice<f32>,
13981        y1: &mut CudaSlice<f32>,
13982    ) -> Result<bool, Box<dyn std::error::Error>> {
13983        use crate::model::GpuTensor;
13984        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
13985            match w {
13986                GpuTensor::Quant {
13987                    qtype, row_bytes, ..
13988                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
13989                _ => None,
13990            }
13991        };
13992        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
13993            return Ok(false);
13994        };
13995        if w0.in_features() != w1.in_features() {
13996            return Ok(false);
13997        }
13998        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
13999            match w {
14000                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
14001                    Some(m) => (m, true),
14002                    None => (bytes, *rp),
14003                },
14004                _ => unreachable!(),
14005            }
14006        }
14007        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
14008        if rp0 != rp1 {
14009            return Ok(false);
14010        }
14011        let rp = rp0;
14012        let rpb: u32 = 4;
14013        let mr1 = rp && Self::q40_mr1_on();
14014        let nb = |o: usize| {
14015            if mr1 {
14016                (o as u32).div_ceil(rpb)
14017            } else {
14018                (o as u32).div_ceil(2).div_ceil(rpb)
14019            }
14020        };
14021        let grid = nb(o0) + nb(o1);
14022        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
14023        let f = self.func(if mr1 {
14024            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
14025        } else if rp {
14026            "qmatvec_q4_0_mmvq_fused2_rp"
14027        } else {
14028            "qmatvec_q4_0_mmvq_fused2"
14029        });
14030        let cfg = LaunchConfig {
14031            grid_dim: (grid, 1, 1),
14032            block_dim: (32, rpb, 1),
14033            shared_mem_bytes: 0,
14034        };
14035        let inf = w0.in_features() as i32;
14036        let (oo0, oo1) = (o0 as i32, o1 as i32);
14037        let (r0, r1) = (rb0 as i64, rb1 as i64);
14038        // PDL wave-A: identical to the owned twin (capture-lane parity).
14039        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
14040            use cudarc::driver::{DevicePtr, DevicePtrMut};
14041            let s = &self.gpu.stream();
14042            let (p0, _g0) = b0.device_ptr(s);
14043            let (p1, _g1) = b1.device_ptr(s);
14044            let (paq, _g2) = aq.device_ptr(s);
14045            let (pad, _g3) = ad.device_ptr(s);
14046            let (py0, _g4) = y0.device_ptr_mut(s);
14047            let (py1, _g5) = y1.device_ptr_mut(s);
14048            let mut ps = [
14049                &p0 as *const _ as *mut std::ffi::c_void,
14050                &p1 as *const _ as *mut _,
14051                &paq as *const _ as *mut _,
14052                &pad as *const _ as *mut _,
14053                &py0 as *const _ as *mut _,
14054                &py1 as *const _ as *mut _,
14055                &inf as *const _ as *mut _,
14056                &oo0 as *const _ as *mut _,
14057                &oo1 as *const _ as *mut _,
14058                &r0 as *const _ as *mut _,
14059                &r1 as *const _ as *mut _,
14060            ];
14061            unsafe {
14062                self.launch_pdl(
14063                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
14064                    (grid, 1, 1),
14065                    (32, rpb, 1),
14066                    &mut ps,
14067                )?;
14068            }
14069            return Ok(true);
14070        }
14071        let __s_b = self.gpu.stream();
14072        let mut b = __s_b.launch_builder(&f);
14073        b.arg(b0)
14074            .arg(b1)
14075            .arg(aq)
14076            .arg(ad)
14077            .arg(&mut *y0)
14078            .arg(&mut *y1)
14079            .arg(&inf)
14080            .arg(&oo0)
14081            .arg(&oo1)
14082            .arg(&r0)
14083            .arg(&r1);
14084        unsafe {
14085            b.launch(cfg)?;
14086        }
14087        Ok(true)
14088    }
14089
14090    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
14091    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
14092    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
14093    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
14094    pub fn matmul_q4_fused2_batched(
14095        &self,
14096        w0: &crate::model::GpuTensor,
14097        w1: &crate::model::GpuTensor,
14098        aq: &CudaSlice<i8>,
14099        ad: &CudaSlice<f32>,
14100        m: usize,
14101    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
14102        use crate::model::GpuTensor;
14103        if m < 2 || m > 8 {
14104            return Ok(None);
14105        }
14106        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
14107            match w {
14108                GpuTensor::Quant {
14109                    qtype, row_bytes, ..
14110                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
14111                _ => None,
14112            }
14113        };
14114        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
14115            return Ok(None);
14116        };
14117        if w0.in_features() != w1.in_features() {
14118            return Ok(None);
14119        }
14120        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
14121            match w {
14122                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
14123                    Some(mr) => (mr, true),
14124                    None => (bytes, *rp),
14125                },
14126                _ => unreachable!(),
14127            }
14128        }
14129        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
14130        if !rp0 || !rp1 {
14131            return Ok(None);
14132        }
14133        let mcols = Self::batched_mcols(m);
14134        let rpb: u32 = 4;
14135        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
14136        let grid = nb(o0) + nb(o1);
14137        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
14138        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
14139        let f = self.func(match mcols {
14140            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
14141            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
14142            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
14143        });
14144        let cfg = LaunchConfig {
14145            grid_dim: (grid, 1, 1),
14146            block_dim: (32, rpb, 1),
14147            shared_mem_bytes: 0,
14148        };
14149        let inf = w0.in_features() as i32;
14150        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
14151        let rb = rb0 as i64;
14152        let __s_b = self.gpu.stream();
14153        let mut b = __s_b.launch_builder(&f);
14154        b.arg(b0)
14155            .arg(b1)
14156            .arg(aq)
14157            .arg(ad)
14158            .arg(&mut y0)
14159            .arg(&mut y1)
14160            .arg(&inf)
14161            .arg(&oo0)
14162            .arg(&oo1)
14163            .arg(&mi)
14164            .arg(&rb);
14165        unsafe {
14166            b.launch(cfg)?;
14167        }
14168        Ok(Some((y0, y1)))
14169    }
14170
14171    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
14172    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
14173    #[allow(clippy::too_many_arguments)]
14174    pub fn matmul_q4_fused3_batched(
14175        &self,
14176        w0: &crate::model::GpuTensor,
14177        w1: &crate::model::GpuTensor,
14178        w2: &crate::model::GpuTensor,
14179        aq: &CudaSlice<i8>,
14180        ad: &CudaSlice<f32>,
14181        m: usize,
14182    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
14183    {
14184        use crate::model::GpuTensor;
14185        if m < 2 || m > 8 {
14186            return Ok(None);
14187        }
14188        let q4 = |w: &GpuTensor| -> Option<usize> {
14189            match w {
14190                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
14191                _ => None,
14192            }
14193        };
14194        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
14195            return Ok(None);
14196        };
14197        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
14198            return Ok(None);
14199        }
14200        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
14201            match w {
14202                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
14203                    Some(mr) => (mr, true),
14204                    None => (bytes, *rp),
14205                },
14206                _ => unreachable!(),
14207            }
14208        }
14209        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
14210        if !rp0 || !rp1 || !rp2 {
14211            return Ok(None);
14212        }
14213        let mcols = Self::batched_mcols(m);
14214        let rpb: u32 = 4;
14215        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
14216        let grid = nb(o0) + nb(o1) + nb(o2);
14217        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
14218        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
14219        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
14220        let f = self.func(match mcols {
14221            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
14222            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
14223            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
14224        });
14225        let cfg = LaunchConfig {
14226            grid_dim: (grid, 1, 1),
14227            block_dim: (32, rpb, 1),
14228            shared_mem_bytes: 0,
14229        };
14230        let inf = w0.in_features() as i32;
14231        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
14232        let rb = 0i64;
14233        let __s_b = self.gpu.stream();
14234        let mut b = __s_b.launch_builder(&f);
14235        b.arg(b0)
14236            .arg(b1)
14237            .arg(b2)
14238            .arg(aq)
14239            .arg(ad)
14240            .arg(&mut y0)
14241            .arg(&mut y1)
14242            .arg(&mut y2)
14243            .arg(&inf)
14244            .arg(&oo0)
14245            .arg(&oo1)
14246            .arg(&oo2)
14247            .arg(&mi)
14248            .arg(&rb);
14249        unsafe {
14250            b.launch(cfg)?;
14251        }
14252        Ok(Some((y0, y1, y2)))
14253    }
14254
14255    pub fn matmul_q8_fused3(
14256        &self,
14257        w0: &crate::model::GpuTensor,
14258        w1: &crate::model::GpuTensor,
14259        w2: &crate::model::GpuTensor,
14260        aq: &CudaSlice<i8>,
14261        ad: &CudaSlice<f32>,
14262    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
14263    {
14264        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
14265        // are per-tensor FP8, so native residency without this arm meant three separate launches.
14266        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
14267            return Ok(Some(self.e4m3_fused3_core(
14268                p0.0,
14269                p1.0,
14270                p2.0,
14271                aq,
14272                ad,
14273                w0.in_features(),
14274                p0.1,
14275                p1.1,
14276                p2.1,
14277                p0.2,
14278                p0.3,
14279                p1.3,
14280                p2.3,
14281            )?));
14282        }
14283        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
14284            return Ok(None);
14285        };
14286        Ok(Some(self.q8_fused3_core(
14287            p0.0,
14288            p1.0,
14289            p2.0,
14290            aq,
14291            ad,
14292            w0.in_features(),
14293            p0.1,
14294            p1.1,
14295            p2.1,
14296            p0.2,
14297        )?))
14298    }
14299
14300    #[allow(clippy::too_many_arguments)]
14301    fn q8_fused3_core(
14302        &self,
14303        b0: &CudaSlice<u8>,
14304        b1: &CudaSlice<u8>,
14305        b2: &CudaSlice<u8>,
14306        aq: &CudaSlice<i8>,
14307        ad: &CudaSlice<f32>,
14308        in_f: usize,
14309        out0: usize,
14310        out1: usize,
14311        out2: usize,
14312        row_bytes: usize,
14313    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14314        const ROWS_PER_BLOCK: u32 = 4;
14315        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14316        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14317        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
14318        let f = self.func("qmatvec_q8_0_mmvq_fused3");
14319        let mut y0 = self.alloc_uninit::<f32>(out0)?;
14320        let mut y1 = self.alloc_uninit::<f32>(out1)?;
14321        let mut y2 = self.alloc_uninit::<f32>(out2)?;
14322        let cfg = LaunchConfig {
14323            grid_dim: (nb0 + nb1 + nb2, 1, 1),
14324            block_dim: (32, ROWS_PER_BLOCK, 1),
14325            shared_mem_bytes: 0,
14326        };
14327        let (inf, o0, o1, o2, rbl) = (
14328            in_f as i32,
14329            out0 as i32,
14330            out1 as i32,
14331            out2 as i32,
14332            row_bytes as i64,
14333        );
14334        let __s_b = self.gpu.stream();
14335        let mut b = __s_b.launch_builder(&f);
14336        b.arg(b0)
14337            .arg(b1)
14338            .arg(b2)
14339            .arg(aq)
14340            .arg(ad)
14341            .arg(&mut y0)
14342            .arg(&mut y1)
14343            .arg(&mut y2)
14344            .arg(&inf)
14345            .arg(&o0)
14346            .arg(&o1)
14347            .arg(&o2)
14348            .arg(&rbl);
14349        unsafe {
14350            b.launch(cfg)?;
14351        }
14352        Ok((y0, y1, y2))
14353    }
14354
14355    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
14356    #[allow(clippy::too_many_arguments)]
14357    pub fn qmatvec_q8_fused3_raw(
14358        &self,
14359        b0: &CudaSlice<u8>,
14360        b1: &CudaSlice<u8>,
14361        b2: &CudaSlice<u8>,
14362        x: &CudaSlice<f32>,
14363        in_f: usize,
14364        out0: usize,
14365        out1: usize,
14366        out2: usize,
14367        row_bytes: usize,
14368    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14369        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
14370        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
14371    }
14372
14373    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
14374    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
14375    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
14376    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
14377    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
14378    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
14379    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
14380    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
14381    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
14382    /// twin must not introduce a batched program the reference path would not run).
14383    pub fn matmul_q8_fused2_t(
14384        &self,
14385        w0: &crate::model::GpuTensor,
14386        w1: &crate::model::GpuTensor,
14387        aq: &CudaSlice<i8>,
14388        ad: &CudaSlice<f32>,
14389        m: usize,
14390    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
14391        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
14392        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
14393        // fuses too — same template body, still bit-identical to the two _b8 launches.
14394        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
14395            return Ok(None);
14396        }
14397        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
14398        // so the fused b8 launch would introduce a batched program the reference path would not run.
14399        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
14400            if m > 4 && !Self::b8_enabled() {
14401                return Ok(None);
14402            }
14403            return Ok(Some(self.e4m3_fused2_t_core(
14404                p0.0,
14405                p1.0,
14406                aq,
14407                ad,
14408                m,
14409                w0.in_features(),
14410                p0.1,
14411                p1.1,
14412                p0.2,
14413                p0.3,
14414                p1.3,
14415            )?));
14416        }
14417        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
14418            return Ok(None);
14419        };
14420        Ok(Some(self.q8_fused2_t_core(
14421            p0.0,
14422            p1.0,
14423            aq,
14424            ad,
14425            m,
14426            w0.in_features(),
14427            p0.1,
14428            p1.1,
14429            p0.2,
14430        )?))
14431    }
14432
14433    #[allow(clippy::too_many_arguments)]
14434    fn q8_fused2_t_core(
14435        &self,
14436        b0: &CudaSlice<u8>,
14437        b1: &CudaSlice<u8>,
14438        aq: &CudaSlice<i8>,
14439        ad: &CudaSlice<f32>,
14440        m: usize,
14441        in_f: usize,
14442        out0: usize,
14443        out1: usize,
14444        row_bytes: usize,
14445    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14446        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
14447        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14448        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14449        let f = self.func(match Self::batched_mcols(m) {
14450            2 => "qmatvec_q8_0_mmvq_fused2_b2",
14451            4 => "qmatvec_q8_0_mmvq_fused2_b4",
14452            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
14453            _ => "qmatvec_q8_0_mmvq_fused2_b8",
14454        });
14455        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
14456        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
14457        let cfg = LaunchConfig {
14458            grid_dim: (nb0 + nb1, 1, 1),
14459            block_dim: (32, ROWS_PER_BLOCK, 1),
14460            shared_mem_bytes: 0,
14461        };
14462        let (inf, o0, o1, mi, rbl) = (
14463            in_f as i32,
14464            out0 as i32,
14465            out1 as i32,
14466            m as i32,
14467            row_bytes as i64,
14468        );
14469        let __s_b = self.gpu.stream();
14470        let mut b = __s_b.launch_builder(&f);
14471        b.arg(b0)
14472            .arg(b1)
14473            .arg(aq)
14474            .arg(ad)
14475            .arg(&mut y0)
14476            .arg(&mut y1)
14477            .arg(&inf)
14478            .arg(&o0)
14479            .arg(&o1)
14480            .arg(&mi)
14481            .arg(&rbl);
14482        unsafe {
14483            b.launch(cfg)?;
14484        }
14485        Ok((y0, y1))
14486    }
14487
14488    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
14489    /// q8_1 quant of the [m, in_f] activation), no env gating.
14490    #[allow(clippy::too_many_arguments)]
14491    pub fn qmatvec_q8_fused2_t_raw(
14492        &self,
14493        b0: &CudaSlice<u8>,
14494        b1: &CudaSlice<u8>,
14495        x: &CudaSlice<f32>,
14496        m: usize,
14497        in_f: usize,
14498        out0: usize,
14499        out1: usize,
14500        row_bytes: usize,
14501    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14502        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14503        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
14504    }
14505
14506    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
14507    /// `matmul_q8_fused2_t` with three ranges.
14508    #[allow(clippy::too_many_arguments)]
14509    pub fn matmul_q8_fused3_t(
14510        &self,
14511        w0: &crate::model::GpuTensor,
14512        w1: &crate::model::GpuTensor,
14513        w2: &crate::model::GpuTensor,
14514        aq: &CudaSlice<i8>,
14515        ad: &CudaSlice<f32>,
14516        m: usize,
14517    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
14518    {
14519        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
14520            return Ok(None);
14521        }
14522        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
14523            return Ok(Some(self.e4m3_fused3_t_core(
14524                p0.0,
14525                p1.0,
14526                p2.0,
14527                aq,
14528                ad,
14529                m,
14530                w0.in_features(),
14531                p0.1,
14532                p1.1,
14533                p2.1,
14534                p0.2,
14535                p0.3,
14536                p1.3,
14537                p2.3,
14538            )?));
14539        }
14540        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
14541            return Ok(None);
14542        };
14543        Ok(Some(self.q8_fused3_t_core(
14544            p0.0,
14545            p1.0,
14546            p2.0,
14547            aq,
14548            ad,
14549            m,
14550            w0.in_features(),
14551            p0.1,
14552            p1.1,
14553            p2.1,
14554            p0.2,
14555        )?))
14556    }
14557
14558    #[allow(clippy::too_many_arguments)]
14559    fn q8_fused3_t_core(
14560        &self,
14561        b0: &CudaSlice<u8>,
14562        b1: &CudaSlice<u8>,
14563        b2: &CudaSlice<u8>,
14564        aq: &CudaSlice<i8>,
14565        ad: &CudaSlice<f32>,
14566        m: usize,
14567        in_f: usize,
14568        out0: usize,
14569        out1: usize,
14570        out2: usize,
14571        row_bytes: usize,
14572    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14573        const ROWS_PER_BLOCK: u32 = 4;
14574        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14575        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14576        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
14577        let f = self.func(if Self::batched_mcols(m) == 2 {
14578            "qmatvec_q8_0_mmvq_fused3_b2"
14579        } else {
14580            "qmatvec_q8_0_mmvq_fused3_b4"
14581        });
14582        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
14583        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
14584        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
14585        let cfg = LaunchConfig {
14586            grid_dim: (nb0 + nb1 + nb2, 1, 1),
14587            block_dim: (32, ROWS_PER_BLOCK, 1),
14588            shared_mem_bytes: 0,
14589        };
14590        let (inf, o0, o1, o2, mi, rbl) = (
14591            in_f as i32,
14592            out0 as i32,
14593            out1 as i32,
14594            out2 as i32,
14595            m as i32,
14596            row_bytes as i64,
14597        );
14598        let __s_b = self.gpu.stream();
14599        let mut b = __s_b.launch_builder(&f);
14600        b.arg(b0)
14601            .arg(b1)
14602            .arg(b2)
14603            .arg(aq)
14604            .arg(ad)
14605            .arg(&mut y0)
14606            .arg(&mut y1)
14607            .arg(&mut y2)
14608            .arg(&inf)
14609            .arg(&o0)
14610            .arg(&o1)
14611            .arg(&o2)
14612            .arg(&mi)
14613            .arg(&rbl);
14614        unsafe {
14615            b.launch(cfg)?;
14616        }
14617        Ok((y0, y1, y2))
14618    }
14619
14620    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
14621    #[allow(clippy::too_many_arguments)]
14622    pub fn qmatvec_q8_fused3_t_raw(
14623        &self,
14624        b0: &CudaSlice<u8>,
14625        b1: &CudaSlice<u8>,
14626        b2: &CudaSlice<u8>,
14627        x: &CudaSlice<f32>,
14628        m: usize,
14629        in_f: usize,
14630        out0: usize,
14631        out1: usize,
14632        out2: usize,
14633        row_bytes: usize,
14634    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14635        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14636        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
14637    }
14638
14639    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
14640    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
14641    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
14642    pub fn q8_ffn_fuse2_on(&self) -> bool {
14643        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14644        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
14645    }
14646
14647    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
14648    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
14649    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
14650    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
14651    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
14652    #[allow(clippy::type_complexity)]
14653    fn q8_fused_params<'w, const N: usize>(
14654        &self,
14655        ws: &[&'w crate::model::GpuTensor; N],
14656    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
14657        use crate::model::GpuTensor;
14658        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
14659            return None;
14660        }
14661        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
14662            return None;
14663        }
14664        let in_f = ws[0].in_features();
14665        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
14666        for (i, w) in ws.iter().enumerate() {
14667            match w {
14668                GpuTensor::Quant {
14669                    bytes,
14670                    qtype,
14671                    row_bytes,
14672                    scale,
14673                    ..
14674                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
14675                    out[i] = Some((bytes, w.out_features(), *row_bytes))
14676                }
14677                _ => return None,
14678            }
14679        }
14680        Some(out.map(|o| o.unwrap()))
14681    }
14682
14683    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
14684    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
14685    pub fn e4m3_dual_on(&self) -> bool {
14686        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14687        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
14688    }
14689
14690    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
14691    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
14692    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
14693    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
14694    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
14695    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
14696    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
14697    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
14698    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
14699    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
14700    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
14701    #[allow(clippy::type_complexity)]
14702    fn e4m3_fused_params<'w, const N: usize>(
14703        &self,
14704        ws: &[&'w crate::model::GpuTensor; N],
14705    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
14706        use crate::model::GpuTensor;
14707        if !self.e4m3_dual_on() {
14708            return None;
14709        }
14710        let in_f = ws[0].in_features();
14711        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
14712        for (i, w) in ws.iter().enumerate() {
14713            match w {
14714                GpuTensor::Quant {
14715                    bytes,
14716                    qtype,
14717                    row_bytes,
14718                    scale,
14719                    rp,
14720                    rp4,
14721                    ..
14722                } if *qtype == QT_F8_E4M3
14723                    && w.in_features() == in_f
14724                    && *row_bytes == in_f
14725                    && !*rp
14726                    && rp4.is_none() =>
14727                {
14728                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
14729                }
14730                _ => return None,
14731            }
14732        }
14733        Some(out.map(|o| o.unwrap()))
14734    }
14735
14736    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
14737    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
14738    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
14739    #[allow(clippy::too_many_arguments)]
14740    fn e4m3_fused2_core(
14741        &self,
14742        b0: &CudaSlice<u8>,
14743        b1: &CudaSlice<u8>,
14744        aq: &CudaSlice<i8>,
14745        ad: &CudaSlice<f32>,
14746        in_f: usize,
14747        out0: usize,
14748        out1: usize,
14749        row_bytes: usize,
14750        ws0: f32,
14751        ws1: f32,
14752    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14753        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
14754        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14755        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14756        let f = self.func("qmatvec_e4m3_mmvq_fused2");
14757        let mut y0 = self.alloc_uninit::<f32>(out0)?;
14758        let mut y1 = self.alloc_uninit::<f32>(out1)?;
14759        let cfg = LaunchConfig {
14760            grid_dim: (nb0 + nb1, 1, 1),
14761            block_dim: (32, ROWS_PER_BLOCK, 1),
14762            shared_mem_bytes: 0,
14763        };
14764        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
14765        let __s_b = self.gpu.stream();
14766        let mut b = __s_b.launch_builder(&f);
14767        b.arg(b0)
14768            .arg(b1)
14769            .arg(aq)
14770            .arg(ad)
14771            .arg(&mut y0)
14772            .arg(&mut y1)
14773            .arg(&inf)
14774            .arg(&o0)
14775            .arg(&o1)
14776            .arg(&rbl)
14777            .arg(&ws0)
14778            .arg(&ws1);
14779        unsafe {
14780            b.launch(cfg)?;
14781        }
14782        Ok((y0, y1))
14783    }
14784
14785    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
14786    #[allow(clippy::too_many_arguments)]
14787    fn e4m3_fused3_core(
14788        &self,
14789        b0: &CudaSlice<u8>,
14790        b1: &CudaSlice<u8>,
14791        b2: &CudaSlice<u8>,
14792        aq: &CudaSlice<i8>,
14793        ad: &CudaSlice<f32>,
14794        in_f: usize,
14795        out0: usize,
14796        out1: usize,
14797        out2: usize,
14798        row_bytes: usize,
14799        ws0: f32,
14800        ws1: f32,
14801        ws2: f32,
14802    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14803        const ROWS_PER_BLOCK: u32 = 4;
14804        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14805        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14806        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
14807        let f = self.func("qmatvec_e4m3_mmvq_fused3");
14808        let mut y0 = self.alloc_uninit::<f32>(out0)?;
14809        let mut y1 = self.alloc_uninit::<f32>(out1)?;
14810        let mut y2 = self.alloc_uninit::<f32>(out2)?;
14811        let cfg = LaunchConfig {
14812            grid_dim: (nb0 + nb1 + nb2, 1, 1),
14813            block_dim: (32, ROWS_PER_BLOCK, 1),
14814            shared_mem_bytes: 0,
14815        };
14816        let (inf, o0, o1, o2, rbl) = (
14817            in_f as i32,
14818            out0 as i32,
14819            out1 as i32,
14820            out2 as i32,
14821            row_bytes as i64,
14822        );
14823        let __s_b = self.gpu.stream();
14824        let mut b = __s_b.launch_builder(&f);
14825        b.arg(b0)
14826            .arg(b1)
14827            .arg(b2)
14828            .arg(aq)
14829            .arg(ad)
14830            .arg(&mut y0)
14831            .arg(&mut y1)
14832            .arg(&mut y2)
14833            .arg(&inf)
14834            .arg(&o0)
14835            .arg(&o1)
14836            .arg(&o2)
14837            .arg(&rbl)
14838            .arg(&ws0)
14839            .arg(&ws1)
14840            .arg(&ws2);
14841        unsafe {
14842            b.launch(cfg)?;
14843        }
14844        Ok((y0, y1, y2))
14845    }
14846
14847    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
14848    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
14849    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
14850    #[allow(clippy::too_many_arguments)]
14851    fn e4m3_fused2_t_core(
14852        &self,
14853        b0: &CudaSlice<u8>,
14854        b1: &CudaSlice<u8>,
14855        aq: &CudaSlice<i8>,
14856        ad: &CudaSlice<f32>,
14857        m: usize,
14858        in_f: usize,
14859        out0: usize,
14860        out1: usize,
14861        row_bytes: usize,
14862        ws0: f32,
14863        ws1: f32,
14864    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14865        const ROWS_PER_BLOCK: u32 = 4;
14866        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14867        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14868        let f = self.func(match Self::batched_mcols(m) {
14869            2 => "qmatvec_e4m3_mmvq_fused2_b2",
14870            4 => "qmatvec_e4m3_mmvq_fused2_b4",
14871            _ => "qmatvec_e4m3_mmvq_fused2_b8",
14872        });
14873        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
14874        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
14875        let cfg = LaunchConfig {
14876            grid_dim: (nb0 + nb1, 1, 1),
14877            block_dim: (32, ROWS_PER_BLOCK, 1),
14878            shared_mem_bytes: 0,
14879        };
14880        let (inf, o0, o1, mi, rbl) = (
14881            in_f as i32,
14882            out0 as i32,
14883            out1 as i32,
14884            m as i32,
14885            row_bytes as i64,
14886        );
14887        let __s_b = self.gpu.stream();
14888        let mut b = __s_b.launch_builder(&f);
14889        b.arg(b0)
14890            .arg(b1)
14891            .arg(aq)
14892            .arg(ad)
14893            .arg(&mut y0)
14894            .arg(&mut y1)
14895            .arg(&inf)
14896            .arg(&o0)
14897            .arg(&o1)
14898            .arg(&mi)
14899            .arg(&rbl);
14900        unsafe {
14901            b.launch(cfg)?;
14902        }
14903        if ws0 != 1.0 {
14904            self.scale_inplace(&mut y0, ws0, m * out0)?;
14905        }
14906        if ws1 != 1.0 {
14907            self.scale_inplace(&mut y1, ws1, m * out1)?;
14908        }
14909        Ok((y0, y1))
14910    }
14911
14912    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
14913    #[allow(clippy::too_many_arguments)]
14914    fn e4m3_fused3_t_core(
14915        &self,
14916        b0: &CudaSlice<u8>,
14917        b1: &CudaSlice<u8>,
14918        b2: &CudaSlice<u8>,
14919        aq: &CudaSlice<i8>,
14920        ad: &CudaSlice<f32>,
14921        m: usize,
14922        in_f: usize,
14923        out0: usize,
14924        out1: usize,
14925        out2: usize,
14926        row_bytes: usize,
14927        ws0: f32,
14928        ws1: f32,
14929        ws2: f32,
14930    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
14931        const ROWS_PER_BLOCK: u32 = 4;
14932        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
14933        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
14934        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
14935        let f = self.func(if Self::batched_mcols(m) == 2 {
14936            "qmatvec_e4m3_mmvq_fused3_b2"
14937        } else {
14938            "qmatvec_e4m3_mmvq_fused3_b4"
14939        });
14940        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
14941        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
14942        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
14943        let cfg = LaunchConfig {
14944            grid_dim: (nb0 + nb1 + nb2, 1, 1),
14945            block_dim: (32, ROWS_PER_BLOCK, 1),
14946            shared_mem_bytes: 0,
14947        };
14948        let (inf, o0, o1, o2, mi, rbl) = (
14949            in_f as i32,
14950            out0 as i32,
14951            out1 as i32,
14952            out2 as i32,
14953            m as i32,
14954            row_bytes as i64,
14955        );
14956        let __s_b = self.gpu.stream();
14957        let mut b = __s_b.launch_builder(&f);
14958        b.arg(b0)
14959            .arg(b1)
14960            .arg(b2)
14961            .arg(aq)
14962            .arg(ad)
14963            .arg(&mut y0)
14964            .arg(&mut y1)
14965            .arg(&mut y2)
14966            .arg(&inf)
14967            .arg(&o0)
14968            .arg(&o1)
14969            .arg(&o2)
14970            .arg(&mi)
14971            .arg(&rbl);
14972        unsafe {
14973            b.launch(cfg)?;
14974        }
14975        if ws0 != 1.0 {
14976            self.scale_inplace(&mut y0, ws0, m * out0)?;
14977        }
14978        if ws1 != 1.0 {
14979            self.scale_inplace(&mut y1, ws1, m * out1)?;
14980        }
14981        if ws2 != 1.0 {
14982            self.scale_inplace(&mut y2, ws2, m * out2)?;
14983        }
14984        Ok((y0, y1, y2))
14985    }
14986
14987    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
14988    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
14989    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
14990    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
14991    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
14992    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
14993    ///
14994    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
14995    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
14996    pub fn qmatvec_e4m3_blk_mmvq(
14997        &self,
14998        bytes: &CudaSlice<u8>,
14999        aq: &CudaSlice<i8>,
15000        ad: &CudaSlice<f32>,
15001        scales: &CudaSlice<f32>,
15002        m: usize,
15003        in_f: usize,
15004        out_f: usize,
15005        row_bytes: usize,
15006        scale_cols: usize,
15007    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15008        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
15009        self.qmatvec_e4m3_blk_mmvq_into(
15010            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
15011        )?;
15012        Ok(y)
15013    }
15014
15015    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
15016    #[allow(clippy::too_many_arguments)]
15017    pub fn qmatvec_e4m3_blk_mmvq_into(
15018        &self,
15019        bytes: &CudaSlice<u8>,
15020        aq: &CudaSlice<i8>,
15021        ad: &CudaSlice<f32>,
15022        scales: &CudaSlice<f32>,
15023        m: usize,
15024        in_f: usize,
15025        out_f: usize,
15026        row_bytes: usize,
15027        scale_cols: usize,
15028        y: &mut CudaSlice<f32>,
15029    ) -> Result<(), Box<dyn std::error::Error>> {
15030        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
15031        let f = self.func("qmatvec_e4m3_blk_mmvq");
15032        let cfg = LaunchConfig {
15033            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
15034            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
15035            shared_mem_bytes: 0,                // warp-only reduce
15036        };
15037        let (inf, outf, mi, rb, sc) = (
15038            in_f as i32,
15039            out_f as i32,
15040            m as i32,
15041            row_bytes as i64,
15042            scale_cols as i32,
15043        );
15044        let __s_b = self.gpu.stream();
15045        let mut b = __s_b.launch_builder(&f);
15046        b.arg(bytes)
15047            .arg(aq)
15048            .arg(ad)
15049            .arg(scales)
15050            .arg(&mut *y)
15051            .arg(&inf)
15052            .arg(&outf)
15053            .arg(&mi)
15054            .arg(&rb)
15055            .arg(&sc);
15056        unsafe {
15057            b.launch(cfg)?;
15058        }
15059        Ok(())
15060    }
15061
15062    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
15063    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
15064    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
15065    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
15066    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
15067    #[allow(clippy::too_many_arguments)]
15068    pub fn qmatvec_e4m3_blk_mmvq_batched(
15069        &self,
15070        bytes: &CudaSlice<u8>,
15071        aq: &CudaSlice<i8>,
15072        ad: &CudaSlice<f32>,
15073        scales: &CudaSlice<f32>,
15074        m: usize,
15075        in_f: usize,
15076        out_f: usize,
15077        row_bytes: usize,
15078        scale_cols: usize,
15079        mcols: usize,
15080    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15081        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
15082        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
15083        let name = match mcols {
15084            2 => "qmatvec_e4m3_blk_mmvq_b2",
15085            4 => "qmatvec_e4m3_blk_mmvq_b4",
15086            8 => "qmatvec_e4m3_blk_mmvq_b8",
15087            16 => "qmatvec_e4m3_blk_mmvq_b16",
15088            _ => {
15089                return Err(
15090                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
15091                );
15092            }
15093        };
15094        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
15095        let f = self.func(name);
15096        let cfg = LaunchConfig {
15097            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
15098            block_dim: (32, ROWS_PER_BLOCK, 1),
15099            shared_mem_bytes: 0,
15100        };
15101        let (inf, outf, mi, rb, sc) = (
15102            in_f as i32,
15103            out_f as i32,
15104            m as i32,
15105            row_bytes as i64,
15106            scale_cols as i32,
15107        );
15108        let __s_b = self.gpu.stream();
15109        let mut b = __s_b.launch_builder(&f);
15110        b.arg(bytes)
15111            .arg(aq)
15112            .arg(ad)
15113            .arg(scales)
15114            .arg(&mut y)
15115            .arg(&inf)
15116            .arg(&outf)
15117            .arg(&mi)
15118            .arg(&rb)
15119            .arg(&sc);
15120        unsafe {
15121            b.launch(cfg)?;
15122        }
15123        Ok(y)
15124    }
15125
15126    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
15127    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
15128    #[allow(clippy::too_many_arguments)]
15129    pub fn qmatvec_e4m3_blk_batched_raw(
15130        &self,
15131        bytes: &CudaSlice<u8>,
15132        x: &CudaSlice<f32>,
15133        scales: &CudaSlice<f32>,
15134        m: usize,
15135        in_f: usize,
15136        out_f: usize,
15137        row_bytes: usize,
15138        scale_cols: usize,
15139        mcols: usize,
15140    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15141        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
15142        self.qmatvec_e4m3_blk_mmvq_batched(
15143            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
15144        )
15145    }
15146
15147    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
15148    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
15149    #[allow(clippy::too_many_arguments)]
15150    pub fn qmatvec_e4m3_blk_mmvq_raw(
15151        &self,
15152        bytes: &CudaSlice<u8>,
15153        x: &CudaSlice<f32>,
15154        scales: &CudaSlice<f32>,
15155        m: usize,
15156        in_f: usize,
15157        out_f: usize,
15158        row_bytes: usize,
15159        scale_cols: usize,
15160    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15161        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
15162        self.qmatvec_e4m3_blk_mmvq(
15163            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
15164        )
15165    }
15166
15167    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
15168    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
15169    #[allow(clippy::too_many_arguments)]
15170    pub fn qmatvec_e4m3_fused2_raw(
15171        &self,
15172        b0: &CudaSlice<u8>,
15173        b1: &CudaSlice<u8>,
15174        x: &CudaSlice<f32>,
15175        in_f: usize,
15176        out0: usize,
15177        out1: usize,
15178        row_bytes: usize,
15179        ws0: f32,
15180        ws1: f32,
15181    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15182        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
15183        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
15184    }
15185
15186    #[allow(clippy::too_many_arguments)]
15187    pub fn qmatvec_e4m3_fused3_raw(
15188        &self,
15189        b0: &CudaSlice<u8>,
15190        b1: &CudaSlice<u8>,
15191        b2: &CudaSlice<u8>,
15192        x: &CudaSlice<f32>,
15193        in_f: usize,
15194        out0: usize,
15195        out1: usize,
15196        out2: usize,
15197        row_bytes: usize,
15198        ws0: f32,
15199        ws1: f32,
15200        ws2: f32,
15201    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15202        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
15203        self.e4m3_fused3_core(
15204            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
15205        )
15206    }
15207
15208    #[allow(clippy::too_many_arguments)]
15209    pub fn qmatvec_e4m3_fused2_t_raw(
15210        &self,
15211        b0: &CudaSlice<u8>,
15212        b1: &CudaSlice<u8>,
15213        x: &CudaSlice<f32>,
15214        m: usize,
15215        in_f: usize,
15216        out0: usize,
15217        out1: usize,
15218        row_bytes: usize,
15219        ws0: f32,
15220        ws1: f32,
15221    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15222        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
15223        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
15224    }
15225
15226    #[allow(clippy::too_many_arguments)]
15227    pub fn qmatvec_e4m3_fused3_t_raw(
15228        &self,
15229        b0: &CudaSlice<u8>,
15230        b1: &CudaSlice<u8>,
15231        b2: &CudaSlice<u8>,
15232        x: &CudaSlice<f32>,
15233        m: usize,
15234        in_f: usize,
15235        out0: usize,
15236        out1: usize,
15237        out2: usize,
15238        row_bytes: usize,
15239        ws0: f32,
15240        ws1: f32,
15241        ws2: f32,
15242    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
15243        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
15244        self.e4m3_fused3_t_core(
15245            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
15246        )
15247    }
15248
15249    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
15250    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
15251    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
15252    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
15253    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
15254    ///
15255    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
15256    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
15257    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
15258    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
15259    fn try_e4m3_blk_pre(
15260        &self,
15261        w: &crate::model::GpuTensor,
15262        aq: &CudaSlice<i8>,
15263        ad: &CudaSlice<f32>,
15264        m: usize,
15265    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
15266        use crate::model::GpuTensor;
15267        if let GpuTensor::Quant {
15268            bytes,
15269            qtype,
15270            row_bytes,
15271            blk: Some(g),
15272            ..
15273        } = w
15274        {
15275            if *qtype == QT_F8_E4M3_BLK {
15276                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
15277                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
15278                // below, so the decode-exactness contract is preserved at every width. Gated by
15279                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
15280                // one rollback door covers every dtype's batched tier.
15281                if (2..=16).contains(&m)
15282                    && std::env::var("MEMRA_NO_BATCHED").is_err()
15283                    && (m <= 4 || Self::b8_enabled())
15284                {
15285                    let mcols = Self::batched_mcols(m);
15286                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
15287                        bytes,
15288                        aq,
15289                        ad,
15290                        &g.scales,
15291                        m,
15292                        w.in_features(),
15293                        w.out_features(),
15294                        *row_bytes,
15295                        g.cols,
15296                        mcols,
15297                    )?));
15298                }
15299                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
15300                    bytes,
15301                    aq,
15302                    ad,
15303                    &g.scales,
15304                    m,
15305                    w.in_features(),
15306                    w.out_features(),
15307                    *row_bytes,
15308                    g.cols,
15309                )?));
15310            }
15311        }
15312        Ok(None)
15313    }
15314
15315    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
15316    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
15317    ///
15318    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
15319    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
15320    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
15321    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
15322    /// prefill keeps the floor's arithmetic and the floor's kernels.
15323    ///
15324    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
15325    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
15326    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
15327    /// (projection, prefill call) and frees immediately.
15328    ///
15329    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
15330    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
15331    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
15332    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
15333    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
15334    /// single-variable comparison instead of a two-variable one.
15335    ///
15336    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
15337    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
15338    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
15339    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
15340    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
15341    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
15342    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
15343    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
15344    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
15345    ///
15346    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
15347    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
15348    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
15349    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
15350    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
15351    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
15352    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
15353    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
15354    /// because v2's denominator had its slab already resident while this class's floor must build it
15355    /// every call; same tile, opposite sign, because the question changed.
15356    ///
15357    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
15358    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
15359    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
15360    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
15361    fn try_e4m3_blk_prefill(
15362        &self,
15363        w: &crate::model::GpuTensor,
15364        x: &CudaSlice<f32>,
15365        m: usize,
15366    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
15367        use crate::model::GpuTensor;
15368        let GpuTensor::Quant {
15369            bytes,
15370            qtype,
15371            blk: Some(g),
15372            ..
15373        } = w
15374        else {
15375            return Ok(None);
15376        };
15377        if *qtype != QT_F8_E4M3_BLK {
15378            return Ok(None);
15379        }
15380        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
15381        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
15382        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
15383        // through to the dequant below when they do, never silently produce nothing.
15384        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
15385            return Ok(Some(y));
15386        }
15387        let (in_f, out_f) = (w.in_features(), w.out_features());
15388        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
15389        let tmp = GpuTensor::Quant {
15390            bytes: slab,
15391            qtype: QT_Q8_0,
15392            row_bytes: in_f / 32 * 34,
15393            ne: vec![in_f as u64, out_f as u64],
15394            scale: 1.0,
15395            rp: false,
15396            #[cfg(memra_cutlass)]
15397            cutlass: None,
15398            fp8: None,
15399            blk: None,
15400            f16: None,
15401            rp4: None,
15402        };
15403        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
15404        Ok(Some(self.matmul(&tmp, x, m)?))
15405    }
15406
15407    pub fn matmul_pre_noscale(
15408        &self,
15409        w: &crate::model::GpuTensor,
15410        aq: &CudaSlice<i8>,
15411        ad: &CudaSlice<f32>,
15412        m: usize,
15413    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
15414        use crate::model::GpuTensor;
15415        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
15416        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
15417        // rather than let the tail below refuse and cost the caller a re-dispatch.
15418        if m == 1 {
15419            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
15420                return Ok(Some((y, 1.0)));
15421            }
15422        }
15423        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
15424        if m != 1 || !self.uses_q8_1_fast(w) {
15425            return Ok(None);
15426        }
15427        let in_f = w.in_features();
15428        let out_f = w.out_features();
15429        let (bytes, qtype, row_bytes, scale, rp) = match w {
15430            GpuTensor::Quant {
15431                bytes,
15432                qtype,
15433                row_bytes,
15434                scale,
15435                rp,
15436                ..
15437            } => (bytes, *qtype, *row_bytes, *scale, *rp),
15438            _ => return Ok(None),
15439        };
15440        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
15441        if self.mmvq_supports(qtype) {
15442            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
15443            let (mbytes, mrp) = match w {
15444                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
15445                _ => (bytes, rp),
15446            };
15447            let y = self.qmatvec_mmvq(
15448                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
15449            )?;
15450            return Ok(Some((y, scale)));
15451        }
15452        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
15453        let name = match qtype {
15454            QT_Q8_0 => "qmatvec_q8_0_dp4a",
15455            QT_Q4_K => "qmatvec_q4_K_dp4a",
15456            QT_Q6_K => "qmatvec_q6_K_dp4a",
15457            QT_Q5_K => "qmatvec_q5_K_dp4a",
15458            QT_Q3_K => "qmatvec_q3_K_dp4a",
15459            QT_NVFP4 => {
15460                if rp {
15461                    "qmatvec_nvfp4_dp4a_rp"
15462                } else {
15463                    "qmatvec_nvfp4_dp4a"
15464                }
15465            }
15466            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
15467            _ => return Ok(None),
15468        };
15469        let f = self.func(name);
15470        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
15471        let cfg = LaunchConfig {
15472            grid_dim: (out_f as u32, m as u32, 1),
15473            block_dim: (128, 1, 1),
15474            shared_mem_bytes: 0,
15475        };
15476        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
15477        let __s_b = self.gpu.stream();
15478        let mut b = __s_b.launch_builder(&f);
15479        b.arg(bytes)
15480            .arg(aq)
15481            .arg(ad)
15482            .arg(&mut y)
15483            .arg(&inf)
15484            .arg(&outf)
15485            .arg(&mi)
15486            .arg(&rb);
15487        unsafe {
15488            b.launch(cfg)?;
15489        }
15490        Ok(Some((y, scale)))
15491    }
15492
15493    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
15494    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
15495    pub fn mmvq_supports(&self, qtype: i32) -> bool {
15496        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
15497        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
15498        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
15499        // is a pure function of the dtype — the decode-parity law holds under every env.
15500        if qtype == QT_F8_E4M3 {
15501            return true;
15502        }
15503        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
15504            return false;
15505        }
15506        matches!(
15507            qtype,
15508            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
15509        )
15510    }
15511
15512    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
15513    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
15514    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
15515    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
15516    pub fn qmatvec_mmvq(
15517        &self,
15518        bytes: &CudaSlice<u8>,
15519        aq: &CudaSlice<i8>,
15520        ad: &CudaSlice<f32>,
15521        m: usize,
15522        in_f: usize,
15523        out_f: usize,
15524        qtype: i32,
15525        row_bytes: usize,
15526        scale: f32,
15527        rp: bool,
15528    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15529        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
15530        self.qmatvec_mmvq_into(
15531            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
15532        )?;
15533        Ok(y)
15534    }
15535
15536    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
15537    #[allow(clippy::too_many_arguments)]
15538    pub fn qmatvec_mmvq_into(
15539        &self,
15540        bytes: &CudaSlice<u8>,
15541        aq: &CudaSlice<i8>,
15542        ad: &CudaSlice<f32>,
15543        m: usize,
15544        in_f: usize,
15545        out_f: usize,
15546        qtype: i32,
15547        row_bytes: usize,
15548        scale: f32,
15549        rp: bool,
15550        y: &mut CudaSlice<f32>,
15551    ) -> Result<(), Box<dyn std::error::Error>> {
15552        debug_assert!(y.len() >= m * out_f);
15553        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
15554        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
15555        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
15556        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
15557        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
15558        if qtype == QT_Q8_0
15559            && rp
15560            && m == 1
15561            && out_f >= 64
15562            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
15563            && {
15564                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15565                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
15566            }
15567        {
15568            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
15569            let cfg = LaunchConfig {
15570                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
15571                block_dim: (32, 2, 1),
15572                shared_mem_bytes: 0,
15573            };
15574            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
15575            let __s_b = self.gpu.stream();
15576            let mut b = __s_b.launch_builder(&f);
15577            b.arg(bytes)
15578                .arg(aq)
15579                .arg(ad)
15580                .arg(&mut *y)
15581                .arg(&inf)
15582                .arg(&outf)
15583                .arg(&mi)
15584                .arg(&rb);
15585            unsafe {
15586                b.launch(cfg)?;
15587            }
15588            if scale != 1.0 {
15589                self.scale_inplace(y, scale, out_f)?;
15590            }
15591            return Ok(());
15592        }
15593        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
15594        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
15595        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
15596        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
15597        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
15598        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
15599        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
15600        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
15601        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
15602            2
15603        } else {
15604            1
15605        };
15606        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
15607        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
15608        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
15609        // valid-window interleaved, bit-identical per row — same dot program).
15610        if m == 1 && qtype == QT_Q4_0 {
15611            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
15612            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
15613            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
15614            mr = *Q40MR.get_or_init(|| {
15615                std::env::var("MEMRA_Q40_MR")
15616                    .ok()
15617                    .and_then(|v| v.parse().ok())
15618                    .unwrap_or(1)
15619            });
15620        }
15621        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
15622        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
15623        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
15624        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
15625        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
15626        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
15627        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
15628        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
15629        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
15630        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
15631        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
15632        let q5_force = q5_mode.as_deref() == Some("2");
15633        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
15634        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
15635        let q5_il = qtype == QT_Q5_K
15636            && m == 1
15637            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
15638        if q5_il && !q5_force && out_f > 65536 {
15639            mr = 1;
15640        }
15641        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
15642        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
15643        if qtype == QT_Q4_0 && rp && mr != 1 {
15644            mr = 2;
15645        }
15646        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
15647        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
15648        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
15649        if qtype == QT_Q8_0 && rp {
15650            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
15651            mr = *Q80MR.get_or_init(|| {
15652                std::env::var("MEMRA_Q80_MR")
15653                    .ok()
15654                    .and_then(|v| v.parse().ok())
15655                    .unwrap_or(1)
15656            });
15657        }
15658        let name = match (qtype, mr, rp) {
15659            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
15660            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
15661            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
15662            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
15663            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
15664            (QT_Q5_K, 2, _) => {
15665                if q5_il {
15666                    "qmatvec_q5_K_mmvq_mr2_il"
15667                } else {
15668                    "qmatvec_q5_K_mmvq_mr2"
15669                }
15670            }
15671            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
15672            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
15673            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
15674            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
15675            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
15676            (QT_Q8_0, _, true)
15677                if in_f % 1024 == 0 && {
15678                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15679                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
15680                } =>
15681            {
15682                "qmatvec_q8_0_mmvq_rpca"
15683            }
15684            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
15685            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
15686            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
15687            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
15688            // reach a GGUF-layout kernel or vice versa.
15689            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
15690            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
15691            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
15692            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
15693            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
15694            (QT_Q5_K, _, _) => {
15695                if q5_il {
15696                    "qmatvec_q5_K_mmvq_il"
15697                } else {
15698                    "qmatvec_q5_K_mmvq"
15699                }
15700            }
15701            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
15702            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
15703            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
15704            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
15705        };
15706        let f = self.func(name);
15707        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
15708        let rows_per_block = ROWS_PER_BLOCK * mr;
15709        let cfg = LaunchConfig {
15710            grid_dim: (
15711                (out_f as u32 + rows_per_block - 1) / rows_per_block,
15712                m as u32,
15713                1,
15714            ),
15715            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
15716            shared_mem_bytes: 0,                // warp-only reduce at m=1
15717        };
15718        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
15719        let __s_b = self.gpu.stream();
15720        let mut b = __s_b.launch_builder(&f);
15721        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
15722        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
15723        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
15724        // weight_scale). Other mmvq kernels keep the 8-arg signature.
15725        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
15726            // PDL wave-B: the nvfp4 mr2_rp single (gemma wo / generic rp singles) joins
15727            // the wave-A launch class — 9-arg flavor (fused macro-scale epilogue).
15728            if Self::pdl_on()
15729                && Self::pdl_mmvq_on()
15730                && Self::pdl_nvfp4q8_on()
15731                && name == "qmatvec_nvfp4_mmvq_mr2_rp"
15732            {
15733                use cudarc::driver::{DevicePtr, DevicePtrMut};
15734                let s = &self.gpu.stream();
15735                let (pw, _g0) = bytes.device_ptr(s);
15736                let (paq, _g1) = aq.device_ptr(s);
15737                let (pad, _g2) = ad.device_ptr(s);
15738                let (py, _g3) = y.device_ptr_mut(s);
15739                let mut ps = [
15740                    &pw as *const _ as *mut std::ffi::c_void,
15741                    &paq as *const _ as *mut _,
15742                    &pad as *const _ as *mut _,
15743                    &py as *const _ as *mut _,
15744                    &inf as *const _ as *mut _,
15745                    &outf as *const _ as *mut _,
15746                    &mi as *const _ as *mut _,
15747                    &rb as *const _ as *mut _,
15748                    &scale as *const _ as *mut _,
15749                ];
15750                unsafe {
15751                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
15752                }
15753                return Ok(());
15754            }
15755            b.arg(bytes)
15756                .arg(aq)
15757                .arg(ad)
15758                .arg(&mut *y)
15759                .arg(&inf)
15760                .arg(&outf)
15761                .arg(&mi)
15762                .arg(&rb)
15763                .arg(&scale);
15764            unsafe {
15765                b.launch(cfg)?;
15766            }
15767        } else if Self::pdl_on()
15768            && Self::pdl_mmvq_on()
15769            && (matches!(
15770                name,
15771                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
15772            ) || (Self::pdl_nvfp4q8_on()
15773                && matches!(name, "qmatvec_q8_0_mmvq_rp" | "qmatvec_q8_0_mmvq_mr2_rp")))
15774        {
15775            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
15776            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
15777            // names may take this launch (unmarked kernels would read unordered).
15778            {
15779                use cudarc::driver::{DevicePtr, DevicePtrMut};
15780                let s = &self.gpu.stream();
15781                let (pw, _g0) = bytes.device_ptr(s);
15782                let (paq, _g1) = aq.device_ptr(s);
15783                let (pad, _g2) = ad.device_ptr(s);
15784                let (py, _g3) = y.device_ptr_mut(s);
15785                let mut ps = [
15786                    &pw as *const _ as *mut std::ffi::c_void,
15787                    &paq as *const _ as *mut _,
15788                    &pad as *const _ as *mut _,
15789                    &py as *const _ as *mut _,
15790                    &inf as *const _ as *mut _,
15791                    &outf as *const _ as *mut _,
15792                    &mi as *const _ as *mut _,
15793                    &rb as *const _ as *mut _,
15794                ];
15795                unsafe {
15796                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
15797                }
15798            }
15799            if scale != 1.0 {
15800                self.scale_inplace(y, scale, m * out_f)?;
15801            }
15802        } else {
15803            b.arg(bytes)
15804                .arg(aq)
15805                .arg(ad)
15806                .arg(&mut *y)
15807                .arg(&inf)
15808                .arg(&outf)
15809                .arg(&mi)
15810                .arg(&rb);
15811            unsafe {
15812                b.launch(cfg)?;
15813            }
15814            if scale != 1.0 {
15815                self.scale_inplace(y, scale, m * out_f)?;
15816            }
15817        }
15818        Ok(())
15819    }
15820
15821    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
15822    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
15823    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
15824    pub fn qmatvec_mmvq_raw(
15825        &self,
15826        bytes: &CudaSlice<u8>,
15827        x: &CudaSlice<f32>,
15828        m: usize,
15829        in_f: usize,
15830        out_f: usize,
15831        qtype: i32,
15832        row_bytes: usize,
15833        rp: bool,
15834    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15835        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
15836        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
15837    }
15838
15839    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
15840    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
15841    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
15842    pub fn batched_supports(&self, qtype: i32) -> bool {
15843        matches!(
15844            qtype,
15845            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
15846        )
15847    }
15848
15849    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
15850    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
15851    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
15852    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
15853    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
15854    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
15855    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
15856    pub fn iq_fast_enabled() -> bool {
15857        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15858        *ON.get_or_init(|| {
15859            std::env::var("MEMRA_IQ_FAST")
15860                .map(|v| v != "0")
15861                .unwrap_or(true)
15862        })
15863    }
15864
15865    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
15866    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
15867    pub fn b8_enabled() -> bool {
15868        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15869        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
15870    }
15871
15872    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
15873    pub fn batched_mcols(m: usize) -> usize {
15874        if m == 2 {
15875            2
15876        } else if m <= 4 {
15877            4
15878        } else if m <= 8 {
15879            8
15880        } else {
15881            16
15882        }
15883    }
15884
15885    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
15886    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
15887    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
15888    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
15889    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
15890        Some(match (qtype, mcols) {
15891            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
15892            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
15893            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
15894            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
15895            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
15896            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
15897            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
15898            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
15899            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
15900            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
15901            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
15902            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
15903            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
15904            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
15905            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
15906            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
15907            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
15908            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
15909            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
15910            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
15911            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
15912            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
15913            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
15914            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
15915            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
15916            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
15917            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
15918            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
15919            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
15920            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
15921            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
15922            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
15923            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
15924            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
15925            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
15926            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
15927            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
15928            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
15929            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
15930            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
15931            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
15932            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
15933            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
15934            _ => return None,
15935        })
15936    }
15937
15938    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
15939    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
15940    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
15941    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
15942    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
15943    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
15944    ///
15945    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
15946    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
15947    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
15948    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
15949    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
15950    /// msweep on all six 27B shapes (2026-07-03):
15951    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
15952    ///          it applies for b4 (-3..-14%), never loses;
15953    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
15954    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
15955    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
15956    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
15957    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
15958    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
15959    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
15960    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
15961    /// b2: in_f>=6144 -> r2, else base.
15962    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
15963    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
15964    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
15965    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
15966    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
15967    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
15968    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
15969    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
15970    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
15971    /// Device SM count (cached) — grid-fill policy input.
15972    pub fn sm_count(&self) -> i32 {
15973        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
15974        *SMS.get_or_init(|| {
15975            use cudarc::driver::sys::CUdevice_attribute_enum as A;
15976            self.gpu
15977                .ctx
15978                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
15979                .unwrap_or(82)
15980        })
15981    }
15982
15983    pub fn batched_variant(
15984        &self,
15985        _m: usize,
15986        in_f: usize,
15987        out_f: usize,
15988        qtype: i32,
15989        row_bytes: usize,
15990        mcols: usize,
15991        rp: bool,
15992    ) -> &'static str {
15993        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
15994        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
15995        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
15996        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
15997        if qtype == QT_Q8_0 {
15998            return if rp { "rp" } else { "base" };
15999        }
16000        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
16001        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
16002            Ok("base") => "base",
16003            Ok("pf") => "pf",
16004            Ok("r2") => "r2",
16005            Ok("r2w8") => "r2w8",
16006            Ok("pfr2") => "pfr2",
16007            Ok("ca") => "ca",
16008            Ok("car2") => "car2",
16009            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
16010            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
16011            Ok("rp") => "rp",
16012            Ok("rpr2") => "rpr2",
16013            Ok("rpr2w8") => "rpr2w8",
16014            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
16015            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
16016            Ok("rpca") => "rpca",
16017            Ok("rpcar2") => "rpcar2",
16018            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
16019            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
16020            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
16021            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
16022            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
16023            // bit-identical to the decode path — measurement corpus ONLY, never auto).
16024            Ok("rpsc") => "rpsc",
16025            Ok("rpms") => "rpms",
16026            Ok("rpmsc") => "rpmsc",
16027            Ok("rpks") => "rpks",
16028            Ok("rpksc") => "rpksc",
16029            _ => "auto",
16030        });
16031        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
16032        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
16033        // shapes qualify; anything else falls back to the register variants.
16034        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
16035        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
16036        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
16037        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
16038        // forced MEMRA_MMVQ_BV values still work).
16039        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16040        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
16041        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
16042        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
16043        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
16044        let sms = *SMS.get_or_init(|| {
16045            use cudarc::driver::sys::CUdevice_attribute_enum as A;
16046            self.gpu
16047                .ctx
16048                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
16049                .unwrap_or(82)
16050        });
16051        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
16052        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
16053        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
16054        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
16055        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
16056        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
16057        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
16058        // AUTO RULE = the measured winners table (differs from NVFP4's!):
16059        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
16060        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
16061        //     r2 1258us) — kernels kept behind the force seam for the corpus;
16062        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
16063        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
16064        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
16065        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
16066        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
16067        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
16068        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
16069        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
16070        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
16071        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
16072        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
16073        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
16074        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
16075            Ok("base") => "base",
16076            Ok("r2") => "r2",
16077            Ok("r2w8") => "r2w8",
16078            _ => "auto",
16079        });
16080        let variant: &'static str = if qtype == QT_Q4_0 {
16081            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
16082            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
16083            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
16084            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
16085            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
16086                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
16087                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
16088                // + syncs cost more than the stalls, bank-pad made no difference);
16089                // register load-ahead flat (nvcc already reorders). The b-tier limiter
16090                // is still unidentified — see the jsonl row.
16091                Ok("base") => "base",
16092                Ok("r2") => "r2",
16093                Ok("ms") => "ms",
16094                Ok("sm") => "sm",
16095                Ok("la") => "la",
16096                _ => "auto",
16097            });
16098            let v = if q40 != "auto" {
16099                q40
16100            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
16101                "r2"
16102            } else {
16103                "base"
16104            };
16105            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
16106            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
16107            // and the limiter is the per-column activation load chain (long_scoreboard
16108            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
16109            if rp {
16110                match v {
16111                    "ms" => "r2ms_rp",
16112                    "sm" => "r2sm_rp",
16113                    "la" => "r2la_rp",
16114                    "r2" => "r2_rp",
16115                    _ => "rp",
16116                }
16117            } else if matches!(v, "ms" | "sm" | "la") {
16118                "r2"
16119            } else {
16120                v
16121            }
16122        } else if qtype != QT_NVFP4 && !kq_r2 {
16123            "base"
16124        } else if kq_r2 && rp {
16125            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
16126            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
16127            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
16128            "rp"
16129        } else if kq_r2 {
16130            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
16131            // mcols != 4 forced r2w8 falls to unbounded r2.
16132            if kq_bv != "auto" {
16133                if kq_bv == "r2w8" && mcols != 4 {
16134                    "r2"
16135                } else {
16136                    kq_bv
16137                }
16138            } else if bv != "auto" {
16139                match bv {
16140                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
16141                    "r2w8" | "rpr2w8" => {
16142                        if mcols != 4 {
16143                            "r2"
16144                        } else {
16145                            "r2w8"
16146                        }
16147                    }
16148                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
16149                }
16150            } else {
16151                let blocks = (out_f + 7) / 8;
16152                let waves = blocks as f64 / (7 * sms as usize) as f64;
16153                let filled = blocks >= 4 * sms as usize;
16154                let use_r2 = if qtype == QT_Q4_K {
16155                    filled
16156                } else {
16157                    waves >= 2.0
16158                };
16159                if use_r2 { "r2" } else { "base" }
16160            }
16161        } else if bv != "auto" {
16162            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
16163            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
16164            // unsupported (shape, mcols) combos fall back to pf/r2.
16165            // On rp buffers, forced legacy names map to their rp twins (layout law).
16166            let v = if bv == "r2w8" && mcols == 2 {
16167                "r2"
16168            } else if bv == "ca" && (!ca_ok || mcols == 8) {
16169                "pf"
16170            } else if bv == "car2" && (!ca_ok || mcols == 8) {
16171                "r2"
16172            } else if bv == "pfr2" && mcols == 8 {
16173                "r2"
16174            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
16175                "rpr2"
16176            }
16177            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
16178            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
16179                if mcols == 8 { "rpr2w8" } else { "rpr2" }
16180            } else if bv == "rpcar2" && mcols == 2 {
16181                "rpca"
16182            }
16183            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
16184            // (rpms has no smem and no alignment need — always valid on rp buffers).
16185            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
16186                "rpr2"
16187            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
16188                "rpr2"
16189            } else {
16190                bv
16191            };
16192            if rp {
16193                match v {
16194                    "base" | "pf" | "ca" | "rp" => "rp",
16195                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
16196                    "r2w8" | "rpr2w8" => {
16197                        if mcols == 2 {
16198                            "rpr2"
16199                        } else {
16200                            "rpr2w8"
16201                        }
16202                    }
16203                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
16204                }
16205            } else {
16206                v
16207            }
16208        } else if mcols == 8 {
16209            // b8 AUTO (2026-07-06 m-small latency arc, rtx6000 DRAM-cold rp msweep m=5/6/8 all five
16210            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
16211            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
16212            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
16213            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
16214            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
16215            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
16216            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
16217            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
16218            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
16219            if rp {
16220                if sc_ok { "rpsc" } else { "rpr2w8" }
16221            } else {
16222                "r2w8"
16223            }
16224        } else if mcols >= 4 {
16225            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
16226            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
16227            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
16228            let blocks = (out_f + 7) / 8;
16229            let r7 = 7 * sms as usize;
16230            let r8 = 8 * sms as usize;
16231            let waves = blocks as f64 / r7 as f64;
16232            let filled = blocks >= 4 * sms as usize;
16233            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
16234            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
16235            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
16236            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
16237                // the extra residency drops the INTEGER wave count -> the straggler wave a
16238                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
16239                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
16240                if rp { "rpr2w8" } else { "r2w8" }
16241            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
16242                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
16243                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
16244                if rp { "rpr2" } else { "r2" }
16245            } else {
16246                // fractional straggler-wave window with no crossing, or grid too small to fill
16247                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
16248                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
16249                if rp { "rp" } else { "pf" }
16250            }
16251        } else if in_f >= 6144 {
16252            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
16253            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
16254            // stays.
16255            if rp { "rpr2" } else { "r2" }
16256        } else if rp {
16257            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
16258            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
16259            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
16260            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
16261            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
16262            if sc_ok && waves >= 0.9 && waves <= 1.1 {
16263                "rpsc"
16264            } else {
16265                "rp"
16266            }
16267        } else {
16268            "base"
16269        };
16270        variant
16271    }
16272
16273    pub fn qmatvec_mmvq_batched(
16274        &self,
16275        bytes: &CudaSlice<u8>,
16276        aq: &CudaSlice<i8>,
16277        ad: &CudaSlice<f32>,
16278        m: usize,
16279        in_f: usize,
16280        out_f: usize,
16281        qtype: i32,
16282        row_bytes: usize,
16283        mcols: usize,
16284        scale: f32,
16285        rp: bool,
16286    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16287        const ROWS_PER_BLOCK: u32 = 4;
16288        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
16289        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
16290        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
16291        // weight keeps its rp-layout kernel family regardless of the override.
16292        let forced: Option<&'static str> = {
16293            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
16294            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
16295                .as_deref()
16296                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
16297        };
16298        let variant = match forced {
16299            Some(v) if !rp || v.contains("rp") => v,
16300            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
16301        };
16302        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
16303            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
16304        })?;
16305        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
16306        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
16307        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
16308        let variant = if mcols == 16 {
16309            if rp { "rp" } else { "base" }
16310        } else {
16311            variant
16312        };
16313        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
16314        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
16315        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
16316        // per-(token,row) chain (columns c >= m never execute in either form) ->
16317        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
16318        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
16319        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16320        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
16321        if b567
16322            && qtype == QT_NVFP4
16323            && rp
16324            && mcols == 8
16325            && (5..=7).contains(&m)
16326            && matches!(variant, "rpsc" | "rpr2w8")
16327        {
16328            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
16329            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
16330            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
16331            let cfg = LaunchConfig {
16332                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
16333                block_dim: (32, ROWS_PER_BLOCK, 1),
16334                shared_mem_bytes: 0,
16335            };
16336            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
16337            let __s_b = self.gpu.stream();
16338            let mut b = __s_b.launch_builder(&f);
16339            b.arg(bytes)
16340                .arg(aq)
16341                .arg(ad)
16342                .arg(&mut y)
16343                .arg(&inf)
16344                .arg(&outf)
16345                .arg(&mi)
16346                .arg(&rb);
16347            unsafe {
16348                b.launch(cfg)?;
16349            }
16350            if scale != 1.0 {
16351                self.scale_inplace(&mut y, scale, m * out_f)?;
16352            }
16353            return Ok(y);
16354        }
16355        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
16356            "base" => (base_name.into(), ROWS_PER_BLOCK),
16357            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
16358            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
16359            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
16360            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
16361            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
16362            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
16363            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
16364            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
16365            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
16366            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
16367            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
16368            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
16369            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
16370            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
16371        };
16372        debug_assert!(
16373            !rp || name.contains("_rp"),
16374            "rp weight dispatched to a GGUF-layout kernel"
16375        );
16376        let f = self.func(&name);
16377        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
16378        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
16379        let smem = if name.contains("_r2sm_rp") {
16380            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
16381        } else {
16382            0
16383        };
16384        let cfg = LaunchConfig {
16385            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
16386            block_dim: (32, ROWS_PER_BLOCK, 1),
16387            shared_mem_bytes: smem,
16388        };
16389        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
16390        let __s_b = self.gpu.stream();
16391        let mut b = __s_b.launch_builder(&f);
16392        b.arg(bytes)
16393            .arg(aq)
16394            .arg(ad)
16395            .arg(&mut y)
16396            .arg(&inf)
16397            .arg(&outf)
16398            .arg(&mi)
16399            .arg(&rb);
16400        unsafe {
16401            b.launch(cfg)?;
16402        }
16403        if scale != 1.0 {
16404            self.scale_inplace(&mut y, scale, m * out_f)?;
16405        }
16406        Ok(y)
16407    }
16408
16409    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
16410    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
16411    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
16412    pub fn qmatvec_batched_raw(
16413        &self,
16414        bytes: &CudaSlice<u8>,
16415        x: &CudaSlice<f32>,
16416        m: usize,
16417        in_f: usize,
16418        out_f: usize,
16419        qtype: i32,
16420        row_bytes: usize,
16421        mcols: usize,
16422        rp: bool,
16423    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16424        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
16425        self.qmatvec_mmvq_batched(
16426            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
16427        )
16428    }
16429
16430    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
16431    pub fn qmatvec_nvfp4_batched_raw(
16432        &self,
16433        bytes: &CudaSlice<u8>,
16434        x: &CudaSlice<f32>,
16435        m: usize,
16436        in_f: usize,
16437        out_f: usize,
16438        row_bytes: usize,
16439        mcols: usize,
16440        rp: bool,
16441    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16442        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
16443    }
16444
16445    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
16446    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
16447    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
16448    fn try_fp4_gemm(
16449        &self,
16450        w: &crate::model::GpuTensor,
16451        x: &CudaSlice<f32>,
16452        m: usize,
16453        in_f: usize,
16454        out_f: usize,
16455    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16456        use crate::model::GpuTensor;
16457        if cfg!(memra_portable_cuda) {
16458            return Ok(None);
16459        }
16460        // MEMRA_FP4 reaches qmatvec_gemm_nvfp4_fp4, which cu/qmatvec_gemm.cu:1234 omits on a
16461        // portable build (the mxf4 block-scale MMA is sm_120a-only). Refuse at the door.
16462        if std::env::var("MEMRA_FP4").is_ok() {
16463            refuse_portable_force("MEMRA_FP4", "the sm_120a mxf4 block-scale MMA");
16464        }
16465        if std::env::var("MEMRA_FP4").is_err() {
16466            return Ok(None);
16467        }
16468        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
16469        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
16470        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
16471        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
16472        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
16473        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
16474        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
16475        // for the common no-macro-scale case.
16476        #[cfg(memra_cutlass)]
16477        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
16478            if let GpuTensor::Quant {
16479                bytes,
16480                qtype,
16481                scale,
16482                row_bytes,
16483                cutlass,
16484                ..
16485            } = w
16486            {
16487                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
16488                    if let Some(cw) = cutlass {
16489                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
16490                        let y = self.cutlass_fp4_gemm(
16491                            &cw.b_packed,
16492                            &cw.sfb_swizzled,
16493                            x,
16494                            *scale,
16495                            m,
16496                            out_f,
16497                            in_f,
16498                        )?;
16499                        return Ok(Some(y));
16500                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
16501                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
16502                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
16503                        // (the load-time repack ~doubles it) — needed for models that don't fit the
16504                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
16505                        let (b_packed, sfb_sw) =
16506                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
16507                        let y =
16508                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
16509                        return Ok(Some(y));
16510                    }
16511                }
16512            }
16513        }
16514        if let GpuTensor::Quant {
16515            bytes,
16516            qtype,
16517            row_bytes,
16518            scale,
16519            rp,
16520            ..
16521        } = w
16522        {
16523            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
16524            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
16525            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
16526                let y =
16527                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
16528                return Ok(Some(y));
16529            }
16530        }
16531        Ok(None)
16532    }
16533
16534    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
16535    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
16536    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
16537    pub fn rms_norm_f16out(
16538        &self,
16539        x: &CudaSlice<f32>,
16540        w: &CudaSlice<f32>,
16541        dst: &mut CudaSlice<f32>,
16542        dst16: &mut CudaSlice<u8>,
16543        ncols: usize,
16544        nrows: usize,
16545        eps: f32,
16546    ) -> Result<(), Box<dyn std::error::Error>> {
16547        let f = self.func("rms_norm_f16out_f32");
16548        let cfg = LaunchConfig {
16549            grid_dim: (nrows as u32, 1, 1),
16550            block_dim: (rms_block(), 1, 1),
16551            shared_mem_bytes: 0,
16552        };
16553        let (nc, e) = (ncols as i32, eps);
16554        let __s_b = self.gpu.stream();
16555        let mut b = __s_b.launch_builder(&f);
16556        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
16557        unsafe {
16558            b.launch(cfg)?;
16559        }
16560        Ok(())
16561    }
16562
16563    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
16564    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
16565    #[allow(clippy::too_many_arguments)]
16566    pub fn add_rms_norm_f16out(
16567        &self,
16568        a: &CudaSlice<f32>,
16569        b: &CudaSlice<f32>,
16570        w: &CudaSlice<f32>,
16571        res: &mut CudaSlice<f32>,
16572        dst: &mut CudaSlice<f32>,
16573        dst16: &mut CudaSlice<u8>,
16574        ncols: usize,
16575        nrows: usize,
16576        eps: f32,
16577    ) -> Result<(), Box<dyn std::error::Error>> {
16578        let f = self.func("add_rms_norm_f16out_f32");
16579        let cfg = LaunchConfig {
16580            grid_dim: (nrows as u32, 1, 1),
16581            block_dim: (rms_block(), 1, 1),
16582            shared_mem_bytes: 0,
16583        };
16584        let (nc, e) = (ncols as i32, eps);
16585        let __s_lb = self.gpu.stream();
16586        let mut lb = __s_lb.launch_builder(&f);
16587        lb.arg(a)
16588            .arg(b)
16589            .arg(w)
16590            .arg(res)
16591            .arg(dst)
16592            .arg(dst16)
16593            .arg(&nc)
16594            .arg(&e);
16595        unsafe {
16596            lb.launch(cfg)?;
16597        }
16598        Ok(())
16599    }
16600
16601    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
16602    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
16603    pub fn matmul_group_xh(
16604        &self,
16605        ws: &[&crate::model::GpuTensor],
16606        x: &CudaSlice<f32>,
16607        xh: &CudaSlice<u8>,
16608        m: usize,
16609    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16610        let mut out = Vec::with_capacity(ws.len());
16611        let in_f = ws[0].in_features();
16612        for w in ws {
16613            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
16614                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
16615                    out.push(y);
16616                    continue;
16617                }
16618            }
16619            out.push(self.matmul(w, x, m)?);
16620        }
16621        Ok(out)
16622    }
16623
16624    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
16625    /// GDN steps). Layouts [T, H].
16626    pub fn gdn_pad_mask(
16627        &self,
16628        beta: &mut CudaSlice<f32>,
16629        g_log: &mut CudaSlice<f32>,
16630        len_d: &CudaSlice<i32>,
16631        h: usize,
16632        t: usize,
16633    ) -> Result<(), Box<dyn std::error::Error>> {
16634        let f = self.func("gdn_pad_mask_f32");
16635        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
16636        let (hi, ti) = (h as i32, t as i32);
16637        let __s_b = self.gpu.stream();
16638        let mut b = __s_b.launch_builder(&f);
16639        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
16640        unsafe {
16641            b.launch(cfg)?;
16642        }
16643        Ok(())
16644    }
16645
16646    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
16647    /// gather for the padded prime graph's h_seed/hlast.
16648    pub fn row_gather_dev(
16649        &self,
16650        src: &CudaSlice<f32>,
16651        dst: &mut CudaSlice<f32>,
16652        len_d: &CudaSlice<i32>,
16653        ncols: usize,
16654    ) -> Result<(), Box<dyn std::error::Error>> {
16655        let f = self.func("row_gather_dev_f32");
16656        let cfg = LaunchConfig::for_num_elems(ncols as u32);
16657        let nc = ncols as i32;
16658        let __s_b = self.gpu.stream();
16659        let mut b = __s_b.launch_builder(&f);
16660        b.arg(src).arg(dst).arg(len_d).arg(&nc);
16661        unsafe {
16662            b.launch(cfg)?;
16663        }
16664        Ok(())
16665    }
16666
16667    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
16668    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
16669    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
16670    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
16671    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
16672    /// different in_f) falls back to its own `matmul` — behavior unchanged.
16673    pub fn matmul_group(
16674        &self,
16675        ws: &[&crate::model::GpuTensor],
16676        x: &CudaSlice<f32>,
16677        m: usize,
16678    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
16679        use crate::model::GpuTensor;
16680        let mut out = Vec::with_capacity(ws.len());
16681        let any_mirror = ws
16682            .iter()
16683            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
16684        if m >= 16 && any_mirror && !self.verify_exact_on() {
16685            let in_f = ws[0].in_features();
16686            let xh = self.f16_act(x, m * in_f, in_f)?;
16687            for w in ws {
16688                if w.in_features() == in_f {
16689                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
16690                        out.push(y);
16691                        continue;
16692                    }
16693                }
16694                out.push(self.matmul(w, x, m)?);
16695            }
16696            return Ok(out);
16697        }
16698        for w in ws {
16699            out.push(self.matmul(w, x, m)?);
16700        }
16701        Ok(out)
16702    }
16703
16704    /// Cross-request grouped matmul (task #13): run ONE projection group over the
16705    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
16706    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
16707    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
16708    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
16709    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
16710    pub fn matmul_group_multi(
16711        &self,
16712        ws: &[&crate::model::GpuTensor],
16713        xs: &[&CudaSlice<f32>],
16714        ms: &[usize],
16715    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
16716        assert_eq!(xs.len(), ms.len());
16717        let in_f = ws[0].in_features();
16718        let total: usize = ms.iter().sum();
16719        let mut xcat = self.uninit(total * in_f)?;
16720        let mut off = 0usize;
16721        for (x, &m) in xs.iter().zip(ms) {
16722            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
16723            off += m;
16724        }
16725        let ys = self.matmul_group(ws, &xcat, total)?;
16726        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
16727        for (w, y) in ws.iter().zip(ys) {
16728            let out_f = w.out_features();
16729            let mut off = 0usize;
16730            for (s, &m) in ms.iter().enumerate() {
16731                let mut ys_s = self.uninit(m * out_f)?;
16732                let src = y.slice(off * out_f..(off + m) * out_f);
16733                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
16734                out[s].push(ys_s);
16735                off += m;
16736            }
16737        }
16738        Ok(out)
16739    }
16740
16741    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
16742    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
16743    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
16744    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
16745    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
16746    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
16747    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
16748    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
16749    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
16750    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
16751        use crate::model::GpuTensor;
16752        if !legacy_quant_gemm_allowed(
16753            cfg!(memra_portable_cuda),
16754            cfg!(memra_hopper_mma),
16755            std::env::var_os("MEMRA_NO_GEMM").is_some(),
16756        ) {
16757            return false;
16758        }
16759        match w {
16760            GpuTensor::Quant { qtype, .. } => {
16761                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
16762                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
16763            }
16764            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
16765        }
16766    }
16767
16768    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
16769    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
16770    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
16771    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
16772    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
16773    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
16774    pub fn qmatvec_gemm(
16775        &self,
16776        w: &crate::model::GpuTensor,
16777        aq: &CudaSlice<i8>,
16778        ad: &CudaSlice<f32>,
16779        m: usize,
16780    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16781        use crate::model::GpuTensor;
16782        let in_f = w.in_features();
16783        let out_f = w.out_features();
16784        let (bytes, qtype, row_bytes, scale, rp) = match w {
16785            GpuTensor::Quant {
16786                bytes,
16787                qtype,
16788                row_bytes,
16789                scale,
16790                rp,
16791                ..
16792            } => (bytes, *qtype, *row_bytes, *scale, *rp),
16793            _ => unreachable!("gemm_supports guaranteed Quant"),
16794        };
16795        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
16796        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
16797        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
16798        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
16799        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
16800        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
16801            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
16802                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
16803                if scale != 1.0 {
16804                    self.scale_inplace(&mut y, scale, m * out_f)?;
16805                }
16806                return Ok(y);
16807            }
16808        }
16809        let name = match qtype {
16810            QT_Q8_0 => "qmatvec_gemm_q8_0",
16811            QT_Q4_K => "qmatvec_gemm_q4_K",
16812            QT_Q4_0 => {
16813                if rp {
16814                    "qmatvec_gemm_q4_0_rp"
16815                } else {
16816                    "qmatvec_gemm_q4_0"
16817                }
16818            }
16819            QT_Q5_K => "qmatvec_gemm_q5_K",
16820            QT_Q6_K => "qmatvec_gemm_q6_K",
16821            QT_NVFP4 => {
16822                if rp {
16823                    "qmatvec_gemm_nvfp4_rp"
16824                } else {
16825                    "qmatvec_gemm_nvfp4"
16826                }
16827            }
16828            _ => unreachable!(),
16829        };
16830        let f = self.func(name);
16831        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
16832        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
16833        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
16834        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
16835        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
16836        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
16837        let k1_tile = if is_k1 {
16838            k1_launch_override().unwrap_or((128, 128, 8))
16839        } else {
16840            (128, 128, 8)
16841        };
16842        let (bm, bn): (u32, u32) = if is_k1 {
16843            (k1_tile.0, k1_tile.1)
16844        } else {
16845            (64, 256)
16846        };
16847        let warps: u32 = if is_k1 {
16848            k1_tile.2
16849        } else {
16850            match qtype {
16851                QT_NVFP4 => 8,
16852                _ => 4,
16853            }
16854        };
16855        let cfg = LaunchConfig {
16856            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
16857            block_dim: (32, warps, 1),
16858            shared_mem_bytes: 0,
16859        };
16860        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
16861        let __s_b = self.gpu.stream();
16862        let mut b = __s_b.launch_builder(&f);
16863        b.arg(bytes)
16864            .arg(aq)
16865            .arg(ad)
16866            .arg(&mut y)
16867            .arg(&inf)
16868            .arg(&outf)
16869            .arg(&mi)
16870            .arg(&rb);
16871        unsafe {
16872            b.launch(cfg)?;
16873        }
16874        if scale != 1.0 {
16875            self.scale_inplace(&mut y, scale, m * out_f)?;
16876        }
16877        Ok(y)
16878    }
16879
16880    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
16881    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
16882    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
16883    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
16884    pub fn qmatvec_gemm_raw(
16885        &self,
16886        bytes: &CudaSlice<u8>,
16887        x: &CudaSlice<f32>,
16888        m: usize,
16889        in_f: usize,
16890        out_f: usize,
16891        qtype: i32,
16892        row_bytes: usize,
16893    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16894        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
16895        let name = match qtype {
16896            QT_Q8_0 => "qmatvec_gemm_q8_0",
16897            QT_Q4_K => "qmatvec_gemm_q4_K",
16898            QT_Q4_0 => "qmatvec_gemm_q4_0",
16899            QT_Q5_K => "qmatvec_gemm_q5_K",
16900            QT_Q6_K => "qmatvec_gemm_q6_K",
16901            QT_NVFP4 => "qmatvec_gemm_nvfp4",
16902            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
16903            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
16904        };
16905        let f = self.func(name);
16906        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
16907        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
16908        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
16909        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
16910        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
16911        let k1_tile = if is_k1 {
16912            k1_launch_override().unwrap_or((128, 128, 8))
16913        } else {
16914            (128, 128, 8)
16915        };
16916        let (bm, bn): (u32, u32) = if is_k1 {
16917            (k1_tile.0, k1_tile.1)
16918        } else {
16919            (64, 256)
16920        };
16921        let warps: u32 = if is_k1 {
16922            k1_tile.2
16923        } else {
16924            match qtype {
16925                QT_NVFP4 | QT_NVFP4_RP => 8,
16926                _ => 4,
16927            }
16928        };
16929        let cfg = LaunchConfig {
16930            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
16931            block_dim: (32, warps, 1),
16932            shared_mem_bytes: 0,
16933        };
16934        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
16935        let __s_b = self.gpu.stream();
16936        let mut b = __s_b.launch_builder(&f);
16937        b.arg(bytes)
16938            .arg(&aq)
16939            .arg(&ad)
16940            .arg(&mut y)
16941            .arg(&inf)
16942            .arg(&outf)
16943            .arg(&mi)
16944            .arg(&rb);
16945        unsafe {
16946            b.launch(cfg)?;
16947        }
16948        Ok(y)
16949    }
16950
16951    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
16952    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
16953    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
16954    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
16955    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
16956    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
16957    pub fn qmatvec_gemm_q8_0_wgmma_raw(
16958        &self,
16959        rp4: &CudaSlice<u8>,
16960        aq: &CudaSlice<i8>,
16961        ad: &CudaSlice<f32>,
16962        m: usize,
16963        in_f: usize,
16964        out_f: usize,
16965    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
16966        assert!(
16967            out_f % 64 == 0 && in_f % 32 == 0,
16968            "wgmma GEMM needs out_f%64==0, in_f%32==0"
16969        );
16970        let f = self.func("qmatvec_gemm_q8_0_wgmma");
16971        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
16972        let cfg = LaunchConfig {
16973            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
16974            block_dim: (128, 1, 1),
16975            shared_mem_bytes: 0,
16976        };
16977        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
16978        let __s_b = self.gpu.stream();
16979        let mut b = __s_b.launch_builder(&f);
16980        b.arg(rp4)
16981            .arg(aq)
16982            .arg(ad)
16983            .arg(&mut y)
16984            .arg(&inf)
16985            .arg(&outf)
16986            .arg(&mi);
16987        unsafe {
16988            b.launch(cfg)?;
16989        }
16990        Ok(y)
16991    }
16992
16993    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
16994    pub fn scale_inplace(
16995        &self,
16996        y: &mut CudaSlice<f32>,
16997        s: f32,
16998        n: usize,
16999    ) -> Result<(), Box<dyn std::error::Error>> {
17000        let f = self.func("scale_f32");
17001        let cfg = LaunchConfig::for_num_elems(n as u32);
17002        let (sf, ni) = (s, n as i32);
17003        let __s_b = self.gpu.stream();
17004        let mut b = __s_b.launch_builder(&f);
17005        b.arg(y).arg(&sf).arg(&ni);
17006        unsafe {
17007            b.launch(cfg)?;
17008        }
17009        Ok(())
17010    }
17011
17012    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
17013    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
17014    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
17015    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
17016    pub fn bf16_to_f32(
17017        &self,
17018        data: &cudarc::driver::CudaView<'_, u8>,
17019        n: usize,
17020    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17021        let mut out = self.alloc_uninit::<f32>(n)?;
17022        let f = self.func("bf16_to_f32");
17023        let cfg = LaunchConfig::for_num_elems(n as u32);
17024        let ni = n as i32;
17025        let __s_b = self.gpu.stream();
17026        let mut b = __s_b.launch_builder(&f);
17027        b.arg(data).arg(&mut out).arg(&ni);
17028        unsafe {
17029            b.launch(cfg)?;
17030        }
17031        Ok(out)
17032    }
17033
17034    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
17035    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
17036    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
17037    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
17038    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
17039    /// calls, the spec-verify contract) vs plain linear.
17040    fn linear_bf16_chunked(
17041        &self,
17042        x: &CudaSlice<f32>,
17043        data: &CudaSlice<u8>,
17044        m: usize,
17045        in_f: usize,
17046        out_f: usize,
17047        exact: bool,
17048        canonical_chunk_rows: Option<usize>,
17049    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17050        // MEMRA_STEP_TP_TIMING=1: cumulative cost of the per-call BF16->F32 expansion class
17051        // (alloc + convert kernel + f32 cuBLASLt = ~5x weight traffic). Prints every 1024 calls.
17052        // The stream sync per call perturbs wall; diagnostic only, never in a receipts run.
17053        static EXP_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
17054        static EXP_CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
17055        static EXP_WBYTES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
17056        let timing = std::env::var("MEMRA_STEP_TP_TIMING").as_deref() == Ok("1");
17057        let started = timing.then(std::time::Instant::now);
17058        let result =
17059            self.linear_bf16_chunked_inner(x, data, m, in_f, out_f, exact, canonical_chunk_rows);
17060        if let Some(started) = started {
17061            use std::sync::atomic::Ordering;
17062            self.stream().synchronize()?;
17063            let ns = EXP_NS.fetch_add(started.elapsed().as_nanos() as u64, Ordering::Relaxed)
17064                + started.elapsed().as_nanos() as u64;
17065            let wb = EXP_WBYTES.fetch_add((in_f * out_f * 2) as u64, Ordering::Relaxed)
17066                + (in_f * out_f * 2) as u64;
17067            let calls = EXP_CALLS.fetch_add(1, Ordering::Relaxed) + 1;
17068            if calls % 1024 == 0 {
17069                eprintln!(
17070                    "[bf16-expand-timing] calls={calls} total_ms={:.1} avg_us={:.1} \
17071                     weight_gb={:.2}",
17072                    ns as f64 / 1.0e6,
17073                    ns as f64 / calls as f64 / 1.0e3,
17074                    wb as f64 / 1.0e9,
17075                );
17076            }
17077        }
17078        result
17079    }
17080
17081    /// MEMRA_BF16_MMV=1: decode-time (m=1) BF16-resident matvec door. Numeric class change vs
17082    /// the expansion path (single-kernel deterministic tree reduce vs f32 cuBLASLt), so it is
17083    /// default OFF and gated by the run-gen argmax gate + boot battery like the other
17084    /// numeric-class doors (DEV_ROUTES precedent).
17085    pub(crate) fn bf16_mmv_on() -> bool {
17086        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17087        *ON.get_or_init(|| std::env::var("MEMRA_BF16_MMV").as_deref() == Ok("1"))
17088    }
17089
17090    /// One-block-per-row BF16 matvec: y[out_f] = W_bf16[out_f, in_f] @ x[in_f], f32 accumulate.
17091    /// Weights read once as raw bf16 (same bits<<16 expansion contract as `deq`'s QT_BF16 arm).
17092    fn matvec_bf16(
17093        &self,
17094        data: &CudaSlice<u8>,
17095        x: &CudaSlice<f32>,
17096        in_f: usize,
17097        out_f: usize,
17098    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
17099        if data.len() != in_f * out_f * 2 || x.len() < in_f || in_f % 8 != 0 {
17100            return Err(format!(
17101                "matvec_bf16 geometry bytes={} x={} in={in_f} out={out_f}",
17102                data.len(),
17103                x.len()
17104            )
17105            .into());
17106        }
17107        let mut y = self.alloc_uninit::<f32>(out_f)?;
17108        let f = self.func("matvec_bf16_f32acc");
17109        let cfg = LaunchConfig {
17110            grid_dim: (out_f as u32, 1, 1),
17111            block_dim: (mmv_block(), 1, 1),
17112            shared_mem_bytes: 0,
17113        };
17114        let ini = in_f as i32;
17115        let __s_bld = self.gpu.stream();
17116        let mut bld = __s_bld.launch_builder(&f);
17117        bld.arg(data).arg(x).arg(&mut y).arg(&ini);
17118        unsafe {
17119            bld.launch(cfg)?;
17120        }
17121        Ok(y)
17122    }
17123
17124    /// Fused QK rms-norm + neox rope (t=1): one launch per rank replaces two rms_norm
17125    /// launches, a position upload, and the rope launch; the position is read directly from
17126    /// the caller's device counter (UVA). Numeric-class door (see the kernel doc).
17127    #[allow(clippy::too_many_arguments)]
17128    /// FUSION #1: qk norms + rope + dcw KV append + last-block len inc, one launch
17129    /// (replaces qk_norm_rope_into + append_kv_quantized_dcw + inc_i32 on the dcw path).
17130    /// Bit-identical to the split kernels; requires head_dim == 128 and
17131    /// kv_dim_v == kv_dim_k == nh_k * head_dim (caller-guarded fallback otherwise).
17132    #[allow(clippy::too_many_arguments)]
17133    pub fn qk_norm_rope_append_inc_dcw(
17134        &self,
17135        q_raw: &CudaSlice<f32>,
17136        k_raw: &CudaSlice<f32>,
17137        v_raw: &CudaSlice<f32>,
17138        qw: &CudaSlice<f32>,
17139        kw: &CudaSlice<f32>,
17140        q_out: &mut CudaSlice<f32>,
17141        k_out: &mut CudaSlice<f32>,
17142        pos: &CudaSlice<i32>,
17143        k_plane: &mut CudaSlice<u8>,
17144        v_plane: &mut CudaSlice<u8>,
17145        // Shared ref by the planes_and_counters_mut split-borrow contract; the kernel is the
17146        // (single) writer, exactly like the split append+inc pair it replaces.
17147        len_dev: &CudaSlice<i32>,
17148        base_dev: Option<&CudaSlice<i32>>,
17149        done_ctr: &mut CudaSlice<u32>,
17150        kv_dim_k: usize,
17151        kv_dim_v: usize,
17152        k_tok_bytes: usize,
17153        v_tok_bytes: usize,
17154        head_dim: usize,
17155        n_dims: usize,
17156        nh_q: usize,
17157        nh_k: usize,
17158        eps: f32,
17159        freq_base: f32,
17160        freq_scale: f32,
17161        ff: Option<&CudaSlice<f32>>,
17162    ) -> Result<(), Box<dyn std::error::Error>> {
17163        if head_dim != 128
17164            || kv_dim_v != kv_dim_k
17165            || kv_dim_k != nh_k * head_dim
17166            || q_raw.len() < nh_q * head_dim
17167            || k_raw.len() < nh_k * head_dim
17168            || v_raw.len() < kv_dim_v
17169            || q_out.len() < nh_q * head_dim
17170            || k_out.len() < nh_k * head_dim
17171            || pos.is_empty()
17172            || done_ctr.is_empty()
17173        {
17174            return Err(format!(
17175                "qk_norm_rope_append_inc geometry head_dim={head_dim} nh_q={nh_q} nh_k={nh_k}                  kv_k={kv_dim_k} kv_v={kv_dim_v}"
17176            )
17177            .into());
17178        }
17179        let f = self.func("qk_norm_rope_append_inc_dcw");
17180        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
17181        let cfg = LaunchConfig {
17182            grid_dim: ((nh_q + nh_k) as u32, 1, 1),
17183            block_dim: (128, 1, 1),
17184            shared_mem_bytes: 0,
17185        };
17186        let (kvk, kvv) = (kv_dim_k as i32, kv_dim_v as i32);
17187        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17188        let (hd, nd, nq) = (head_dim as i32, n_dims as i32, nh_q as i32);
17189        let null: u64 = 0;
17190        let __s_b = self.gpu.stream();
17191        let mut b = __s_b.launch_builder(&f);
17192        b.arg(q_raw)
17193            .arg(k_raw)
17194            .arg(v_raw)
17195            .arg(qw)
17196            .arg(kw)
17197            .arg(q_out)
17198            .arg(k_out)
17199            .arg(pos)
17200            .arg(&mut *k_plane)
17201            .arg(&mut *v_plane)
17202            .arg(len_dev);
17203        match base_dev {
17204            Some(base) => {
17205                b.arg(base);
17206            }
17207            None => {
17208                b.arg(&null);
17209            }
17210        }
17211        b.arg(&mut *done_ctr)
17212            .arg(&kvk)
17213            .arg(&kvv)
17214            .arg(&ktb)
17215            .arg(&vtb)
17216            .arg(&hd)
17217            .arg(&nd)
17218            .arg(&nq)
17219            .arg(&eps)
17220            .arg(&theta_scale)
17221            .arg(&freq_scale);
17222        match ff {
17223            Some(freqs) => {
17224                b.arg(freqs);
17225            }
17226            None => {
17227                b.arg(&null);
17228            }
17229        }
17230        unsafe {
17231            b.launch(cfg)?;
17232        }
17233        Ok(())
17234    }
17235
17236    pub fn qk_norm_rope_into(
17237        &self,
17238        q_raw: &CudaSlice<f32>,
17239        k_raw: &CudaSlice<f32>,
17240        qw: &CudaSlice<f32>,
17241        kw: &CudaSlice<f32>,
17242        q_out: &mut CudaSlice<f32>,
17243        k_out: &mut CudaSlice<f32>,
17244        pos: &CudaSlice<i32>,
17245        head_dim: usize,
17246        n_dims: usize,
17247        nh_q: usize,
17248        nh_k: usize,
17249        eps: f32,
17250        freq_base: f32,
17251        freq_scale: f32,
17252        ff: Option<&CudaSlice<f32>>,
17253    ) -> Result<(), Box<dyn std::error::Error>> {
17254        if head_dim > 512
17255            || q_raw.len() < nh_q * head_dim
17256            || k_raw.len() < nh_k * head_dim
17257            || q_out.len() < nh_q * head_dim
17258            || k_out.len() < nh_k * head_dim
17259            || qw.len() < head_dim
17260            || kw.len() < head_dim
17261            || pos.is_empty()
17262        {
17263            return Err(format!(
17264                "qk_norm_rope geometry head_dim={head_dim} nh_q={nh_q} nh_k={nh_k}"
17265            )
17266            .into());
17267        }
17268        let f = self.func("qk_norm_rope_f32");
17269        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
17270        let cfg = LaunchConfig {
17271            grid_dim: ((nh_q + nh_k) as u32, 1, 1),
17272            block_dim: (128, 1, 1),
17273            shared_mem_bytes: 0,
17274        };
17275        let (hd, nd, nq) = (head_dim as i32, n_dims as i32, nh_q as i32);
17276        let __s_b = self.gpu.stream();
17277        let mut b = __s_b.launch_builder(&f);
17278        b.arg(q_raw)
17279            .arg(k_raw)
17280            .arg(qw)
17281            .arg(kw)
17282            .arg(q_out)
17283            .arg(k_out)
17284            .arg(pos)
17285            .arg(&hd)
17286            .arg(&nd)
17287            .arg(&nq)
17288            .arg(&eps)
17289            .arg(&theta_scale)
17290            .arg(&freq_scale);
17291        match ff {
17292            Some(ffv) => {
17293                b.arg(ffv);
17294                unsafe {
17295                    b.launch(cfg)?;
17296                }
17297            }
17298            None => {
17299                let null: u64 = 0;
17300                b.arg(&null);
17301                unsafe {
17302                    b.launch(cfg)?;
17303                }
17304            }
17305        }
17306        Ok(())
17307    }
17308
17309    /// Four-block F32 matvec with in-order block accumulation (see the kernel doc): one
17310    /// launch computes a rank's whole O partial from its four canonical column blocks.
17311    #[allow(clippy::too_many_arguments)]
17312    pub fn matvec_f32_b4_into(
17313        &self,
17314        w: [&CudaSlice<f32>; 4],
17315        x: &CudaSlice<f32>,
17316        y: &mut CudaSlice<f32>,
17317        block_cols: usize,
17318        out_f: usize,
17319    ) -> Result<(), Box<dyn std::error::Error>> {
17320        if block_cols % 4 != 0
17321            || x.len() < 4 * block_cols
17322            || y.len() < out_f
17323            || w.iter().any(|w| w.len() != out_f * block_cols)
17324        {
17325            return Err(format!(
17326                "matvec_f32_b4 geometry block_cols={block_cols} out={out_f} x={}",
17327                x.len()
17328            )
17329            .into());
17330        }
17331        let f = self.func("matvec_f32_b4");
17332        let cfg = LaunchConfig {
17333            grid_dim: (out_f as u32, 1, 1),
17334            block_dim: (128, 1, 1),
17335            shared_mem_bytes: 0,
17336        };
17337        let (bc, of) = (block_cols as i32, out_f as i32);
17338        let __s_b = self.gpu.stream();
17339        let mut b = __s_b.launch_builder(&f);
17340        b.arg(w[0])
17341            .arg(w[1])
17342            .arg(w[2])
17343            .arg(w[3])
17344            .arg(x)
17345            .arg(y)
17346            .arg(&bc)
17347            .arg(&of);
17348        unsafe {
17349            b.launch(cfg)?;
17350        }
17351        Ok(())
17352    }
17353
17354    /// Sequential weighted row-sum: y[i] = sum_p w[p] * x[p*width+i] in row order — the exact
17355    /// per-element FP chain of a zero-reset plus n_rows sequential axpy launches.
17356    pub fn axpy_rows_seq_into(
17357        &self,
17358        x: &CudaSlice<f32>,
17359        w: &CudaSlice<f32>,
17360        y: &mut CudaSlice<f32>,
17361        width: usize,
17362        n_rows: usize,
17363    ) -> Result<(), Box<dyn std::error::Error>> {
17364        if x.len() < n_rows * width || w.len() < n_rows || y.len() < width {
17365            return Err(format!(
17366                "axpy_rows_seq geometry x={} w={} y={} width={width} rows={n_rows}",
17367                x.len(),
17368                w.len(),
17369                y.len()
17370            )
17371            .into());
17372        }
17373        let f = self.func("axpy_rows_seq_f32");
17374        let cfg = LaunchConfig::for_num_elems(width as u32);
17375        let (wi, nr) = (width as i32, n_rows as i32);
17376        let __s_b = self.gpu.stream();
17377        let mut b = __s_b.launch_builder(&f);
17378        b.arg(x).arg(w).arg(y).arg(&wi).arg(&nr);
17379        unsafe {
17380            b.launch(cfg)?;
17381        }
17382        Ok(())
17383    }
17384
17385    /// Row-offset twin of `axpy_rows_seq_md_into` (spec verify t-column combine): the
17386    /// accumulation runs over rows [row0, row0+n_rows) of a taller partial slab — the
17387    /// exact sequential FP chain of the base kernel over that window.
17388    #[allow(clippy::too_many_arguments)]
17389    pub fn axpy_rows_seq_md_off_into(
17390        &self,
17391        x: &CudaSlice<f32>,
17392        w_route: &CudaSlice<f32>,
17393        md: &CudaSlice<f32>,
17394        sel: &CudaSlice<i32>,
17395        y: &mut CudaSlice<f32>,
17396        width: usize,
17397        n_rows: usize,
17398        row0: usize,
17399    ) -> Result<(), Box<dyn std::error::Error>> {
17400        if x.len() < (row0 + n_rows) * width
17401            || w_route.len() < row0 + n_rows
17402            || sel.len() < row0 + n_rows
17403            || y.len() < width
17404        {
17405            return Err(format!(
17406                "axpy_rows_seq_md_off geometry x={} w={} sel={} y={} width={width} \
17407                 rows={n_rows} row0={row0}",
17408                x.len(),
17409                w_route.len(),
17410                sel.len(),
17411                y.len()
17412            )
17413            .into());
17414        }
17415        let f = self.func("axpy_rows_seq_md_off_f32");
17416        let cfg = LaunchConfig::for_num_elems(width as u32);
17417        let (wi, nr, r0) = (width as i32, n_rows as i32, row0 as i32);
17418        let __s_b = self.gpu.stream();
17419        let mut b = __s_b.launch_builder(&f);
17420        b.arg(x)
17421            .arg(w_route)
17422            .arg(md)
17423            .arg(sel)
17424            .arg(y)
17425            .arg(&wi)
17426            .arg(&nr)
17427            .arg(&r0);
17428        unsafe {
17429            b.launch(cfg)?;
17430        }
17431        Ok(())
17432    }
17433
17434    /// T-COLUMN twin of `qmatvec_nvfp4_sel_gu_into` (spec verify, MEMRA_TCOL_FFN):
17435    /// 2*n_sel_col selection pairs over TWO activation rows (pair t reads row
17436    /// t/n_sel_col). Per-(pair,row) FP program == the t=1 gu kernel: each column's
17437    /// outputs are bit-equal to its own t=1 launch.
17438    #[allow(clippy::too_many_arguments)]
17439    pub fn qmatvec_nvfp4_sel_gu_tcol_into(
17440        &self,
17441        gate_bank: &CudaSlice<u8>,
17442        up_bank: &CudaSlice<u8>,
17443        sel: &CudaSlice<i32>,
17444        aq: &CudaSlice<i8>,
17445        ad: &CudaSlice<f32>,
17446        yg: &mut CudaSlice<f32>,
17447        yu: &mut CudaSlice<f32>,
17448        n_sel: usize,
17449        n_sel_col: usize,
17450        in_f: usize,
17451        out_f: usize,
17452        row_bytes: usize,
17453        expert_stride: usize,
17454        act_row_stride: usize,
17455        ad_row_stride: usize,
17456    ) -> Result<(), Box<dyn std::error::Error>> {
17457        assert!(in_f % 64 == 0, "NVFP4 dp4a requires in_f % 64 == 0");
17458        if yg.len() < n_sel * out_f
17459            || yu.len() < n_sel * out_f
17460            || sel.len() < n_sel
17461            || n_sel_col == 0
17462            || n_sel % n_sel_col != 0
17463        {
17464            return Err("NVFP4 gu tcol geometry".into());
17465        }
17466        let f = self.func("qmatvec_nvfp4_dp4a_sel_v2_gu_tcol");
17467        let cfg = LaunchConfig {
17468            grid_dim: ((2 * out_f) as u32, n_sel as u32, 1),
17469            block_dim: (128, 1, 1),
17470            shared_mem_bytes: 0,
17471        };
17472        let (inf, outf, ns, nsc) = (in_f as i32, out_f as i32, n_sel as i32, n_sel_col as i32);
17473        let (rb, es) = (row_bytes as i64, expert_stride as i64);
17474        let (ars, adrs) = (act_row_stride as i64, ad_row_stride as i64);
17475        let __s_b = self.gpu.stream();
17476        let mut b = __s_b.launch_builder(&f);
17477        b.arg(gate_bank)
17478            .arg(up_bank)
17479            .arg(sel)
17480            .arg(aq)
17481            .arg(ad)
17482            .arg(yg)
17483            .arg(yu)
17484            .arg(&inf)
17485            .arg(&outf)
17486            .arg(&ns)
17487            .arg(&rb)
17488            .arg(&es)
17489            .arg(&ars)
17490            .arg(&adrs)
17491            .arg(&nsc);
17492        unsafe {
17493            b.launch(cfg)?;
17494        }
17495        Ok(())
17496    }
17497
17498    /// Device-routed twin of `axpy_rows_seq_into`: the per-row weight folds in-kernel
17499    /// (w_route[p] * md[sel[p]] — the same single f32 multiply the host fold performs).
17500    #[allow(clippy::too_many_arguments)]
17501    pub fn axpy_rows_seq_md_into(
17502        &self,
17503        x: &CudaSlice<f32>,
17504        w_route: &CudaSlice<f32>,
17505        md: &CudaSlice<f32>,
17506        sel: &CudaSlice<i32>,
17507        y: &mut CudaSlice<f32>,
17508        width: usize,
17509        n_rows: usize,
17510    ) -> Result<(), Box<dyn std::error::Error>> {
17511        if x.len() < n_rows * width
17512            || w_route.len() < n_rows
17513            || sel.len() < n_rows
17514            || y.len() < width
17515        {
17516            return Err(format!(
17517                "axpy_rows_seq_md geometry x={} w={} sel={} y={} width={width} rows={n_rows}",
17518                x.len(),
17519                w_route.len(),
17520                sel.len(),
17521                y.len()
17522            )
17523            .into());
17524        }
17525        let f = self.func("axpy_rows_seq_md_f32");
17526        let cfg = LaunchConfig::for_num_elems(width as u32);
17527        let (wi, nr) = (width as i32, n_rows as i32);
17528        let __s_b = self.gpu.stream();
17529        let mut b = __s_b.launch_builder(&f);
17530        b.arg(x)
17531            .arg(w_route)
17532            .arg(md)
17533            .arg(sel)
17534            .arg(y)
17535            .arg(&wi)
17536            .arg(&nr);
17537        unsafe {
17538            b.launch(cfg)?;
17539        }
17540        Ok(())
17541    }
17542
17543    /// BF16 twin of `matvec_f32_qkv_into` (weights as raw checkpoint bf16 bytes).
17544    #[allow(clippy::too_many_arguments)]
17545    /// T-COLUMN twin of `matvec_bf16_qkvg_into` (spec verify): weights read once, T input
17546    /// columns accumulated with per-column FP order identical to the t=1 kernel. Outputs
17547    /// land column-major-of-rows: yq[c*out_q + row] etc.
17548    #[allow(clippy::too_many_arguments)]
17549    pub fn matvec_bf16_qkvg_tcol_into(
17550        &self,
17551        wq: &CudaSlice<u8>,
17552        wk: &CudaSlice<u8>,
17553        wv: &CudaSlice<u8>,
17554        wg: &CudaSlice<u8>,
17555        x_t: &CudaSlice<f32>,
17556        yq: &mut CudaSlice<f32>,
17557        yk: &mut CudaSlice<f32>,
17558        yv: &mut CudaSlice<f32>,
17559        yg: &mut CudaSlice<f32>,
17560        in_f: usize,
17561        out_q: usize,
17562        out_kv: usize,
17563        out_g: usize,
17564        t: usize,
17565    ) -> Result<(), Box<dyn std::error::Error>> {
17566        if t == 0
17567            || t > 8
17568            || in_f % 8 != 0
17569            || x_t.len() < t * in_f
17570            || yq.len() < t * out_q
17571            || yk.len() < t * out_kv
17572            || yv.len() < t * out_kv
17573            || (out_g > 0 && yg.len() < t * out_g)
17574        {
17575            return Err("matvec_bf16_qkvg_tcol geometry".into());
17576        }
17577        let f = self.func("matvec_bf16_qkvg_tcol");
17578        let grid = out_q + 2 * out_kv + out_g;
17579        let cfg = LaunchConfig {
17580            grid_dim: (grid as u32, 1, 1),
17581            block_dim: (mmv_block(), 1, 1),
17582            shared_mem_bytes: 0,
17583        };
17584        let (ini, oq, okv, og, ti) = (
17585            in_f as i32,
17586            out_q as i32,
17587            out_kv as i32,
17588            out_g as i32,
17589            t as i32,
17590        );
17591        let __s_b = self.gpu.stream();
17592        let mut b = __s_b.launch_builder(&f);
17593        b.arg(wq)
17594            .arg(wk)
17595            .arg(wv)
17596            .arg(wg)
17597            .arg(x_t)
17598            .arg(yq)
17599            .arg(yk)
17600            .arg(yv)
17601            .arg(yg)
17602            .arg(&ini)
17603            .arg(&oq)
17604            .arg(&okv)
17605            .arg(&og)
17606            .arg(&ti);
17607        unsafe {
17608            b.launch(cfg)?;
17609        }
17610        Ok(())
17611    }
17612
17613    pub fn matvec_bf16_qkvg_into(
17614        &self,
17615        wq: &CudaSlice<u8>,
17616        wk: &CudaSlice<u8>,
17617        wv: &CudaSlice<u8>,
17618        wg: &CudaSlice<u8>,
17619        x: &CudaSlice<f32>,
17620        yq: &mut CudaSlice<f32>,
17621        yk: &mut CudaSlice<f32>,
17622        yv: &mut CudaSlice<f32>,
17623        yg: &mut CudaSlice<f32>,
17624        in_f: usize,
17625        out_q: usize,
17626        out_kv: usize,
17627        out_g: usize,
17628    ) -> Result<(), Box<dyn std::error::Error>> {
17629        if in_f % 8 != 0
17630            || wq.len() != out_q * in_f * 2
17631            || wk.len() != out_kv * in_f * 2
17632            || wv.len() != out_kv * in_f * 2
17633            || wg.len() < out_g * in_f * 2
17634            || x.len() < in_f
17635            || yq.len() < out_q
17636            || yk.len() < out_kv
17637            || yv.len() < out_kv
17638            || (out_g > 0 && yg.len() < out_g)
17639        {
17640            return Err(format!(
17641                "fused bf16 QKV geometry in={in_f} out_q={out_q} out_kv={out_kv} out_g={out_g}"
17642            )
17643            .into());
17644        }
17645        let f = self.func("matvec_bf16_qkvg");
17646        let cfg = LaunchConfig {
17647            grid_dim: ((out_q + 2 * out_kv + out_g) as u32, 1, 1),
17648            block_dim: (mmv_block(), 1, 1),
17649            shared_mem_bytes: 0,
17650        };
17651        let (inf, oq, okv, og) = (in_f as i32, out_q as i32, out_kv as i32, out_g as i32);
17652        let __s_b = self.gpu.stream();
17653        let mut b = __s_b.launch_builder(&f);
17654        b.arg(wq)
17655            .arg(wk)
17656            .arg(wv)
17657            .arg(wg)
17658            .arg(x)
17659            .arg(yq)
17660            .arg(yk)
17661            .arg(yv)
17662            .arg(yg)
17663            .arg(&inf)
17664            .arg(&oq)
17665            .arg(&okv)
17666            .arg(&og);
17667        unsafe {
17668            b.launch(cfg)?;
17669        }
17670        Ok(())
17671    }
17672
17673    /// BF16 twin of `matvec_f32_b4_into` (weights as raw checkpoint bf16 bytes).
17674    pub fn matvec_bf16_b4_into(
17675        &self,
17676        w: [&CudaSlice<u8>; 4],
17677        x: &CudaSlice<f32>,
17678        y: &mut CudaSlice<f32>,
17679        block_cols: usize,
17680        out_f: usize,
17681    ) -> Result<(), Box<dyn std::error::Error>> {
17682        if block_cols % 8 != 0
17683            || x.len() < 4 * block_cols
17684            || y.len() < out_f
17685            || w.iter().any(|w| w.len() != out_f * block_cols * 2)
17686        {
17687            return Err(format!(
17688                "bf16 b4 geometry block_cols={block_cols} out={out_f} x={}",
17689                x.len()
17690            )
17691            .into());
17692        }
17693        // MEMRA_B4_X2=1: the #2b grid-halving twin — half the blocks, two rows each,
17694        // bit-identical per row (the second row's stream hides the first's reduce tail).
17695        static B4_X2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17696        let x2 = *B4_X2.get_or_init(|| std::env::var("MEMRA_B4_X2").as_deref() == Ok("1"));
17697        let f = self.func(if x2 {
17698            "matvec_bf16_b4_x2"
17699        } else {
17700            "matvec_bf16_b4"
17701        });
17702        let grid = if x2 { out_f.div_ceil(2) } else { out_f };
17703        let cfg = LaunchConfig {
17704            grid_dim: (grid as u32, 1, 1),
17705            block_dim: (mmv_block(), 1, 1),
17706            shared_mem_bytes: 0,
17707        };
17708        let (bc, of) = (block_cols as i32, out_f as i32);
17709        let __s_b = self.gpu.stream();
17710        let mut b = __s_b.launch_builder(&f);
17711        b.arg(w[0])
17712            .arg(w[1])
17713            .arg(w[2])
17714            .arg(w[3])
17715            .arg(x)
17716            .arg(y)
17717            .arg(&bc)
17718            .arg(&of);
17719        unsafe {
17720            b.launch(cfg)?;
17721        }
17722        Ok(())
17723    }
17724
17725    /// T-COLUMN twin of `matvec_bf16_b4_into` (spec verify o_proj): weights read once, T
17726    /// gated rows (each 4*block_cols wide) accumulated with per-column FP order identical
17727    /// to the t=1 kernel. Outputs land y[c*out_f + row]. Same blockDim as the t=1 launch —
17728    /// the shared-memory reduce order depends on it. Refuses under MEMRA_B4_X2 (different
17729    /// t=1 program).
17730    pub fn matvec_bf16_b4_tcol_into(
17731        &self,
17732        w: [&CudaSlice<u8>; 4],
17733        x_t: &CudaSlice<f32>,
17734        y_t: &mut CudaSlice<f32>,
17735        block_cols: usize,
17736        out_f: usize,
17737        t: usize,
17738    ) -> Result<(), Box<dyn std::error::Error>> {
17739        if block_cols % 8 != 0
17740            || t == 0
17741            || t > 8
17742            || x_t.len() < t * 4 * block_cols
17743            || y_t.len() < t * out_f
17744            || w.iter().any(|w| w.len() != out_f * block_cols * 2)
17745        {
17746            return Err(format!(
17747                "bf16 b4 tcol geometry block_cols={block_cols} out={out_f} t={t} x={}",
17748                x_t.len()
17749            )
17750            .into());
17751        }
17752        if std::env::var("MEMRA_B4_X2").as_deref() == Ok("1") {
17753            return Err(
17754                "b4 tcol verify is qualified against the plain b4 kernel only \
17755                        (MEMRA_B4_X2=1 is a different t=1 program)"
17756                    .into(),
17757            );
17758        }
17759        let f = self.func("matvec_bf16_b4_tcol");
17760        let cfg = LaunchConfig {
17761            grid_dim: (out_f as u32, 1, 1),
17762            block_dim: (mmv_block(), 1, 1),
17763            shared_mem_bytes: 0,
17764        };
17765        let (bc, of, ti) = (block_cols as i32, out_f as i32, t as i32);
17766        let __s_b = self.gpu.stream();
17767        let mut b = __s_b.launch_builder(&f);
17768        b.arg(w[0])
17769            .arg(w[1])
17770            .arg(w[2])
17771            .arg(w[3])
17772            .arg(x_t)
17773            .arg(y_t)
17774            .arg(&bc)
17775            .arg(&of)
17776            .arg(&ti);
17777        unsafe {
17778            b.launch(cfg)?;
17779        }
17780        Ok(())
17781    }
17782
17783    /// `matvec_bf16` writing into a caller-owned output (persistent-workspace form).
17784    pub fn matvec_bf16_into(
17785        &self,
17786        data: &CudaSlice<u8>,
17787        x: &CudaSlice<f32>,
17788        y: &mut CudaSlice<f32>,
17789        in_f: usize,
17790        out_f: usize,
17791    ) -> Result<(), Box<dyn std::error::Error>> {
17792        if data.len() != in_f * out_f * 2 || x.len() < in_f || in_f % 8 != 0 || y.len() < out_f {
17793            return Err(format!(
17794                "matvec_bf16_into geometry bytes={} x={} y={} in={in_f} out={out_f}",
17795                data.len(),
17796                x.len(),
17797                y.len()
17798            )
17799            .into());
17800        }
17801        // MEMRA_DOWN_X4=1 (short-row shapes, in_f<=2048): four sequential rows per
17802        // block, exact f32acc per-row program — cures the 1-iteration latency
17803        // starvation (shexp down measured 420GB/s at in_f=1280).
17804        static X4: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17805        let x4 = *X4.get_or_init(|| std::env::var("MEMRA_DOWN_X4").as_deref() == Ok("1"))
17806            && in_f <= 2048;
17807        if x4 {
17808            let f = self.func("matvec_bf16_f32acc_x4");
17809            let cfg = LaunchConfig {
17810                grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
17811                block_dim: (mmv_block(), 1, 1),
17812                shared_mem_bytes: 0,
17813            };
17814            let (ini, outi) = (in_f as i32, out_f as i32);
17815            let __s_b = self.gpu.stream();
17816            let mut b = __s_b.launch_builder(&f);
17817            b.arg(data).arg(x).arg(y).arg(&ini).arg(&outi);
17818            unsafe {
17819                b.launch(cfg)?;
17820            }
17821            return Ok(());
17822        }
17823        let f = self.func("matvec_bf16_f32acc");
17824        let cfg = LaunchConfig {
17825            grid_dim: (out_f as u32, 1, 1),
17826            block_dim: (mmv_block(), 1, 1),
17827            shared_mem_bytes: 0,
17828        };
17829        let ini = in_f as i32;
17830        let __s_b = self.gpu.stream();
17831        let mut b = __s_b.launch_builder(&f);
17832        b.arg(data).arg(x).arg(y).arg(&ini);
17833        unsafe {
17834            b.launch(cfg)?;
17835        }
17836        Ok(())
17837    }
17838
17839    /// `matvec_bf16_into` over a WEIGHT VIEW (row-range slice of a bf16 tensor): the head-split
17840    /// door feeds each device its half of the lm-head rows. Same kernel, same per-row program.
17841    pub fn matvec_bf16_view_into(
17842        &self,
17843        data: &cudarc::driver::CudaView<'_, u8>,
17844        x: &CudaSlice<f32>,
17845        y: &mut CudaSlice<f32>,
17846        in_f: usize,
17847        out_f: usize,
17848    ) -> Result<(), Box<dyn std::error::Error>> {
17849        if data.len() != in_f * out_f * 2 || x.len() < in_f || in_f % 8 != 0 || y.len() < out_f {
17850            return Err(format!(
17851                "matvec_bf16_view_into geometry bytes={} x={} y={} in={in_f} out={out_f}",
17852                data.len(),
17853                x.len(),
17854                y.len()
17855            )
17856            .into());
17857        }
17858        let f = self.func("matvec_bf16_f32acc");
17859        let cfg = LaunchConfig {
17860            grid_dim: (out_f as u32, 1, 1),
17861            block_dim: (mmv_block(), 1, 1),
17862            shared_mem_bytes: 0,
17863        };
17864        let ini = in_f as i32;
17865        let __s_b = self.gpu.stream();
17866        let mut b = __s_b.launch_builder(&f);
17867        b.arg(data).arg(x).arg(y).arg(&ini);
17868        unsafe {
17869            b.launch(cfg)?;
17870        }
17871        Ok(())
17872    }
17873
17874    /// `matvec_bf16_into` with a RAW u64 output pointer (UVA — the dev1-shexp down row
17875    /// lands root-resident over P2P). Same kernel, same per-row program: bit-identical.
17876    pub fn matvec_bf16_raw_out(
17877        &self,
17878        w: &CudaSlice<u8>,
17879        x: &CudaSlice<f32>,
17880        y_raw: u64,
17881        in_f: usize,
17882        out_f: usize,
17883    ) -> Result<(), Box<dyn std::error::Error>> {
17884        if w.len() != in_f * out_f * 2 || x.len() < in_f || in_f % 8 != 0 || y_raw == 0 {
17885            return Err("matvec_bf16_raw_out geometry".into());
17886        }
17887        let f = self.func("matvec_bf16_f32acc");
17888        let cfg = LaunchConfig {
17889            grid_dim: (out_f as u32, 1, 1),
17890            block_dim: (mmv_block(), 1, 1),
17891            shared_mem_bytes: 0,
17892        };
17893        let ini = in_f as i32;
17894        let __s_b = self.gpu.stream();
17895        let mut b = __s_b.launch_builder(&f);
17896        b.arg(w).arg(x).arg(&y_raw).arg(&ini);
17897        unsafe {
17898            b.launch(cfg)?;
17899        }
17900        Ok(())
17901    }
17902
17903    /// MOE TAIL FUSION M1: dst = (a + b) + sh*scale[0] in one launch (sh/scale as RAW
17904    /// UVA pointers so the caller passes persistent-static rows without holding locks).
17905    /// Exact per-element sequence of the split add + add_scaled_rows pair.
17906    pub fn add3_raw(
17907        &self,
17908        a: &CudaSlice<f32>,
17909        b: &CudaSlice<f32>,
17910        sh_raw: u64,
17911        scale_raw: u64,
17912        dst: &mut CudaSlice<f32>,
17913        n: usize,
17914    ) -> Result<(), Box<dyn std::error::Error>> {
17915        if a.len() < n || b.len() < n || dst.len() < n || sh_raw == 0 || scale_raw == 0 {
17916            return Err("add3_raw geometry".into());
17917        }
17918        let f = self.func("add3_f32");
17919        let cfg = LaunchConfig {
17920            grid_dim: ((n as u32).div_ceil(256), 1, 1),
17921            block_dim: (256, 1, 1),
17922            shared_mem_bytes: 0,
17923        };
17924        let ni = n as i32;
17925        let __s_b = self.gpu.stream();
17926        let mut bld = __s_b.launch_builder(&f);
17927        bld.arg(a)
17928            .arg(b)
17929            .arg(&sh_raw)
17930            .arg(&scale_raw)
17931            .arg(dst)
17932            .arg(&ni);
17933        unsafe {
17934            bld.launch(cfg)?;
17935        }
17936        Ok(())
17937    }
17938
17939    /// FUSION #2e: shexp down matvec + scaled accumulate (dst[r] += dot_r * scale[0]),
17940    /// one launch replacing matvec_bf16_into + the ownership copy + add_scaled_rows.
17941    pub fn matvec_bf16_down_addscale_into(
17942        &self,
17943        w: &CudaSlice<u8>,
17944        x: &CudaSlice<f32>,
17945        scale: &CudaSlice<f32>,
17946        dst: &mut CudaSlice<f32>,
17947        in_f: usize,
17948        out_f: usize,
17949    ) -> Result<(), Box<dyn std::error::Error>> {
17950        if w.len() != in_f * out_f * 2
17951            || x.len() < in_f
17952            || in_f % 8 != 0
17953            || dst.len() < out_f
17954            || scale.is_empty()
17955        {
17956            return Err("matvec_bf16_down_addscale geometry".into());
17957        }
17958        let f = self.func("matvec_bf16_down_addscale");
17959        let cfg = LaunchConfig {
17960            grid_dim: (out_f as u32, 1, 1),
17961            block_dim: (mmv_block(), 1, 1),
17962            shared_mem_bytes: 0,
17963        };
17964        let ini = in_f as i32;
17965        let __s_b = self.gpu.stream();
17966        let mut b = __s_b.launch_builder(&f);
17967        b.arg(w).arg(x).arg(scale).arg(dst).arg(&ini);
17968        unsafe {
17969            b.launch(cfg)?;
17970        }
17971        Ok(())
17972    }
17973
17974    /// FUSION #2b: shexp dual matvec + SwiGLU act, one launch (bit-identical to
17975    /// matvec_bf16_dual_into + ffn_act_lim at gs=us=1; limit=None takes plain silu).
17976    pub fn matvec_bf16_dual_silu_into(
17977        &self,
17978        wg: &CudaSlice<u8>,
17979        wu: &CudaSlice<u8>,
17980        x: &CudaSlice<f32>,
17981        act: &mut CudaSlice<f32>,
17982        in_f: usize,
17983        out_f: usize,
17984        limit: Option<f32>,
17985    ) -> Result<(), Box<dyn std::error::Error>> {
17986        if wg.len() != in_f * out_f * 2
17987            || wu.len() != in_f * out_f * 2
17988            || x.len() < in_f
17989            || in_f % 8 != 0
17990            || act.len() < out_f
17991        {
17992            return Err("matvec_bf16_dual_silu geometry".into());
17993        }
17994        let f = self.func("matvec_bf16_dual_silu");
17995        let cfg = LaunchConfig {
17996            grid_dim: (out_f as u32, 1, 1),
17997            block_dim: (mmv_block(), 1, 1),
17998            shared_mem_bytes: 0,
17999        };
18000        let (ini, outi) = (in_f as i32, out_f as i32);
18001        let lim = limit.unwrap_or(0.0);
18002        let __s_b = self.gpu.stream();
18003        let mut b = __s_b.launch_builder(&f);
18004        b.arg(wg)
18005            .arg(wu)
18006            .arg(x)
18007            .arg(act)
18008            .arg(&ini)
18009            .arg(&outi)
18010            .arg(&lim);
18011        unsafe {
18012            b.launch(cfg)?;
18013        }
18014        Ok(())
18015    }
18016
18017    /// `matvec_bf16_dual_into` over WEIGHT VIEWS (row-range slices): the shexp row-split
18018    /// door feeds each device its half of the gate/up rows. Same kernel, same per-row program.
18019    #[allow(clippy::too_many_arguments)]
18020    pub fn matvec_bf16_dual_view_into(
18021        &self,
18022        wg: &cudarc::driver::CudaView<'_, u8>,
18023        wu: &cudarc::driver::CudaView<'_, u8>,
18024        x: &CudaSlice<f32>,
18025        yg: &mut CudaSlice<f32>,
18026        yu: &mut CudaSlice<f32>,
18027        in_f: usize,
18028        out_f: usize,
18029    ) -> Result<(), Box<dyn std::error::Error>> {
18030        if wg.len() != in_f * out_f * 2
18031            || wu.len() != in_f * out_f * 2
18032            || x.len() < in_f
18033            || in_f % 8 != 0
18034            || yg.len() < out_f
18035            || yu.len() < out_f
18036        {
18037            return Err(format!(
18038                "matvec_bf16_dual_view_into geometry wg={} wu={} x={} in={in_f} out={out_f}",
18039                wg.len(),
18040                wu.len(),
18041                x.len()
18042            )
18043            .into());
18044        }
18045        let f = self.func("matvec_bf16_dual");
18046        let cfg = LaunchConfig {
18047            grid_dim: ((2 * out_f) as u32, 1, 1),
18048            block_dim: (mmv_block(), 1, 1),
18049            shared_mem_bytes: 0,
18050        };
18051        let (ini, outi) = (in_f as i32, out_f as i32);
18052        let __s_b = self.gpu.stream();
18053        let mut b = __s_b.launch_builder(&f);
18054        b.arg(wg)
18055            .arg(wu)
18056            .arg(x)
18057            .arg(yg)
18058            .arg(yu)
18059            .arg(&ini)
18060            .arg(&outi);
18061        unsafe {
18062            b.launch(cfg)?;
18063        }
18064        Ok(())
18065    }
18066
18067    /// `matvec_bf16_dual` writing into caller-owned outputs (persistent-workspace form).
18068    #[allow(clippy::too_many_arguments)]
18069    pub fn matvec_bf16_dual_into(
18070        &self,
18071        wg: &CudaSlice<u8>,
18072        wu: &CudaSlice<u8>,
18073        x: &CudaSlice<f32>,
18074        yg: &mut CudaSlice<f32>,
18075        yu: &mut CudaSlice<f32>,
18076        in_f: usize,
18077        out_f: usize,
18078    ) -> Result<(), Box<dyn std::error::Error>> {
18079        if wg.len() != in_f * out_f * 2
18080            || wu.len() != in_f * out_f * 2
18081            || x.len() < in_f
18082            || in_f % 8 != 0
18083            || yg.len() < out_f
18084            || yu.len() < out_f
18085        {
18086            return Err(format!(
18087                "matvec_bf16_dual_into geometry wg={} wu={} x={} in={in_f} out={out_f}",
18088                wg.len(),
18089                wu.len(),
18090                x.len()
18091            )
18092            .into());
18093        }
18094        let f = self.func("matvec_bf16_dual");
18095        let cfg = LaunchConfig {
18096            grid_dim: ((2 * out_f) as u32, 1, 1),
18097            block_dim: (mmv_block(), 1, 1),
18098            shared_mem_bytes: 0,
18099        };
18100        let (ini, outi) = (in_f as i32, out_f as i32);
18101        let __s_b = self.gpu.stream();
18102        let mut b = __s_b.launch_builder(&f);
18103        b.arg(wg)
18104            .arg(wu)
18105            .arg(x)
18106            .arg(yg)
18107            .arg(yu)
18108            .arg(&ini)
18109            .arg(&outi);
18110        unsafe {
18111            b.launch(cfg)?;
18112        }
18113        Ok(())
18114    }
18115
18116    /// Dual bf16 matvec: gate/up (same shape) from one shared input in one launch. Per row
18117    /// bit-identical to two `matvec_bf16` launches. Returns (gate, up).
18118    pub(crate) fn matvec_bf16_dual(
18119        &self,
18120        wg: &CudaSlice<u8>,
18121        wu: &CudaSlice<u8>,
18122        x: &CudaSlice<f32>,
18123        in_f: usize,
18124        out_f: usize,
18125    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
18126        if wg.len() != in_f * out_f * 2
18127            || wu.len() != in_f * out_f * 2
18128            || x.len() < in_f
18129            || in_f % 8 != 0
18130        {
18131            return Err(format!(
18132                "matvec_bf16_dual geometry wg={} wu={} x={} in={in_f} out={out_f}",
18133                wg.len(),
18134                wu.len(),
18135                x.len()
18136            )
18137            .into());
18138        }
18139        let mut yg = self.alloc_uninit::<f32>(out_f)?;
18140        let mut yu = self.alloc_uninit::<f32>(out_f)?;
18141        let f = self.func("matvec_bf16_dual");
18142        let cfg = LaunchConfig {
18143            grid_dim: ((2 * out_f) as u32, 1, 1),
18144            block_dim: (mmv_block(), 1, 1),
18145            shared_mem_bytes: 0,
18146        };
18147        let (ini, outi) = (in_f as i32, out_f as i32);
18148        let __s_b = self.gpu.stream();
18149        let mut b = __s_b.launch_builder(&f);
18150        b.arg(wg)
18151            .arg(wu)
18152            .arg(x)
18153            .arg(&mut yg)
18154            .arg(&mut yu)
18155            .arg(&ini)
18156            .arg(&outi);
18157        unsafe {
18158            b.launch(cfg)?;
18159        }
18160        Ok((yg, yu))
18161    }
18162
18163    #[allow(clippy::too_many_arguments)]
18164    fn linear_bf16_chunked_inner(
18165        &self,
18166        x: &CudaSlice<f32>,
18167        data: &CudaSlice<u8>,
18168        m: usize,
18169        in_f: usize,
18170        out_f: usize,
18171        exact: bool,
18172        canonical_chunk_rows: Option<usize>,
18173    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18174        const CHUNK_BYTES: usize = 256 << 20;
18175        // canonical_chunk_rows callers are the step TP resident ranks whose cross-topology
18176        // equality program is defined in cuBLASLt chunk shapes — the door leaves them alone.
18177        if m == 1
18178            && !exact
18179            && canonical_chunk_rows.is_none()
18180            && in_f % 8 == 0
18181            && Self::bf16_mmv_on()
18182        {
18183            return self.matvec_bf16(data, x, in_f, out_f);
18184        }
18185        let row_bytes = in_f
18186            .checked_mul(std::mem::size_of::<f32>())
18187            .ok_or("BF16 chunk row byte count overflow")?;
18188        if row_bytes == 0 || out_f == 0 {
18189            return Err("BF16 chunk dimensions must be nonzero".into());
18190        }
18191        let max_chunk_rows = (CHUNK_BYTES / row_bytes).max(1).min(out_f);
18192        let chunk_rows = match canonical_chunk_rows {
18193            Some(rows) if rows == 0 => {
18194                return Err("canonical BF16 chunk rows must be nonzero".into());
18195            }
18196            Some(rows) if rows > max_chunk_rows => {
18197                return Err(format!(
18198                    "canonical BF16 chunk rows {rows} exceed the {max_chunk_rows}-row scratch limit"
18199                )
18200                .into());
18201            }
18202            Some(rows) if out_f % rows != 0 => {
18203                return Err(format!(
18204                    "BF16 output width {out_f} is not divisible by canonical {rows}-row chunks"
18205                )
18206                .into());
18207            }
18208            Some(rows) => rows,
18209            None => max_chunk_rows,
18210        };
18211        if chunk_rows >= out_f {
18212            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
18213            return if exact {
18214                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
18215            } else {
18216                self.linear(x, &wf32, m, in_f, out_f)
18217            };
18218        }
18219        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
18220        let mut r0 = 0usize;
18221        while r0 < out_f {
18222            let rows = chunk_rows.min(out_f - r0);
18223            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
18224            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
18225            let yc = if exact {
18226                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
18227            } else {
18228                self.linear(x, &wf32, m, in_f, rows)?
18229            };
18230            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
18231            for mi in 0..m {
18232                let src = yc.slice(mi * rows..(mi + 1) * rows);
18233                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
18234                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
18235            }
18236            r0 += rows;
18237        }
18238        Ok(y)
18239    }
18240
18241    /// Execute an already resident BF16 projection. This is the model-faithful substrate used by
18242    /// Step tensor-parallel correctness ranks; it preserves checkpoint bytes and the existing
18243    /// chunked BF16 numerical program instead of re-encoding the weight.
18244    pub fn linear_bf16_resident(
18245        &self,
18246        x: &CudaSlice<f32>,
18247        data: &CudaSlice<u8>,
18248        m: usize,
18249        in_f: usize,
18250        out_f: usize,
18251    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18252        if data.len() != in_f * out_f * 2 {
18253            return Err(format!("resident BF16 bytes {} != {out_f}x{in_f}x2", data.len()).into());
18254        }
18255        self.linear_bf16_chunked(x, data, m, in_f, out_f, false, None)
18256    }
18257
18258    /// Execute a resident BF16 projection as fixed-width output-row chunks.
18259    ///
18260    /// Tensor-parallel ranks use this to give TP1/TP2/TP4/TP8 the same cuBLASLt problem shape
18261    /// for every checkpoint row. Callers must derive `canonical_chunk_rows` from the registered
18262    /// model topology rather than the active rank count.
18263    pub fn linear_bf16_resident_canonical_rows(
18264        &self,
18265        x: &CudaSlice<f32>,
18266        data: &CudaSlice<u8>,
18267        m: usize,
18268        in_f: usize,
18269        out_f: usize,
18270        canonical_chunk_rows: usize,
18271    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18272        if data.len() != in_f * out_f * 2 {
18273            return Err(format!("resident BF16 bytes {} != {out_f}x{in_f}x2", data.len()).into());
18274        }
18275        self.linear_bf16_chunked(x, data, m, in_f, out_f, false, Some(canonical_chunk_rows))
18276    }
18277
18278    /// Execute a load-time F32 mirror with the same fixed output-row chunks as the BF16 path.
18279    ///
18280    /// Expanding the checkpoint bytes once changes residency, not arithmetic: every cuBLASLt
18281    /// call receives the same F32 values and problem shape as `linear_bf16_chunked`.
18282    pub fn linear_f32_resident_canonical_rows(
18283        &self,
18284        x: &CudaSlice<f32>,
18285        data: &CudaSlice<f32>,
18286        m: usize,
18287        in_f: usize,
18288        out_f: usize,
18289        canonical_chunk_rows: usize,
18290    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18291        self.linear_f32_resident_canonical_rows_inner(
18292            x,
18293            data,
18294            m,
18295            in_f,
18296            out_f,
18297            canonical_chunk_rows,
18298            false,
18299        )
18300    }
18301
18302    /// Execute fixed output-row chunks and assemble them with one strided placement per chunk.
18303    ///
18304    /// The projection shapes and values are identical to
18305    /// [`Self::linear_f32_resident_canonical_rows`]. Only the byte-preserving output layout step
18306    /// changes, replacing one device copy per token with one placement kernel per output chunk.
18307    pub fn linear_f32_resident_canonical_rows_strided(
18308        &self,
18309        x: &CudaSlice<f32>,
18310        data: &CudaSlice<f32>,
18311        m: usize,
18312        in_f: usize,
18313        out_f: usize,
18314        canonical_chunk_rows: usize,
18315    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18316        self.linear_f32_resident_canonical_rows_inner(
18317            x,
18318            data,
18319            m,
18320            in_f,
18321            out_f,
18322            canonical_chunk_rows,
18323            true,
18324        )
18325    }
18326
18327    fn linear_f32_resident_canonical_rows_inner(
18328        &self,
18329        x: &CudaSlice<f32>,
18330        data: &CudaSlice<f32>,
18331        m: usize,
18332        in_f: usize,
18333        out_f: usize,
18334        canonical_chunk_rows: usize,
18335        strided_output: bool,
18336    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18337        if data.len() != in_f * out_f {
18338            return Err(format!("resident F32 values {} != {out_f}x{in_f}", data.len()).into());
18339        }
18340        if canonical_chunk_rows == 0
18341            || canonical_chunk_rows > out_f
18342            || out_f % canonical_chunk_rows != 0
18343        {
18344            return Err(format!(
18345                "invalid canonical F32 chunk rows {canonical_chunk_rows} for output width {out_f}"
18346            )
18347            .into());
18348        }
18349        if canonical_chunk_rows == out_f {
18350            return self.linear(x, data, m, in_f, out_f);
18351        }
18352
18353        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
18354        let input = x.slice(0..x.len());
18355        for r0 in (0..out_f).step_by(canonical_chunk_rows) {
18356            let weights = data.slice(r0 * in_f..(r0 + canonical_chunk_rows) * in_f);
18357            if m == 1 {
18358                let mut destination = y.slice_mut(r0..r0 + canonical_chunk_rows);
18359                self.linear_device_into(
18360                    &input,
18361                    &weights,
18362                    &mut destination,
18363                    1,
18364                    in_f,
18365                    canonical_chunk_rows,
18366                )?;
18367                continue;
18368            }
18369            let chunk = self.linear_device(&input, &weights, m, in_f, canonical_chunk_rows)?;
18370            if strided_output {
18371                self.place_rows_strided(&chunk, &mut y, canonical_chunk_rows, m, out_f, r0)?;
18372            } else {
18373                for token in 0..m {
18374                    let source = chunk
18375                        .slice(token * canonical_chunk_rows..(token + 1) * canonical_chunk_rows);
18376                    let mut destination =
18377                        y.slice_mut(token * out_f + r0..token * out_f + r0 + canonical_chunk_rows);
18378                    self.gpu.stream().memcpy_dtod(&source, &mut destination)?;
18379                }
18380            }
18381        }
18382        Ok(y)
18383    }
18384
18385    /// One-token twin of `linear_f32_resident_canonical_rows` writing into a caller-owned
18386    /// output. Same cuBLASLt calls, values, and chunk order as the allocating variant at
18387    /// `m == 1`; only the output residency changes (persistent workspace instead of a fresh
18388    /// allocation per call). This is the projection substrate of the v2 Step TP decode driver.
18389    pub fn linear_f32_resident_canonical_rows_t1_into(
18390        &self,
18391        x: &CudaSlice<f32>,
18392        data: &CudaSlice<f32>,
18393        y: &mut CudaSlice<f32>,
18394        in_f: usize,
18395        out_f: usize,
18396        canonical_chunk_rows: usize,
18397    ) -> Result<(), Box<dyn std::error::Error>> {
18398        if data.len() != in_f * out_f {
18399            return Err(format!("resident F32 values {} != {out_f}x{in_f}", data.len()).into());
18400        }
18401        if y.len() != out_f || x.len() != in_f {
18402            return Err(format!(
18403                "resident F32 t1 shapes x={} y={} != in {in_f} out {out_f}",
18404                x.len(),
18405                y.len()
18406            )
18407            .into());
18408        }
18409        if canonical_chunk_rows == 0
18410            || canonical_chunk_rows > out_f
18411            || out_f % canonical_chunk_rows != 0
18412        {
18413            return Err(format!(
18414                "invalid canonical F32 chunk rows {canonical_chunk_rows} for output width {out_f}"
18415            )
18416            .into());
18417        }
18418        let input = x.slice(0..x.len());
18419        for r0 in (0..out_f).step_by(canonical_chunk_rows) {
18420            let weights = data.slice(r0 * in_f..(r0 + canonical_chunk_rows) * in_f);
18421            let mut destination = y.slice_mut(r0..r0 + canonical_chunk_rows);
18422            self.linear_device_into(
18423                &input,
18424                &weights,
18425                &mut destination,
18426                1,
18427                in_f,
18428                canonical_chunk_rows,
18429            )?;
18430        }
18431        Ok(())
18432    }
18433
18434    /// One-token view-to-view linear into a caller-owned destination — the `linear` twin
18435    /// without the allocation, for workspace-resident operands.
18436    pub fn linear_t1_into(
18437        &self,
18438        x: &cudarc::driver::CudaView<'_, f32>,
18439        w: &cudarc::driver::CudaView<'_, f32>,
18440        y: &mut cudarc::driver::CudaViewMut<'_, f32>,
18441        in_f: usize,
18442        out_f: usize,
18443    ) -> Result<(), Box<dyn std::error::Error>> {
18444        self.linear_device_into(x, w, y, 1, in_f, out_f)
18445    }
18446
18447    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
18448    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
18449    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
18450    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
18451    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
18452    /// router/shexp sites and matmul_decode_exact's Float arm.
18453    pub fn linear_decode_exact(
18454        &self,
18455        x: &CudaSlice<f32>,
18456        w: &CudaSlice<f32>,
18457        m_tokens: usize,
18458        in_f: usize,
18459        out_f: usize,
18460    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18461        if m_tokens == 1 {
18462            return self.linear(x, w, 1, in_f, out_f);
18463        }
18464        let xv = self.view(x, m_tokens * in_f);
18465        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
18466        for t in 0..m_tokens {
18467            let row = xv.slice(t * in_f..(t + 1) * in_f);
18468            let mut xr = self.alloc_uninit::<f32>(in_f)?;
18469            self.copy_view_into(&mut xr, 0, &row, in_f)?;
18470            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
18471            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
18472        }
18473        Ok(y)
18474    }
18475
18476    pub fn linear(
18477        &self,
18478        x: &CudaSlice<f32>,
18479        w: &CudaSlice<f32>,
18480        m_tokens: usize,
18481        in_f: usize,
18482        out_f: usize,
18483    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
18484        self.linear_device(x, w, m_tokens, in_f, out_f)
18485    }
18486
18487    fn linear_device<I>(
18488        &self,
18489        x: &I,
18490        w: &I,
18491        m_tokens: usize,
18492        in_f: usize,
18493        out_f: usize,
18494    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>>
18495    where
18496        I: cudarc::driver::DevicePtr<f32>,
18497    {
18498        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
18499        self.linear_device_into(x, w, &mut c, m_tokens, in_f, out_f)?;
18500        Ok(c)
18501    }
18502
18503    fn linear_device_into<I, O>(
18504        &self,
18505        x: &I,
18506        w: &I,
18507        c: &mut O,
18508        m_tokens: usize,
18509        in_f: usize,
18510        out_f: usize,
18511    ) -> Result<(), Box<dyn std::error::Error>>
18512    where
18513        I: cudarc::driver::DevicePtr<f32>,
18514        O: cudarc::driver::DevicePtrMut<f32>,
18515    {
18516        use cudarc::cublaslt::{Matmul, MatmulConfig};
18517        let cfg = MatmulConfig {
18518            transa: true,
18519            transb: false,
18520            transc: false,
18521            m: out_f as u64,
18522            n: m_tokens as u64,
18523            k: in_f as u64,
18524            alpha: 1.0,
18525            lda: in_f as i64,
18526            ldb: in_f as i64,
18527            beta: 0.0,
18528            ldc: out_f as i64,
18529            stride_a: None,
18530            stride_b: None,
18531            stride_c: None,
18532            stride_bias: None,
18533            batch_size: None,
18534        };
18535        let blas = self.gpu.blas();
18536        unsafe {
18537            blas.matmul(cfg, w, x, c, None, None)?;
18538        }
18539        Ok(())
18540    }
18541
18542    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
18543    ///
18544    /// LONG-CTX DISPATCH (lane/hermes-perf-fixes, 2026-08-23): the smem kernel's `T_kv*4`
18545    /// dynamic shared memory exceeds the 48KB launch bound past T_kv=12288 — the plain
18546    /// full-attn sibling of the DFlash2 B2 crash the windowed layers fixed with
18547    /// `sdpa_naive_w_lo`. Past the bound this transparently takes the byte-identical
18548    /// gmem-scores twin (`sdpa_naive_gmem`, kernel_check-pinned) instead of returning the
18549    /// launch error mid-request.
18550    pub fn sdpa_naive(
18551        &self,
18552        q: &CudaSlice<f32>,
18553        k: &CudaSlice<f32>,
18554        v: &CudaSlice<f32>,
18555        o: &mut CudaSlice<f32>,
18556        head_dim: usize,
18557        n_head: usize,
18558        n_head_kv: usize,
18559        t: usize,
18560        t_kv: usize,
18561        scale: f32,
18562        causal: bool,
18563    ) -> Result<(), Box<dyn std::error::Error>> {
18564        if t_kv * 4 > SDPA_NAIVE_SMEM_MAX {
18565            return self.sdpa_naive_gmem(
18566                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
18567            );
18568        }
18569        let f = self.func("sdpa_naive_f32");
18570        let cfg = LaunchConfig {
18571            grid_dim: (n_head as u32, t as u32, 1),
18572            block_dim: (128, 1, 1),
18573            shared_mem_bytes: (t_kv * 4) as u32,
18574        };
18575        let (hd, nh, nhkv, ti, tkvi, cz) = (
18576            head_dim as i32,
18577            n_head as i32,
18578            n_head_kv as i32,
18579            t as i32,
18580            t_kv as i32,
18581            causal as i32,
18582        );
18583        let __s_b = self.gpu.stream();
18584        let mut b = __s_b.launch_builder(&f);
18585        b.arg(q)
18586            .arg(k)
18587            .arg(v)
18588            .arg(o)
18589            .arg(&hd)
18590            .arg(&nh)
18591            .arg(&nhkv)
18592            .arg(&ti)
18593            .arg(&tkvi)
18594            .arg(&scale)
18595            .arg(&cz);
18596        unsafe {
18597            b.launch(cfg)?;
18598        }
18599        Ok(())
18600    }
18601
18602    /// Global-memory-scores twin of [`Self::sdpa_naive`] (lane/hermes-perf-fixes, 2026-08-23).
18603    /// Same kernel body with the per-(head, query) scores row in a device workspace instead
18604    /// of dynamic shared memory: identical loop structure and reduction order, so the output
18605    /// is BYTE-IDENTICAL to the smem kernel wherever both launch (kernel_check
18606    /// `sdpa_naive_gmem` pins bit-identity plus the >12k arm where the smem kernel MUST
18607    /// fail). O(n_head * T * T_kv * 4) workspace — fine for the tall-KV block shapes that
18608    /// hit the bound (dspark/dflash full-attn: T <= block size), guarded so a square
18609    /// T==T_kv caller cannot silently allocate tens of GB.
18610    #[allow(clippy::too_many_arguments)]
18611    pub fn sdpa_naive_gmem(
18612        &self,
18613        q: &CudaSlice<f32>,
18614        k: &CudaSlice<f32>,
18615        v: &CudaSlice<f32>,
18616        o: &mut CudaSlice<f32>,
18617        head_dim: usize,
18618        n_head: usize,
18619        n_head_kv: usize,
18620        t: usize,
18621        t_kv: usize,
18622        scale: f32,
18623        causal: bool,
18624    ) -> Result<(), Box<dyn std::error::Error>> {
18625        let ws_len = n_head
18626            .checked_mul(t)
18627            .and_then(|x| x.checked_mul(t_kv))
18628            .ok_or("sdpa_naive_gmem: scores workspace size overflow")?;
18629        let ws_bytes = ws_len
18630            .checked_mul(std::mem::size_of::<f32>())
18631            .ok_or("sdpa_naive_gmem: scores workspace byte count overflow")?;
18632        if ws_bytes > SDPA_NAIVE_GMEM_WS_MAX {
18633            return Err(format!(
18634                "sdpa_naive_gmem: scores workspace {ws_bytes} bytes (heads {n_head} x T {t} x \
18635                 T_kv {t_kv}) exceeds the {SDPA_NAIVE_GMEM_WS_MAX}-byte guard — this shape \
18636                 needs a tiled/flash kernel, not the naive oracle"
18637            )
18638            .into());
18639        }
18640        let mut scores = self.uninit(ws_len)?;
18641        let f = self.func("sdpa_naive_gmem_f32");
18642        let cfg = LaunchConfig {
18643            grid_dim: (n_head as u32, t as u32, 1),
18644            block_dim: (128, 1, 1),
18645            shared_mem_bytes: 0,
18646        };
18647        let (hd, nh, nhkv, ti, tkvi, cz) = (
18648            head_dim as i32,
18649            n_head as i32,
18650            n_head_kv as i32,
18651            t as i32,
18652            t_kv as i32,
18653            causal as i32,
18654        );
18655        let __s_b = self.gpu.stream();
18656        let mut b = __s_b.launch_builder(&f);
18657        b.arg(q)
18658            .arg(k)
18659            .arg(v)
18660            .arg(o)
18661            .arg(&mut scores)
18662            .arg(&hd)
18663            .arg(&nh)
18664            .arg(&nhkv)
18665            .arg(&ti)
18666            .arg(&tkvi)
18667            .arg(&scale)
18668            .arg(&cz);
18669        unsafe {
18670            b.launch(cfg)?;
18671        }
18672        Ok(())
18673    }
18674
18675    /// Island twin (lane/gemma-vision): causal + sliding-window attention with
18676    /// bidirectional image islands. `span_id` labels each absolute kv position
18677    /// (-1 text, >=0 island id); same-island keys are visible unconditionally,
18678    /// reproducing the reference's non-causal image batch. window 0 = no window.
18679    #[allow(clippy::too_many_arguments)]
18680    pub fn sdpa_naive_island(
18681        &self,
18682        q: &CudaSlice<f32>,
18683        k: &CudaSlice<f32>,
18684        v: &CudaSlice<f32>,
18685        o: &mut CudaSlice<f32>,
18686        span_id: &CudaSlice<i32>,
18687        head_dim: usize,
18688        n_head: usize,
18689        n_head_kv: usize,
18690        t: usize,
18691        t_kv: usize,
18692        scale: f32,
18693        window: usize,
18694    ) -> Result<(), Box<dyn std::error::Error>> {
18695        let f = self.func("sdpa_naive_island_f32");
18696        let cfg = LaunchConfig {
18697            grid_dim: (n_head as u32, t as u32, 1),
18698            block_dim: (128, 1, 1),
18699            shared_mem_bytes: (t_kv * 4) as u32,
18700        };
18701        let (hd, nh, nhkv, ti, tkvi, wi) = (
18702            head_dim as i32,
18703            n_head as i32,
18704            n_head_kv as i32,
18705            t as i32,
18706            t_kv as i32,
18707            window as i32,
18708        );
18709        let __s_b = self.gpu.stream();
18710        let mut b = __s_b.launch_builder(&f);
18711        b.arg(q)
18712            .arg(k)
18713            .arg(v)
18714            .arg(o)
18715            .arg(span_id)
18716            .arg(&hd)
18717            .arg(&nh)
18718            .arg(&nhkv)
18719            .arg(&ti)
18720            .arg(&tkvi)
18721            .arg(&scale)
18722            .arg(&wi);
18723        unsafe {
18724            b.launch(cfg)?;
18725        }
18726        Ok(())
18727    }
18728
18729    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
18730    #[allow(clippy::too_many_arguments)]
18731    pub fn sdpa_naive_w(
18732        &self,
18733        q: &CudaSlice<f32>,
18734        k: &CudaSlice<f32>,
18735        v: &CudaSlice<f32>,
18736        o: &mut CudaSlice<f32>,
18737        head_dim: usize,
18738        n_head: usize,
18739        n_head_kv: usize,
18740        t: usize,
18741        t_kv: usize,
18742        scale: f32,
18743        causal: bool,
18744        window: usize,
18745    ) -> Result<(), Box<dyn std::error::Error>> {
18746        let f = self.func("sdpa_naive_w_f32");
18747        let cfg = LaunchConfig {
18748            grid_dim: (n_head as u32, t as u32, 1),
18749            block_dim: (128, 1, 1),
18750            shared_mem_bytes: (t_kv * 4) as u32,
18751        };
18752        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
18753            head_dim as i32,
18754            n_head as i32,
18755            n_head_kv as i32,
18756            t as i32,
18757            t_kv as i32,
18758            causal as i32,
18759            window as i32,
18760        );
18761        let __s_b = self.gpu.stream();
18762        let mut b = __s_b.launch_builder(&f);
18763        b.arg(q)
18764            .arg(k)
18765            .arg(v)
18766            .arg(o)
18767            .arg(&hd)
18768            .arg(&nh)
18769            .arg(&nhkv)
18770            .arg(&ti)
18771            .arg(&tkvi)
18772            .arg(&scale)
18773            .arg(&cz)
18774            .arg(&wi);
18775        unsafe {
18776            b.launch(cfg)?;
18777        }
18778        Ok(())
18779    }
18780
18781    /// Lo-clipped windowed sdpa_naive twin (lane/dflash2-longctx, DFLASH2-EVAL §10.6(c)).
18782    /// Same mask law as `sdpa_naive_w`, but keys below every query's window floor are never
18783    /// read: kv_lo = max(0, (t_kv - t) + 1 - window) — the oldest key visible to the OLDEST
18784    /// query row (q_pos = t_kv - t). Dynamic shared memory shrinks from t_kv*4 bytes (which
18785    /// blows the 48KB launch bound at ~12k rows — the B2 ctx crash) to (t_kv - kv_lo)*4 =
18786    /// (window - 1 + t)*4, and the key scan drops from O(t_kv) to O(window + t). Output is
18787    /// byte-identical to `sdpa_naive_w` (masked keys contribute exact zeros to same-order
18788    /// reductions; kernel_check `sdpa_naive_w_lo` pins it). window == 0 (no window) keeps
18789    /// kv_lo = 0 and is then shape-identical to the legacy kernel, including its bound.
18790    #[allow(clippy::too_many_arguments)]
18791    pub fn sdpa_naive_w_lo(
18792        &self,
18793        q: &CudaSlice<f32>,
18794        k: &CudaSlice<f32>,
18795        v: &CudaSlice<f32>,
18796        o: &mut CudaSlice<f32>,
18797        head_dim: usize,
18798        n_head: usize,
18799        n_head_kv: usize,
18800        t: usize,
18801        t_kv: usize,
18802        scale: f32,
18803        causal: bool,
18804        window: usize,
18805    ) -> Result<(), Box<dyn std::error::Error>> {
18806        let kv_lo = if window > 0 {
18807            (t_kv - t + 1).saturating_sub(window)
18808        } else {
18809            0
18810        };
18811        let smem = (t_kv - kv_lo) * 4;
18812        if smem > 48 * 1024 {
18813            return Err(format!(
18814                "sdpa_naive_w_lo: window {window} + T {t} rows need {smem} bytes of dynamic \
18815                 shared memory (> 48KB launch bound) — this kernel clips the OLD side only; \
18816                 a window this wide needs the multi-pass long-ctx kernel"
18817            )
18818            .into());
18819        }
18820        let f = self.func("sdpa_naive_w_lo_f32");
18821        let cfg = LaunchConfig {
18822            grid_dim: (n_head as u32, t as u32, 1),
18823            block_dim: (128, 1, 1),
18824            shared_mem_bytes: smem as u32,
18825        };
18826        let (hd, nh, nhkv, ti, tkvi, cz, wi, lo) = (
18827            head_dim as i32,
18828            n_head as i32,
18829            n_head_kv as i32,
18830            t as i32,
18831            t_kv as i32,
18832            causal as i32,
18833            window as i32,
18834            kv_lo as i32,
18835        );
18836        let __s_b = self.gpu.stream();
18837        let mut b = __s_b.launch_builder(&f);
18838        b.arg(q)
18839            .arg(k)
18840            .arg(v)
18841            .arg(o)
18842            .arg(&hd)
18843            .arg(&nh)
18844            .arg(&nhkv)
18845            .arg(&ti)
18846            .arg(&tkvi)
18847            .arg(&scale)
18848            .arg(&cz)
18849            .arg(&wi)
18850            .arg(&lo);
18851        unsafe {
18852            b.launch(cfg)?;
18853        }
18854        Ok(())
18855    }
18856
18857    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
18858    pub fn sdpa_naive_view(
18859        &self,
18860        q: &CudaSlice<f32>,
18861        k: &cudarc::driver::CudaView<f32>,
18862        v: &cudarc::driver::CudaView<f32>,
18863        o: &mut CudaSlice<f32>,
18864        head_dim: usize,
18865        n_head: usize,
18866        n_head_kv: usize,
18867        t: usize,
18868        t_kv: usize,
18869        scale: f32,
18870        causal: bool,
18871    ) -> Result<(), Box<dyn std::error::Error>> {
18872        let f = self.func("sdpa_naive_f32");
18873        let cfg = LaunchConfig {
18874            grid_dim: (n_head as u32, t as u32, 1),
18875            block_dim: (128, 1, 1),
18876            shared_mem_bytes: (t_kv * 4) as u32,
18877        };
18878        let (hd, nh, nhkv, ti, tkvi, cz) = (
18879            head_dim as i32,
18880            n_head as i32,
18881            n_head_kv as i32,
18882            t as i32,
18883            t_kv as i32,
18884            causal as i32,
18885        );
18886        let __s_b = self.gpu.stream();
18887        let mut b = __s_b.launch_builder(&f);
18888        b.arg(q)
18889            .arg(k)
18890            .arg(v)
18891            .arg(o)
18892            .arg(&hd)
18893            .arg(&nh)
18894            .arg(&nhkv)
18895            .arg(&ti)
18896            .arg(&tkvi)
18897            .arg(&scale)
18898            .arg(&cz);
18899        unsafe {
18900            b.launch(cfg)?;
18901        }
18902        Ok(())
18903    }
18904
18905    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
18906    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
18907    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
18908    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
18909    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
18910    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
18911    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
18912    #[allow(clippy::too_many_arguments)]
18913    pub fn fa_dequant_kv_view_f32(
18914        &self,
18915        k: &cudarc::driver::CudaView<u8>,
18916        v: &cudarc::driver::CudaView<u8>,
18917        kf: &mut CudaSlice<f32>,
18918        vf: &mut CudaSlice<f32>,
18919        kv_dim_k: usize,
18920        kv_dim_v: usize,
18921        t_kv: usize,
18922        k_tok_bytes: usize,
18923        v_tok_bytes: usize,
18924        g: bool,
18925    ) -> Result<(), Box<dyn std::error::Error>> {
18926        let f = if g {
18927            self.func_g("fa_dequant_kv_ws_f32")
18928        } else {
18929            self.func("fa_dequant_kv_ws_f32")
18930        };
18931        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
18932        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
18933        let cfg = LaunchConfig {
18934            grid_dim: (nblk.max(1), 1, 1),
18935            block_dim: (256, 1, 1),
18936            shared_mem_bytes: 0,
18937        };
18938        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
18939        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18940        let __s_b = self.gpu.stream();
18941        let mut b = __s_b.launch_builder(&f);
18942        b.arg(k)
18943            .arg(v)
18944            .arg(&mut *kf)
18945            .arg(&mut *vf)
18946            .arg(&kdk)
18947            .arg(&kdv)
18948            .arg(&tkvi)
18949            .arg(&ktb)
18950            .arg(&vtb);
18951        unsafe {
18952            b.launch(cfg)?;
18953        }
18954        Ok(())
18955    }
18956
18957    #[allow(clippy::too_many_arguments)]
18958    pub fn sdpa_naive_quantized_view(
18959        &self,
18960        q: &CudaSlice<f32>,
18961        k: &cudarc::driver::CudaView<u8>,
18962        v: &cudarc::driver::CudaView<u8>,
18963        o: &mut CudaSlice<f32>,
18964        head_dim: usize,
18965        n_head: usize,
18966        n_head_kv: usize,
18967        t: usize,
18968        t_kv: usize,
18969        scale: f32,
18970        causal: bool,
18971        k_tok_bytes: usize,
18972        v_tok_bytes: usize,
18973    ) -> Result<(), Box<dyn std::error::Error>> {
18974        let kv_dim = n_head_kv * head_dim;
18975        let mut kf = self.uninit(t_kv * kv_dim)?;
18976        let mut vf = self.uninit(t_kv * kv_dim)?;
18977        let f = self.func("fa_dequant_kv_ws_f32");
18978        let total = (2 * t_kv * kv_dim) as u64;
18979        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
18980        let cfg = LaunchConfig {
18981            grid_dim: (nblk.max(1), 1, 1),
18982            block_dim: (256, 1, 1),
18983            shared_mem_bytes: 0,
18984        };
18985        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
18986        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
18987        let __s_b = self.gpu.stream();
18988        let mut b = __s_b.launch_builder(&f);
18989        b.arg(k)
18990            .arg(v)
18991            .arg(&mut kf)
18992            .arg(&mut vf)
18993            .arg(&kv_dim_i)
18994            .arg(&kv_dim_i)
18995            .arg(&t_kv_i)
18996            .arg(&k_tok_bytes_i)
18997            .arg(&v_tok_bytes_i);
18998        unsafe { b.launch(cfg)? };
18999        self.sdpa_naive(
19000            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
19001        )
19002    }
19003
19004    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
19005    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
19006    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
19007    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
19008    /// unwindowed function above and produces bit-identical output at window == 0.
19009    ///
19010    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
19011    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
19012    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
19013    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
19014    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
19015    #[allow(clippy::too_many_arguments)]
19016    pub fn sdpa_naive_w_quantized_view(
19017        &self,
19018        q: &CudaSlice<f32>,
19019        k: &cudarc::driver::CudaView<u8>,
19020        v: &cudarc::driver::CudaView<u8>,
19021        o: &mut CudaSlice<f32>,
19022        head_dim: usize,
19023        n_head: usize,
19024        n_head_kv: usize,
19025        t: usize,
19026        t_kv: usize,
19027        scale: f32,
19028        causal: bool,
19029        window: usize,
19030        k_tok_bytes: usize,
19031        v_tok_bytes: usize,
19032    ) -> Result<(), Box<dyn std::error::Error>> {
19033        let kv_dim = n_head_kv * head_dim;
19034        let mut kf = self.uninit(t_kv * kv_dim)?;
19035        let mut vf = self.uninit(t_kv * kv_dim)?;
19036        let f = self.func("fa_dequant_kv_ws_f32");
19037        let total = (2 * t_kv * kv_dim) as u64;
19038        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
19039        let cfg = LaunchConfig {
19040            grid_dim: (nblk.max(1), 1, 1),
19041            block_dim: (256, 1, 1),
19042            shared_mem_bytes: 0,
19043        };
19044        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
19045        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
19046        let __s_b = self.gpu.stream();
19047        let mut b = __s_b.launch_builder(&f);
19048        b.arg(k)
19049            .arg(v)
19050            .arg(&mut kf)
19051            .arg(&mut vf)
19052            .arg(&kv_dim_i)
19053            .arg(&kv_dim_i)
19054            .arg(&t_kv_i)
19055            .arg(&k_tok_bytes_i)
19056            .arg(&v_tok_bytes_i);
19057        unsafe { b.launch(cfg)? };
19058        self.sdpa_naive_w(
19059            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
19060        )
19061    }
19062
19063    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
19064    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
19065    /// Q/K/V/O [head_dim, n_head(_kv), T].
19066    pub fn fa_prefill(
19067        &self,
19068        q: &CudaSlice<f32>,
19069        k: &CudaSlice<f32>,
19070        v: &CudaSlice<f32>,
19071        o: &mut CudaSlice<f32>,
19072        head_dim: usize,
19073        n_head: usize,
19074        n_head_kv: usize,
19075        t: usize,
19076        t_kv: usize,
19077        scale: f32,
19078        causal: bool,
19079    ) -> Result<(), Box<dyn std::error::Error>> {
19080        if portable_mma_gated() {
19081            return self.sdpa_naive(
19082                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
19083            );
19084        }
19085        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
19086        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
19087        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
19088        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
19089        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
19090        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
19091        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
19092        let fa3_on = head_dim == 256
19093            && causal
19094            && t == t_kv
19095            && match std::env::var("MEMRA_FA3").as_deref() {
19096                Ok("0") => false,
19097                // The force arm consults the arch now: the bf16 stage below calls
19098                // f32_to_bf16_into -> func("f32_to_bf16_bulk"), which cu/hybrid.cu:1623 omits on
19099                // a portable build. Refuse at the switch, not at the lookup.
19100                Ok("1") => {
19101                    refuse_portable_force("MEMRA_FA3=1", "the sm_90a fa3/bf16 kernels");
19102                    true
19103                }
19104                _ => cfg!(memra_hopper_mma),
19105            };
19106        if fa3_on {
19107            let n = t * n_head * head_dim;
19108            let nkv = t * n_head_kv * head_dim;
19109            let mut q16 = self.alloc_u8_uninit(n * 2)?;
19110            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
19111            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
19112            self.f32_to_bf16_into(q, &mut q16, n)?;
19113            self.f32_to_bf16_into(k, &mut k16, nkv)?;
19114            self.f32_to_bf16_into(v, &mut v16, nkv)?;
19115            let rc = {
19116                use cudarc::driver::{DevicePtr, DevicePtrMut};
19117                let stream = self.gpu.stream();
19118                let (qp, _g1) = q16.device_ptr(&stream);
19119                let (kp, _g2) = k16.device_ptr(&stream);
19120                let (vp, _g3) = v16.device_ptr(&stream);
19121                let (op, _g4) = o.device_ptr_mut(&stream);
19122                unsafe {
19123                    memra_fa3_prefill(
19124                        qp as *const core::ffi::c_void,
19125                        kp as *const core::ffi::c_void,
19126                        vp as *const core::ffi::c_void,
19127                        op as *mut f32,
19128                        t as i32,
19129                        n_head as i32,
19130                        n_head_kv as i32,
19131                        head_dim as i32,
19132                        scale,
19133                        stream.cu_stream() as *mut core::ffi::c_void,
19134                    )
19135                }
19136            };
19137            if rc != 0 {
19138                return Err(format!("memra_fa3_prefill rc={rc}").into());
19139            }
19140            return Ok(());
19141        }
19142        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
19143        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
19144        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
19145        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
19146        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19147        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
19148        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
19149            const BLOCK_Q: usize = 64;
19150            const BKX: usize = 32;
19151            let f = self.func("fa_prefill_bf16_p1");
19152            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
19153                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
19154            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19155            f.set_attribute(
19156                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19157                shmem as i32,
19158            )?;
19159            let cfg = LaunchConfig {
19160                grid_dim: (
19161                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
19162                    n_head as u32,
19163                    1,
19164                ),
19165                block_dim: (32, 4, 1),
19166                shared_mem_bytes: shmem,
19167            };
19168            let (hd, nh, nhkv, ti, tkvi, cz) = (
19169                head_dim as i32,
19170                n_head as i32,
19171                n_head_kv as i32,
19172                t as i32,
19173                t_kv as i32,
19174                causal as i32,
19175            );
19176            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19177            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
19178            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
19179            let __s_b = self.gpu.stream();
19180            let mut b = __s_b.launch_builder(&f);
19181            b.arg(&qb)
19182                .arg(&kb)
19183                .arg(&vb)
19184                .arg(o)
19185                .arg(&hd)
19186                .arg(&nh)
19187                .arg(&nhkv)
19188                .arg(&ti)
19189                .arg(&tkvi)
19190                .arg(&scale)
19191                .arg(&cz);
19192            unsafe {
19193                b.launch(cfg)?;
19194            }
19195            return Ok(());
19196        }
19197        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
19198        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
19199        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
19200        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
19201        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
19202        const BK: usize = 32;
19203        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
19204        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
19205        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
19206        let (block_q, warps, w2_sfx): (usize, u32, &str) =
19207            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
19208        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
19209        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
19210        // other head_dims to sdpa_naive before reaching here.
19211        let hd_sfx = fa_hd_suffix(head_dim)?;
19212        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
19213        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
19214        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
19215        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
19216        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
19217        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
19218        let (kb16, vb16) = if bf16kv {
19219            let n = t_kv * n_head_kv * head_dim;
19220            let mut kb = self.alloc_u8_uninit(n * 2)?;
19221            let mut vb = self.alloc_u8_uninit(n * 2)?;
19222            let fcv = self.func("f32_to_bf16_bulk");
19223            let ni = n as i64;
19224            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
19225            let __s_b = self.gpu.stream();
19226            let mut b = __s_b.launch_builder(&fcv);
19227            b.arg(k).arg(&mut kb).arg(&ni);
19228            unsafe {
19229                b.launch(cfgc)?;
19230            }
19231            let __s_b = self.gpu.stream();
19232            let mut b = __s_b.launch_builder(&fcv);
19233            b.arg(v).arg(&mut vb).arg(&ni);
19234            unsafe {
19235                b.launch(cfgc)?;
19236            }
19237            (Some(kb), Some(vb))
19238        } else {
19239            (None, None)
19240        };
19241        let f = self.func(&if bf16kv {
19242            format!("fa_prefill_bf16kv_pp{hd_sfx}")
19243        } else {
19244            format!(
19245                "fa_prefill_f32{}{}{hd_sfx}",
19246                if floor { "" } else { "_pp" },
19247                if floor { "" } else { w2_sfx }
19248            )
19249        });
19250        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
19251        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
19252        let kv_stages = if bf16kv { 2 } else { 1 };
19253        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
19254            + 4 * (block_q * BK + 2 * block_q)) as u32;
19255        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19256        f.set_attribute(
19257            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19258            shmem as i32,
19259        )?;
19260        let cfg = LaunchConfig {
19261            grid_dim: (
19262                (t as u32 + block_q as u32 - 1) / block_q as u32,
19263                n_head as u32,
19264                1,
19265            ),
19266            block_dim: (32, warps, 1),
19267            shared_mem_bytes: shmem,
19268        };
19269        let (hd, nh, nhkv, ti, tkvi, cz) = (
19270            head_dim as i32,
19271            n_head as i32,
19272            n_head_kv as i32,
19273            t as i32,
19274            t_kv as i32,
19275            causal as i32,
19276        );
19277        let __s_b = self.gpu.stream();
19278        let mut b = __s_b.launch_builder(&f);
19279        b.arg(q);
19280        match (&kb16, &vb16) {
19281            (Some(kb), Some(vb)) => {
19282                b.arg(kb).arg(vb);
19283            }
19284            _ => {
19285                b.arg(k).arg(v);
19286            }
19287        }
19288        b.arg(o)
19289            .arg(&hd)
19290            .arg(&nh)
19291            .arg(&nhkv)
19292            .arg(&ti)
19293            .arg(&tkvi)
19294            .arg(&scale)
19295            .arg(&cz);
19296        unsafe {
19297            b.launch(cfg)?;
19298        }
19299        Ok(())
19300    }
19301
19302    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
19303    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
19304    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
19305    #[allow(clippy::too_many_arguments)]
19306    pub fn fa_prefill_w(
19307        &self,
19308        q: &CudaSlice<f32>,
19309        k: &CudaSlice<f32>,
19310        v: &CudaSlice<f32>,
19311        o: &mut CudaSlice<f32>,
19312        head_dim: usize,
19313        n_head: usize,
19314        n_head_kv: usize,
19315        t: usize,
19316        t_kv: usize,
19317        scale: f32,
19318        causal: bool,
19319        window: usize,
19320    ) -> Result<(), Box<dyn std::error::Error>> {
19321        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
19322        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
19323        if portable_mma_gated() {
19324            return self.sdpa_naive_w(
19325                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
19326            );
19327        }
19328        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
19329        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
19330        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
19331        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19332        let faw_f32 =
19333            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
19334        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
19335        self.fa_prefill_w_arm(
19336            q,
19337            k,
19338            v,
19339            o,
19340            head_dim,
19341            n_head,
19342            n_head_kv,
19343            t,
19344            t_kv,
19345            scale,
19346            causal,
19347            window,
19348            floor || faw_f32,
19349            floor,
19350        )
19351    }
19352
19353    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
19354    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
19355    #[allow(clippy::too_many_arguments)]
19356    pub fn fa_prefill_w_pre(
19357        &self,
19358        qb: &CudaSlice<u8>,
19359        kb: &CudaSlice<u8>,
19360        vb: &CudaSlice<u8>,
19361        o: &mut CudaSlice<f32>,
19362        head_dim: usize,
19363        n_head: usize,
19364        n_head_kv: usize,
19365        t: usize,
19366        t_kv: usize,
19367        scale: f32,
19368        causal: bool,
19369        window: usize,
19370        v_f16: bool,
19371    ) -> Result<(), Box<dyn std::error::Error>> {
19372        const BLOCK_Q: usize = 64;
19373        const BK: usize = 32;
19374        debug_assert_eq!(head_dim, 256);
19375        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
19376        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
19377        if hp {
19378            const BLOCK_QH: usize = 32;
19379            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
19380            // else re-encode through the pooled scratch (stream-ordered reuse).
19381            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
19382            let vh: &CudaSlice<u8> = if v_f16 {
19383                vb
19384            } else {
19385                let n = t_kv * n_head_kv * head_dim;
19386                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
19387                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
19388                }
19389                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
19390                vguard.as_ref().unwrap()
19391            };
19392            let f = self.func("fa_prefill_w_bf16_p1h2");
19393            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
19394            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19395            f.set_attribute(
19396                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19397                shmem as i32,
19398            )?;
19399            let cfg = LaunchConfig {
19400                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
19401                block_dim: (32, 4, 1),
19402                shared_mem_bytes: shmem,
19403            };
19404            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19405                head_dim as i32,
19406                n_head as i32,
19407                n_head_kv as i32,
19408                t as i32,
19409                t_kv as i32,
19410                causal as i32,
19411                window as i32,
19412            );
19413            let __s_b = self.gpu.stream();
19414            let mut b = __s_b.launch_builder(&f);
19415            b.arg(qb)
19416                .arg(kb)
19417                .arg(vh)
19418                .arg(o)
19419                .arg(&hd)
19420                .arg(&nh)
19421                .arg(&nhkv)
19422                .arg(&ti)
19423                .arg(&tkvi)
19424                .arg(&scale)
19425                .arg(&cz)
19426                .arg(&wi);
19427            unsafe {
19428                b.launch(cfg)?;
19429            }
19430            return Ok(());
19431        }
19432        let f = self.func("fa_prefill_w_bf16_p1");
19433        let shmem =
19434            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
19435        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19436        f.set_attribute(
19437            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19438            shmem as i32,
19439        )?;
19440        let cfg = LaunchConfig {
19441            grid_dim: (
19442                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
19443                n_head as u32,
19444                1,
19445            ),
19446            block_dim: (32, 4, 1),
19447            shared_mem_bytes: shmem,
19448        };
19449        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19450            head_dim as i32,
19451            n_head as i32,
19452            n_head_kv as i32,
19453            t as i32,
19454            t_kv as i32,
19455            causal as i32,
19456            window as i32,
19457        );
19458        let __s_b = self.gpu.stream();
19459        let mut b = __s_b.launch_builder(&f);
19460        b.arg(qb)
19461            .arg(kb)
19462            .arg(vb)
19463            .arg(o)
19464            .arg(&hd)
19465            .arg(&nh)
19466            .arg(&nhkv)
19467            .arg(&ti)
19468            .arg(&tkvi)
19469            .arg(&scale)
19470            .arg(&cz)
19471            .arg(&wi);
19472        unsafe {
19473            b.launch(cfg)?;
19474        }
19475        Ok(())
19476    }
19477
19478    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
19479    #[allow(clippy::too_many_arguments)]
19480    pub fn fa_prefill_w_arm(
19481        &self,
19482        q: &CudaSlice<f32>,
19483        k: &CudaSlice<f32>,
19484        v: &CudaSlice<f32>,
19485        o: &mut CudaSlice<f32>,
19486        head_dim: usize,
19487        n_head: usize,
19488        n_head_kv: usize,
19489        t: usize,
19490        t_kv: usize,
19491        scale: f32,
19492        causal: bool,
19493        window: usize,
19494        f32_stage: bool,
19495        floor: bool,
19496    ) -> Result<(), Box<dyn std::error::Error>> {
19497        const BLOCK_Q: usize = 64;
19498        const BK: usize = 32;
19499        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
19500        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
19501        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
19502        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
19503        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19504        let p1 = !floor
19505            && !f32_stage
19506            && *P1_ON.get_or_init(|| {
19507                std::env::var("MEMRA_FAW_P1")
19508                    .map(|v| v != "0")
19509                    .unwrap_or(true)
19510            });
19511        let hp =
19512            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
19513        if hp {
19514            const BLOCK_QH: usize = 32;
19515            let f = self.func("fa_prefill_w_bf16_p1h2");
19516            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
19517            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19518            f.set_attribute(
19519                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19520                shmem as i32,
19521            )?;
19522            let cfg = LaunchConfig {
19523                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
19524                block_dim: (32, 4, 1),
19525                shared_mem_bytes: shmem,
19526            };
19527            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19528                head_dim as i32,
19529                n_head as i32,
19530                n_head_kv as i32,
19531                t as i32,
19532                t_kv as i32,
19533                causal as i32,
19534                window as i32,
19535            );
19536            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19537            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
19538            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
19539            let __s_b = self.gpu.stream();
19540            let mut b = __s_b.launch_builder(&f);
19541            b.arg(&qb)
19542                .arg(&kb)
19543                .arg(&vh)
19544                .arg(o)
19545                .arg(&hd)
19546                .arg(&nh)
19547                .arg(&nhkv)
19548                .arg(&ti)
19549                .arg(&tkvi)
19550                .arg(&scale)
19551                .arg(&cz)
19552                .arg(&wi);
19553            unsafe {
19554                b.launch(cfg)?;
19555            }
19556            return Ok(());
19557        }
19558        if p1 {
19559            let f = self.func("fa_prefill_w_bf16_p1");
19560            let shmem =
19561                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
19562            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19563            f.set_attribute(
19564                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19565                shmem as i32,
19566            )?;
19567            let cfg = LaunchConfig {
19568                grid_dim: (
19569                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
19570                    n_head as u32,
19571                    1,
19572                ),
19573                block_dim: (32, 4, 1),
19574                shared_mem_bytes: shmem,
19575            };
19576            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19577                head_dim as i32,
19578                n_head as i32,
19579                n_head_kv as i32,
19580                t as i32,
19581                t_kv as i32,
19582                causal as i32,
19583                window as i32,
19584            );
19585            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19586            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
19587            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
19588            let __s_b = self.gpu.stream();
19589            let mut b = __s_b.launch_builder(&f);
19590            b.arg(&qb)
19591                .arg(&kb)
19592                .arg(&vb)
19593                .arg(o)
19594                .arg(&hd)
19595                .arg(&nh)
19596                .arg(&nhkv)
19597                .arg(&ti)
19598                .arg(&tkvi)
19599                .arg(&scale)
19600                .arg(&cz)
19601                .arg(&wi);
19602            unsafe {
19603                b.launch(cfg)?;
19604            }
19605            return Ok(());
19606        }
19607        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
19608        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
19609        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19610        let g4 = !floor
19611            && !f32_stage
19612            && n_head_kv == 1
19613            && n_head % 4 == 0
19614            && *G4_ON.get_or_init(|| {
19615                std::env::var("MEMRA_FAW_G4")
19616                    .map(|v| v != "0")
19617                    .unwrap_or(true)
19618            });
19619        if g4 {
19620            const SP_M: usize = 16;
19621            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
19622            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
19623            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19624            let o2 = *O2_ON.get_or_init(|| {
19625                std::env::var("MEMRA_FAW_O2")
19626                    .map(|v| v != "0")
19627                    .unwrap_or(true)
19628            });
19629            let f = self.func(if o2 {
19630                "fa_prefill_w_bf16_g4o2"
19631            } else {
19632                "fa_prefill_w_bf16_g4"
19633            });
19634            let shmem = if o2 {
19635                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
19636            } else {
19637                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
19638                    as u32
19639            };
19640            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19641            f.set_attribute(
19642                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19643                shmem as i32,
19644            )?;
19645            let cfg = LaunchConfig {
19646                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
19647                block_dim: (32, 4, 1),
19648                shared_mem_bytes: shmem,
19649            };
19650            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19651                head_dim as i32,
19652                n_head as i32,
19653                n_head_kv as i32,
19654                t as i32,
19655                t_kv as i32,
19656                causal as i32,
19657                window as i32,
19658            );
19659            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19660            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
19661            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
19662            let __s_b = self.gpu.stream();
19663            let mut b = __s_b.launch_builder(&f);
19664            b.arg(&qb)
19665                .arg(&kb)
19666                .arg(&vb)
19667                .arg(o)
19668                .arg(&hd)
19669                .arg(&nh)
19670                .arg(&nhkv)
19671                .arg(&ti)
19672                .arg(&tkvi)
19673                .arg(&scale)
19674                .arg(&cz)
19675                .arg(&wi);
19676            unsafe {
19677                b.launch(cfg)?;
19678            }
19679            return Ok(());
19680        }
19681        let f = self.func(if floor {
19682            "fa_prefill_w_f32"
19683        } else if f32_stage {
19684            "fa_prefill_w_f32_pp"
19685        } else {
19686            "fa_prefill_w_bf16_pp"
19687        });
19688        let shmem =
19689            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
19690        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19691        f.set_attribute(
19692            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19693            shmem as i32,
19694        )?;
19695        let cfg = LaunchConfig {
19696            grid_dim: (
19697                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
19698                n_head as u32,
19699                1,
19700            ),
19701            block_dim: (32, 4, 1),
19702            shared_mem_bytes: shmem,
19703        };
19704        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
19705            head_dim as i32,
19706            n_head as i32,
19707            n_head_kv as i32,
19708            t as i32,
19709            t_kv as i32,
19710            causal as i32,
19711            window as i32,
19712        );
19713        if f32_stage {
19714            let __s_b = self.gpu.stream();
19715            let mut b = __s_b.launch_builder(&f);
19716            b.arg(q)
19717                .arg(k)
19718                .arg(v)
19719                .arg(o)
19720                .arg(&hd)
19721                .arg(&nh)
19722                .arg(&nhkv)
19723                .arg(&ti)
19724                .arg(&tkvi)
19725                .arg(&scale)
19726                .arg(&cz)
19727                .arg(&wi);
19728            unsafe {
19729                b.launch(cfg)?;
19730            }
19731        } else {
19732            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19733            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
19734            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
19735            let __s_b = self.gpu.stream();
19736            let mut b = __s_b.launch_builder(&f);
19737            b.arg(&qb)
19738                .arg(&kb)
19739                .arg(&vb)
19740                .arg(o)
19741                .arg(&hd)
19742                .arg(&nh)
19743                .arg(&nhkv)
19744                .arg(&ti)
19745                .arg(&tkvi)
19746                .arg(&scale)
19747                .arg(&cz)
19748                .arg(&wi);
19749            unsafe {
19750                b.launch(cfg)?;
19751            }
19752        }
19753        Ok(())
19754    }
19755
19756    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
19757    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
19758    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
19759    #[allow(clippy::too_many_arguments)]
19760    pub fn fa_prefill_hd512(
19761        &self,
19762        q: &CudaSlice<f32>,
19763        k: &CudaSlice<f32>,
19764        v: &CudaSlice<f32>,
19765        o: &mut CudaSlice<f32>,
19766        head_dim: usize,
19767        n_head: usize,
19768        n_head_kv: usize,
19769        t: usize,
19770        t_kv: usize,
19771        scale: f32,
19772        causal: bool,
19773    ) -> Result<(), Box<dyn std::error::Error>> {
19774        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
19775        if portable_mma_gated() {
19776            return self.sdpa_naive(
19777                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
19778            );
19779        }
19780        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
19781        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
19782        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
19783        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
19784        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
19785        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19786        let f32_stage =
19787            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
19788        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
19789        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
19790        // Own numeric config (partial-sum order) — battery-gated.
19791        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19792        let sp = !f32_stage
19793            && *SP_ON.get_or_init(|| {
19794                std::env::var("MEMRA_FA512_SP")
19795                    .map(|v| v != "0")
19796                    .unwrap_or(true)
19797            });
19798        self.fa_prefill_hd512_arm(
19799            q,
19800            k,
19801            v,
19802            o,
19803            head_dim,
19804            n_head,
19805            n_head_kv,
19806            t,
19807            t_kv,
19808            scale,
19809            causal,
19810            f32_stage,
19811            sp,
19812            sp && fa_f16pv_on(),
19813        )
19814    }
19815
19816    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
19817    #[allow(clippy::too_many_arguments)]
19818    pub fn fa_prefill_hd512_pre(
19819        &self,
19820        qb: &CudaSlice<u8>,
19821        kb: &CudaSlice<u8>,
19822        vb: &CudaSlice<u8>,
19823        o: &mut CudaSlice<f32>,
19824        head_dim: usize,
19825        n_head: usize,
19826        n_head_kv: usize,
19827        t: usize,
19828        t_kv: usize,
19829        scale: f32,
19830        causal: bool,
19831        v_f16: bool,
19832    ) -> Result<(), Box<dyn std::error::Error>> {
19833        debug_assert_eq!(head_dim, 512);
19834        const SP_M: usize = 16;
19835        const BKS: usize = 32;
19836        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
19837        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
19838        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
19839        let f16pv = fa_f16pv_on();
19840        let nw = if f16pv { fa512_wide_warps() } else { 2 };
19841        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
19842        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
19843        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
19844        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
19845            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
19846            let n = t_kv * n_head_kv * head_dim;
19847            let need = n * 2;
19848            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
19849                *vguard = Some(self.alloc_uninit::<u8>(need)?);
19850            }
19851            let dst = vguard.as_mut().unwrap();
19852            self.bf16_to_f16_into(vb, n, dst)?;
19853            vguard.as_ref().unwrap()
19854        } else {
19855            vb
19856        };
19857        let f = self.func(if hp {
19858            "fa_prefill_bf16_hd512_sp16h2"
19859        } else {
19860            match (f16pv, nw) {
19861                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
19862                (true, _) => "fa_prefill_bf16_hd512_sp16",
19863                _ => "fa_prefill_bf16_hd512_sp",
19864            }
19865        });
19866        let (nwarp, npart) = if hp {
19867            (4usize, 4usize)
19868        } else if nw > 2 {
19869            (nw, nw)
19870        } else {
19871            (2, 1)
19872        };
19873        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
19874        let shmem = if hp {
19875            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
19876                as u32
19877        } else {
19878            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
19879                + 4 * (npart * SP_M * BKS + SP_M)) as u32
19880        };
19881        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19882        f.set_attribute(
19883            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19884            shmem as i32,
19885        )?;
19886        let grid_y = if hp {
19887            (n_head / 2) as u32
19888        } else {
19889            n_head as u32
19890        };
19891        let cfg = LaunchConfig {
19892            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
19893            block_dim: (32, nwarp as u32, 1),
19894            shared_mem_bytes: shmem,
19895        };
19896        let (hd, nh, nhkv, ti, tkvi, cz) = (
19897            head_dim as i32,
19898            n_head as i32,
19899            n_head_kv as i32,
19900            t as i32,
19901            t_kv as i32,
19902            causal as i32,
19903        );
19904        let __s_b = self.gpu.stream();
19905        let mut b = __s_b.launch_builder(&f);
19906        b.arg(qb)
19907            .arg(kb)
19908            .arg(vref)
19909            .arg(o)
19910            .arg(&hd)
19911            .arg(&nh)
19912            .arg(&nhkv)
19913            .arg(&ti)
19914            .arg(&tkvi)
19915            .arg(&scale)
19916            .arg(&cz);
19917        unsafe {
19918            b.launch(cfg)?;
19919        }
19920        Ok(())
19921    }
19922
19923    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
19924    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
19925    #[allow(clippy::too_many_arguments)]
19926    pub fn fa_prefill_hd512_arm(
19927        &self,
19928        q: &CudaSlice<f32>,
19929        k: &CudaSlice<f32>,
19930        v: &CudaSlice<f32>,
19931        o: &mut CudaSlice<f32>,
19932        head_dim: usize,
19933        n_head: usize,
19934        n_head_kv: usize,
19935        t: usize,
19936        t_kv: usize,
19937        scale: f32,
19938        causal: bool,
19939        f32_stage: bool,
19940        sp: bool,
19941        f16pv: bool,
19942    ) -> Result<(), Box<dyn std::error::Error>> {
19943        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
19944        if sp && !f32_stage {
19945            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
19946            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
19947            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
19948            const SP_M: usize = 16;
19949            const BKS: usize = 32;
19950            let nw = if f16pv { fa512_wide_warps() } else { 2 };
19951            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
19952            let f = self.func(if hp {
19953                "fa_prefill_bf16_hd512_sp16h2"
19954            } else {
19955                match (f16pv, nw) {
19956                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
19957                    (true, _) => "fa_prefill_bf16_hd512_sp16",
19958                    _ => "fa_prefill_bf16_hd512_sp",
19959                }
19960            });
19961            let (nwarp, npart) = if hp {
19962                (4usize, 4usize)
19963            } else if nw > 2 {
19964                (nw, nw)
19965            } else {
19966                (2, 1)
19967            };
19968            let shmem = if hp {
19969                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
19970                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
19971            } else {
19972                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
19973                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
19974            };
19975            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19976            f.set_attribute(
19977                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19978                shmem as i32,
19979            )?;
19980            let grid_y = if hp {
19981                (n_head / 2) as u32
19982            } else {
19983                n_head as u32
19984            };
19985            let cfg = LaunchConfig {
19986                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
19987                block_dim: (32, nwarp as u32, 1),
19988                shared_mem_bytes: shmem,
19989            };
19990            let (hd, nh, nhkv, ti, tkvi, cz) = (
19991                head_dim as i32,
19992                n_head as i32,
19993                n_head_kv as i32,
19994                t as i32,
19995                t_kv as i32,
19996                causal as i32,
19997            );
19998            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
19999            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
20000            let vb = if f16pv {
20001                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
20002            } else {
20003                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
20004            };
20005            let __s_b = self.gpu.stream();
20006            let mut b = __s_b.launch_builder(&f);
20007            b.arg(&qb)
20008                .arg(&kb)
20009                .arg(&vb)
20010                .arg(o)
20011                .arg(&hd)
20012                .arg(&nh)
20013                .arg(&nhkv)
20014                .arg(&ti)
20015                .arg(&tkvi)
20016                .arg(&scale)
20017                .arg(&cz);
20018            unsafe {
20019                b.launch(cfg)?;
20020            }
20021            return Ok(());
20022        }
20023        const BLOCK_Q: usize = 32;
20024        const BK: usize = 32;
20025        const HALF: usize = 256;
20026        let f = self.func(if f32_stage {
20027            "fa_prefill_f32_hd512"
20028        } else {
20029            "fa_prefill_bf16_hd512"
20030        });
20031        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
20032        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
20033            + 4 * BLOCK_Q) as u32;
20034        use cudarc::driver::sys::CUfunction_attribute_enum as A;
20035        f.set_attribute(
20036            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
20037            shmem as i32,
20038        )?;
20039        let cfg = LaunchConfig {
20040            grid_dim: (
20041                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
20042                n_head as u32,
20043                2,
20044            ),
20045            block_dim: (32, 2, 1),
20046            shared_mem_bytes: shmem,
20047        };
20048        let (hd, nh, nhkv, ti, tkvi, cz) = (
20049            head_dim as i32,
20050            n_head as i32,
20051            n_head_kv as i32,
20052            t as i32,
20053            t_kv as i32,
20054            causal as i32,
20055        );
20056        if f32_stage {
20057            let __s_b = self.gpu.stream();
20058            let mut b = __s_b.launch_builder(&f);
20059            b.arg(q)
20060                .arg(k)
20061                .arg(v)
20062                .arg(o)
20063                .arg(&hd)
20064                .arg(&nh)
20065                .arg(&nhkv)
20066                .arg(&ti)
20067                .arg(&tkvi)
20068                .arg(&scale)
20069                .arg(&cz);
20070            unsafe {
20071                b.launch(cfg)?;
20072            }
20073        } else {
20074            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
20075            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
20076            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
20077            let __s_b = self.gpu.stream();
20078            let mut b = __s_b.launch_builder(&f);
20079            b.arg(&qb)
20080                .arg(&kb)
20081                .arg(&vb)
20082                .arg(o)
20083                .arg(&hd)
20084                .arg(&nh)
20085                .arg(&nhkv)
20086                .arg(&ti)
20087                .arg(&tkvi)
20088                .arg(&scale)
20089                .arg(&cz);
20090            unsafe {
20091                b.launch(cfg)?;
20092            }
20093        }
20094        Ok(())
20095    }
20096
20097    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
20098    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
20099    /// separate f32_to_bf16 the FA entries would run).
20100    #[allow(clippy::too_many_arguments)]
20101    pub fn rope_neox2_bf16e(
20102        &self,
20103        q: &mut CudaSlice<f32>,
20104        k: &mut CudaSlice<f32>,
20105        qb: &mut CudaSlice<u8>,
20106        kb: &mut CudaSlice<u8>,
20107        pos: &CudaSlice<i32>,
20108        head_dim: usize,
20109        n_dims: usize,
20110        nh_q: usize,
20111        nh_k: usize,
20112        n_tokens: usize,
20113        base: f32,
20114        freq_scale: f32,
20115        ff: Option<&CudaSlice<f32>>,
20116    ) -> Result<(), Box<dyn std::error::Error>> {
20117        let f = self.func("rope_neox2_bf16e_f32");
20118        let rows = ((nh_q + nh_k) * n_tokens) as u32;
20119        let cfg = LaunchConfig {
20120            grid_dim: (rows, 1, 1),
20121            block_dim: ((head_dim / 2) as u32, 1, 1),
20122            shared_mem_bytes: 0,
20123        };
20124        let theta_scale = base.powf(-2.0 / n_dims as f32);
20125        let (hd, nd, nhq, nhk, nt) = (
20126            head_dim as i32,
20127            n_dims as i32,
20128            nh_q as i32,
20129            nh_k as i32,
20130            n_tokens as i32,
20131        );
20132        let __s_b = self.gpu.stream();
20133        let mut b = __s_b.launch_builder(&f);
20134        match ff {
20135            Some(t) => {
20136                b.arg(&mut *q)
20137                    .arg(&mut *k)
20138                    .arg(&mut *qb)
20139                    .arg(&mut *kb)
20140                    .arg(pos)
20141                    .arg(&hd)
20142                    .arg(&nd)
20143                    .arg(&nhq)
20144                    .arg(&nhk)
20145                    .arg(&nt)
20146                    .arg(&theta_scale)
20147                    .arg(&freq_scale)
20148                    .arg(t);
20149                unsafe {
20150                    b.launch(cfg)?;
20151                }
20152            }
20153            None => {
20154                let null: u64 = 0;
20155                b.arg(&mut *q)
20156                    .arg(&mut *k)
20157                    .arg(&mut *qb)
20158                    .arg(&mut *kb)
20159                    .arg(pos)
20160                    .arg(&hd)
20161                    .arg(&nd)
20162                    .arg(&nhq)
20163                    .arg(&nhk)
20164                    .arg(&nt)
20165                    .arg(&theta_scale)
20166                    .arg(&freq_scale)
20167                    .arg(&null);
20168                unsafe {
20169                    b.launch(cfg)?;
20170                }
20171            }
20172        }
20173        Ok(())
20174    }
20175
20176    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
20177    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
20178    pub fn f32_to_bf16(
20179        &self,
20180        x: &CudaSlice<f32>,
20181        n: usize,
20182    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
20183        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
20184        let mut y = self.alloc_uninit::<u8>(n * 2)?;
20185        let f = self.func("f32_to_bf16_flat");
20186        let n_i = n as i64;
20187        let cfg = LaunchConfig {
20188            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
20189            block_dim: (256, 1, 1),
20190            shared_mem_bytes: 0,
20191        };
20192        let __s_b = self.gpu.stream();
20193        let mut b = __s_b.launch_builder(&f);
20194        b.arg(x).arg(&mut y).arg(&n_i);
20195        unsafe {
20196            b.launch(cfg)?;
20197        }
20198        Ok(y)
20199    }
20200
20201    pub fn f32_to_f16(
20202        &self,
20203        x: &CudaSlice<f32>,
20204        n: usize,
20205    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
20206        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
20207        let mut y = self.alloc_uninit::<u8>(n * 2)?;
20208        let f = self.func("f32_to_f16_flat");
20209        let n_i = n as i64;
20210        let cfg = LaunchConfig {
20211            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
20212            block_dim: (256, 1, 1),
20213            shared_mem_bytes: 0,
20214        };
20215        let __s_b = self.gpu.stream();
20216        let mut b = __s_b.launch_builder(&f);
20217        b.arg(x).arg(&mut y).arg(&n_i);
20218        unsafe {
20219            b.launch(cfg)?;
20220        }
20221        Ok(y)
20222    }
20223
20224    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
20225    pub fn bf16_to_f16(
20226        &self,
20227        xb: &CudaSlice<u8>,
20228        n: usize,
20229    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
20230        let mut y = self.alloc_uninit::<u8>(n * 2)?;
20231        self.bf16_to_f16_into(xb, n, &mut y)?;
20232        Ok(y)
20233    }
20234
20235    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
20236    pub fn bf16_to_f16_into(
20237        &self,
20238        xb: &CudaSlice<u8>,
20239        n: usize,
20240        y: &mut CudaSlice<u8>,
20241    ) -> Result<(), Box<dyn std::error::Error>> {
20242        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
20243        assert!(y.len() >= n * 2);
20244        let f = self.func("bf16_to_f16_flat");
20245        let n2 = (n / 2) as i64;
20246        let cfg = LaunchConfig {
20247            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
20248            block_dim: (256, 1, 1),
20249            shared_mem_bytes: 0,
20250        };
20251        let __s_b = self.gpu.stream();
20252        let mut b = __s_b.launch_builder(&f);
20253        b.arg(xb).arg(y).arg(&n2);
20254        unsafe {
20255            b.launch(cfg)?;
20256        }
20257        Ok(())
20258    }
20259
20260    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
20261    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
20262    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
20263    /// head_dim in {256, 128}, bf16kv lane on.
20264    #[allow(clippy::too_many_arguments)]
20265    pub fn fa_prefill_vl8(
20266        &self,
20267        seqs: &[FaSeqVl],
20268        head_dim: usize,
20269        n_head: usize,
20270        n_head_kv: usize,
20271        scale: f32,
20272    ) -> Result<(), Box<dyn std::error::Error>> {
20273        const BK: usize = 32;
20274        let b = seqs.len();
20275        assert!(b >= 1 && b <= 8);
20276        let mut packed = [FaSeqVl::default(); 8];
20277        packed[..b].copy_from_slice(seqs);
20278        let v = FaVl8(packed);
20279        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20280        let ept = (n_head_kv * head_dim) as i32;
20281        {
20282            let f = self.func("fa_mirror_vl");
20283            let max_n = (max_t as i64) * ept as i64;
20284            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
20285            for which in 0..2i32 {
20286                let cfg = LaunchConfig {
20287                    grid_dim: (blocks, 1, b as u32),
20288                    block_dim: (256, 1, 1),
20289                    shared_mem_bytes: 0,
20290                };
20291                let __s_lb = self.gpu.stream();
20292                let mut lb = __s_lb.launch_builder(&f);
20293                lb.arg(&v).arg(&ept).arg(&which);
20294                unsafe {
20295                    lb.launch(cfg)?;
20296                }
20297            }
20298        }
20299        let hd_sfx = fa_hd_suffix(head_dim)?;
20300        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
20301        let block_q = 64usize;
20302        let kv_stages = 2usize;
20303        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
20304            + 4 * (block_q * BK + 2 * block_q)) as u32;
20305        use cudarc::driver::sys::CUfunction_attribute_enum as A;
20306        f.set_attribute(
20307            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
20308            shmem as i32,
20309        )?;
20310        let cfg = LaunchConfig {
20311            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
20312            block_dim: (32, 4, 1),
20313            shared_mem_bytes: shmem,
20314        };
20315        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
20316        let __s_lb = self.gpu.stream();
20317        let mut lb = __s_lb.launch_builder(&f);
20318        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
20319        unsafe {
20320            lb.launch(cfg)?;
20321        }
20322        Ok(())
20323    }
20324
20325    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
20326    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
20327    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
20328    #[allow(clippy::too_many_arguments)]
20329    pub fn attn_pre_vl8(
20330        &self,
20331        seqs: &[AttnPreVl],
20332        wq: &CudaSlice<f32>,
20333        wk: &CudaSlice<f32>,
20334        head_dim: usize,
20335        rope_dims: usize,
20336        n_head: usize,
20337        n_head_kv: usize,
20338        eps: f32,
20339        freq_base: f32,
20340        freq_scale: f32,
20341        kv_dim_k: usize,
20342        kv_dim_v: usize,
20343        k_tok_bytes: usize,
20344        v_tok_bytes: usize,
20345    ) -> Result<(), Box<dyn std::error::Error>> {
20346        let b = seqs.len();
20347        assert!(b >= 1 && b <= 8);
20348        let mut packed = [AttnPreVl::default(); 8];
20349        packed[..b].copy_from_slice(seqs);
20350        let v = AttnPreVl8(packed);
20351        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20352        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
20353        {
20354            let f = self.func("q_gate_split_vl");
20355            let n = max_t * (n_head * head_dim) as u32;
20356            let cfg = LaunchConfig {
20357                grid_dim: (n.div_ceil(256), 1, b as u32),
20358                block_dim: (256, 1, 1),
20359                shared_mem_bytes: 0,
20360            };
20361            let __s_lb = self.gpu.stream();
20362            let mut lb = __s_lb.launch_builder(&f);
20363            lb.arg(&v).arg(&hd).arg(&nh);
20364            unsafe {
20365                lb.launch(cfg)?;
20366            }
20367        }
20368        {
20369            let f = self.func("attn_rms_vl");
20370            let cfg = LaunchConfig {
20371                grid_dim: (max_t * n_head as u32, 2, b as u32),
20372                block_dim: (rms_block(), 1, 1),
20373                shared_mem_bytes: 0,
20374            };
20375            let __s_lb = self.gpu.stream();
20376            let mut lb = __s_lb.launch_builder(&f);
20377            lb.arg(&v)
20378                .arg(wq)
20379                .arg(wk)
20380                .arg(&hd)
20381                .arg(&nh)
20382                .arg(&nhkv)
20383                .arg(&eps);
20384            unsafe {
20385                lb.launch(cfg)?;
20386            }
20387        }
20388        {
20389            let f = self.func("attn_rope_vl");
20390            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
20391            let nd = rope_dims as i32;
20392            let cfg = LaunchConfig {
20393                grid_dim: (max_t * n_head as u32, 2, b as u32),
20394                block_dim: ((head_dim / 2) as u32, 1, 1),
20395                shared_mem_bytes: 0,
20396            };
20397            let __s_lb = self.gpu.stream();
20398            let mut lb = __s_lb.launch_builder(&f);
20399            lb.arg(&v)
20400                .arg(&hd)
20401                .arg(&nd)
20402                .arg(&nh)
20403                .arg(&nhkv)
20404                .arg(&theta_scale)
20405                .arg(&freq_scale);
20406            unsafe {
20407                lb.launch(cfg)?;
20408            }
20409        }
20410        {
20411            let f = self.func("append_kv_vl");
20412            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
20413            let cfg = LaunchConfig {
20414                grid_dim: (nblk, max_t, b as u32),
20415                block_dim: (32, 1, 1),
20416                shared_mem_bytes: 0,
20417            };
20418            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
20419            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
20420            let __s_lb = self.gpu.stream();
20421            let mut lb = __s_lb.launch_builder(&f);
20422            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
20423            unsafe {
20424                lb.launch(cfg)?;
20425            }
20426        }
20427        Ok(())
20428    }
20429
20430    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
20431    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
20432    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
20433    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
20434    pub fn fa_prefill_view(
20435        &self,
20436        q: &CudaSlice<f32>,
20437        k: &cudarc::driver::CudaView<u8>,
20438        v: &cudarc::driver::CudaView<u8>,
20439        o: &mut CudaSlice<f32>,
20440        head_dim: usize,
20441        n_head: usize,
20442        n_head_kv: usize,
20443        t: usize,
20444        t_kv: usize,
20445        scale: f32,
20446        causal: bool,
20447        k_tok_bytes: usize,
20448        v_tok_bytes: usize,
20449        g: bool,
20450    ) -> Result<(), Box<dyn std::error::Error>> {
20451        if portable_mma_gated() {
20452            return self.sdpa_naive_quantized_view(
20453                q,
20454                k,
20455                v,
20456                o,
20457                head_dim,
20458                n_head,
20459                n_head_kv,
20460                t,
20461                t_kv,
20462                scale,
20463                causal,
20464                k_tok_bytes,
20465                v_tok_bytes,
20466            );
20467        }
20468        const BLOCK_Q: usize = 64;
20469        const BK: usize = 32;
20470        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
20471        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
20472        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
20473        let f = if g {
20474            self.func_g(&name)
20475        } else {
20476            self.func(&name)
20477        };
20478        let shmem =
20479            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
20480        use cudarc::driver::sys::CUfunction_attribute_enum as A;
20481        f.set_attribute(
20482            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
20483            shmem as i32,
20484        )?;
20485        let cfg = LaunchConfig {
20486            grid_dim: (
20487                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
20488                n_head as u32,
20489                1,
20490            ),
20491            block_dim: (32, 4, 1),
20492            shared_mem_bytes: shmem,
20493        };
20494        let (hd, nh, nhkv, ti, tkvi, cz) = (
20495            head_dim as i32,
20496            n_head as i32,
20497            n_head_kv as i32,
20498            t as i32,
20499            t_kv as i32,
20500            causal as i32,
20501        );
20502        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
20503        let __s_b = self.gpu.stream();
20504        let mut b = __s_b.launch_builder(&f);
20505        b.arg(q)
20506            .arg(k)
20507            .arg(v)
20508            .arg(o)
20509            .arg(&hd)
20510            .arg(&nh)
20511            .arg(&nhkv)
20512            .arg(&ti)
20513            .arg(&tkvi)
20514            .arg(&scale)
20515            .arg(&cz)
20516            .arg(&ktb)
20517            .arg(&vtb);
20518        unsafe {
20519            b.launch(cfg)?;
20520        }
20521        Ok(())
20522    }
20523
20524    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
20525    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
20526    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
20527    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
20528    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
20529    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
20530    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
20531    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
20532    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
20533    #[allow(clippy::too_many_arguments)]
20534    pub fn fa_prefill_view_ws(
20535        &self,
20536        q: &CudaSlice<f32>,
20537        k: &cudarc::driver::CudaView<u8>,
20538        v: &cudarc::driver::CudaView<u8>,
20539        o: &mut CudaSlice<f32>,
20540        head_dim: usize,
20541        n_head: usize,
20542        n_head_kv: usize,
20543        t: usize,
20544        t_kv: usize,
20545        scale: f32,
20546        causal: bool,
20547        k_tok_bytes: usize,
20548        v_tok_bytes: usize,
20549        g: bool,
20550    ) -> Result<(), Box<dyn std::error::Error>> {
20551        if portable_mma_gated() {
20552            return self.sdpa_naive_quantized_view(
20553                q,
20554                k,
20555                v,
20556                o,
20557                head_dim,
20558                n_head,
20559                n_head_kv,
20560                t,
20561                t_kv,
20562                scale,
20563                causal,
20564                k_tok_bytes,
20565                v_tok_bytes,
20566            );
20567        }
20568        const BLOCK_Q: usize = 64;
20569        const BK: usize = 32;
20570        let kv_dim_k = n_head_kv * head_dim;
20571        let kv_dim_v = n_head_kv * head_dim;
20572        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
20573        let v_ws_bytes = t_kv * kv_dim_v * 2;
20574        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
20575        let mut guard = self.prime_deqw_ws.lock().unwrap();
20576        let need_grow = match guard.as_ref() {
20577            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
20578            None => true,
20579        };
20580        if need_grow {
20581            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
20582            let (ck, cv) = guard
20583                .as_ref()
20584                .map(|(a, b)| (a.len(), b.len()))
20585                .unwrap_or((0, 0));
20586            *guard = Some((
20587                self.alloc_u8(grow(ck, k_ws_bytes))?,
20588                self.alloc_u8(grow(cv, v_ws_bytes))?,
20589            ));
20590        }
20591        let (kw, vw) = guard.as_mut().unwrap();
20592        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
20593        {
20594            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
20595            let f = if g {
20596                self.func_g("fa_dequant_kv_ws_bf16")
20597            } else {
20598                self.func("fa_dequant_kv_ws_bf16")
20599            };
20600            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
20601            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
20602            let cfg = LaunchConfig {
20603                grid_dim: (nblk.max(1), 1, 1),
20604                block_dim: (256, 1, 1),
20605                shared_mem_bytes: 0,
20606            };
20607            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
20608            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
20609            let __s_b = self.gpu.stream();
20610            let mut b = __s_b.launch_builder(&f);
20611            b.arg(k)
20612                .arg(v)
20613                .arg(&mut *kw)
20614                .arg(&mut *vw)
20615                .arg(&kdk)
20616                .arg(&kdv)
20617                .arg(&tkvi)
20618                .arg(&ktb)
20619                .arg(&vtb);
20620            unsafe {
20621                b.launch(cfg)?;
20622            }
20623        }
20624        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
20625        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
20626        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
20627        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
20628        // both twins). A/B (27B rtx6000, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
20629        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
20630        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
20631        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
20632            .map(|v| v != "0")
20633            .unwrap_or(true);
20634        {
20635            let hd_sfx = fa_hd_suffix(head_dim)?;
20636            let f = self.func(&format!(
20637                "fa_prefill_qw{}{hd_sfx}",
20638                if db { "_db" } else { "" }
20639            ));
20640            let shmem = if db {
20641                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
20642                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
20643            } else {
20644                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
20645            };
20646            use cudarc::driver::sys::CUfunction_attribute_enum as A;
20647            f.set_attribute(
20648                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
20649                shmem as i32,
20650            )?;
20651            let cfg = LaunchConfig {
20652                grid_dim: (
20653                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
20654                    n_head as u32,
20655                    1,
20656                ),
20657                block_dim: (32, 4, 1),
20658                shared_mem_bytes: shmem,
20659            };
20660            let (hd, nh, nhkv, ti, tkvi, cz) = (
20661                head_dim as i32,
20662                n_head as i32,
20663                n_head_kv as i32,
20664                t as i32,
20665                t_kv as i32,
20666                causal as i32,
20667            );
20668            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
20669            let __s_b = self.gpu.stream();
20670            let mut b = __s_b.launch_builder(&f);
20671            b.arg(q)
20672                .arg(&*kw)
20673                .arg(&*vw)
20674                .arg(o)
20675                .arg(&hd)
20676                .arg(&nh)
20677                .arg(&nhkv)
20678                .arg(&ti)
20679                .arg(&tkvi)
20680                .arg(&scale)
20681                .arg(&cz)
20682                .arg(&kdk)
20683                .arg(&kdv);
20684            unsafe {
20685                b.launch(cfg)?;
20686            }
20687        }
20688        Ok(())
20689    }
20690
20691    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
20692    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
20693    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
20694    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
20695    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
20696    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
20697    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
20698    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
20699    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
20700    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
20701    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
20702    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
20703    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
20704    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
20705    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
20706    #[allow(clippy::too_many_arguments)]
20707    pub fn fa_prefill_view_ws_w_hd128(
20708        &self,
20709        q: &CudaSlice<f32>,
20710        k: &cudarc::driver::CudaView<u8>,
20711        v: &cudarc::driver::CudaView<u8>,
20712        o: &mut CudaSlice<f32>,
20713        head_dim: usize,
20714        n_head: usize,
20715        n_head_kv: usize,
20716        t: usize,
20717        t_kv: usize,
20718        scale: f32,
20719        causal: bool,
20720        window: usize,
20721        k_tok_bytes: usize,
20722        v_tok_bytes: usize,
20723    ) -> Result<(), Box<dyn std::error::Error>> {
20724        assert_eq!(
20725            head_dim, 128,
20726            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
20727        );
20728        if portable_mma_gated() {
20729            return self.sdpa_naive_w_quantized_view(
20730                q,
20731                k,
20732                v,
20733                o,
20734                head_dim,
20735                n_head,
20736                n_head_kv,
20737                t,
20738                t_kv,
20739                scale,
20740                causal,
20741                window,
20742                k_tok_bytes,
20743                v_tok_bytes,
20744            );
20745        }
20746        const BLOCK_Q: usize = 64;
20747        const BK: usize = 32;
20748        let kv_dim_k = n_head_kv * head_dim;
20749        let kv_dim_v = n_head_kv * head_dim;
20750        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
20751        let v_ws_bytes = t_kv * kv_dim_v * 2;
20752        let mut guard = self.prime_deqw_ws.lock().unwrap();
20753        let need_grow = match guard.as_ref() {
20754            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
20755            None => true,
20756        };
20757        if need_grow {
20758            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
20759            let (ck, cv) = guard
20760                .as_ref()
20761                .map(|(a, b)| (a.len(), b.len()))
20762                .unwrap_or((0, 0));
20763            *guard = Some((
20764                self.alloc_u8(grow(ck, k_ws_bytes))?,
20765                self.alloc_u8(grow(cv, v_ws_bytes))?,
20766            ));
20767        }
20768        let (kw, vw) = guard.as_mut().unwrap();
20769        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
20770        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
20771        {
20772            let f = self.func("fa_dequant_kv_ws_bf16");
20773            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
20774            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
20775            let cfg = LaunchConfig {
20776                grid_dim: (nblk.max(1), 1, 1),
20777                block_dim: (256, 1, 1),
20778                shared_mem_bytes: 0,
20779            };
20780            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
20781            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
20782            let __s_b = self.gpu.stream();
20783            let mut b = __s_b.launch_builder(&f);
20784            b.arg(k)
20785                .arg(v)
20786                .arg(&mut *kw)
20787                .arg(&mut *vw)
20788                .arg(&kdk)
20789                .arg(&kdv)
20790                .arg(&tkvi)
20791                .arg(&ktb)
20792                .arg(&vtb);
20793            unsafe {
20794                b.launch(cfg)?;
20795            }
20796        }
20797        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
20798        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
20799            .map(|v| v != "0")
20800            .unwrap_or(true);
20801        {
20802            let f = self.func(if db {
20803                "fa_prefill_qw_db_w_hd128"
20804            } else {
20805                "fa_prefill_qw_w_hd128"
20806            });
20807            let shmem = if db {
20808                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
20809            } else {
20810                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
20811            };
20812            use cudarc::driver::sys::CUfunction_attribute_enum as A;
20813            f.set_attribute(
20814                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
20815                shmem as i32,
20816            )?;
20817            let cfg = LaunchConfig {
20818                grid_dim: (
20819                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
20820                    n_head as u32,
20821                    1,
20822                ),
20823                block_dim: (32, 4, 1),
20824                shared_mem_bytes: shmem,
20825            };
20826            let (hd, nh, nhkv, ti, tkvi, cz) = (
20827                head_dim as i32,
20828                n_head as i32,
20829                n_head_kv as i32,
20830                t as i32,
20831                t_kv as i32,
20832                causal as i32,
20833            );
20834            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
20835            let __s_b = self.gpu.stream();
20836            let mut b = __s_b.launch_builder(&f);
20837            b.arg(q)
20838                .arg(&*kw)
20839                .arg(&*vw)
20840                .arg(o)
20841                .arg(&hd)
20842                .arg(&nh)
20843                .arg(&nhkv)
20844                .arg(&ti)
20845                .arg(&tkvi)
20846                .arg(&scale)
20847                .arg(&cz)
20848                .arg(&kdk)
20849                .arg(&kdv)
20850                .arg(&wnd);
20851            unsafe {
20852                b.launch(cfg)?;
20853            }
20854        }
20855        Ok(())
20856    }
20857
20858    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
20859    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
20860    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
20861    pub fn fa_decode(
20862        &self,
20863        q: &CudaSlice<f32>,
20864        k: &cudarc::driver::CudaView<u8>,
20865        v: &cudarc::driver::CudaView<u8>,
20866        o: &mut CudaSlice<f32>,
20867        head_dim: usize,
20868        n_head: usize,
20869        n_head_kv: usize,
20870        t_kv: usize,
20871        scale: f32,
20872        k_tok_bytes: usize,
20873        v_tok_bytes: usize,
20874    ) -> Result<(), Box<dyn std::error::Error>> {
20875        self.fa_decode_kvmod(
20876            q,
20877            k,
20878            v,
20879            o,
20880            head_dim,
20881            n_head,
20882            n_head_kv,
20883            t_kv,
20884            scale,
20885            k_tok_bytes,
20886            v_tok_bytes,
20887            false,
20888        )
20889    }
20890
20891    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
20892    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
20893    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
20894    #[allow(clippy::too_many_arguments)]
20895    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
20896    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
20897    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
20898    #[allow(clippy::too_many_arguments)]
20899    #[allow(clippy::too_many_arguments)]
20900    fn fa_decode_scalar_unified(
20901        &self,
20902        q: &cudarc::driver::CudaView<f32>,
20903        k: &cudarc::driver::CudaView<u8>,
20904        v: &cudarc::driver::CudaView<u8>,
20905        o: &mut cudarc::driver::CudaViewMut<f32>,
20906        head_dim: usize,
20907        n_head: usize,
20908        n_head_kv: usize,
20909        t_kv_host: usize,
20910        t_kv_dev: Option<&CudaSlice<i32>>,
20911        scale: f32,
20912        n_splits: usize,
20913        split_keys: usize,
20914        k_tok_bytes: usize,
20915        v_tok_bytes: usize,
20916        g: bool,
20917        part_o: &mut CudaSlice<f32>,
20918        part_m: &mut CudaSlice<f32>,
20919        part_l: &mut CudaSlice<f32>,
20920        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
20921    ) -> Result<(), Box<dyn std::error::Error>> {
20922        let f = if g {
20923            self.func_g("fa_decode_f32")
20924        } else {
20925            self.fa_func("fa_decode_f32", head_dim)
20926        };
20927        let cfg = LaunchConfig {
20928            grid_dim: (n_head as u32, n_splits as u32, 1),
20929            block_dim: (head_dim as u32, 1, 1),
20930            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
20931        };
20932        let (hd, nh, nhkv, nsp) = (
20933            head_dim as i32,
20934            n_head as i32,
20935            n_head_kv as i32,
20936            n_splits as i32,
20937        );
20938        let (ktb, vtb, tkvi, ski) = (
20939            k_tok_bytes as i64,
20940            v_tok_bytes as i64,
20941            t_kv_host as i32,
20942            split_keys as i32,
20943        );
20944        let __s_b = self.gpu.stream();
20945        let mut b = __s_b.launch_builder(&f);
20946        match t_kv_dev {
20947            Some(d) => {
20948                b.arg(q)
20949                    .arg(k)
20950                    .arg(v)
20951                    .arg(&mut *part_o)
20952                    .arg(&mut *part_m)
20953                    .arg(&mut *part_l)
20954                    .arg(&hd)
20955                    .arg(&nh)
20956                    .arg(&nhkv)
20957                    .arg(&tkvi)
20958                    .arg(d)
20959                    .arg(&scale)
20960                    .arg(&nsp)
20961                    .arg(&ski)
20962                    .arg(&ktb)
20963                    .arg(&vtb);
20964                unsafe {
20965                    b.launch(cfg)?;
20966                }
20967            }
20968            None => {
20969                let null: u64 = 0;
20970                b.arg(q)
20971                    .arg(k)
20972                    .arg(v)
20973                    .arg(&mut *part_o)
20974                    .arg(&mut *part_m)
20975                    .arg(&mut *part_l)
20976                    .arg(&hd)
20977                    .arg(&nh)
20978                    .arg(&nhkv)
20979                    .arg(&tkvi)
20980                    .arg(&null)
20981                    .arg(&scale)
20982                    .arg(&nsp)
20983                    .arg(&ski)
20984                    .arg(&ktb)
20985                    .arg(&vtb);
20986                unsafe {
20987                    b.launch(cfg)?;
20988                }
20989            }
20990        }
20991        let cfg2 = LaunchConfig {
20992            grid_dim: (n_head as u32, 1, 1),
20993            block_dim: (head_dim as u32, 1, 1),
20994            shared_mem_bytes: 0,
20995        };
20996        if let Some((oq, od)) = q8_out {
20997            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
20998            let fc = if g {
20999                self.func_g("fa_decode_combine_q8_1")
21000            } else {
21001                self.fa_func("fa_decode_combine_q8_1", head_dim)
21002            };
21003            let __s_b2 = self.gpu.stream();
21004            let mut b2 = __s_b2.launch_builder(&fc);
21005            b2.arg(&*part_o)
21006                .arg(&*part_m)
21007                .arg(&*part_l)
21008                .arg(oq)
21009                .arg(od)
21010                .arg(&hd)
21011                .arg(&nh)
21012                .arg(&nsp);
21013            unsafe {
21014                b2.launch(cfg2)?;
21015            }
21016            return Ok(());
21017        }
21018        let fc = if g {
21019            self.func_g("fa_decode_combine_f32")
21020        } else {
21021            self.fa_func("fa_decode_combine_f32", head_dim)
21022        };
21023        let __s_b2 = self.gpu.stream();
21024        let mut b2 = __s_b2.launch_builder(&fc);
21025        b2.arg(&*part_o)
21026            .arg(&*part_m)
21027            .arg(&*part_l)
21028            .arg(o)
21029            .arg(&hd)
21030            .arg(&nh)
21031            .arg(&nsp);
21032        unsafe {
21033            b2.launch(cfg2)?;
21034        }
21035        Ok(())
21036    }
21037
21038    pub fn fa_decode_kvmod(
21039        &self,
21040        q: &CudaSlice<f32>,
21041        k: &cudarc::driver::CudaView<u8>,
21042        v: &cudarc::driver::CudaView<u8>,
21043        o: &mut CudaSlice<f32>,
21044        head_dim: usize,
21045        n_head: usize,
21046        n_head_kv: usize,
21047        t_kv: usize,
21048        scale: f32,
21049        k_tok_bytes: usize,
21050        v_tok_bytes: usize,
21051        g: bool,
21052    ) -> Result<(), Box<dyn std::error::Error>> {
21053        let q_view = q.as_view();
21054        let mut o_view = o.as_view_mut();
21055        self.fa_decode_kvmod_view(
21056            &q_view,
21057            k,
21058            v,
21059            &mut o_view,
21060            head_dim,
21061            n_head,
21062            n_head_kv,
21063            t_kv,
21064            scale,
21065            k_tok_bytes,
21066            v_tok_bytes,
21067            g,
21068        )
21069    }
21070
21071    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
21072    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
21073    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
21074    /// per-session KV view and FA launch.
21075    #[allow(clippy::too_many_arguments)]
21076    pub fn fa_decode_kvmod_view(
21077        &self,
21078        q: &cudarc::driver::CudaView<f32>,
21079        k: &cudarc::driver::CudaView<u8>,
21080        v: &cudarc::driver::CudaView<u8>,
21081        o: &mut cudarc::driver::CudaViewMut<f32>,
21082        head_dim: usize,
21083        n_head: usize,
21084        n_head_kv: usize,
21085        t_kv: usize,
21086        scale: f32,
21087        k_tok_bytes: usize,
21088        v_tok_bytes: usize,
21089        g: bool,
21090    ) -> Result<(), Box<dyn std::error::Error>> {
21091        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
21092        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
21093        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
21094        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
21095        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
21096        //
21097        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
21098        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
21099        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
21100        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
21101        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
21102        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
21103        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
21104        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
21105        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
21106        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
21107        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
21108        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
21109        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
21110        // fall to the exact scalar there instead of the broken register arm.
21111        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
21112        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
21113        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
21114        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
21115        if g && head_dim == 256 && !fa_v4_at(t_kv) {
21116            fa_vec = false;
21117        }
21118        let sp = fa_split_keys(t_kv, n_head_kv);
21119        let n_splits = if fa_vec {
21120            ((t_kv + sp - 1) / sp).max(1)
21121        } else {
21122            ((t_kv + 255) / 256).max(1)
21123        };
21124        let o_len = n_head * n_splits * head_dim;
21125        let ml_len = n_head * n_splits;
21126        let mut part_guard = self.fa_part_pool.lock().unwrap();
21127        if part_guard
21128            .as_ref()
21129            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
21130            .unwrap_or(true)
21131        {
21132            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
21133            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
21134            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
21135            // later live allocations land at those addresses, and the next graph REPLAY writes
21136            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
21137            // output corruption began the burst after the trunk's t_kv growth first realloc'd
21138            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
21139            // the baked addresses alive (single-stream: eager writes the new buffers, replays
21140            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
21141            // (total retired < final size).
21142            let old = part_guard.take();
21143            let (co, cm) = old
21144                .as_ref()
21145                .map(|pp| (pp.0.len(), pp.1.len()))
21146                .unwrap_or((0, 0));
21147            if let Some(old) = old {
21148                self.fa_part_retired.lock().unwrap().push(old);
21149            }
21150            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
21151                eprintln!(
21152                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
21153                    co, o_len, cm, ml_len
21154                );
21155            }
21156            *part_guard = Some((
21157                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
21158                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21159                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21160            ));
21161        }
21162        let pg = part_guard.as_mut().unwrap();
21163        self.gpu
21164            .stream()
21165            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
21166        self.gpu
21167            .stream()
21168            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
21169        self.gpu
21170            .stream()
21171            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
21172        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
21173        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
21174        let (hd, nh, nhkv, tkvi, nsp) = (
21175            head_dim as i32,
21176            n_head as i32,
21177            n_head_kv as i32,
21178            t_kv as i32,
21179            n_splits as i32,
21180        );
21181        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
21182        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
21183        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
21184        // silently truncating the accumulator.
21185        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
21186        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
21187        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
21188        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
21189        // 178.4 -> 173.7 when 512 rode vec unconditionally).
21190        let fa512_min = fa512_min_tkv();
21191        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
21192        // g-module keeps the v4 pick (its class is not the depth-decay class).
21193        let deep = fa_vec
21194            && head_dim == 256
21195            && fa_v4_at(t_kv)
21196            && !g
21197            && fa_deep_at(t_kv)
21198            && !matches!(fa_v4_mode(), "noB3" | "stage");
21199        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
21200            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
21201            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
21202            let gqa = (n_head / n_head_kv).max(1) as u32;
21203            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
21204            (
21205                fv,
21206                LaunchConfig {
21207                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21208                    block_dim: (32, gqa, 1),
21209                    shared_mem_bytes: 0,
21210                },
21211            )
21212        } else if fa_vec && head_dim <= 256 {
21213            let gqa = (n_head / n_head_kv).max(1) as u32;
21214            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
21215            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
21216            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
21217            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
21218            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
21219            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
21220            // dequant each tile ONCE per block.
21221            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
21222            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
21223            // there by 12x — latency, not bandwidth, rules small KV).
21224            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
21225            let smem_tkv = *SMEM_TKV.get_or_init(|| {
21226                std::env::var("MEMRA_FA_SMEM_TKV")
21227                    .ok()
21228                    .and_then(|v| v.parse().ok())
21229                    .unwrap_or_else(|| {
21230                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
21231                    })
21232            });
21233            if fa_v4_at(t_kv) && head_dim == 256 {
21234                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
21235                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
21236                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
21237                let v4name = match fa_v4_mode() {
21238                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
21239                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
21240                    _ if deep => "fa_decode_vec_q_v4_deep",
21241                    _ => "fa_decode_vec_q_v4",
21242                };
21243                let fv = if g {
21244                    self.func_g(v4name)
21245                } else {
21246                    self.func(v4name)
21247                };
21248                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
21249                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
21250                let shmem = (if deep { 12160 } else { 11520 }
21251                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
21252                use cudarc::driver::sys::CUfunction_attribute_enum as A;
21253                fv.set_attribute(
21254                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
21255                    shmem as i32,
21256                )?;
21257                (
21258                    fv,
21259                    LaunchConfig {
21260                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21261                        block_dim: (32, gqa, 1),
21262                        shared_mem_bytes: shmem,
21263                    },
21264                )
21265            } else if fa_v3_active(head_dim) {
21266                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
21267                // smem = sV only (half of v2's).
21268                let fv = if g {
21269                    self.func_g("fa_decode_vec_q_v3")
21270                } else {
21271                    self.func("fa_decode_vec_q_v3")
21272                };
21273                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
21274                (
21275                    fv,
21276                    LaunchConfig {
21277                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21278                        block_dim: (32, gqa, 1),
21279                        shared_mem_bytes: shmem,
21280                    },
21281                )
21282            } else if fa_v2_on() {
21283                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
21284                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
21285                // partials; same 32KB sK+sV tile as the smem twin.
21286                let fv = if g {
21287                    self.func_g("fa_decode_vec_q_v2")
21288                } else {
21289                    self.func("fa_decode_vec_q_v2")
21290                };
21291                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
21292                (
21293                    fv,
21294                    LaunchConfig {
21295                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21296                        block_dim: (32, gqa, 1),
21297                        shared_mem_bytes: shmem,
21298                    },
21299                )
21300            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
21301            {
21302                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
21303                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
21304                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
21305                let fv = if g {
21306                    self.func_g("fa_decode_vec_q_smem")
21307                } else {
21308                    self.func("fa_decode_vec_q_smem")
21309                };
21310                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
21311                use cudarc::driver::sys::CUfunction_attribute_enum as A;
21312                fv.set_attribute(
21313                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
21314                    shmem as i32,
21315                )?;
21316                (
21317                    fv,
21318                    LaunchConfig {
21319                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21320                        block_dim: (32, gqa, 1),
21321                        shared_mem_bytes: shmem,
21322                    },
21323                )
21324            } else {
21325                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
21326                // dequant, zero dynamic shared memory.
21327                let fv = if g {
21328                    self.func_g("fa_decode_vec_q")
21329                } else {
21330                    self.func("fa_decode_vec_q")
21331                };
21332                (
21333                    fv,
21334                    LaunchConfig {
21335                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
21336                        block_dim: (32, gqa, 1),
21337                        shared_mem_bytes: 0,
21338                    },
21339                )
21340            }
21341        } else {
21342            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
21343            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
21344            return self.fa_decode_scalar_unified(
21345                q,
21346                k,
21347                v,
21348                o,
21349                head_dim,
21350                n_head,
21351                n_head_kv,
21352                t_kv,
21353                None,
21354                scale,
21355                n_splits,
21356                if fa_vec { sp } else { 256 },
21357                k_tok_bytes,
21358                v_tok_bytes,
21359                g,
21360                part_o,
21361                part_m,
21362                part_l,
21363                None,
21364            );
21365        };
21366        let __s_b = self.gpu.stream();
21367        let mut b = __s_b.launch_builder(&f);
21368        b.arg(q)
21369            .arg(k)
21370            .arg(v)
21371            .arg(&mut *part_o)
21372            .arg(&mut *part_m)
21373            .arg(&mut *part_l)
21374            .arg(&hd)
21375            .arg(&nh)
21376            .arg(&nhkv)
21377            .arg(&tkvi)
21378            .arg(&scale)
21379            .arg(&nsp)
21380            .arg(&ktb)
21381            .arg(&vtb);
21382        unsafe {
21383            b.launch(cfg)?;
21384        }
21385        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
21386        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
21387        let (fc, cfg2) = (
21388            if g {
21389                self.func_g("fa_decode_combine_f32")
21390            } else {
21391                self.fa_func("fa_decode_combine_f32", head_dim)
21392            },
21393            LaunchConfig {
21394                grid_dim: (n_head as u32, 1, 1),
21395                block_dim: (head_dim as u32, 1, 1),
21396                shared_mem_bytes: 0,
21397            },
21398        );
21399        let __s_b2 = self.gpu.stream();
21400        let mut b2 = __s_b2.launch_builder(&fc);
21401        b2.arg(&*part_o)
21402            .arg(&*part_m)
21403            .arg(&*part_l)
21404            .arg(o)
21405            .arg(&hd)
21406            .arg(&nh)
21407            .arg(&nsp);
21408        unsafe {
21409            b2.launch(cfg2)?;
21410        }
21411        Ok(())
21412    }
21413
21414    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
21415    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
21416    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
21417    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
21418    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
21419    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
21420    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
21421    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
21422    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
21423    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
21424    #[allow(clippy::too_many_arguments)]
21425    pub fn fa_decode_batch_seqs_v4(
21426        &self,
21427        q: &CudaSlice<f32>,
21428        kv_ptrs: &cudarc::driver::CudaView<u64>,
21429        pos_seq: &CudaSlice<i32>,
21430        o: &mut CudaSlice<f32>,
21431        head_dim: usize,
21432        n_head: usize,
21433        n_head_kv: usize,
21434        b_n: usize,
21435        t_kv_max: usize,
21436        scale: f32,
21437        split_keys: usize,
21438        k_tok_bytes: usize,
21439        v_tok_bytes: usize,
21440    ) -> Result<(), Box<dyn std::error::Error>> {
21441        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
21442        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
21443        let o_len = b_n * n_head * n_splits_max * head_dim;
21444        let ml_len = b_n * n_head * n_splits_max;
21445        let mut part_guard = self.fa_part_pool.lock().unwrap();
21446        if part_guard
21447            .as_ref()
21448            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
21449            .unwrap_or(true)
21450        {
21451            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
21452            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
21453            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
21454            // later live allocations land at those addresses, and the next graph REPLAY writes
21455            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
21456            // output corruption began the burst after the trunk's t_kv growth first realloc'd
21457            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
21458            // the baked addresses alive (single-stream: eager writes the new buffers, replays
21459            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
21460            // (total retired < final size).
21461            let old = part_guard.take();
21462            let (co, cm) = old
21463                .as_ref()
21464                .map(|pp| (pp.0.len(), pp.1.len()))
21465                .unwrap_or((0, 0));
21466            if let Some(old) = old {
21467                self.fa_part_retired.lock().unwrap().push(old);
21468            }
21469            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
21470                eprintln!(
21471                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
21472                    co, o_len, cm, ml_len
21473                );
21474            }
21475            *part_guard = Some((
21476                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
21477                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21478                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21479            ));
21480        }
21481        let pg = part_guard.as_mut().unwrap();
21482        self.gpu
21483            .stream()
21484            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
21485        self.gpu
21486            .stream()
21487            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
21488        self.gpu
21489            .stream()
21490            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
21491        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
21492        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
21493        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
21494        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
21495        let gqa = (n_head / n_head_kv).max(1) as u32;
21496        let f = self.func("fa_decode_vec_q_seqs_v4");
21497        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
21498        let shmem = (11520 + 32 * head_dim * 2) as u32;
21499        use cudarc::driver::sys::CUfunction_attribute_enum as A;
21500        f.set_attribute(
21501            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
21502            shmem as i32,
21503        )?;
21504        let cfg = LaunchConfig {
21505            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
21506            block_dim: (32, gqa, 1),
21507            shared_mem_bytes: shmem,
21508        };
21509        {
21510            let __s_b = self.gpu.stream();
21511            let mut b = __s_b.launch_builder(&f);
21512            b.arg(q)
21513                .arg(kv_ptrs)
21514                .arg(pos_seq)
21515                .arg(&mut *part_o)
21516                .arg(&mut *part_m)
21517                .arg(&mut *part_l)
21518                .arg(&hd)
21519                .arg(&nh)
21520                .arg(&nhkv)
21521                .arg(&scale)
21522                .arg(&nspm)
21523                .arg(&spk)
21524                .arg(&ktb)
21525                .arg(&vtb);
21526            unsafe {
21527                b.launch(cfg)?;
21528            }
21529        }
21530        let fc = self.func("fa_decode_combine_seqs");
21531        let cfg2 = LaunchConfig {
21532            grid_dim: (n_head as u32, b_n as u32, 1),
21533            block_dim: (head_dim as u32, 1, 1),
21534            shared_mem_bytes: 0,
21535        };
21536        let __s_b2 = self.gpu.stream();
21537        let mut b2 = __s_b2.launch_builder(&fc);
21538        b2.arg(&*part_o)
21539            .arg(&*part_m)
21540            .arg(&*part_l)
21541            .arg(o)
21542            .arg(&hd)
21543            .arg(&nh)
21544            .arg(pos_seq)
21545            .arg(&nspm)
21546            .arg(&spk);
21547        unsafe {
21548            b2.launch(cfg2)?;
21549        }
21550        Ok(())
21551    }
21552
21553    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
21554    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
21555    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
21556    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
21557    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
21558    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
21559    #[allow(clippy::too_many_arguments)]
21560    pub fn append_kv_quantized_seqs(
21561        &self,
21562        k_rows: &CudaSlice<f32>,
21563        v_rows: &CudaSlice<f32>,
21564        kv_ptrs: &cudarc::driver::CudaView<u64>,
21565        pos_seq: &CudaSlice<i32>,
21566        b_n: usize,
21567        kv_dim_k: usize,
21568        kv_dim_v: usize,
21569        k_tok_bytes: usize,
21570        v_tok_bytes: usize,
21571    ) -> Result<(), Box<dyn std::error::Error>> {
21572        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
21573        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
21574        let cfg = LaunchConfig {
21575            grid_dim: (nblk, b_n as u32, 1),
21576            block_dim: (32, 1, 1),
21577            shared_mem_bytes: 0,
21578        };
21579        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
21580        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
21581        let __s_b = self.gpu.stream();
21582        let mut b = __s_b.launch_builder(&f);
21583        b.arg(k_rows)
21584            .arg(v_rows)
21585            .arg(kv_ptrs)
21586            .arg(pos_seq)
21587            .arg(&kdk)
21588            .arg(&kdv)
21589            .arg(&ktb)
21590            .arg(&vtb);
21591        unsafe {
21592            b.launch(cfg)?;
21593        }
21594        Ok(())
21595    }
21596
21597    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
21598    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
21599    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
21600    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
21601    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
21602    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
21603        std::env::var("MEMRA_NO_FA_VEC").is_err()
21604            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
21605            && base_len + 1 >= fa_vec_min_tkv()
21606            && head_dim <= 256
21607            && head_dim % 32 == 0
21608    }
21609
21610    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
21611    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
21612    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
21613    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
21614    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
21615    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
21616    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
21617    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
21618    #[allow(clippy::too_many_arguments)]
21619    pub fn fa_decode_rows(
21620        &self,
21621        q: &CudaSlice<f32>,
21622        k: &cudarc::driver::CudaView<u8>,
21623        v: &cudarc::driver::CudaView<u8>,
21624        o: &mut CudaSlice<f32>,
21625        head_dim: usize,
21626        n_head: usize,
21627        n_head_kv: usize,
21628        base_len: usize,
21629        t: usize,
21630        scale: f32,
21631        k_tok_bytes: usize,
21632        v_tok_bytes: usize,
21633        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
21634        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
21635        // keep the host arg. None is a bug for hd512 (asserted below).
21636        base_dev: Option<(&CudaSlice<i32>, i32)>,
21637        // K and V planes hold the same values (gemma globals, wv:=wk): pick
21638        // the _kv twin — V plane never read, value rides the q8_0 key dq.
21639        kv_shared: bool,
21640        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
21641        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
21642        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
21643        g: bool,
21644        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
21645        // (hd512 path) — the standalone quantize launch folds away.
21646        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
21647    ) -> Result<(), Box<dyn std::error::Error>> {
21648        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
21649        let t_kv_max = base_len + t; // LAST row's key bound
21650        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
21651        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
21652        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
21653        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
21654        // (parity law), so the partition is freely tunable — verify and decode move together.
21655        if head_dim == 512 {
21656            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
21657            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
21658            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
21659            let v = *SP512.get_or_init(|| {
21660                std::env::var("MEMRA_FA_SP512")
21661                    .ok()
21662                    .and_then(|x| x.parse().ok())
21663                    .unwrap_or(0)
21664            });
21665            sp = if v >= 8 {
21666                v
21667            } else {
21668                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
21669            };
21670        }
21671        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
21672        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
21673        let gqa = (n_head / n_head_kv).max(1) as u32;
21674        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, rtx6000-proven): one sp for every row
21675        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
21676        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
21677        // the different partition changes the combine's FP order (greedy tie flips at depth;
21678        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact rtx6000 failing config). Fix: group
21679        // consecutive rows by their OWN ladder value and launch once per group — each row then
21680        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
21681        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
21682        // sp override is t_kv-independent by construction).
21683        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
21684        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
21685            groups.push((0, t, sp));
21686        } else {
21687            let mut r0 = 0usize;
21688            while r0 < t {
21689                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
21690                let mut r1 = r0 + 1;
21691                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
21692                    r1 += 1;
21693                }
21694                groups.push((r0, r1 - r0, sp_g));
21695                r0 = r1;
21696            }
21697        }
21698        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
21699        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
21700        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
21701        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
21702        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
21703            std::env::var("MEMRA_FA_SMEM_TKV")
21704                .ok()
21705                .and_then(|v| v.parse().ok())
21706                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
21707        });
21708        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
21709        let v3 = fa_v3_active(head_dim);
21710        let smem_rows =
21711            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
21712        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
21713        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
21714        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
21715        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
21716        let _ = kv_shared;
21717        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
21718        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
21719        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
21720        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
21721        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
21722        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
21723        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
21724        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
21725        // (kv_head, split) stages its tile once and loops the rows over it — kills the
21726        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
21727        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
21728        // shared by every hd512 caller through this wrapper (decode+verify flip together;
21729        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
21730        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
21731        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
21732        // not unpack-bound; jsonl 2026-07-14.
21733        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
21734        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
21735        let tb512 = head_dim == 512
21736            && sp <= 32
21737            && n_head / n_head_kv.max(1) <= 16
21738            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
21739        let fname = if tb512 {
21740            "fa_decode_vec_q_rows_v4_512_tb"
21741        } else if i2 {
21742            "fa_decode_vec_q_rows_dpl16_i2"
21743        } else if head_dim == 512 {
21744            "fa_decode_vec_q_rows_dpl16"
21745        }
21746        // gemma globals (parity law)
21747        else if v4 {
21748            "fa_decode_vec_q_rows_v4"
21749        } else if v3 {
21750            "fa_decode_vec_q_rows_v3"
21751        } else if fa_v2_on() {
21752            "fa_decode_vec_q_rows_v2"
21753        } else if smem_rows {
21754            "fa_decode_vec_q_rows_smem"
21755        } else {
21756            "fa_decode_vec_q_rows"
21757        };
21758        let f = if head_dim == 512 {
21759            self.fa_func(fname, head_dim)
21760        } else if g {
21761            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
21762            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
21763            // g-module rows against decode's g-module v4 — different programs, short-VG
21764            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
21765            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
21766            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
21767            // dq macros are format-aware.
21768            self.func_g(if smem_rows {
21769                "fa_decode_vec_q_rows"
21770            } else {
21771                fname
21772            })
21773        } else {
21774            self.func(fname)
21775        };
21776        let shmem = if tb512 {
21777            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
21778            let gk = Self::gkv_on();
21779            let sh =
21780                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
21781            use cudarc::driver::sys::CUfunction_attribute_enum as A;
21782            f.set_attribute(
21783                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
21784                sh as i32,
21785            )?;
21786            sh
21787        } else if v4 || v3 || smem_rows || fa_v2_on() {
21788            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
21789            let sh = (if v4 {
21790                11520 + 32 * head_dim * if g { 1 } else { 2 }
21791            } else if v3 {
21792                32 * head_dim * 2
21793            } else {
21794                2 * 32 * head_dim * 2
21795            }) as u32;
21796            use cudarc::driver::sys::CUfunction_attribute_enum as A;
21797            f.set_attribute(
21798                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
21799                sh as i32,
21800            )?;
21801            sh
21802        } else {
21803            0
21804        };
21805        // Per-GROUP launches (single group in the common case — identical to the pre-fix
21806        // single launch there): each group gets its own partials (the rows kernel indexes
21807        // partials by its LOCAL grid.z row) and q/o row-offset views.
21808        for &(r0, t_g, sp_g) in &groups {
21809            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
21810            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
21811            let base_i = (base_len + r0) as i32;
21812            let o_len = t_g * n_head * n_splits_g * head_dim;
21813            let ml_len = t_g * n_head * n_splits_g;
21814            let mut part_guard = self.fa_part_pool.lock().unwrap();
21815            if part_guard
21816                .as_ref()
21817                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
21818                .unwrap_or(true)
21819            {
21820                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
21821                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
21822                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
21823                // later live allocations land at those addresses, and the next graph REPLAY writes
21824                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
21825                // output corruption began the burst after the trunk's t_kv growth first realloc'd
21826                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
21827                // the baked addresses alive (single-stream: eager writes the new buffers, replays
21828                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
21829                // (total retired < final size).
21830                let old = part_guard.take();
21831                let (co, cm) = old
21832                    .as_ref()
21833                    .map(|pp| (pp.0.len(), pp.1.len()))
21834                    .unwrap_or((0, 0));
21835                if let Some(old) = old {
21836                    self.fa_part_retired.lock().unwrap().push(old);
21837                }
21838                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
21839                    eprintln!(
21840                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
21841                        co, o_len, cm, ml_len
21842                    );
21843                }
21844                *part_guard = Some((
21845                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
21846                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21847                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
21848                ));
21849            }
21850            let pg = part_guard.as_mut().unwrap();
21851            self.gpu
21852                .stream()
21853                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
21854            self.gpu
21855                .stream()
21856                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
21857            self.gpu
21858                .stream()
21859                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
21860            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
21861            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
21862            let qv = self.view(q, t * n_head * head_dim);
21863            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
21864            let cfg = LaunchConfig {
21865                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
21866                block_dim: (32, gqa, 1),
21867                shared_mem_bytes: shmem,
21868            };
21869            {
21870                let __s_b = self.gpu.stream();
21871                let mut b = __s_b.launch_builder(&f);
21872                if tb512 {
21873                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
21874                    let (bd, plus) =
21875                        base_dev.expect("hd512 rows twin requires a device base counter");
21876                    let plus_g = plus + r0 as i32;
21877                    let nr = t_g as i32;
21878                    if Self::pdl_on() && Self::pdl_wb_on() {
21879                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
21880                        use cudarc::driver::{DevicePtr, DevicePtrMut};
21881                        let s = &self.gpu.stream();
21882                        let (pq, _b0) = q_g.device_ptr(s);
21883                        let (pk, _b1) = k.device_ptr(s);
21884                        let (pv, _b2) = v.device_ptr(s);
21885                        let (po, _b3) = part_o.device_ptr_mut(s);
21886                        let (pm, _b4) = part_m.device_ptr_mut(s);
21887                        let (pl, _b5) = part_l.device_ptr_mut(s);
21888                        let (pb, _b6) = bd.device_ptr(s);
21889                        let mut ps = [
21890                            &pq as *const _ as *mut std::ffi::c_void,
21891                            &pk as *const _ as *mut _,
21892                            &pv as *const _ as *mut _,
21893                            &po as *const _ as *mut _,
21894                            &pm as *const _ as *mut _,
21895                            &pl as *const _ as *mut _,
21896                            &hd as *const _ as *mut _,
21897                            &nh as *const _ as *mut _,
21898                            &nhkv as *const _ as *mut _,
21899                            &pb as *const _ as *mut _,
21900                            &plus_g as *const _ as *mut _,
21901                            &scale as *const _ as *mut _,
21902                            &nspm as *const _ as *mut _,
21903                            &spk as *const _ as *mut _,
21904                            &ktb as *const _ as *mut _,
21905                            &vtb as *const _ as *mut _,
21906                            &nr as *const _ as *mut _,
21907                        ];
21908                        unsafe {
21909                            self.launch_pdl_flash(
21910                                Self::gkv_on(),
21911                                "fa_decode_vec_q_rows_v4_512_tb",
21912                                (n_head_kv as u32, n_splits_g as u32, 1),
21913                                (32, gqa, 1),
21914                                shmem,
21915                                &mut ps,
21916                            )?;
21917                        }
21918                    } else {
21919                        let cfg_tb = LaunchConfig {
21920                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
21921                            block_dim: (32, gqa, 1),
21922                            shared_mem_bytes: shmem,
21923                        };
21924                        b.arg(&q_g)
21925                            .arg(k)
21926                            .arg(v)
21927                            .arg(&mut *part_o)
21928                            .arg(&mut *part_m)
21929                            .arg(&mut *part_l)
21930                            .arg(&hd)
21931                            .arg(&nh)
21932                            .arg(&nhkv)
21933                            .arg(bd)
21934                            .arg(&plus_g)
21935                            .arg(&scale)
21936                            .arg(&nspm)
21937                            .arg(&spk)
21938                            .arg(&ktb)
21939                            .arg(&vtb)
21940                            .arg(&nr);
21941                        unsafe {
21942                            b.launch(cfg_tb)?;
21943                        }
21944                    }
21945                } else if head_dim == 512 {
21946                    let (bd, plus) =
21947                        base_dev.expect("hd512 rows twin requires a device base counter");
21948                    let plus_g = plus + r0 as i32;
21949                    b.arg(&q_g)
21950                        .arg(k)
21951                        .arg(v)
21952                        .arg(&mut *part_o)
21953                        .arg(&mut *part_m)
21954                        .arg(&mut *part_l)
21955                        .arg(&hd)
21956                        .arg(&nh)
21957                        .arg(&nhkv)
21958                        .arg(bd)
21959                        .arg(&plus_g)
21960                        .arg(&scale)
21961                        .arg(&nspm)
21962                        .arg(&spk)
21963                        .arg(&ktb)
21964                        .arg(&vtb);
21965                    unsafe {
21966                        b.launch(cfg)?;
21967                    }
21968                } else {
21969                    b.arg(&q_g)
21970                        .arg(k)
21971                        .arg(v)
21972                        .arg(&mut *part_o)
21973                        .arg(&mut *part_m)
21974                        .arg(&mut *part_l)
21975                        .arg(&hd)
21976                        .arg(&nh)
21977                        .arg(&nhkv)
21978                        .arg(&base_i)
21979                        .arg(&scale)
21980                        .arg(&nspm)
21981                        .arg(&spk)
21982                        .arg(&ktb)
21983                        .arg(&vtb);
21984                    unsafe {
21985                        b.launch(cfg)?;
21986                    }
21987                }
21988            }
21989            let cfg2 = LaunchConfig {
21990                grid_dim: (n_head as u32, t_g as u32, 1),
21991                block_dim: (head_dim as u32, 1, 1),
21992                shared_mem_bytes: 0,
21993            };
21994            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
21995            if head_dim == 512 {
21996                // device-len combine (shared by verify/eager/graph — parity by symbol): the
21997                // per-row n_splits derives from the SAME counter the rows kernel read.
21998                let (bd, plus) = base_dev.unwrap();
21999                let plus_g = plus + r0 as i32;
22000                if let Some((oq, od)) = q8_out.as_mut() {
22001                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
22002                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
22003                    if Self::pdl_on() && Self::pdl_wb_on() {
22004                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
22005                        use cudarc::driver::{DevicePtr, DevicePtrMut};
22006                        let s = &self.gpu.stream();
22007                        let (po, _g0) = part_o.device_ptr(s);
22008                        let (pm, _g1) = part_m.device_ptr(s);
22009                        let (pl, _g2) = part_l.device_ptr(s);
22010                        let (pq, _g3) = oq.device_ptr_mut(s);
22011                        let (pd, _g4) = od.device_ptr_mut(s);
22012                        let (pb, _g5) = bd.device_ptr(s);
22013                        let mut ps = [
22014                            &po as *const _ as *mut std::ffi::c_void,
22015                            &pm as *const _ as *mut _,
22016                            &pl as *const _ as *mut _,
22017                            &pq as *const _ as *mut _,
22018                            &pd as *const _ as *mut _,
22019                            &hd as *const _ as *mut _,
22020                            &nh as *const _ as *mut _,
22021                            &pb as *const _ as *mut _,
22022                            &plus_g as *const _ as *mut _,
22023                            &nspm as *const _ as *mut _,
22024                            &spk as *const _ as *mut _,
22025                        ];
22026                        unsafe {
22027                            self.launch_pdl_flash(
22028                                Self::gkv_on(),
22029                                "fa_decode_combine_rows_dc_q8_1",
22030                                cfg2.grid_dim,
22031                                cfg2.block_dim,
22032                                0,
22033                                &mut ps,
22034                            )?;
22035                        }
22036                        continue;
22037                    }
22038                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
22039                    let __s_b2 = self.gpu.stream();
22040                    let mut b2 = __s_b2.launch_builder(&fc);
22041                    b2.arg(&*part_o)
22042                        .arg(&*part_m)
22043                        .arg(&*part_l)
22044                        .arg(&mut **oq)
22045                        .arg(&mut **od)
22046                        .arg(&hd)
22047                        .arg(&nh)
22048                        .arg(bd)
22049                        .arg(&plus_g)
22050                        .arg(&nspm)
22051                        .arg(&spk);
22052                    unsafe {
22053                        b2.launch(cfg2)?;
22054                    }
22055                    continue;
22056                }
22057                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
22058                let __s_b2 = self.gpu.stream();
22059                let mut b2 = __s_b2.launch_builder(&fc);
22060                b2.arg(&*part_o)
22061                    .arg(&*part_m)
22062                    .arg(&*part_l)
22063                    .arg(&mut o_g)
22064                    .arg(&hd)
22065                    .arg(&nh)
22066                    .arg(bd)
22067                    .arg(&plus_g)
22068                    .arg(&nspm)
22069                    .arg(&spk);
22070                unsafe {
22071                    b2.launch(cfg2)?;
22072                }
22073            } else {
22074                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
22075                // leave the caller's pair unwritten (consumer would read garbage).
22076                assert!(
22077                    q8_out.is_none(),
22078                    "rows q8 emit requires the hd512 dc combine"
22079                );
22080                let fc = self.func("fa_decode_combine_rows");
22081                let __s_b2 = self.gpu.stream();
22082                let mut b2 = __s_b2.launch_builder(&fc);
22083                b2.arg(&*part_o)
22084                    .arg(&*part_m)
22085                    .arg(&*part_l)
22086                    .arg(&mut o_g)
22087                    .arg(&hd)
22088                    .arg(&nh)
22089                    .arg(&base_i)
22090                    .arg(&nspm)
22091                    .arg(&spk);
22092                unsafe {
22093                    b2.launch(cfg2)?;
22094                }
22095            }
22096        }
22097        Ok(())
22098    }
22099
22100    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
22101    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
22102    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
22103    #[allow(clippy::too_many_arguments)]
22104    pub fn fa_decode_rows_w(
22105        &self,
22106        q: &CudaSlice<f32>,
22107        k: &cudarc::driver::CudaView<u8>,
22108        v: &cudarc::driver::CudaView<u8>,
22109        o: &mut CudaSlice<f32>,
22110        head_dim: usize,
22111        n_head: usize,
22112        n_head_kv: usize,
22113        base_dev: &CudaSlice<i32>,
22114        base_plus: i32,
22115        t: usize,
22116        scale: f32,
22117        window: usize,
22118        k_tok_bytes: usize,
22119        v_tok_bytes: usize,
22120        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
22121    ) -> Result<(), Box<dyn std::error::Error>> {
22122        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
22123        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
22124        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
22125        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
22126        debug_assert!(head_dim == 256);
22127        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
22128        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
22129        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
22130        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
22131        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
22132        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
22133        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
22134        let sp = {
22135            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
22136            let v = *SPW.get_or_init(|| {
22137                std::env::var("MEMRA_FA_SPW")
22138                    .ok()
22139                    .and_then(|x| x.parse().ok())
22140                    .unwrap_or(0)
22141            });
22142            if v >= 8 {
22143                v
22144            } else {
22145                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
22146            }
22147        };
22148        let n_splits_max = (window + sp - 1) / sp;
22149        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
22150        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
22151        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
22152        let gqa = (n_head / n_head_kv).max(1) as u32;
22153        let o_len = t * n_head * n_splits_max * head_dim;
22154        let ml_len = t * n_head * n_splits_max;
22155        let mut part_guard = self.fa_part_pool.lock().unwrap();
22156        if part_guard
22157            .as_ref()
22158            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
22159            .unwrap_or(true)
22160        {
22161            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
22162            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
22163            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
22164            // later live allocations land at those addresses, and the next graph REPLAY writes
22165            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
22166            // output corruption began the burst after the trunk's t_kv growth first realloc'd
22167            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
22168            // the baked addresses alive (single-stream: eager writes the new buffers, replays
22169            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
22170            // (total retired < final size).
22171            let old = part_guard.take();
22172            let (co, cm) = old
22173                .as_ref()
22174                .map(|pp| (pp.0.len(), pp.1.len()))
22175                .unwrap_or((0, 0));
22176            if let Some(old) = old {
22177                self.fa_part_retired.lock().unwrap().push(old);
22178            }
22179            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
22180                eprintln!(
22181                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
22182                    co, o_len, cm, ml_len
22183                );
22184            }
22185            *part_guard = Some((
22186                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
22187                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22188                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22189            ));
22190        }
22191        let pg = part_guard.as_mut().unwrap();
22192        self.gpu
22193            .stream()
22194            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
22195        self.gpu
22196            .stream()
22197            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
22198        self.gpu
22199            .stream()
22200            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
22201        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
22202        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
22203        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
22204        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
22205        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
22206        // floor (deep-ctx broadcast win); register twin between.
22207        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
22208        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
22209            std::env::var("MEMRA_FA_SMEM_TKV")
22210                .ok()
22211                .and_then(|v| v.parse().ok())
22212                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
22213        });
22214        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
22215        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
22216        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
22217        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
22218        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
22219        use cudarc::driver::sys::CUfunction_attribute_enum as A;
22220        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
22221        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
22222        // per (lane, format-module) keeps parity structural; the old register-i2 detour
22223        // (-33%) is retired.
22224        let wg = Self::wkv_on();
22225        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
22226        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
22227        let sp2 =
22228            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
22229        if sp2 {
22230            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
22231            if Self::pdl_on() && Self::pdl_wb_on() {
22232                // wave-B2b: flavor mirrors wg.
22233                use cudarc::driver::{DevicePtr, DevicePtrMut};
22234                let s = &self.gpu.stream();
22235                let (pq, _b0) = q.device_ptr(s);
22236                let (pk, _b1) = k.device_ptr(s);
22237                let (pv, _b2) = v.device_ptr(s);
22238                let (po, _b3) = part_o.device_ptr_mut(s);
22239                let (pm, _b4) = part_m.device_ptr_mut(s);
22240                let (pl, _b5) = part_l.device_ptr_mut(s);
22241                let (pb, _b6) = base_dev.device_ptr(s);
22242                let mut ps = [
22243                    &pq as *const _ as *mut std::ffi::c_void,
22244                    &pk as *const _ as *mut _,
22245                    &pv as *const _ as *mut _,
22246                    &po as *const _ as *mut _,
22247                    &pm as *const _ as *mut _,
22248                    &pl as *const _ as *mut _,
22249                    &hd as *const _ as *mut _,
22250                    &nh as *const _ as *mut _,
22251                    &nhkv as *const _ as *mut _,
22252                    &pb as *const _ as *mut _,
22253                    &base_plus as *const _ as *mut _,
22254                    &scale as *const _ as *mut _,
22255                    &nspm as *const _ as *mut _,
22256                    &spk as *const _ as *mut _,
22257                    &ktb as *const _ as *mut _,
22258                    &vtb as *const _ as *mut _,
22259                    &wini as *const _ as *mut _,
22260                ];
22261                unsafe {
22262                    self.launch_pdl_flash(
22263                        wg,
22264                        "fa_decode_vec_q_rows_v4_w_sp",
22265                        (n_head_kv as u32, n_splits_max as u32, t as u32),
22266                        (32, gqa + 1, 1),
22267                        sh,
22268                        &mut ps,
22269                    )?;
22270                }
22271            } else {
22272                let f = if wg {
22273                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
22274                } else {
22275                    self.func("fa_decode_vec_q_rows_v4_w_sp")
22276                };
22277                f.set_attribute(
22278                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
22279                    sh as i32,
22280                )?;
22281                let cfg = LaunchConfig {
22282                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
22283                    block_dim: (32, gqa + 1, 1),
22284                    shared_mem_bytes: sh,
22285                };
22286                let __s_b = self.gpu.stream();
22287                let mut b = __s_b.launch_builder(&f);
22288                b.arg(q)
22289                    .arg(k)
22290                    .arg(v)
22291                    .arg(&mut *part_o)
22292                    .arg(&mut *part_m)
22293                    .arg(&mut *part_l)
22294                    .arg(&hd)
22295                    .arg(&nh)
22296                    .arg(&nhkv)
22297                    .arg(base_dev)
22298                    .arg(&base_plus)
22299                    .arg(&scale)
22300                    .arg(&nspm)
22301                    .arg(&spk)
22302                    .arg(&ktb)
22303                    .arg(&vtb)
22304                    .arg(&wini);
22305                unsafe {
22306                    b.launch(cfg)?;
22307                }
22308            }
22309        } else {
22310            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
22311                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
22312                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
22313                use cudarc::driver::{DevicePtr, DevicePtrMut};
22314                let s = &self.gpu.stream();
22315                let (pq, _b0) = q.device_ptr(s);
22316                let (pk, _b1) = k.device_ptr(s);
22317                let (pv, _b2) = v.device_ptr(s);
22318                let (po, _b3) = part_o.device_ptr_mut(s);
22319                let (pm, _b4) = part_m.device_ptr_mut(s);
22320                let (pl, _b5) = part_l.device_ptr_mut(s);
22321                let (pb, _b6) = base_dev.device_ptr(s);
22322                let mut ps = [
22323                    &pq as *const _ as *mut std::ffi::c_void,
22324                    &pk as *const _ as *mut _,
22325                    &pv as *const _ as *mut _,
22326                    &po as *const _ as *mut _,
22327                    &pm as *const _ as *mut _,
22328                    &pl as *const _ as *mut _,
22329                    &hd as *const _ as *mut _,
22330                    &nh as *const _ as *mut _,
22331                    &nhkv as *const _ as *mut _,
22332                    &pb as *const _ as *mut _,
22333                    &base_plus as *const _ as *mut _,
22334                    &scale as *const _ as *mut _,
22335                    &nspm as *const _ as *mut _,
22336                    &spk as *const _ as *mut _,
22337                    &ktb as *const _ as *mut _,
22338                    &vtb as *const _ as *mut _,
22339                    &wini as *const _ as *mut _,
22340                ];
22341                unsafe {
22342                    self.launch_pdl_flash(
22343                        wg,
22344                        "fa_decode_vec_q_rows_v4_w",
22345                        (n_head_kv as u32, n_splits_max as u32, t as u32),
22346                        (32, gqa, 1),
22347                        sh,
22348                        &mut ps,
22349                    )?;
22350                }
22351            } else {
22352                let pick = |name: &str| {
22353                    if wg {
22354                        self.func_g(name)
22355                    } else {
22356                        self.func(name)
22357                    }
22358                };
22359                let (f, sh) = if fa_v4_at(window) {
22360                    let f = pick("fa_decode_vec_q_rows_v4_w");
22361                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
22362                } else if smem_tkv > 0 && window >= smem_tkv {
22363                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
22364                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
22365                    (
22366                        pick("fa_decode_vec_q_rows_smem_w"),
22367                        (2 * 32 * head_dim * 2) as u32,
22368                    )
22369                } else {
22370                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
22371                };
22372                f.set_attribute(
22373                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
22374                    sh as i32,
22375                )?;
22376                let cfg = LaunchConfig {
22377                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
22378                    block_dim: (32, gqa, 1),
22379                    shared_mem_bytes: sh,
22380                };
22381                let __s_b = self.gpu.stream();
22382                let mut b = __s_b.launch_builder(&f);
22383                b.arg(q)
22384                    .arg(k)
22385                    .arg(v)
22386                    .arg(&mut *part_o)
22387                    .arg(&mut *part_m)
22388                    .arg(&mut *part_l)
22389                    .arg(&hd)
22390                    .arg(&nh)
22391                    .arg(&nhkv)
22392                    .arg(base_dev)
22393                    .arg(&base_plus)
22394                    .arg(&scale)
22395                    .arg(&nspm)
22396                    .arg(&spk)
22397                    .arg(&ktb)
22398                    .arg(&vtb)
22399                    .arg(&wini);
22400                unsafe {
22401                    b.launch(cfg)?;
22402                }
22403            }
22404        }
22405        let cfg2 = LaunchConfig {
22406            grid_dim: (n_head as u32, t as u32, 1),
22407            block_dim: (head_dim as u32, 1, 1),
22408            shared_mem_bytes: 0,
22409        };
22410        if let Some((oq, od)) = q8_out {
22411            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
22412            // consumes the pair directly; the standalone quantize launch folds away.
22413            if Self::pdl_on() && Self::pdl_wb_on() {
22414                // wave-B2: flavor mirrors the builder's wg choice.
22415                use cudarc::driver::{DevicePtr, DevicePtrMut};
22416                let s = &self.gpu.stream();
22417                let (po, _g0) = part_o.device_ptr(s);
22418                let (pm, _g1) = part_m.device_ptr(s);
22419                let (pl, _g2) = part_l.device_ptr(s);
22420                let (pq, _g3) = oq.device_ptr_mut(s);
22421                let (pd, _g4) = od.device_ptr_mut(s);
22422                let mut ps = [
22423                    &po as *const _ as *mut std::ffi::c_void,
22424                    &pm as *const _ as *mut _,
22425                    &pl as *const _ as *mut _,
22426                    &pq as *const _ as *mut _,
22427                    &pd as *const _ as *mut _,
22428                    &hd as *const _ as *mut _,
22429                    &nh as *const _ as *mut _,
22430                    &nspm as *const _ as *mut _,
22431                    &spk as *const _ as *mut _,
22432                    &wini as *const _ as *mut _,
22433                ];
22434                unsafe {
22435                    self.launch_pdl_flash(
22436                        wg,
22437                        "fa_decode_combine_rows_w_q8_1",
22438                        cfg2.grid_dim,
22439                        cfg2.block_dim,
22440                        0,
22441                        &mut ps,
22442                    )?;
22443                }
22444                return Ok(());
22445            }
22446            let fc = if wg {
22447                self.func_g("fa_decode_combine_rows_w_q8_1")
22448            } else {
22449                self.func("fa_decode_combine_rows_w_q8_1")
22450            };
22451            let __s_b2 = self.gpu.stream();
22452            let mut b2 = __s_b2.launch_builder(&fc);
22453            b2.arg(&*part_o)
22454                .arg(&*part_m)
22455                .arg(&*part_l)
22456                .arg(oq)
22457                .arg(od)
22458                .arg(&hd)
22459                .arg(&nh)
22460                .arg(&nspm)
22461                .arg(&spk)
22462                .arg(&wini);
22463            unsafe {
22464                b2.launch(cfg2)?;
22465            }
22466            return Ok(());
22467        }
22468        let fc = if wg {
22469            self.func_g("fa_decode_combine_rows_w")
22470        } else {
22471            self.func("fa_decode_combine_rows_w")
22472        };
22473        let __s_b2 = self.gpu.stream();
22474        let mut b2 = __s_b2.launch_builder(&fc);
22475        b2.arg(&*part_o)
22476            .arg(&*part_m)
22477            .arg(&*part_l)
22478            .arg(o)
22479            .arg(&hd)
22480            .arg(&nh)
22481            .arg(&nspm)
22482            .arg(&spk)
22483            .arg(&wini);
22484        unsafe {
22485            b2.launch(cfg2)?;
22486        }
22487        Ok(())
22488    }
22489
22490    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
22491    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
22492    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
22493    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
22494    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
22495    #[allow(clippy::too_many_arguments)]
22496    pub fn fa_decode_rows_dc(
22497        &self,
22498        q: &CudaSlice<f32>,
22499        k: &cudarc::driver::CudaView<u8>,
22500        v: &cudarc::driver::CudaView<u8>,
22501        o: &mut CudaSlice<f32>,
22502        head_dim: usize,
22503        n_head: usize,
22504        n_head_kv: usize,
22505        base_dev: &CudaSlice<i32>,
22506        t_kv_upper: usize,
22507        t: usize,
22508        scale: f32,
22509        k_tok_bytes: usize,
22510        v_tok_bytes: usize,
22511        base_plus: i32,
22512        g: bool,
22513    ) -> Result<(), Box<dyn std::error::Error>> {
22514        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
22515        assert!(
22516            v4 || fa_v3_active(head_dim),
22517            "stream fa rows requires the v3 or v4 lane"
22518        );
22519        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
22520        if v4 {
22521            let sp = fa_split_keys(t_kv_upper, n_head_kv);
22522            let n_splits_max = (t_kv_upper + sp - 1) / sp;
22523            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
22524            let (nspm, spk) = (n_splits_max as i32, sp as i32);
22525            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
22526            let gqa = (n_head / n_head_kv).max(1) as u32;
22527            let o_len = t * n_head * n_splits_max * head_dim;
22528            let ml_len = t * n_head * n_splits_max;
22529            let mut part_guard = self.fa_part_pool.lock().unwrap();
22530            if part_guard
22531                .as_ref()
22532                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
22533                .unwrap_or(true)
22534            {
22535                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
22536                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
22537                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
22538                // later live allocations land at those addresses, and the next graph REPLAY writes
22539                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
22540                // output corruption began the burst after the trunk's t_kv growth first realloc'd
22541                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
22542                // the baked addresses alive (single-stream: eager writes the new buffers, replays
22543                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
22544                // (total retired < final size).
22545                let old = part_guard.take();
22546                let (co, cm) = old
22547                    .as_ref()
22548                    .map(|pp| (pp.0.len(), pp.1.len()))
22549                    .unwrap_or((0, 0));
22550                if let Some(old) = old {
22551                    self.fa_part_retired.lock().unwrap().push(old);
22552                }
22553                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
22554                    eprintln!(
22555                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
22556                        co, o_len, cm, ml_len
22557                    );
22558                }
22559                *part_guard = Some((
22560                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
22561                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22562                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22563                ));
22564            }
22565            let pg = part_guard.as_mut().unwrap();
22566            self.gpu
22567                .stream()
22568                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
22569            self.gpu
22570                .stream()
22571                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
22572            self.gpu
22573                .stream()
22574                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
22575            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
22576            let f = if g {
22577                self.func_g("fa_decode_vec_q_rows_v4_dc")
22578            } else {
22579                self.func("fa_decode_vec_q_rows_v4_dc")
22580            };
22581            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
22582            use cudarc::driver::sys::CUfunction_attribute_enum as A;
22583            f.set_attribute(
22584                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
22585                sh as i32,
22586            )?;
22587            let cfg = LaunchConfig {
22588                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
22589                block_dim: (32, gqa, 1),
22590                shared_mem_bytes: sh,
22591            };
22592            let __s_b = self.gpu.stream();
22593            let mut b = __s_b.launch_builder(&f);
22594            b.arg(q)
22595                .arg(k)
22596                .arg(v)
22597                .arg(&mut *part_o)
22598                .arg(&mut *part_m)
22599                .arg(&mut *part_l)
22600                .arg(&hd)
22601                .arg(&nh)
22602                .arg(&nhkv)
22603                .arg(base_dev)
22604                .arg(&base_plus)
22605                .arg(&scale)
22606                .arg(&nspm)
22607                .arg(&spk)
22608                .arg(&ktb)
22609                .arg(&vtb);
22610            unsafe {
22611                b.launch(cfg)?;
22612            }
22613            let fc = self.func("fa_decode_combine_rows_dc");
22614            let cfg2 = LaunchConfig {
22615                grid_dim: (n_head as u32, t as u32, 1),
22616                block_dim: (head_dim as u32, 1, 1),
22617                shared_mem_bytes: 0,
22618            };
22619            let __s_b2 = self.gpu.stream();
22620            let mut b2 = __s_b2.launch_builder(&fc);
22621            b2.arg(&*part_o)
22622                .arg(&*part_m)
22623                .arg(&*part_l)
22624                .arg(o)
22625                .arg(&hd)
22626                .arg(&nh)
22627                .arg(base_dev)
22628                .arg(&base_plus)
22629                .arg(&nspm)
22630                .arg(&spk);
22631            unsafe {
22632                b2.launch(cfg2)?;
22633            }
22634            return Ok(());
22635        }
22636        let sp = fa_split_keys(t_kv_upper, n_head_kv);
22637        let n_splits_max = (t_kv_upper + sp - 1) / sp;
22638        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
22639        let (nspm, spk) = (n_splits_max as i32, sp as i32);
22640        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
22641        let gqa = (n_head / n_head_kv).max(1) as u32;
22642        let o_len = t * n_head * n_splits_max * head_dim;
22643        let ml_len = t * n_head * n_splits_max;
22644        let mut part_guard = self.fa_part_pool.lock().unwrap();
22645        if part_guard
22646            .as_ref()
22647            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
22648            .unwrap_or(true)
22649        {
22650            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
22651            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
22652            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
22653            // later live allocations land at those addresses, and the next graph REPLAY writes
22654            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
22655            // output corruption began the burst after the trunk's t_kv growth first realloc'd
22656            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
22657            // the baked addresses alive (single-stream: eager writes the new buffers, replays
22658            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
22659            // (total retired < final size).
22660            let old = part_guard.take();
22661            let (co, cm) = old
22662                .as_ref()
22663                .map(|pp| (pp.0.len(), pp.1.len()))
22664                .unwrap_or((0, 0));
22665            if let Some(old) = old {
22666                self.fa_part_retired.lock().unwrap().push(old);
22667            }
22668            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
22669                eprintln!(
22670                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
22671                    co, o_len, cm, ml_len
22672                );
22673            }
22674            *part_guard = Some((
22675                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
22676                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22677                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22678            ));
22679        }
22680        let pg = part_guard.as_mut().unwrap();
22681        self.gpu
22682            .stream()
22683            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
22684        self.gpu
22685            .stream()
22686            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
22687        self.gpu
22688            .stream()
22689            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
22690        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
22691        let f = self.func("fa_decode_vec_q_rows_v3_dc");
22692        let sh = (32 * head_dim * 2) as u32;
22693        use cudarc::driver::sys::CUfunction_attribute_enum as A;
22694        f.set_attribute(
22695            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
22696            sh as i32,
22697        )?;
22698        let cfg = LaunchConfig {
22699            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
22700            block_dim: (32, gqa, 1),
22701            shared_mem_bytes: sh,
22702        };
22703        let __s_b = self.gpu.stream();
22704        let mut b = __s_b.launch_builder(&f);
22705        b.arg(q)
22706            .arg(k)
22707            .arg(v)
22708            .arg(&mut *part_o)
22709            .arg(&mut *part_m)
22710            .arg(&mut *part_l)
22711            .arg(&hd)
22712            .arg(&nh)
22713            .arg(&nhkv)
22714            .arg(base_dev)
22715            .arg(&scale)
22716            .arg(&nspm)
22717            .arg(&spk)
22718            .arg(&ktb)
22719            .arg(&vtb);
22720        unsafe {
22721            b.launch(cfg)?;
22722        }
22723        let fc = self.func("fa_decode_combine_rows_dc");
22724        let cfg2 = LaunchConfig {
22725            grid_dim: (n_head as u32, t as u32, 1),
22726            block_dim: (head_dim as u32, 1, 1),
22727            shared_mem_bytes: 0,
22728        };
22729        let plus0 = 0i32;
22730        let __s_b2 = self.gpu.stream();
22731        let mut b2 = __s_b2.launch_builder(&fc);
22732        b2.arg(&*part_o)
22733            .arg(&*part_m)
22734            .arg(&*part_l)
22735            .arg(o)
22736            .arg(&hd)
22737            .arg(&nh)
22738            .arg(base_dev)
22739            .arg(&plus0)
22740            .arg(&nspm)
22741            .arg(&spk);
22742        unsafe {
22743            b2.launch(cfg2)?;
22744        }
22745        Ok(())
22746    }
22747
22748    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
22749    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
22750    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
22751    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
22752    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
22753    ///
22754    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
22755    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
22756    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
22757    /// grouping (different but mathematically-equal log-sum-exp merge).
22758    pub fn fa_decode_dc(
22759        &self,
22760        q: &CudaSlice<f32>,
22761        k: &cudarc::driver::CudaView<u8>,
22762        v: &cudarc::driver::CudaView<u8>,
22763        o: &mut CudaSlice<f32>,
22764        head_dim: usize,
22765        n_head: usize,
22766        n_head_kv: usize,
22767        t_kv_dev: &CudaSlice<i32>,
22768        bucket_max: usize,
22769        scale: f32,
22770        k_tok_bytes: usize,
22771        v_tok_bytes: usize,
22772        g: bool,
22773    ) -> Result<(), Box<dyn std::error::Error>> {
22774        self.fa_decode_dc_q8(
22775            q,
22776            k,
22777            v,
22778            o,
22779            head_dim,
22780            n_head,
22781            n_head_kv,
22782            t_kv_dev,
22783            bucket_max,
22784            scale,
22785            k_tok_bytes,
22786            v_tok_bytes,
22787            g,
22788            None,
22789        )
22790    }
22791
22792    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
22793    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
22794    #[allow(clippy::too_many_arguments)]
22795    pub fn fa_decode_dc_q8(
22796        &self,
22797        q: &CudaSlice<f32>,
22798        k: &cudarc::driver::CudaView<u8>,
22799        v: &cudarc::driver::CudaView<u8>,
22800        o: &mut CudaSlice<f32>,
22801        head_dim: usize,
22802        n_head: usize,
22803        n_head_kv: usize,
22804        t_kv_dev: &CudaSlice<i32>,
22805        bucket_max: usize,
22806        scale: f32,
22807        k_tok_bytes: usize,
22808        v_tok_bytes: usize,
22809        g: bool,
22810        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
22811    ) -> Result<(), Box<dyn std::error::Error>> {
22812        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
22813        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
22814        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
22815        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
22816        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
22817        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
22818        // 2026-07-12).
22819        let mut fa_vec =
22820            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
22821        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
22822            fa_vec = false;
22823        } // mirror kvmod/geom
22824        let sp = fa_split_keys(bucket_max, n_head_kv);
22825        let n_splits = if fa_vec {
22826            ((bucket_max + sp - 1) / sp).max(1)
22827        } else {
22828            ((bucket_max + 255) / 256).max(1)
22829        };
22830        let o_len = n_head * n_splits * head_dim;
22831        let ml_len = n_head * n_splits;
22832        let mut part_guard = self.fa_part_pool.lock().unwrap();
22833        if part_guard
22834            .as_ref()
22835            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
22836            .unwrap_or(true)
22837        {
22838            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
22839            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
22840            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
22841            // later live allocations land at those addresses, and the next graph REPLAY writes
22842            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
22843            // output corruption began the burst after the trunk's t_kv growth first realloc'd
22844            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
22845            // the baked addresses alive (single-stream: eager writes the new buffers, replays
22846            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
22847            // (total retired < final size).
22848            let old = part_guard.take();
22849            let (co, cm) = old
22850                .as_ref()
22851                .map(|pp| (pp.0.len(), pp.1.len()))
22852                .unwrap_or((0, 0));
22853            if let Some(old) = old {
22854                self.fa_part_retired.lock().unwrap().push(old);
22855            }
22856            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
22857                eprintln!(
22858                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
22859                    co, o_len, cm, ml_len
22860                );
22861            }
22862            *part_guard = Some((
22863                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
22864                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22865                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
22866            ));
22867        }
22868        let pg = part_guard.as_mut().unwrap();
22869        self.gpu
22870            .stream()
22871            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
22872        self.gpu
22873            .stream()
22874            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
22875        self.gpu
22876            .stream()
22877            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
22878        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
22879        let (hd, nh, nhkv, nsp) = (
22880            head_dim as i32,
22881            n_head as i32,
22882            n_head_kv as i32,
22883            n_splits as i32,
22884        );
22885        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
22886        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
22887        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
22888        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
22889        let deep = fa_vec
22890            && head_dim == 256
22891            && fa_v4_at(bucket_max)
22892            && !g
22893            && fa_deep_at(bucket_max)
22894            && !matches!(fa_v4_mode(), "noB3" | "stage");
22895        let (f, cfg) = if fa_vec
22896            && head_dim == 512
22897            && bucket_max >= {
22898                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
22899                *FA512_MIN_DC.get_or_init(|| {
22900                    std::env::var("MEMRA_FA512_MIN")
22901                        .ok()
22902                        .and_then(|v| v.parse().ok())
22903                        .unwrap_or(512)
22904                })
22905            } {
22906            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
22907            let gqa = (n_head / n_head_kv).max(1) as u32;
22908            (
22909                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
22910                LaunchConfig {
22911                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
22912                    block_dim: (32, gqa, 1),
22913                    shared_mem_bytes: 0,
22914                },
22915            )
22916        } else if fa_vec && head_dim == 512 {
22917            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
22918            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
22919            let q_view = q.as_view();
22920            let mut o_view = o.as_view_mut();
22921            return self.fa_decode_scalar_unified(
22922                &q_view,
22923                k,
22924                v,
22925                &mut o_view,
22926                head_dim,
22927                n_head,
22928                n_head_kv,
22929                0,
22930                Some(t_kv_dev),
22931                scale,
22932                n_splits,
22933                sp,
22934                k_tok_bytes,
22935                v_tok_bytes,
22936                g,
22937                &mut *part_o,
22938                &mut *part_m,
22939                &mut *part_l,
22940                q8_out,
22941            );
22942        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
22943            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
22944            // incl the g-module route + raw-e4m3 sV sizing.
22945            let gqa = (n_head / n_head_kv).max(1) as u32;
22946            let fv = if g {
22947                self.func_g("fa_decode_vec_q_v4_dc")
22948            } else if deep {
22949                self.func("fa_decode_vec_q_v4_deep_dc")
22950            } else {
22951                self.func("fa_decode_vec_q_v4_dc")
22952            };
22953            let shmem =
22954                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
22955            use cudarc::driver::sys::CUfunction_attribute_enum as A;
22956            fv.set_attribute(
22957                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
22958                shmem as i32,
22959            )?;
22960            (
22961                fv,
22962                LaunchConfig {
22963                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
22964                    block_dim: (32, gqa, 1),
22965                    shared_mem_bytes: shmem,
22966                },
22967            )
22968        } else if fa_vec && fa_v3_active(head_dim) {
22969            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
22970            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
22971            let gqa = (n_head / n_head_kv).max(1) as u32;
22972            let fv = if g {
22973                self.func_g("fa_decode_vec_q_v3_dc")
22974            } else {
22975                self.func("fa_decode_vec_q_v3_dc")
22976            };
22977            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
22978            (
22979                fv,
22980                LaunchConfig {
22981                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
22982                    block_dim: (32, gqa, 1),
22983                    shared_mem_bytes: shmem,
22984                },
22985            )
22986        } else if fa_vec && fa_v2_on() {
22987            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
22988            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
22989            // a numeric config; eager, rows-verify and graph all switch together).
22990            let gqa = (n_head / n_head_kv).max(1) as u32;
22991            let fv = if g {
22992                self.func_g("fa_decode_vec_q_v2_dc")
22993            } else {
22994                self.func("fa_decode_vec_q_v2_dc")
22995            };
22996            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
22997            (
22998                fv,
22999                LaunchConfig {
23000                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
23001                    block_dim: (32, gqa, 1),
23002                    shared_mem_bytes: shmem,
23003                },
23004            )
23005        } else if fa_vec {
23006            let gqa = (n_head / n_head_kv).max(1) as u32;
23007            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
23008            let fv = if g {
23009                self.func_g("fa_decode_vec_q_dc")
23010            } else {
23011                self.func("fa_decode_vec_q_dc")
23012            };
23013            (
23014                fv,
23015                LaunchConfig {
23016                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
23017                    block_dim: (32, gqa, 1),
23018                    shared_mem_bytes: 0,
23019                },
23020            )
23021        } else {
23022            let q_view = q.as_view();
23023            let mut o_view = o.as_view_mut();
23024            return self.fa_decode_scalar_unified(
23025                &q_view,
23026                k,
23027                v,
23028                &mut o_view,
23029                head_dim,
23030                n_head,
23031                n_head_kv,
23032                0,
23033                Some(t_kv_dev),
23034                scale,
23035                n_splits,
23036                if fa_vec { sp } else { 256 },
23037                k_tok_bytes,
23038                v_tok_bytes,
23039                g,
23040                &mut *part_o,
23041                &mut *part_m,
23042                &mut *part_l,
23043                q8_out,
23044            );
23045        };
23046        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
23047        let __s_b = self.gpu.stream();
23048        let mut b = __s_b.launch_builder(&f);
23049        b.arg(q)
23050            .arg(k)
23051            .arg(v)
23052            .arg(&mut *part_o)
23053            .arg(&mut *part_m)
23054            .arg(&mut *part_l)
23055            .arg(&hd)
23056            .arg(&nh)
23057            .arg(&nhkv)
23058            .arg(t_kv_dev)
23059            .arg(&scale)
23060            .arg(&nsp)
23061            .arg(&ski)
23062            .arg(&ktb)
23063            .arg(&vtb);
23064        unsafe {
23065            b.launch(cfg)?;
23066        }
23067        let cfg2 = LaunchConfig {
23068            grid_dim: (n_head as u32, 1, 1),
23069            block_dim: (head_dim as u32, 1, 1),
23070            shared_mem_bytes: 0,
23071        };
23072        if let Some((oq, od)) = q8_out {
23073            let fc = if g {
23074                self.func_g("fa_decode_combine_q8_1")
23075            } else {
23076                self.fa_func("fa_decode_combine_q8_1", head_dim)
23077            };
23078            let __s_b2 = self.gpu.stream();
23079            let mut b2 = __s_b2.launch_builder(&fc);
23080            b2.arg(&*part_o)
23081                .arg(&*part_m)
23082                .arg(&*part_l)
23083                .arg(oq)
23084                .arg(od)
23085                .arg(&hd)
23086                .arg(&nh)
23087                .arg(&nsp);
23088            unsafe {
23089                b2.launch(cfg2)?;
23090            }
23091            return Ok(());
23092        }
23093        let fc = if g {
23094            self.func_g("fa_decode_combine_f32")
23095        } else {
23096            self.fa_func("fa_decode_combine_f32", head_dim)
23097        };
23098        let __s_b2 = self.gpu.stream();
23099        let mut b2 = __s_b2.launch_builder(&fc);
23100        b2.arg(&*part_o)
23101            .arg(&*part_m)
23102            .arg(&*part_l)
23103            .arg(o)
23104            .arg(&hd)
23105            .arg(&nh)
23106            .arg(&nsp);
23107        unsafe {
23108            b2.launch(cfg2)?;
23109        }
23110        Ok(())
23111    }
23112
23113    /// _dcw append (t=1): physical write row = len_dev[0] - base_dev[0] in-kernel; follow
23114    /// with `inc_i32(len_dev)` on the same stream. Bit-identical bytes to the host-row append
23115    /// at equal rows.
23116    #[allow(clippy::too_many_arguments)]
23117    pub fn append_kv_quantized_dcw(
23118        &self,
23119        k_row: &CudaSlice<f32>,
23120        v_row: &CudaSlice<f32>,
23121        kc: &mut CudaSlice<u8>,
23122        vc: &mut CudaSlice<u8>,
23123        len_dev: &CudaSlice<i32>,
23124        base_dev: Option<&CudaSlice<i32>>,
23125        kv_dim_k: usize,
23126        kv_dim_v: usize,
23127        k_tok_bytes: usize,
23128        v_tok_bytes: usize,
23129    ) -> Result<(), Box<dyn std::error::Error>> {
23130        let f = self.func("append_quantize_kv_q8_0_q5_1_dcw");
23131        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
23132        let cfg = LaunchConfig {
23133            grid_dim: (nblk, 1, 1),
23134            block_dim: (32, 1, 1),
23135            shared_mem_bytes: 0,
23136        };
23137        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
23138        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
23139        let null: u64 = 0;
23140        let __s_b = self.gpu.stream();
23141        let mut b = __s_b.launch_builder(&f);
23142        b.arg(k_row).arg(v_row).arg(kc).arg(vc).arg(len_dev);
23143        match base_dev {
23144            Some(base) => {
23145                b.arg(base);
23146            }
23147            None => {
23148                b.arg(&null);
23149            }
23150        }
23151        b.arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
23152        unsafe {
23153            b.launch(cfg)?;
23154        }
23155        Ok(())
23156    }
23157
23158    /// Increment a device i32 counter (graph-capturable; the `inc_i32` kernel).
23159    pub fn inc_i32(&self, counter: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
23160        let f = self.func("inc_i32");
23161        let cfg = LaunchConfig {
23162            grid_dim: (1, 1, 1),
23163            block_dim: (1, 1, 1),
23164            shared_mem_bytes: 0,
23165        };
23166        let __s_b = self.gpu.stream();
23167        let mut b = __s_b.launch_builder(&f);
23168        b.arg(counter);
23169        unsafe {
23170            b.launch(cfg)?;
23171        }
23172        Ok(())
23173    }
23174
23175    /// Windowed device-counter fa decode (step TP graph increment A): the KV view derives
23176    /// entirely from device state — `len_dev` (staged length), `base_dev` (physical row of
23177    /// logical 0 after the last ring rebase; None reads as 0), and `window` (0 = global) — so
23178    /// a captured child replays with ZERO per-token node updates. v3-vec only (the default
23179    /// kernel class on this lane); callers keep eager below the vec floor and for any other
23180    /// class. Scratch comes from the engine's fa partial pool sized at `bucket_max` (for SWA
23181    /// layers pass min(bucket, window)); the pool's retire-on-grow keeps captured addresses
23182    /// alive across bucket growth.
23183    #[allow(clippy::too_many_arguments)]
23184    /// Retire-on-grow ensure for the fa partial pool (see the #68 comment on the eager
23185    /// twin). Split out so graph capture can pre-run it OUTSIDE the capture region — an
23186    /// alloc inside a captured section becomes a mem node, and child graphs reject those.
23187    fn fa_part_pool_grow(
23188        &self,
23189        part_guard: &mut Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>,
23190        o_len: usize,
23191        ml_len: usize,
23192    ) -> Result<(), Box<dyn std::error::Error>> {
23193        if part_guard
23194            .as_ref()
23195            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
23196            .unwrap_or(true)
23197        {
23198            let old = part_guard.take();
23199            let (co, cm) = old
23200                .as_ref()
23201                .map(|pp| (pp.0.len(), pp.1.len()))
23202                .unwrap_or((0, 0));
23203            if let Some(old) = old {
23204                self.fa_part_retired.lock().unwrap().push(old);
23205            }
23206            *part_guard = Some((
23207                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
23208                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
23209                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
23210            ));
23211        }
23212        Ok(())
23213    }
23214
23215    /// Pre-grow the fa partial pool for a dcw call at (n_head, bucket_max) geometry, from
23216    /// OUTSIDE any capture region. Idempotent and cheap when already big enough.
23217    pub fn fa_dcw_pool_ensure(
23218        &self,
23219        head_dim: usize,
23220        n_head: usize,
23221        n_head_kv: usize,
23222        bucket_max: usize,
23223    ) -> Result<(), Box<dyn std::error::Error>> {
23224        let sp = fa_split_keys(bucket_max, n_head_kv);
23225        let n_splits = ((bucket_max + sp - 1) / sp).max(1);
23226        let o_len = n_head * n_splits * head_dim;
23227        let ml_len = n_head * n_splits;
23228        let mut part_guard = self.fa_part_pool.lock().unwrap();
23229        self.fa_part_pool_grow(&mut part_guard, o_len, ml_len)
23230    }
23231
23232    /// T=2 dcw decode attention (MEMRA_SPEC_FA2): both verify columns' rows are ALREADY
23233    /// appended; one launch walks the KV stream once with two query rows (per-row causal
23234    /// bounds len-1 / len) and the per-row combine consumes each half of the partials.
23235    /// BIT-IDENTICAL per row to that row's own per-column launch under the equal-partition
23236    /// guard the caller enforces (ns_eff/per equal for both bounds; boundary rounds fall
23237    /// back per column). `q2` = [2, n_head, head_dim]; `o2` = [2, n_head*head_dim] gated
23238    /// outputs (the head gate fuses into the combine as in the t=1 path).
23239    #[allow(clippy::too_many_arguments)]
23240    pub fn fa_decode_dcw2(
23241        &self,
23242        q2: &CudaSlice<f32>,
23243        k_ring: &cudarc::driver::CudaView<u8>,
23244        v_ring: &cudarc::driver::CudaView<u8>,
23245        o2: &mut CudaSlice<f32>,
23246        head_dim: usize,
23247        n_head: usize,
23248        n_head_kv: usize,
23249        len_dev: &CudaSlice<i32>,
23250        base_dev: Option<&CudaSlice<i32>>,
23251        window: usize,
23252        bucket_max: usize,
23253        scale: f32,
23254        k_tok_bytes: usize,
23255        v_tok_bytes: usize,
23256        gate2: &CudaSlice<f32>,
23257    ) -> Result<(), Box<dyn std::error::Error>> {
23258        let fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
23259        if !fa_vec || head_dim > 256 || head_dim % 32 != 0 || !fa_v3_on() {
23260            return Err("fa_decode_dcw2 supports the default v3-vec class only".into());
23261        }
23262        let sp = fa_split_keys(bucket_max, n_head_kv);
23263        let n_splits = ((bucket_max + sp - 1) / sp).max(1);
23264        // Partials for BOTH rows: row-major halves.
23265        let o_len = 2 * n_head * n_splits * head_dim;
23266        let ml_len = 2 * n_head * n_splits;
23267        let mut part_guard = self.fa_part_pool.lock().unwrap();
23268        Self::fa_part_pool_grow(self, &mut part_guard, o_len, ml_len)?;
23269        let pg = part_guard.as_mut().unwrap();
23270        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
23271        let (hd, nh, nhkv, nsp) = (
23272            head_dim as i32,
23273            n_head as i32,
23274            n_head_kv as i32,
23275            n_splits as i32,
23276        );
23277        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
23278        let (ski, win) = (sp as i32, window as i32);
23279        let gqa = (n_head / n_head_kv).max(1) as u32;
23280        let smem = (32 * head_dim * 2) as u32;
23281        let f = self.func("fa_decode_vec_q_v3_dcw2");
23282        let cfg = LaunchConfig {
23283            grid_dim: (n_head_kv as u32, n_splits as u32, 1),
23284            block_dim: (32, gqa, 1),
23285            shared_mem_bytes: smem,
23286        };
23287        let null: u64 = 0;
23288        {
23289            let __s_b = self.gpu.stream();
23290            let mut b = __s_b.launch_builder(&f);
23291            b.arg(q2)
23292                .arg(k_ring)
23293                .arg(v_ring)
23294                .arg(&mut *part_o)
23295                .arg(&mut *part_m)
23296                .arg(&mut *part_l)
23297                .arg(&hd)
23298                .arg(&nh)
23299                .arg(&nhkv)
23300                .arg(len_dev);
23301            match base_dev {
23302                Some(base) => {
23303                    b.arg(base);
23304                }
23305                None => {
23306                    b.arg(&null);
23307                }
23308            }
23309            b.arg(&win)
23310                .arg(&scale)
23311                .arg(&nsp)
23312                .arg(&ski)
23313                .arg(&ktb)
23314                .arg(&vtb);
23315            unsafe {
23316                b.launch(cfg)?;
23317            }
23318        }
23319        // Per-row combine+gate: the t=1 combine kernel over each half (its `head` axis spans
23320        // 2*n_head rows laid out row-major, and the gate rows are stacked the same way), so
23321        // one launch covers both rows with the exact t=1 program per (row, head).
23322        let fc = {
23323            static CS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23324            if *CS.get_or_init(|| std::env::var("MEMRA_FA_COMBINE_S").as_deref() == Ok("1")) {
23325                self.func("fa_decode_combine_gate_f32_s")
23326            } else {
23327                self.func("fa_decode_combine_gate_f32")
23328            }
23329        };
23330        let combine_shared = std::env::var("MEMRA_FA_COMBINE_S").as_deref() == Ok("1");
23331        let nh2 = (2 * n_head) as i32;
23332        let cfg2 = LaunchConfig {
23333            grid_dim: ((2 * n_head) as u32, 1, 1),
23334            block_dim: (head_dim as u32, 1, 1),
23335            shared_mem_bytes: if combine_shared {
23336                (2 * n_splits * 4) as u32
23337            } else {
23338                0
23339            },
23340        };
23341        let __s_b2 = self.gpu.stream();
23342        let mut b2 = __s_b2.launch_builder(&fc);
23343        b2.arg(&*part_o)
23344            .arg(&*part_m)
23345            .arg(&*part_l)
23346            .arg(gate2)
23347            .arg(o2)
23348            .arg(&hd)
23349            .arg(&nh2)
23350            .arg(&nsp);
23351        unsafe {
23352            b2.launch(cfg2)?;
23353        }
23354        Ok(())
23355    }
23356
23357    pub fn fa_decode_dcw(
23358        &self,
23359        q: &CudaSlice<f32>,
23360        k_ring: &cudarc::driver::CudaView<u8>,
23361        v_ring: &cudarc::driver::CudaView<u8>,
23362        o: &mut CudaSlice<f32>,
23363        head_dim: usize,
23364        n_head: usize,
23365        n_head_kv: usize,
23366        len_dev: &CudaSlice<i32>,
23367        base_dev: Option<&CudaSlice<i32>>,
23368        window: usize,
23369        bucket_max: usize,
23370        scale: f32,
23371        k_tok_bytes: usize,
23372        v_tok_bytes: usize,
23373        // FUSION #2d: Some(gate_row) fuses the head gate into the combine (bit-identical,
23374        // one launch saved); `o` then receives the GATED output and the caller skips its
23375        // attn_head_gate call.
23376        fused_gate: Option<&CudaSlice<f32>>,
23377    ) -> Result<(), Box<dyn std::error::Error>> {
23378        let fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
23379        if !fa_vec || head_dim > 256 || head_dim % 32 != 0 || !fa_v3_on() {
23380            return Err("fa_decode_dcw supports the default v3-vec class only                         (bucket >= vec floor, head_dim <= 256, MEMRA_FA_V3 on);                         keep eager outside it"
23381                .into());
23382        }
23383        let sp = fa_split_keys(bucket_max, n_head_kv);
23384        let n_splits = ((bucket_max + sp - 1) / sp).max(1);
23385        let o_len = n_head * n_splits * head_dim;
23386        let ml_len = n_head * n_splits;
23387        let mut part_guard = self.fa_part_pool.lock().unwrap();
23388        Self::fa_part_pool_grow(self, &mut part_guard, o_len, ml_len)?;
23389        let pg = part_guard.as_mut().unwrap();
23390        // MEMRA_FA_DCW_MEMSET=0: skip the partial-pool zeroing — every (head, split) in
23391        // [0, nsp) writes its partial before the combine reads it (per = ceil(len/nsp), so
23392        // split s starts at s*per < len for all s < nsp), making the zeros dead stores.
23393        // Door-gated pending the identity battery; =0 saves 3 memset launches/rank/layer.
23394        static MEMSET_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23395        // Token-graph capture ALWAYS keeps the memsets: the retarget path (increment C)
23396        // finds the attention children BY their three-memset signature and updates the
23397        // memset widths per bucket — capturing without them silently kills retargeting
23398        // (battery-v8 token drift, 2026-08-21).
23399        let memset_on = *MEMSET_ON
23400            .get_or_init(|| std::env::var("MEMRA_FA_DCW_MEMSET").as_deref() != Ok("0"))
23401            || crate::tp::token_graph_building();
23402        if memset_on {
23403            self.gpu
23404                .stream()
23405                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
23406            self.gpu
23407                .stream()
23408                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
23409            self.gpu
23410                .stream()
23411                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
23412        }
23413        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
23414        let (hd, nh, nhkv, nsp) = (
23415            head_dim as i32,
23416            n_head as i32,
23417            n_head_kv as i32,
23418            n_splits as i32,
23419        );
23420        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
23421        let (ski, win) = (sp as i32, window as i32);
23422        let gqa = (n_head / n_head_kv).max(1) as u32;
23423        let smem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd] (v3 uses sV only)
23424        // MEMRA_FA_UNROLL=8: the B1-unroll-8 twin (deeper K load pipeline, bit-identical —
23425        // see fa_dec_v3_walk_u). Same launch geometry.
23426        static U8: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23427        static HOIST: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
23428        let hoist = *HOIST.get_or_init(|| match std::env::var("MEMRA_FA_HOIST").as_deref() {
23429            Ok("2") => 2,
23430            Ok("1") => 1,
23431            _ => 0,
23432        });
23433        // MEMRA_FA_PROF=1: clock64() phase profile of the decode-attention walk. ncu is
23434        // permission-blocked in this container and the module params are not exposed, so this
23435        // is how the ~1.18us/key gets localised. Diagnostic only (extra atomics per block);
23436        // prints cumulative cycle shares every 430 launches.
23437        static FPROF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23438        let fprof = *FPROF.get_or_init(|| std::env::var("MEMRA_FA_PROF").as_deref() == Ok("1"));
23439        static PROF_BUF: std::sync::Mutex<Option<(usize, CudaSlice<u64>)>> =
23440            std::sync::Mutex::new(None);
23441        // MEMRA_FA_HSPLIT=2: split each kv_head's gqa warp group across TWO blocks (2x grid,
23442        // duplicated Phase A staging) — bit-identical per (head, split). Tests whether B1's
23443        // 59-63% cycle share is occupancy-starved latency (grid is only n_head_kv x n_splits).
23444        static HS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23445        let hs2 = *HS.get_or_init(|| std::env::var("MEMRA_FA_HSPLIT").as_deref() == Ok("2"))
23446            && (n_head / n_head_kv) % 2 == 0
23447            && (n_head / n_head_kv) >= 2;
23448        let f = if fprof {
23449            self.func("fa_decode_vec_q_v3_dcw_prof")
23450        } else if hs2 {
23451            self.func("fa_decode_vec_q_v3_dcw_hs2")
23452        } else if hoist == 2 {
23453            // + typed 4-byte K loads (memcpy from uint8_t* can lower to byte loads).
23454            self.func("fa_decode_vec_q_v3_dcw_hc")
23455        } else if hoist == 1 {
23456            // Loop-invariant K alignment class hoisted out of B1 (bit-identical).
23457            self.func("fa_decode_vec_q_v3_dcw_h")
23458        } else if *U8.get_or_init(|| std::env::var("MEMRA_FA_UNROLL").as_deref() == Ok("8")) {
23459            self.func("fa_decode_vec_q_v3_dcw_u8")
23460        } else {
23461            self.func("fa_decode_vec_q_v3_dcw")
23462        };
23463        let cfg = LaunchConfig {
23464            grid_dim: if hs2 {
23465                ((2 * n_head_kv) as u32, n_splits as u32, 1)
23466            } else {
23467                (n_head_kv as u32, n_splits as u32, 1)
23468            },
23469            block_dim: if hs2 { (32, gqa / 2, 1) } else { (32, gqa, 1) },
23470            shared_mem_bytes: smem,
23471        };
23472        let null: u64 = 0;
23473        let __s_b = self.gpu.stream();
23474        let mut b = __s_b.launch_builder(&f);
23475        b.arg(q)
23476            .arg(k_ring)
23477            .arg(v_ring)
23478            .arg(&mut *part_o)
23479            .arg(&mut *part_m)
23480            .arg(&mut *part_l)
23481            .arg(&hd)
23482            .arg(&nh)
23483            .arg(&nhkv)
23484            .arg(len_dev);
23485        match base_dev {
23486            Some(base) => {
23487                b.arg(base);
23488            }
23489            None => {
23490                b.arg(&null);
23491            }
23492        }
23493        b.arg(&win)
23494            .arg(&scale)
23495            .arg(&nsp)
23496            .arg(&ski)
23497            .arg(&ktb)
23498            .arg(&vtb);
23499        if fprof {
23500            let mut guard = PROF_BUF.lock().map_err(|_| "fa prof buffer lock")?;
23501            if guard
23502                .as_ref()
23503                .is_none_or(|(d, _)| *d != self.ctx().ordinal())
23504            {
23505                *guard = Some((self.ctx().ordinal(), self.htod_u64(&vec![0u64; 8])?));
23506            }
23507            let (_, buf) = guard.as_mut().expect("armed above");
23508            b.arg(&*buf);
23509            unsafe {
23510                b.launch(cfg)?;
23511            }
23512            static CALLS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
23513            let n = CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
23514            if n % 430 == 0 {
23515                self.stream().synchronize()?;
23516                let h = self.dtoh_u64(buf)?;
23517                let phases = ["setup", "stageV", "b1_klo", "b2_soft", "sync", "b3_vacc"];
23518                let tot: u64 = h[..6].iter().sum();
23519                let mut line = format!("[fa-prof] calls={n} keys={} cycles={tot}", h[6]);
23520                for (i, name) in phases.iter().enumerate() {
23521                    let pct = if tot > 0 {
23522                        h[i] as f64 / tot as f64 * 100.0
23523                    } else {
23524                        0.0
23525                    };
23526                    line.push_str(&format!(" {name}={pct:.1}%"));
23527                }
23528                if h[6] > 0 {
23529                    line.push_str(&format!(" cyc/key={:.0}", tot as f64 / h[6] as f64));
23530                }
23531                eprintln!("{line}");
23532            }
23533        } else {
23534            unsafe {
23535                b.launch(cfg)?;
23536            }
23537        }
23538        let mut combine_shared = false;
23539        let fc = if fused_gate.is_some() {
23540            // MEMRA_FA_COMBINE_S=1: shared-staged split metadata (bit-identical; kills the
23541            // n_splits-deep dependent global load chain every thread used to walk twice).
23542            static CS: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23543            if *CS.get_or_init(|| std::env::var("MEMRA_FA_COMBINE_S").as_deref() == Ok("1")) {
23544                combine_shared = true;
23545                self.func("fa_decode_combine_gate_f32_s")
23546            } else {
23547                self.func("fa_decode_combine_gate_f32")
23548            }
23549        } else {
23550            self.fa_func("fa_decode_combine_f32", head_dim)
23551        };
23552        let cfg2 = LaunchConfig {
23553            grid_dim: (n_head as u32, 1, 1),
23554            block_dim: (head_dim as u32, 1, 1),
23555            shared_mem_bytes: if combine_shared {
23556                (2 * n_splits * 4) as u32
23557            } else {
23558                0
23559            },
23560        };
23561        let __s_b2 = self.gpu.stream();
23562        let mut b2 = __s_b2.launch_builder(&fc);
23563        b2.arg(&*part_o).arg(&*part_m).arg(&*part_l);
23564        if let Some(gate_row) = fused_gate {
23565            b2.arg(gate_row);
23566        }
23567        b2.arg(o).arg(&hd).arg(&nh).arg(&nsp);
23568        unsafe {
23569            b2.launch(cfg2)?;
23570        }
23571        Ok(())
23572    }
23573
23574    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
23575    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
23576    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
23577    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
23578    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
23579    pub fn fa_geom_eager(
23580        &self,
23581        t_kv: usize,
23582        head_dim: usize,
23583        n_head_kv: usize,
23584        g: bool,
23585    ) -> (bool, usize) {
23586        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
23587        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
23588        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
23589        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
23590        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
23591        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
23592        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
23593        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
23594        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
23595        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
23596        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
23597        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
23598        // family; everything else falls to the g-module scalar.
23599        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
23600        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
23601        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
23602        if g && head_dim == 256 && !fa_v4_at(t_kv) {
23603            fa_vec = false;
23604        }
23605        let sp = fa_split_keys(t_kv, n_head_kv);
23606        let n_splits = if fa_vec {
23607            ((t_kv + sp - 1) / sp).max(1)
23608        } else {
23609            ((t_kv + 255) / 256).max(1)
23610        };
23611        (fa_vec, n_splits)
23612    }
23613
23614    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
23615    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
23616    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
23617    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
23618    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
23619    pub fn fa_bucket_key(
23620        &self,
23621        t_kv: usize,
23622        head_dim: usize,
23623        n_head_kv: usize,
23624        g: bool,
23625    ) -> (bool, usize) {
23626        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
23627    }
23628
23629    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
23630    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
23631    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
23632    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
23633    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
23634    /// device data) — every per-step varying scalar must come from a device counter. Returns the
23635    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
23636    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
23637    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
23638    /// replays (transients returning to the pool get reused by unrelated work and corrupt
23639    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
23640    pub fn capture_graph_retained<F>(
23641        &self,
23642        step: F,
23643    ) -> Result<
23644        (
23645            cudarc::driver::CudaGraph,
23646            Vec<Box<dyn std::any::Any + Send>>,
23647        ),
23648        Box<dyn std::error::Error>,
23649    >
23650    where
23651        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
23652    {
23653        use cudarc::driver::sys::CUgraphInstantiate_flags;
23654        self.capture_graph_retained_flags(
23655            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
23656            step,
23657        )
23658    }
23659
23660    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
23661    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
23662    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
23663    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
23664    pub fn capture_graph_retained_flags<F>(
23665        &self,
23666        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
23667        mut step: F,
23668    ) -> Result<
23669        (
23670            cudarc::driver::CudaGraph,
23671            Vec<Box<dyn std::any::Any + Send>>,
23672        ),
23673        Box<dyn std::error::Error>,
23674    >
23675    where
23676        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
23677    {
23678        use cudarc::driver::sys::CUstreamCaptureMode;
23679        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
23680        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
23681        // while the capture region is open become dead copy NODES replayed every launch
23682        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
23683        // warmup runs allocate the same transient sequence at the same pool addresses, so
23684        // retaining the warmup clones preserves the draft-graph fix without polluting the
23685        // captured graph.
23686        self.capture_keep.lock().unwrap().clear();
23687        let was_tracking = self.gpu.ctx.is_event_tracking();
23688        if was_tracking {
23689            unsafe {
23690                self.gpu.ctx.disable_event_tracking();
23691            }
23692        }
23693        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
23694            self.capture_keep_on
23695                .store(true, std::sync::atomic::Ordering::Relaxed);
23696            let w = (|| {
23697                step(self)?;
23698                step(self)
23699            })();
23700            self.capture_keep_on
23701                .store(false, std::sync::atomic::Ordering::Relaxed);
23702            w?;
23703            self.gpu.stream().synchronize()?;
23704            self.gpu
23705                .stream()
23706                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
23707            let r = step(self);
23708            let g = self.gpu.stream().end_capture(flags);
23709            r?;
23710            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
23711            graph.upload()?;
23712            Ok(graph)
23713        };
23714        let result = run();
23715        self.capture_keep_on
23716            .store(false, std::sync::atomic::Ordering::Relaxed);
23717        if was_tracking {
23718            unsafe {
23719                self.gpu.ctx.enable_event_tracking();
23720            }
23721        }
23722        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
23723        Ok((result?, keeper))
23724    }
23725
23726    /// Retained capture WITHOUT the two warmup executions. The warmups exist for transient
23727    /// pool-address stability (draft-graph lanes); the step TP token-graph sections are
23728    /// alloc-free with persistent operands, and their bodies carry device side effects
23729    /// (dcw KV appends + counter incs) that a warmup would REALLY EXECUTE — measured as a
23730    /// +2/rank len_d drift per bucket build that marched appends past the ring planes.
23731    pub fn capture_graph_retained_nowarm<F>(
23732        &self,
23733        mut step: F,
23734    ) -> Result<
23735        (
23736            cudarc::driver::CudaGraph,
23737            Vec<Box<dyn std::any::Any + Send>>,
23738        ),
23739        Box<dyn std::error::Error>,
23740    >
23741    where
23742        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
23743    {
23744        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
23745        let was_tracking = self.gpu.ctx.is_event_tracking();
23746        if was_tracking {
23747            unsafe {
23748                self.gpu.ctx.disable_event_tracking();
23749            }
23750        }
23751        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
23752            self.gpu.stream().synchronize()?;
23753            self.gpu
23754                .stream()
23755                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
23756            let r = step(self);
23757            let g = self.gpu.stream().end_capture(
23758                CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
23759            );
23760            r?;
23761            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
23762            graph.upload()?;
23763            Ok(graph)
23764        };
23765        let result = run();
23766        if was_tracking {
23767            unsafe {
23768                self.gpu.ctx.enable_event_tracking();
23769            }
23770        }
23771        Ok((result?, Vec::new()))
23772    }
23773
23774    pub fn capture_graph<F>(
23775        &self,
23776        mut step: F,
23777    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
23778    where
23779        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
23780    {
23781        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
23782        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
23783        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
23784        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
23785        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
23786        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
23787        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
23788        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
23789        let was_tracking = self.gpu.ctx.is_event_tracking();
23790        if was_tracking {
23791            unsafe {
23792                self.gpu.ctx.disable_event_tracking();
23793            }
23794        }
23795        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
23796        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
23797        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
23798        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
23799        // measure that scan's real cost on the generic path. Diagnostic door only; the
23800        // default stays AUTO_FREE until a measured A/B justifies moving it.
23801        let iflag = {
23802            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
23803            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
23804                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
23805                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
23806                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
23807                Ok("priority") => {
23808                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
23809                }
23810                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
23811            })
23812        };
23813        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
23814        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
23815        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
23816        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
23817        // eager step executions and are node-count-invariant. Printing the split bounds the
23818        // refactor's ceiling instead of assuming it.
23819        let ct = {
23820            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
23821            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
23822        };
23823        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
23824        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
23825        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
23826        // chased, and node-count-invariant, so no capture-body refactor could touch it.
23827        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
23828        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
23829        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
23830        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
23831        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
23832        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
23833        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
23834        // grow and never frees, resident counters/scratch, cache set in place), and the
23835        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
23836        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
23837        // settling and pool mapping. Arbitrated adversarially, not by taste:
23838        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
23839        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
23840        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
23841        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
23842        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
23843        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
23844        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
23845        let warmups = {
23846            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
23847            *W.get_or_init(|| {
23848                std::env::var("MEMRA_GRAPH_WARMUPS")
23849                    .ok()
23850                    .and_then(|v| v.parse().ok())
23851                    .filter(|n| *n >= 1)
23852                    .unwrap_or(1)
23853            })
23854        };
23855        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
23856            let t_w = std::time::Instant::now();
23857            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
23858            for _ in 0..warmups {
23859                step(self)?;
23860            }
23861            self.gpu.stream().synchronize()?;
23862            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
23863            // capture the third run.
23864            let t_c = std::time::Instant::now();
23865            self.gpu
23866                .stream()
23867                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
23868            // If the body errors mid-capture, end the capture before propagating so the stream isn't
23869            // left in a capturing state.
23870            let r = step(self);
23871            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
23872            let t_i = std::time::Instant::now();
23873            let g = self.gpu.stream().end_capture(iflag);
23874            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
23875            r?;
23876            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
23877            let t_u = std::time::Instant::now();
23878            graph.upload()?;
23879            if ct {
23880                println!(
23881                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
23882                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
23883                    t_u.elapsed().as_secs_f64() * 1e3
23884                );
23885            }
23886            Ok(graph)
23887        };
23888        let result = run();
23889        if was_tracking {
23890            unsafe {
23891                self.gpu.ctx.enable_event_tracking();
23892            }
23893        }
23894        result
23895    }
23896
23897    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
23898    pub fn gdn_scan_s128_view(
23899        &self,
23900        q: &CudaSlice<f32>,
23901        k: &CudaSlice<f32>,
23902        v: &CudaSlice<f32>,
23903        g: &CudaSlice<f32>,
23904        beta: &CudaSlice<f32>,
23905        state_in: &cudarc::driver::CudaView<f32>,
23906        state_out: &mut cudarc::driver::CudaViewMut<f32>,
23907        o: &mut CudaSlice<f32>,
23908        n_head: usize,
23909        t: usize,
23910        scale: f32,
23911    ) -> Result<(), Box<dyn std::error::Error>> {
23912        let f = self.func("gdn_scan_s128");
23913        const S_V: u32 = 128;
23914        const WARP: u32 = 32;
23915        const COLS: u32 = 4;
23916        let cfg = LaunchConfig {
23917            grid_dim: (n_head as u32, 1, S_V / COLS),
23918            block_dim: (WARP, COLS, 1),
23919            shared_mem_bytes: 0,
23920        };
23921        let (h, ti) = (n_head as i32, t as i32);
23922        let __s_b = self.gpu.stream();
23923        let mut b = __s_b.launch_builder(&f);
23924        b.arg(q)
23925            .arg(k)
23926            .arg(v)
23927            .arg(g)
23928            .arg(beta)
23929            .arg(state_in)
23930            .arg(state_out)
23931            .arg(o)
23932            .arg(&h)
23933            .arg(&ti)
23934            .arg(&scale);
23935        unsafe {
23936            b.launch(cfg)?;
23937        }
23938        Ok(())
23939    }
23940
23941    /// conv1d where the input is a CudaView (resident conv state assembled in place).
23942    pub fn ssm_conv1d_view(
23943        &self,
23944        x: &cudarc::driver::CudaView<f32>,
23945        w: &CudaSlice<f32>,
23946        y: &mut CudaSlice<f32>,
23947        conv_dim: usize,
23948        t: usize,
23949        d_conv: usize,
23950        silu: bool,
23951    ) -> Result<(), Box<dyn std::error::Error>> {
23952        let f = self.func("ssm_conv1d_silu_f32");
23953        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
23954        let cfg = LaunchConfig {
23955            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
23956            block_dim: (256, 1, 1),
23957            shared_mem_bytes: 0,
23958        };
23959        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
23960        let __s_b = self.gpu.stream();
23961        let mut b = __s_b.launch_builder(&f);
23962        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
23963        unsafe {
23964            b.launch(cfg)?;
23965        }
23966        Ok(())
23967    }
23968
23969    /// Depthwise causal conv1d + optional SiLU.
23970    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
23971    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
23972    /// FUSED prefill conv (token-major input, zero left-state): replaces
23973    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
23974    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
23975    pub fn ssm_conv1d_tm(
23976        &self,
23977        qkv_tm: &CudaSlice<f32>,
23978        w: &CudaSlice<f32>,
23979        y: &mut CudaSlice<f32>,
23980        conv_dim: usize,
23981        t: usize,
23982        d_conv: usize,
23983    ) -> Result<(), Box<dyn std::error::Error>> {
23984        let f = self.func("ssm_conv1d_tm_f32");
23985        let cfg = LaunchConfig {
23986            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
23987            block_dim: (256, 1, 1),
23988            shared_mem_bytes: 0,
23989        };
23990        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
23991        let __s_b = self.gpu.stream();
23992        let mut b = __s_b.launch_builder(&f);
23993        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
23994        unsafe {
23995            b.launch(cfg)?;
23996        }
23997        Ok(())
23998    }
23999
24000    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
24001    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
24002    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
24003    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
24004    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
24005    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
24006    /// columns; the final ring == what T sequential decode ring rolls leave).
24007    pub fn ssm_conv1d_tm_state(
24008        &self,
24009        qkv_tm: &CudaSlice<f32>,
24010        conv_state: &mut CudaSlice<f32>,
24011        w: &CudaSlice<f32>,
24012        y: &mut CudaSlice<f32>,
24013        conv_dim: usize,
24014        t: usize,
24015        d_conv: usize,
24016    ) -> Result<(), Box<dyn std::error::Error>> {
24017        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
24018    }
24019
24020    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
24021    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
24022    #[allow(clippy::too_many_arguments)]
24023    pub fn ssm_conv1d_tm_state_pad(
24024        &self,
24025        qkv_tm: &CudaSlice<f32>,
24026        conv_state: &mut CudaSlice<f32>,
24027        w: &CudaSlice<f32>,
24028        y: &mut CudaSlice<f32>,
24029        conv_dim: usize,
24030        t: usize,
24031        d_conv: usize,
24032        pad_len: Option<&CudaSlice<i32>>,
24033    ) -> Result<(), Box<dyn std::error::Error>> {
24034        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
24035        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
24036        // the window kernel both read the pre-roll ring; the roll launches after both) — but
24037        // cloning first keeps the ordering trivially correct under any future stream split.
24038        let ring_old = if t < d_conv - 1 {
24039            Some(self.clone_dtod(conv_state)?)
24040        } else {
24041            None
24042        };
24043        {
24044            let f = self.func("ssm_conv1d_tm_state_f32");
24045            let cfg = LaunchConfig {
24046                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
24047                block_dim: (256, 1, 1),
24048                shared_mem_bytes: 0,
24049            };
24050            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24051            let __s_b = self.gpu.stream();
24052            let mut b = __s_b.launch_builder(&f);
24053            b.arg(qkv_tm)
24054                .arg(&*conv_state)
24055                .arg(w)
24056                .arg(y)
24057                .arg(&cd)
24058                .arg(&ti)
24059                .arg(&dc);
24060            unsafe {
24061                b.launch(cfg)?;
24062            }
24063        }
24064        match (ring_old, pad_len) {
24065            (None, Some(len_d)) => {
24066                let f = self.func("ssm_conv_ring_update_dev_f32");
24067                let n = conv_dim * (d_conv - 1);
24068                let cfg = LaunchConfig::for_num_elems(n as u32);
24069                let (cd, dc) = (conv_dim as i32, d_conv as i32);
24070                let __s_b = self.gpu.stream();
24071                let mut b = __s_b.launch_builder(&f);
24072                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
24073                unsafe {
24074                    b.launch(cfg)?;
24075                }
24076            }
24077            (None, None) => {
24078                let f = self.func("ssm_conv_ring_update_f32");
24079                let n = conv_dim * (d_conv - 1);
24080                let cfg = LaunchConfig::for_num_elems(n as u32);
24081                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24082                let __s_b = self.gpu.stream();
24083                let mut b = __s_b.launch_builder(&f);
24084                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
24085                unsafe {
24086                    b.launch(cfg)?;
24087                }
24088            }
24089            (Some(old), _) => {
24090                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
24091            }
24092        }
24093        Ok(())
24094    }
24095
24096    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
24097    pub fn ssm_conv1d_tm_state_pad_v(
24098        &self,
24099        qkv_tm: &cudarc::driver::CudaView<f32>,
24100        conv_state: &mut CudaSlice<f32>,
24101        w: &CudaSlice<f32>,
24102        y: &mut CudaSlice<f32>,
24103        conv_dim: usize,
24104        t: usize,
24105        d_conv: usize,
24106        pad_len: Option<&CudaSlice<i32>>,
24107    ) -> Result<(), Box<dyn std::error::Error>> {
24108        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
24109        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
24110        // the window kernel both read the pre-roll ring; the roll launches after both) — but
24111        // cloning first keeps the ordering trivially correct under any future stream split.
24112        let ring_old = if t < d_conv - 1 {
24113            Some(self.clone_dtod(conv_state)?)
24114        } else {
24115            None
24116        };
24117        {
24118            let f = self.func("ssm_conv1d_tm_state_f32");
24119            let cfg = LaunchConfig {
24120                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
24121                block_dim: (256, 1, 1),
24122                shared_mem_bytes: 0,
24123            };
24124            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24125            let __s_b = self.gpu.stream();
24126            let mut b = __s_b.launch_builder(&f);
24127            b.arg(qkv_tm)
24128                .arg(&*conv_state)
24129                .arg(w)
24130                .arg(y)
24131                .arg(&cd)
24132                .arg(&ti)
24133                .arg(&dc);
24134            unsafe {
24135                b.launch(cfg)?;
24136            }
24137        }
24138        match (ring_old, pad_len) {
24139            (None, Some(len_d)) => {
24140                let f = self.func("ssm_conv_ring_update_dev_f32");
24141                let n = conv_dim * (d_conv - 1);
24142                let cfg = LaunchConfig::for_num_elems(n as u32);
24143                let (cd, dc) = (conv_dim as i32, d_conv as i32);
24144                let __s_b = self.gpu.stream();
24145                let mut b = __s_b.launch_builder(&f);
24146                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
24147                unsafe {
24148                    b.launch(cfg)?;
24149                }
24150            }
24151            (None, None) => {
24152                let f = self.func("ssm_conv_ring_update_f32");
24153                let n = conv_dim * (d_conv - 1);
24154                let cfg = LaunchConfig::for_num_elems(n as u32);
24155                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24156                let __s_b = self.gpu.stream();
24157                let mut b = __s_b.launch_builder(&f);
24158                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
24159                unsafe {
24160                    b.launch(cfg)?;
24161                }
24162            }
24163            (Some(_), _) => unreachable!(
24164                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
24165            ),
24166        }
24167        Ok(())
24168    }
24169
24170    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
24171    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
24172    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
24173    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
24174    pub fn ssm_conv_ring_rebuild(
24175        &self,
24176        qkv_tm: &CudaSlice<f32>,
24177        ring_old: &CudaSlice<f32>,
24178        conv_state: &mut CudaSlice<f32>,
24179        conv_dim: usize,
24180        tc: usize,
24181        d_conv: usize,
24182    ) -> Result<(), Box<dyn std::error::Error>> {
24183        let f = self.func("ssm_conv_ring_rebuild_f32");
24184        let n = conv_dim * (d_conv - 1);
24185        let cfg = LaunchConfig::for_num_elems(n as u32);
24186        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
24187        let __s_b = self.gpu.stream();
24188        let mut b = __s_b.launch_builder(&f);
24189        b.arg(qkv_tm)
24190            .arg(ring_old)
24191            .arg(conv_state)
24192            .arg(&cd)
24193            .arg(&ti)
24194            .arg(&dc);
24195        unsafe {
24196            b.launch(cfg)?;
24197        }
24198        Ok(())
24199    }
24200
24201    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
24202    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
24203    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
24204    /// the argmax + run-spec gates are the authority.
24205    #[allow(clippy::too_many_arguments)]
24206    pub fn gdn_prep_decode(
24207        &self,
24208        conv_out: &CudaSlice<f32>,
24209        beta_raw: &CudaSlice<f32>,
24210        alpha: &CudaSlice<f32>,
24211        dt_bias: &CudaSlice<f32>,
24212        a: &CudaSlice<f32>,
24213        q_l2: &mut CudaSlice<f32>,
24214        k_l2: &mut CudaSlice<f32>,
24215        v_g: &mut CudaSlice<f32>,
24216        beta: &mut CudaSlice<f32>,
24217        g_log: &mut CudaSlice<f32>,
24218        d_state: usize,
24219        num_v: usize,
24220        num_k: usize,
24221        key_dim: usize,
24222        eps: f32,
24223    ) -> Result<(), Box<dyn std::error::Error>> {
24224        let f = self.func("gdn_prep_decode_f32");
24225        let cfg = LaunchConfig {
24226            grid_dim: (num_v as u32, 1, 1),
24227            block_dim: (32, 4, 1),
24228            shared_mem_bytes: 0,
24229        };
24230        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
24231        let __s_b = self.gpu.stream();
24232        let mut b = __s_b.launch_builder(&f);
24233        b.arg(conv_out)
24234            .arg(beta_raw)
24235            .arg(alpha)
24236            .arg(dt_bias)
24237            .arg(a)
24238            .arg(q_l2)
24239            .arg(k_l2)
24240            .arg(v_g)
24241            .arg(beta)
24242            .arg(g_log)
24243            .arg(&ds)
24244            .arg(&nv)
24245            .arg(&nk)
24246            .arg(&kd)
24247            .arg(&eps);
24248        unsafe {
24249            b.launch(cfg)?;
24250        }
24251        Ok(())
24252    }
24253
24254    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
24255    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
24256    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
24257    #[allow(clippy::too_many_arguments)]
24258    pub fn ssm_conv1d_gdn(
24259        &self,
24260        qkv_tm: &CudaSlice<f32>,
24261        w: &CudaSlice<f32>,
24262        q_g: &mut CudaSlice<f32>,
24263        k_g: &mut CudaSlice<f32>,
24264        v_g: &mut CudaSlice<f32>,
24265        conv_dim: usize,
24266        t: usize,
24267        d_conv: usize,
24268        d_state: usize,
24269        num_v: usize,
24270        num_k: usize,
24271        key_dim: usize,
24272    ) -> Result<(), Box<dyn std::error::Error>> {
24273        let f = self.func("ssm_conv1d_gdn_f32");
24274        let cfg = LaunchConfig {
24275            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
24276            block_dim: (256, 1, 1),
24277            shared_mem_bytes: 0,
24278        };
24279        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24280        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
24281        let __s_b = self.gpu.stream();
24282        let mut b = __s_b.launch_builder(&f);
24283        b.arg(qkv_tm)
24284            .arg(w)
24285            .arg(q_g)
24286            .arg(k_g)
24287            .arg(v_g)
24288            .arg(&cd)
24289            .arg(&ti)
24290            .arg(&dc)
24291            .arg(&ds)
24292            .arg(&nv)
24293            .arg(&nk)
24294            .arg(&kd);
24295        unsafe {
24296            b.launch(cfg)?;
24297        }
24298        Ok(())
24299    }
24300
24301    pub fn ssm_conv1d(
24302        &self,
24303        x: &CudaSlice<f32>,
24304        w: &CudaSlice<f32>,
24305        y: &mut CudaSlice<f32>,
24306        conv_dim: usize,
24307        t: usize,
24308        d_conv: usize,
24309        silu: bool,
24310    ) -> Result<(), Box<dyn std::error::Error>> {
24311        let f = self.func("ssm_conv1d_silu_f32");
24312        let cfg = LaunchConfig {
24313            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
24314            block_dim: (256, 1, 1),
24315            shared_mem_bytes: 0,
24316        };
24317        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
24318        let __s_b = self.gpu.stream();
24319        let mut b = __s_b.launch_builder(&f);
24320        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
24321        unsafe {
24322            b.launch(cfg)?;
24323        }
24324        Ok(())
24325    }
24326
24327    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
24328    /// o:[128,H,T]. Single sequence.
24329    pub fn gdn_scan_s128(
24330        &self,
24331        q: &CudaSlice<f32>,
24332        k: &CudaSlice<f32>,
24333        v: &CudaSlice<f32>,
24334        g: &CudaSlice<f32>,
24335        beta: &CudaSlice<f32>,
24336        state_in: &CudaSlice<f32>,
24337        state_out: &mut CudaSlice<f32>,
24338        o: &mut CudaSlice<f32>,
24339        n_head: usize,
24340        t: usize,
24341        scale: f32,
24342    ) -> Result<(), Box<dyn std::error::Error>> {
24343        let f = self.func("gdn_scan_s128");
24344        const S_V: u32 = 128;
24345        const WARP: u32 = 32;
24346        const COLS_PER_BLOCK: u32 = 4;
24347        let cfg = LaunchConfig {
24348            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
24349            block_dim: (WARP, COLS_PER_BLOCK, 1),
24350            shared_mem_bytes: 0,
24351        };
24352        let (h, ti) = (n_head as i32, t as i32);
24353        let __s_b = self.gpu.stream();
24354        let mut b = __s_b.launch_builder(&f);
24355        b.arg(q)
24356            .arg(k)
24357            .arg(v)
24358            .arg(g)
24359            .arg(beta)
24360            .arg(state_in)
24361            .arg(state_out)
24362            .arg(o)
24363            .arg(&h)
24364            .arg(&ti)
24365            .arg(&scale);
24366        unsafe {
24367            b.launch(cfg)?;
24368        }
24369        Ok(())
24370    }
24371
24372    // ==== B2' batched decode state ops (decode_batch.rs) ====
24373    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
24374    // Bodies are the single-seq kernels per sequence — bit-identical per row.
24375
24376    #[allow(clippy::too_many_arguments)]
24377    pub fn ssm_conv1d_fused_decode_b(
24378        &self,
24379        qkv_cols: &CudaSlice<f32>,
24380        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
24381        w: &CudaSlice<f32>,
24382        conv_outs: &mut CudaSlice<f32>,
24383        conv_dim: usize,
24384        d_conv: usize,
24385        b_n: usize,
24386    ) -> Result<(), Box<dyn std::error::Error>> {
24387        let f = self.func("ssm_conv1d_fused_decode_b_f32");
24388        let cfg = LaunchConfig {
24389            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
24390            block_dim: (256, 1, 1),
24391            shared_mem_bytes: 0,
24392        };
24393        let (cd, dc) = (conv_dim as i32, d_conv as i32);
24394        let __s_b = self.gpu.stream();
24395        let mut b = __s_b.launch_builder(&f);
24396        b.arg(qkv_cols)
24397            .arg(conv_state_ptrs)
24398            .arg(w)
24399            .arg(conv_outs)
24400            .arg(&cd)
24401            .arg(&dc);
24402        unsafe {
24403            b.launch(cfg)?;
24404        }
24405        Ok(())
24406    }
24407
24408    #[allow(clippy::too_many_arguments)]
24409    pub fn gdn_prep_decode_b(
24410        &self,
24411        conv_outs: &CudaSlice<f32>,
24412        beta_raws: &CudaSlice<f32>,
24413        alphas: &CudaSlice<f32>,
24414        dt_bias: &CudaSlice<f32>,
24415        a: &CudaSlice<f32>,
24416        q_l2: &mut CudaSlice<f32>,
24417        k_l2: &mut CudaSlice<f32>,
24418        v_g: &mut CudaSlice<f32>,
24419        beta: &mut CudaSlice<f32>,
24420        g_log: &mut CudaSlice<f32>,
24421        d_state: usize,
24422        num_v: usize,
24423        num_k: usize,
24424        key_dim: usize,
24425        eps: f32,
24426        conv_dim: usize,
24427        b_n: usize,
24428    ) -> Result<(), Box<dyn std::error::Error>> {
24429        let f = self.func("gdn_prep_decode_b_f32");
24430        let cfg = LaunchConfig {
24431            grid_dim: (num_v as u32, 1, b_n as u32),
24432            block_dim: (32, 4, 1),
24433            shared_mem_bytes: 0,
24434        };
24435        let (ds, nv, nk, kd, cd) = (
24436            d_state as i32,
24437            num_v as i32,
24438            num_k as i32,
24439            key_dim as i32,
24440            conv_dim as i32,
24441        );
24442        let __s_b = self.gpu.stream();
24443        let mut b = __s_b.launch_builder(&f);
24444        b.arg(conv_outs)
24445            .arg(beta_raws)
24446            .arg(alphas)
24447            .arg(dt_bias)
24448            .arg(a)
24449            .arg(q_l2)
24450            .arg(k_l2)
24451            .arg(v_g)
24452            .arg(beta)
24453            .arg(g_log)
24454            .arg(&ds)
24455            .arg(&nv)
24456            .arg(&nk)
24457            .arg(&kd)
24458            .arg(&eps)
24459            .arg(&cd);
24460        unsafe {
24461            b.launch(cfg)?;
24462        }
24463        Ok(())
24464    }
24465
24466    #[allow(clippy::too_many_arguments)]
24467    pub fn gdn_scan_s128_batched(
24468        &self,
24469        q: &CudaSlice<f32>,
24470        k: &CudaSlice<f32>,
24471        v: &CudaSlice<f32>,
24472        g: &CudaSlice<f32>,
24473        beta: &CudaSlice<f32>,
24474        state_in_ptrs: &cudarc::driver::CudaView<u64>,
24475        state_out_ptrs: &cudarc::driver::CudaView<u64>,
24476        o: &mut CudaSlice<f32>,
24477        n_head: usize,
24478        b_n: usize,
24479        scale: f32,
24480    ) -> Result<(), Box<dyn std::error::Error>> {
24481        let f = self.func("gdn_scan_s128_b");
24482        const S_V: u32 = 128;
24483        const WARP: u32 = 32;
24484        const COLS_PER_BLOCK: u32 = 4;
24485        let cfg = LaunchConfig {
24486            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
24487            block_dim: (WARP, COLS_PER_BLOCK, 1),
24488            shared_mem_bytes: 0,
24489        };
24490        let h = n_head as i32;
24491        let __s_b = self.gpu.stream();
24492        let mut b = __s_b.launch_builder(&f);
24493        b.arg(q)
24494            .arg(k)
24495            .arg(v)
24496            .arg(g)
24497            .arg(beta)
24498            .arg(state_in_ptrs)
24499            .arg(state_out_ptrs)
24500            .arg(o)
24501            .arg(&h)
24502            .arg(&scale);
24503        unsafe {
24504            b.launch(cfg)?;
24505        }
24506        Ok(())
24507    }
24508
24509    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
24510    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
24511    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
24512    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
24513    /// numeric class; only the pointer arithmetic moved host-side.
24514    #[allow(clippy::too_many_arguments)]
24515    pub fn ssm_conv1d_fused_decode_b_view(
24516        &self,
24517        qkv_cols: &cudarc::driver::CudaView<f32>,
24518        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
24519        w: &CudaSlice<f32>,
24520        conv_outs: &mut CudaSlice<f32>,
24521        conv_dim: usize,
24522        d_conv: usize,
24523        b_n: usize,
24524    ) -> Result<(), Box<dyn std::error::Error>> {
24525        let f = self.func("ssm_conv1d_fused_decode_b_f32");
24526        let cfg = LaunchConfig {
24527            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
24528            block_dim: (256, 1, 1),
24529            shared_mem_bytes: 0,
24530        };
24531        let (cd, dc) = (conv_dim as i32, d_conv as i32);
24532        let __s_b = self.gpu.stream();
24533        let mut b = __s_b.launch_builder(&f);
24534        b.arg(qkv_cols)
24535            .arg(conv_state_ptrs)
24536            .arg(w)
24537            .arg(conv_outs)
24538            .arg(&cd)
24539            .arg(&dc);
24540        unsafe {
24541            b.launch(cfg)?;
24542        }
24543        Ok(())
24544    }
24545
24546    #[allow(clippy::too_many_arguments)]
24547    pub fn gdn_prep_decode_b_view(
24548        &self,
24549        conv_outs: &CudaSlice<f32>,
24550        beta_raws: &cudarc::driver::CudaView<f32>,
24551        alphas: &cudarc::driver::CudaView<f32>,
24552        dt_bias: &CudaSlice<f32>,
24553        a: &CudaSlice<f32>,
24554        q_l2: &mut CudaSlice<f32>,
24555        k_l2: &mut CudaSlice<f32>,
24556        v_g: &mut CudaSlice<f32>,
24557        beta: &mut CudaSlice<f32>,
24558        g_log: &mut CudaSlice<f32>,
24559        d_state: usize,
24560        num_v: usize,
24561        num_k: usize,
24562        key_dim: usize,
24563        eps: f32,
24564        conv_dim: usize,
24565        b_n: usize,
24566    ) -> Result<(), Box<dyn std::error::Error>> {
24567        let f = self.func("gdn_prep_decode_b_f32");
24568        let cfg = LaunchConfig {
24569            grid_dim: (num_v as u32, 1, b_n as u32),
24570            block_dim: (32, 4, 1),
24571            shared_mem_bytes: 0,
24572        };
24573        let (ds, nv, nk, kd, cd) = (
24574            d_state as i32,
24575            num_v as i32,
24576            num_k as i32,
24577            key_dim as i32,
24578            conv_dim as i32,
24579        );
24580        let __s_b = self.gpu.stream();
24581        let mut b = __s_b.launch_builder(&f);
24582        b.arg(conv_outs)
24583            .arg(beta_raws)
24584            .arg(alphas)
24585            .arg(dt_bias)
24586            .arg(a)
24587            .arg(q_l2)
24588            .arg(k_l2)
24589            .arg(v_g)
24590            .arg(beta)
24591            .arg(g_log)
24592            .arg(&ds)
24593            .arg(&nv)
24594            .arg(&nk)
24595            .arg(&kd)
24596            .arg(&eps)
24597            .arg(&cd);
24598        unsafe {
24599            b.launch(cfg)?;
24600        }
24601        Ok(())
24602    }
24603
24604    #[allow(clippy::too_many_arguments)]
24605    pub fn gdn_scan_s128_batched_view(
24606        &self,
24607        q: &CudaSlice<f32>,
24608        k: &CudaSlice<f32>,
24609        v: &CudaSlice<f32>,
24610        g: &CudaSlice<f32>,
24611        beta: &CudaSlice<f32>,
24612        state_in_ptrs: &cudarc::driver::CudaView<u64>,
24613        state_out_ptrs: &cudarc::driver::CudaView<u64>,
24614        o: &mut cudarc::driver::CudaViewMut<f32>,
24615        n_head: usize,
24616        b_n: usize,
24617        scale: f32,
24618    ) -> Result<(), Box<dyn std::error::Error>> {
24619        let f = self.func("gdn_scan_s128_b");
24620        const S_V: u32 = 128;
24621        const WARP: u32 = 32;
24622        const COLS_PER_BLOCK: u32 = 4;
24623        let cfg = LaunchConfig {
24624            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
24625            block_dim: (WARP, COLS_PER_BLOCK, 1),
24626            shared_mem_bytes: 0,
24627        };
24628        let h = n_head as i32;
24629        let __s_b = self.gpu.stream();
24630        let mut b = __s_b.launch_builder(&f);
24631        b.arg(q)
24632            .arg(k)
24633            .arg(v)
24634            .arg(g)
24635            .arg(beta)
24636            .arg(state_in_ptrs)
24637            .arg(state_out_ptrs)
24638            .arg(o)
24639            .arg(&h)
24640            .arg(&scale);
24641        unsafe {
24642            b.launch(cfg)?;
24643        }
24644        Ok(())
24645    }
24646
24647    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
24648    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
24649    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
24650    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
24651    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
24652    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
24653    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
24654    /// identity law); prime_cache/forward/forward_last are the only callers.
24655    pub fn gdn_chunked_enabled() -> bool {
24656        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
24657        *E.get_or_init(|| {
24658            std::env::var("MEMRA_GDN_CHUNKED")
24659                .map(|v| v != "0")
24660                .unwrap_or(true)
24661        })
24662    }
24663
24664    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
24665    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
24666    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
24667    /// of 32 in [32, 128] (kernel row mappings require it).
24668    pub fn gdn_chunk_size() -> usize {
24669        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
24670        *C.get_or_init(|| {
24671            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
24672                .ok()
24673                .and_then(|v| v.parse().ok())
24674                .unwrap_or(32);
24675            c.clamp(32, 128) / 32 * 32
24676        })
24677    }
24678
24679    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
24680    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
24681    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
24682    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
24683    #[allow(clippy::too_many_arguments)]
24684    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
24685    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
24686    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
24687    #[allow(clippy::too_many_arguments)]
24688    pub fn gdn_chunk_k123(
24689        &self,
24690        q: &CudaSlice<f32>,
24691        k: &CudaSlice<f32>,
24692        v: &CudaSlice<f32>,
24693        g: &CudaSlice<f32>,
24694        beta: &CudaSlice<f32>,
24695        wb16: Option<&mut CudaSlice<u8>>,
24696        n_head: usize,
24697        t: usize,
24698        c: usize,
24699        hk: usize,
24700        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
24701    ) -> Result<
24702        (
24703            CudaSlice<f32>,
24704            CudaSlice<f32>,
24705            CudaSlice<f32>,
24706            CudaSlice<f32>,
24707        ),
24708        Box<dyn std::error::Error>,
24709    > {
24710        const D: usize = 128;
24711        let h = n_head;
24712        let nc = (t + c - 1) / c;
24713        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
24714        let mut gcum = self.uninit(t * h)?;
24715        let mut a = self.uninit(nc * h * c * c)?;
24716        let mut p = self.uninit(nc * h * c * c)?;
24717        let mut u = self.uninit(nc * h * c * D)?;
24718        let mut w = self.uninit(nc * h * c * D)?;
24719        {
24720            // K1
24721            let f = self.func("gdn_chunk_cumgate_f32");
24722            let cfg = LaunchConfig {
24723                grid_dim: (nc as u32, h as u32, 1),
24724                block_dim: (32, 1, 1),
24725                shared_mem_bytes: 0,
24726            };
24727            let __s_b = self.gpu.stream();
24728            let mut b = __s_b.launch_builder(&f);
24729            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
24730            unsafe {
24731                b.launch(cfg)?;
24732            }
24733        }
24734        if let Some((qb, kb, pb)) = k2w {
24735            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
24736            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
24737            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
24738            let f = self.func("gdn_k2_wgmma");
24739            let cfg = LaunchConfig {
24740                grid_dim: (nc as u32, h as u32, 1),
24741                block_dim: (128, 1, 1),
24742                shared_mem_bytes: 0,
24743            };
24744            let hki = hk as i32;
24745            let __s_b = self.gpu.stream();
24746            let mut b = __s_b.launch_builder(&f);
24747            b.arg(qb)
24748                .arg(kb)
24749                .arg(&gcum)
24750                .arg(beta)
24751                .arg(&mut a)
24752                .arg(&mut *pb)
24753                .arg(&hi)
24754                .arg(&ti)
24755                .arg(&ci)
24756                .arg(&hki);
24757            unsafe {
24758                b.launch(cfg)?;
24759            }
24760        } else if c <= 64 && !portable_mma_gated() {
24761            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
24762            let f = self.func("gdn_chunk_attn_f32");
24763            f.set_attribute(
24764                CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
24765                GDN_K2_DYNAMIC_SHARED_BYTES as i32,
24766            )?;
24767            let jt = ((c + 31) / 32) as u32;
24768            let cfg = LaunchConfig {
24769                grid_dim: (nc as u32, h as u32, jt),
24770                block_dim: (256, 1, 1),
24771                shared_mem_bytes: GDN_K2_DYNAMIC_SHARED_BYTES,
24772            };
24773            let hki = hk as i32;
24774            let __s_b = self.gpu.stream();
24775            let mut b = __s_b.launch_builder(&f);
24776            b.arg(q)
24777                .arg(k)
24778                .arg(&gcum)
24779                .arg(beta)
24780                .arg(&mut a)
24781                .arg(&mut p)
24782                .arg(&hi)
24783                .arg(&ti)
24784                .arg(&ci)
24785                .arg(&hki);
24786            unsafe {
24787                b.launch(cfg)?;
24788            }
24789        } else {
24790            // K2 generic (C = 128, or the portable target's low-smem fallback)
24791            assert!(
24792                hk == h,
24793                "generic K2 is broadcast-only (de-broadcast rides C==32)"
24794            );
24795            let f = self.func("gdn_chunk_attn_g_f32");
24796            let cfg = LaunchConfig {
24797                grid_dim: (nc as u32, h as u32, 1),
24798                block_dim: (32, 8, 1),
24799                shared_mem_bytes: 0,
24800            };
24801            let __s_b = self.gpu.stream();
24802            let mut b = __s_b.launch_builder(&f);
24803            b.arg(q)
24804                .arg(k)
24805                .arg(&gcum)
24806                .arg(beta)
24807                .arg(&mut a)
24808                .arg(&mut p)
24809                .arg(&hi)
24810                .arg(&ti)
24811                .arg(&ci);
24812            unsafe {
24813                b.launch(cfg)?;
24814            }
24815        }
24816        {
24817            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
24818            let cfg = LaunchConfig {
24819                grid_dim: (nc as u32, h as u32, 1),
24820                block_dim: (256, 1, 1),
24821                shared_mem_bytes: 0,
24822            };
24823            match c {
24824                32 | 64 => {
24825                    let f = self.func(if c == 32 {
24826                        "gdn_chunk_solve32_f32"
24827                    } else {
24828                        "gdn_chunk_solve64_f32"
24829                    });
24830                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
24831                    let wb: u64 = match wb16 {
24832                        Some(d) => self.addr_u8(d),
24833                        None => 0,
24834                    };
24835                    let hki = hk as i32;
24836                    let __s_b = self.gpu.stream();
24837                    let mut b = __s_b.launch_builder(&f);
24838                    b.arg(v)
24839                        .arg(k)
24840                        .arg(&a)
24841                        .arg(&gcum)
24842                        .arg(&mut u)
24843                        .arg(&mut w)
24844                        .arg(&wb)
24845                        .arg(&hi)
24846                        .arg(&ti)
24847                        .arg(&hki);
24848                    unsafe {
24849                        b.launch(cfg)?;
24850                    }
24851                }
24852                _ => {
24853                    assert!(hk == h, "generic K3 is broadcast-only");
24854                    let f = self.func("gdn_chunk_solve_f32");
24855                    let __s_b = self.gpu.stream();
24856                    let mut b = __s_b.launch_builder(&f);
24857                    b.arg(v)
24858                        .arg(k)
24859                        .arg(&a)
24860                        .arg(&gcum)
24861                        .arg(&mut u)
24862                        .arg(&mut w)
24863                        .arg(&hi)
24864                        .arg(&ti)
24865                        .arg(&ci);
24866                    unsafe {
24867                        b.launch(cfg)?;
24868                    }
24869                }
24870            }
24871        }
24872        Ok((gcum, p, u, w))
24873    }
24874
24875    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
24876    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
24877    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
24878    pub fn gdn_db_on() -> bool {
24879        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
24880    }
24881
24882    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
24883    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
24884    /// DEFAULT ON for sm_120a builds too (lane/moeprime-nvfp4-direct, 2026-08-21): the pair
24885    /// was qualified on 90a only and left env-opt-in elsewhere; measured on Blackwell it
24886    /// wins on BOTH rigs — one RTX PRO 6000 (ornith15 pp14715 12,036 -> 12,751/12,957,
24887    /// +6-8%, both orders) and the local 5090 (q38-27b pp6435 1,397/1,429 -> 1,427/1,446,
24888    /// both orders) — with kernel-check/run-gen/margin-gate/run-spec green under the flag.
24889    /// bf16 HMMA (m16n8k16) is sm_80-class PTX; only the wgmma nest stays Hopper-gated.
24890    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
24891        !portable_mma_gated()
24892            && c == 32
24893            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
24894                Ok("1") => true,
24895                Ok("0") => false,
24896                _ => gdn_mma_default_on(),
24897            }
24898    }
24899
24900    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
24901    /// mma config; same per-call env read discipline). HARD-gated to the Hopper build:
24902    /// the wgmma asm bodies exist only at __CUDA_ARCH__ == 900 (MEMRA_K45_REAL,
24903    /// wgmma_common.cuh) — on every other arch the kernel compiles EMPTY, so an env
24904    /// force would silently produce garbage. Required since the sm_120a mma default
24905    /// flip made MEMRA_GDN_WGMMA=1 alone reach this branch there.
24906    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
24907        cfg!(memra_hopper_mma)
24908            && self.gdn_mma_enabled(c)
24909            && std::env::var("MEMRA_GDN_WGMMA").as_deref() != Ok("0")
24910    }
24911
24912    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
24913    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
24914    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
24915    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
24916    #[allow(clippy::too_many_arguments)]
24917    pub fn ssm_conv1d_gdn_state_pad(
24918        &self,
24919        qkv_tm: &cudarc::driver::CudaView<f32>,
24920        conv_state: &mut CudaSlice<f32>,
24921        w: &CudaSlice<f32>,
24922        q_g: &mut CudaSlice<f32>,
24923        k_g: &mut CudaSlice<f32>,
24924        v_g: &mut CudaSlice<f32>,
24925        conv_dim: usize,
24926        t: usize,
24927        d_conv: usize,
24928        d_state: usize,
24929        num_v: usize,
24930        num_k: usize,
24931        key_dim: usize,
24932        hk: usize,
24933        pad_len: Option<&CudaSlice<i32>>,
24934    ) -> Result<(), Box<dyn std::error::Error>> {
24935        assert!(
24936            t >= d_conv - 1,
24937            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
24938        );
24939        {
24940            let f = self.func("ssm_conv1d_gdn_state_f32");
24941            let cfg = LaunchConfig {
24942                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
24943                block_dim: (256, 1, 1),
24944                shared_mem_bytes: 0,
24945            };
24946            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24947            let (ds, nv, nk, kd, hki) = (
24948                d_state as i32,
24949                num_v as i32,
24950                num_k as i32,
24951                key_dim as i32,
24952                hk as i32,
24953            );
24954            let __s_b = self.gpu.stream();
24955            let mut b = __s_b.launch_builder(&f);
24956            b.arg(qkv_tm)
24957                .arg(&*conv_state)
24958                .arg(w)
24959                .arg(q_g)
24960                .arg(k_g)
24961                .arg(v_g)
24962                .arg(&cd)
24963                .arg(&ti)
24964                .arg(&dc)
24965                .arg(&ds)
24966                .arg(&nv)
24967                .arg(&nk)
24968                .arg(&kd)
24969                .arg(&hki);
24970            unsafe {
24971                b.launch(cfg)?;
24972            }
24973        }
24974        match pad_len {
24975            Some(len_d) => {
24976                let f = self.func("ssm_conv_ring_update_dev_f32");
24977                let n = conv_dim * (d_conv - 1);
24978                let cfg = LaunchConfig::for_num_elems(n as u32);
24979                let (cd, dc) = (conv_dim as i32, d_conv as i32);
24980                let __s_b = self.gpu.stream();
24981                let mut b = __s_b.launch_builder(&f);
24982                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
24983                unsafe {
24984                    b.launch(cfg)?;
24985                }
24986            }
24987            None => {
24988                let f = self.func("ssm_conv_ring_update_f32");
24989                let n = conv_dim * (d_conv - 1);
24990                let cfg = LaunchConfig::for_num_elems(n as u32);
24991                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
24992                let __s_b = self.gpu.stream();
24993                let mut b = __s_b.launch_builder(&f);
24994                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
24995                unsafe {
24996                    b.launch(cfg)?;
24997                }
24998            }
24999        }
25000        Ok(())
25001    }
25002
25003    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
25004    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
25005    /// K2/K3 can write them.
25006    pub fn gdn_chunk_alloc(
25007        &self,
25008        n_head: usize,
25009        t: usize,
25010        c: usize,
25011        hk: usize,
25012    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
25013        const D: usize = 128;
25014        assert!(
25015            c == 32,
25016            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
25017        );
25018        let h = n_head;
25019        let nc = (t + c - 1) / c;
25020        Ok(GdnChunkBufs {
25021            gcum: self.uninit(t * h)?,
25022            a: self.uninit(nc * h * c * c)?,
25023            p: self.uninit(nc * h * c * c)?,
25024            u: self.uninit(nc * h * c * D)?,
25025            w: self.uninit(nc * h * c * D)?,
25026            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
25027            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
25028            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
25029            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
25030            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
25031            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
25032            o: self.uninit(D * h * t)?,
25033            t,
25034            nc,
25035        })
25036    }
25037
25038    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
25039    pub fn f32_to_bf16_v(
25040        &self,
25041        x: &cudarc::driver::CudaView<f32>,
25042        dst: &mut CudaSlice<u8>,
25043        n: usize,
25044    ) -> Result<(), Box<dyn std::error::Error>> {
25045        let f = self.func("f32_to_bf16_bulk");
25046        let ni = n as i64;
25047        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
25048        let __s_b = self.gpu.stream();
25049        let mut b = __s_b.launch_builder(&f);
25050        b.arg(x).arg(dst).arg(&ni);
25051        unsafe {
25052            b.launch(cfg)?;
25053        }
25054        Ok(())
25055    }
25056
25057    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
25058    pub fn f32_to_bf16_into(
25059        &self,
25060        x: &CudaSlice<f32>,
25061        dst: &mut CudaSlice<u8>,
25062        n: usize,
25063    ) -> Result<(), Box<dyn std::error::Error>> {
25064        let f = self.func("f32_to_bf16_bulk");
25065        let ni = n as i64;
25066        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
25067        let __s_b = self.gpu.stream();
25068        let mut b = __s_b.launch_builder(&f);
25069        b.arg(x).arg(dst).arg(&ni);
25070        unsafe {
25071            b.launch(cfg)?;
25072        }
25073        Ok(())
25074    }
25075
25076    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
25077    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
25078    pub fn gdn_chunk_k123_vl8(
25079        &self,
25080        seqs: &[GdnSeqVl],
25081        n_head: usize,
25082        hk: usize,
25083        wq: Option<&GdnWVl8>,
25084    ) -> Result<(), Box<dyn std::error::Error>> {
25085        let b = seqs.len();
25086        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
25087        let mut packed = [GdnSeqVl::default(); 8];
25088        packed[..b].copy_from_slice(seqs);
25089        let v = GdnVl8(packed);
25090        let (hi, ci) = (n_head as i32, 32i32);
25091        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
25092        {
25093            let f = self.func("gdn_chunk_cumgate_vl");
25094            let cfg = LaunchConfig {
25095                grid_dim: (max_nc, n_head as u32, b as u32),
25096                block_dim: (32, 1, 1),
25097                shared_mem_bytes: 0,
25098            };
25099            let __s_lb = self.gpu.stream();
25100            let mut lb = __s_lb.launch_builder(&f);
25101            lb.arg(&v).arg(&hi).arg(&ci);
25102            unsafe {
25103                lb.launch(cfg)?;
25104            }
25105        }
25106        let hki = hk as i32;
25107        if let Some(w) = wq {
25108            // K2-wgmma vl twin (writes A + pre-masked Pb16)
25109            let f = self.func("gdn_k2_wgmma_vl");
25110            let cfg = LaunchConfig {
25111                grid_dim: (max_nc, n_head as u32, b as u32),
25112                block_dim: (128, 1, 1),
25113                shared_mem_bytes: 0,
25114            };
25115            let __s_lb = self.gpu.stream();
25116            let mut lb = __s_lb.launch_builder(&f);
25117            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
25118            unsafe {
25119                lb.launch(cfg)?;
25120            }
25121        } else {
25122            let f = self.func("gdn_chunk_attn_vl");
25123            f.set_attribute(
25124                CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
25125                GDN_K2_DYNAMIC_SHARED_BYTES as i32,
25126            )?;
25127            let cfg = LaunchConfig {
25128                grid_dim: (max_nc, n_head as u32, b as u32),
25129                block_dim: (256, 1, 1),
25130                shared_mem_bytes: GDN_K2_DYNAMIC_SHARED_BYTES,
25131            };
25132            let __s_lb = self.gpu.stream();
25133            let mut lb = __s_lb.launch_builder(&f);
25134            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
25135            unsafe {
25136                lb.launch(cfg)?;
25137            }
25138        }
25139        {
25140            let f = self.func("gdn_chunk_solve32_vl");
25141            let cfg = LaunchConfig {
25142                grid_dim: (max_nc, n_head as u32, b as u32),
25143                block_dim: (256, 1, 1),
25144                shared_mem_bytes: 0,
25145            };
25146            let __s_lb = self.gpu.stream();
25147            let mut lb = __s_lb.launch_builder(&f);
25148            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
25149            unsafe {
25150                lb.launch(cfg)?;
25151            }
25152        }
25153        Ok(())
25154    }
25155
25156    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
25157    /// fused gate-prep, 5 launches for every sequence (per-element math identical
25158    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
25159    #[allow(clippy::too_many_arguments)]
25160    pub fn gdn_prep_vl8(
25161        &self,
25162        seqs: &[GdnPrepVl],
25163        conv_w: &CudaSlice<f32>,
25164        dt_bias: &CudaSlice<f32>,
25165        a: &CudaSlice<f32>,
25166        conv_dim: usize,
25167        d_conv: usize,
25168        d_state: usize,
25169        num_v: usize,
25170        num_k: usize,
25171        key_dim: usize,
25172        hk: usize,
25173        eps: f32,
25174    ) -> Result<(), Box<dyn std::error::Error>> {
25175        let b = seqs.len();
25176        assert!(b >= 1 && b <= 8);
25177        let mut packed = [GdnPrepVl::default(); 8];
25178        packed[..b].copy_from_slice(seqs);
25179        let v = GdnPrepVl8(packed);
25180        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
25181        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
25182        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
25183        assert!(
25184            conv_fuse || hk == num_v,
25185            "de-broadcast requires the fused conv"
25186        );
25187        if conv_fuse {
25188            let f = self.func("ssm_conv1d_gdn_state_vl");
25189            let cfg = LaunchConfig {
25190                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
25191                block_dim: (256, 1, 1),
25192                shared_mem_bytes: 0,
25193            };
25194            let (dsi, nvi, nki, kdi, hki) = (
25195                d_state as i32,
25196                num_v as i32,
25197                num_k as i32,
25198                key_dim as i32,
25199                hk as i32,
25200            );
25201            let __s_lb = self.gpu.stream();
25202            let mut lb = __s_lb.launch_builder(&f);
25203            lb.arg(&v)
25204                .arg(conv_w)
25205                .arg(&cdi)
25206                .arg(&dci)
25207                .arg(&dsi)
25208                .arg(&nvi)
25209                .arg(&nki)
25210                .arg(&kdi)
25211                .arg(&hki);
25212            unsafe {
25213                lb.launch(cfg)?;
25214            }
25215        } else {
25216            let f = self.func("ssm_conv1d_tm_state_vl");
25217            let cfg = LaunchConfig {
25218                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
25219                block_dim: (256, 1, 1),
25220                shared_mem_bytes: 0,
25221            };
25222            let __s_lb = self.gpu.stream();
25223            let mut lb = __s_lb.launch_builder(&f);
25224            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
25225            unsafe {
25226                lb.launch(cfg)?;
25227            }
25228        }
25229        {
25230            let f = self.func("ssm_conv_ring_update_vl");
25231            let n = (conv_dim * (d_conv - 1)) as u32;
25232            let cfg = LaunchConfig {
25233                grid_dim: (n.div_ceil(256), 1, b as u32),
25234                block_dim: (256, 1, 1),
25235                shared_mem_bytes: 0,
25236            };
25237            let __s_lb = self.gpu.stream();
25238            let mut lb = __s_lb.launch_builder(&f);
25239            lb.arg(&v).arg(&cdi).arg(&dci);
25240            unsafe {
25241                lb.launch(cfg)?;
25242            }
25243        }
25244        if !conv_fuse {
25245            let f = self.func("qkv_to_gdn_repack_vl");
25246            let n = max_t * (num_v * d_state) as u32;
25247            let cfg = LaunchConfig {
25248                grid_dim: (n.div_ceil(256), 1, b as u32),
25249                block_dim: (256, 1, 1),
25250                shared_mem_bytes: 0,
25251            };
25252            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
25253            let __s_lb = self.gpu.stream();
25254            let mut lb = __s_lb.launch_builder(&f);
25255            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
25256            unsafe {
25257                lb.launch(cfg)?;
25258            }
25259        }
25260        if Self::l2_v2_on(d_state) {
25261            let f = self.func("gdn_l2_v2_vl");
25262            let cfg = LaunchConfig {
25263                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
25264                block_dim: (256, 1, 1),
25265                shared_mem_bytes: 0,
25266            };
25267            let (dsi, nvi) = (d_state as i32, hk as i32);
25268            let __s_lb = self.gpu.stream();
25269            let mut lb = __s_lb.launch_builder(&f);
25270            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
25271            unsafe {
25272                lb.launch(cfg)?;
25273            }
25274        } else {
25275            let f = self.func("gdn_l2_vl");
25276            let cfg = LaunchConfig {
25277                grid_dim: (max_t * hk as u32, 2, b as u32),
25278                block_dim: (256, 1, 1),
25279                shared_mem_bytes: 0,
25280            };
25281            let (dsi, nvi) = (d_state as i32, hk as i32);
25282            let __s_lb = self.gpu.stream();
25283            let mut lb = __s_lb.launch_builder(&f);
25284            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
25285            unsafe {
25286                lb.launch(cfg)?;
25287            }
25288        }
25289        {
25290            let f = self.func("gdn_gate_prep_vl");
25291            let n = max_t * num_v as u32;
25292            let cfg = LaunchConfig {
25293                grid_dim: (n.div_ceil(256), 1, b as u32),
25294                block_dim: (256, 1, 1),
25295                shared_mem_bytes: 0,
25296            };
25297            let nvi = num_v as i32;
25298            let __s_lb = self.gpu.stream();
25299            let mut lb = __s_lb.launch_builder(&f);
25300            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
25301            unsafe {
25302                lb.launch(cfg)?;
25303            }
25304        }
25305        Ok(())
25306    }
25307
25308    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
25309    pub fn gdn_mirror_vl8(
25310        &self,
25311        seqs: &[GdnSeqVl],
25312        n_head: usize,
25313        which: i32,
25314        hk: usize,
25315    ) -> Result<(), Box<dyn std::error::Error>> {
25316        let b = seqs.len();
25317        assert!(b >= 1 && b <= 8);
25318        let mut packed = [GdnSeqVl::default(); 8];
25319        packed[..b].copy_from_slice(seqs);
25320        let v = GdnVl8(packed);
25321        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
25322        let max_n = seqs
25323            .iter()
25324            .map(|s| {
25325                if which == 0 {
25326                    s.t as i64 * ept as i64
25327                } else {
25328                    s.nc as i64 * ept as i64 * 32
25329                }
25330            })
25331            .max()
25332            .unwrap();
25333        let f = self.func("gdn_mirror_vl");
25334        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
25335        let cfg = LaunchConfig {
25336            grid_dim: (blocks, 1, b as u32),
25337            block_dim: (256, 1, 1),
25338            shared_mem_bytes: 0,
25339        };
25340        let __s_lb = self.gpu.stream();
25341        let mut lb = __s_lb.launch_builder(&f);
25342        lb.arg(&v).arg(&ept).arg(&which);
25343        unsafe {
25344            lb.launch(cfg)?;
25345        }
25346        Ok(())
25347    }
25348
25349    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
25350    pub fn gdn_tail_vl8(
25351        &self,
25352        seqs: &[GdnPrepVl],
25353        norm_w: &CudaSlice<f32>,
25354        d_state: usize,
25355        num_v: usize,
25356        eps: f32,
25357    ) -> Result<(), Box<dyn std::error::Error>> {
25358        let b = seqs.len();
25359        assert!(b >= 1 && b <= 8);
25360        let mut packed = [GdnPrepVl::default(); 8];
25361        packed[..b].copy_from_slice(seqs);
25362        let v = GdnPrepVl8(packed);
25363        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
25364        let f = self.func("gated_rmsnorm_f16out_vl");
25365        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
25366        let cfg = LaunchConfig {
25367            grid_dim: (max_t * num_v as u32, 1, b as u32),
25368            block_dim: (128, 1, 1),
25369            shared_mem_bytes: 0,
25370        };
25371        let (dsi, nvi) = (d_state as i32, num_v as i32);
25372        let __s_lb = self.gpu.stream();
25373        let mut lb = __s_lb.launch_builder(&f);
25374        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
25375        unsafe {
25376            lb.launch(cfg)?;
25377        }
25378        Ok(())
25379    }
25380
25381    /// Raw device address helpers for the varlen by-value arg struct (single-stream
25382    /// launches; every buffer outlives the call — the f16 FFI discipline).
25383    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
25384        use cudarc::driver::DevicePtr;
25385        let s = self.gpu.stream();
25386        let (p, _g) = x.device_ptr(&s);
25387        p as u64
25388    }
25389    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
25390        use cudarc::driver::DevicePtrMut;
25391        let s = self.gpu.stream();
25392        let (p, _g) = x.device_ptr_mut(&s);
25393        p as u64
25394    }
25395    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
25396        use cudarc::driver::DevicePtr;
25397        let s = self.gpu.stream();
25398        let (p, _g) = x.device_ptr(&s);
25399        p as u64
25400    }
25401    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
25402        use cudarc::driver::DevicePtr;
25403        let s = self.gpu.stream();
25404        let (p, _g) = x.device_ptr(&s);
25405        p as u64
25406    }
25407
25408    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
25409    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
25410    /// launches, so this is strictly bit-gateable against them).
25411    pub fn gdn_chunk_vl8(
25412        &self,
25413        seqs: &[GdnSeqVl],
25414        n_head: usize,
25415        scale: f32,
25416        hk: usize,
25417        wq: Option<&GdnWVl8>,
25418    ) -> Result<(), Box<dyn std::error::Error>> {
25419        const NSPLIT: u32 = 4;
25420        let b = seqs.len();
25421        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
25422        let mut packed = [GdnSeqVl::default(); 8];
25423        packed[..b].copy_from_slice(seqs);
25424        let v = GdnVl8(packed);
25425        let (hi, ci) = (n_head as i32, 32i32);
25426        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
25427        let hki = hk as i32;
25428        if let Some(w) = wq {
25429            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
25430            let f = self.func("gdn_k45_wgmma_vl");
25431            let cfg = LaunchConfig {
25432                grid_dim: (n_head as u32, NSPLIT, b as u32),
25433                block_dim: (256, 1, 1),
25434                shared_mem_bytes: 0,
25435            };
25436            let __s_lb = self.gpu.stream();
25437            let mut lb = __s_lb.launch_builder(&f);
25438            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
25439            unsafe {
25440                lb.launch(cfg)?;
25441            }
25442            let _ = max_nc;
25443            return Ok(());
25444        }
25445        {
25446            let f = self.func("gdn_chunk_state_mma_vl");
25447            let cfg = LaunchConfig {
25448                grid_dim: (n_head as u32, NSPLIT, b as u32),
25449                block_dim: (256, 1, 1),
25450                shared_mem_bytes: 0,
25451            };
25452            let __s_lb = self.gpu.stream();
25453            let mut lb = __s_lb.launch_builder(&f);
25454            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
25455            unsafe {
25456                lb.launch(cfg)?;
25457            }
25458        }
25459        {
25460            let f = self.func("gdn_chunk_output_mma_vl");
25461            let cfg = LaunchConfig {
25462                grid_dim: (max_nc, n_head as u32, b as u32),
25463                block_dim: (256, 1, 1),
25464                shared_mem_bytes: 0,
25465            };
25466            let __s_lb = self.gpu.stream();
25467            let mut lb = __s_lb.launch_builder(&f);
25468            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
25469            unsafe {
25470                lb.launch(cfg)?;
25471            }
25472        }
25473        Ok(())
25474    }
25475    pub fn gdn_scan_chunked(
25476        &self,
25477        q: &CudaSlice<f32>,
25478        k: &CudaSlice<f32>,
25479        v: &CudaSlice<f32>,
25480        g: &CudaSlice<f32>,
25481        beta: &CudaSlice<f32>,
25482        kb16_pre: Option<&CudaSlice<u8>>,
25483        qb16_pre: Option<&CudaSlice<u8>>,
25484        state_in: &CudaSlice<f32>,
25485        state_out: &mut CudaSlice<f32>,
25486        o: &mut CudaSlice<f32>,
25487        n_head: usize,
25488        t: usize,
25489        scale: f32,
25490        c: usize,
25491        hk: usize,
25492    ) -> Result<(), Box<dyn std::error::Error>> {
25493        const D: usize = 128;
25494        const NSPLIT: u32 = 4;
25495        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
25496        let h = n_head;
25497        let nc = (t + c - 1) / c;
25498        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
25499        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
25500        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
25501        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
25502        // default mirrors gdn_mma_enabled (incl. the sm_120a-build ON, lane/moeprime-nvfp4-direct)
25503        let gdn_mma_pre = !portable_mma_gated()
25504            && c == 32
25505            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
25506                Ok("1") => true,
25507                Ok("0") => false,
25508                _ => gdn_mma_default_on(),
25509            };
25510        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
25511            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
25512        } else {
25513            None
25514        };
25515        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
25516        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
25517        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
25518        // mirrors gdn_wgmma_on: hard Hopper-build gate (empty wgmma bodies elsewhere)
25519        let gdn_wgmma_pre = cfg!(memra_hopper_mma)
25520            && gdn_mma_pre
25521            && std::env::var("MEMRA_GDN_WGMMA").as_deref() != Ok("0");
25522        let nk = t * hk * D;
25523        let mut kb16_local: Option<CudaSlice<u8>> = None;
25524        if gdn_mma_pre && kb16_pre.is_none() {
25525            let mut kb = self.alloc_u8_uninit(nk * 2)?;
25526            let f = self.func("f32_to_bf16_bulk");
25527            let n2 = nk as i64;
25528            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
25529            let __s_b = self.gpu.stream();
25530            let mut b = __s_b.launch_builder(&f);
25531            b.arg(k).arg(&mut kb).arg(&n2);
25532            unsafe {
25533                b.launch(cfg2)?;
25534            }
25535            kb16_local = Some(kb);
25536        }
25537        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
25538        if let Some(kb) = kb16_pre {
25539            assert!(kb.len() >= nk * 2, "kb16_pre too small");
25540        }
25541        let mut qb16: Option<CudaSlice<u8>> = None;
25542        let mut pb16: Option<CudaSlice<u8>> = None;
25543        if gdn_wgmma_pre {
25544            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
25545            // the standalone bulk cvt only serves callers without the prep mirror.
25546            if qb16_pre.is_none() {
25547                let mut qb = self.alloc_u8_uninit(nk * 2)?;
25548                let f = self.func("f32_to_bf16_bulk");
25549                let n2 = nk as i64;
25550                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
25551                let __s_b = self.gpu.stream();
25552                let mut b = __s_b.launch_builder(&f);
25553                b.arg(q).arg(&mut qb).arg(&n2);
25554                unsafe {
25555                    b.launch(cfg2)?;
25556                }
25557                qb16 = Some(qb);
25558            } else if let Some(qb) = qb16_pre {
25559                assert!(qb.len() >= nk * 2, "qb16_pre too small");
25560            }
25561            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
25562        }
25563        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
25564        let k2w = if gdn_wgmma_pre {
25565            Some((
25566                *qb16_ref0.as_ref().unwrap(),
25567                *kb16_ref0.as_ref().unwrap(),
25568                pb16.as_mut().unwrap(),
25569            ))
25570        } else {
25571            None
25572        };
25573        let (gcum, p, u, w) =
25574            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
25575        let _ = &w;
25576        let mut y = self.uninit(nc * h * c * D)?;
25577        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
25578        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
25579        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
25580        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
25581        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
25582        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
25583        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
25584        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
25585        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
25586        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
25587        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
25588        // default mirrors gdn_mma_enabled (incl. the sm_120a-build ON) — all three read
25589        // sites must agree or the pre-work arms while the scan takes the scalar route.
25590        let gdn_mma = !portable_mma_gated()
25591            && c == 32
25592            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
25593                Ok("1") => true,
25594                Ok("0") => false,
25595                _ => gdn_mma_default_on(),
25596            };
25597        if gdn_mma {
25598            let wb16 = wb16_pre
25599                .take()
25600                .expect("mma path pre-allocates wb16 (K3 store fold)");
25601            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
25602            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
25603            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
25604            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
25605            // pass runs inside the persistent-M kernel; Y and Ssnap are never
25606            // materialized. New numeric class (gk folds into k^T instead of ys) —
25607            // explicit opt-in until the state-carry battery promotes it. Env read per
25608            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
25609            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
25610            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
25611            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
25612            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
25613            if gdn_wgmma_pre {
25614                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
25615                let qb16 = qb16_ref0.unwrap();
25616                let pb16 = pb16.as_ref().unwrap();
25617                {
25618                    let f = self.func("gdn_k45_wgmma");
25619                    let cfg = LaunchConfig {
25620                        grid_dim: (h as u32, 4, 1),
25621                        block_dim: (256, 1, 1),
25622                        shared_mem_bytes: 0,
25623                    };
25624                    let hki = hk as i32;
25625                    let __s_b = self.gpu.stream();
25626                    let mut b = __s_b.launch_builder(&f);
25627                    b.arg(kb16_ref)
25628                        .arg(&gcum)
25629                        .arg(beta)
25630                        .arg(&u)
25631                        .arg(&wb16)
25632                        .arg(qb16)
25633                        .arg(pb16)
25634                        .arg(o)
25635                        .arg(&scale)
25636                        .arg(state_in)
25637                        .arg(&mut *state_out)
25638                        .arg(&hi)
25639                        .arg(&ti)
25640                        .arg(&ci)
25641                        .arg(&hki);
25642                    unsafe {
25643                        b.launch(cfg)?;
25644                    }
25645                }
25646                return Ok(());
25647            }
25648            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
25649            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
25650            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
25651            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
25652            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
25653            {
25654                let f = self.func("gdn_chunk_state_mma");
25655                let cfg = LaunchConfig {
25656                    grid_dim: (h as u32, NSPLIT, 1),
25657                    block_dim: (256, 1, 1),
25658                    shared_mem_bytes: 0,
25659                };
25660                let hki = hk as i32;
25661                let __s_b = self.gpu.stream();
25662                let mut b = __s_b.launch_builder(&f);
25663                b.arg(kb16_ref)
25664                    .arg(&gcum)
25665                    .arg(beta)
25666                    .arg(&u)
25667                    .arg(&wb16)
25668                    .arg(&mut y16)
25669                    .arg(&mut ssnap16)
25670                    .arg(state_in)
25671                    .arg(&mut *state_out)
25672                    .arg(&hi)
25673                    .arg(&ti)
25674                    .arg(&ci)
25675                    .arg(&hki);
25676                unsafe {
25677                    b.launch(cfg)?;
25678                }
25679            }
25680            {
25681                // K5-mma (bf16 St/Y consumers)
25682                let f = self.func("gdn_chunk_output_mma");
25683                let jt = ((c + 31) / 32) as u32;
25684                let cfg = LaunchConfig {
25685                    grid_dim: (nc as u32, h as u32, jt),
25686                    block_dim: (256, 1, 1),
25687                    shared_mem_bytes: 0,
25688                };
25689                let hki = hk as i32;
25690                let __s_b = self.gpu.stream();
25691                let mut b = __s_b.launch_builder(&f);
25692                b.arg(q)
25693                    .arg(&gcum)
25694                    .arg(&p)
25695                    .arg(&y16)
25696                    .arg(&ssnap16)
25697                    .arg(o)
25698                    .arg(&hi)
25699                    .arg(&ti)
25700                    .arg(&ci)
25701                    .arg(&scale)
25702                    .arg(&hki);
25703                unsafe {
25704                    b.launch(cfg)?;
25705                }
25706            }
25707            return Ok(());
25708        }
25709        {
25710            // K4 (sequential over chunks inside; blocks col-partition the state)
25711            let f = self.func("gdn_chunk_state_f32");
25712            let cfg = LaunchConfig {
25713                grid_dim: (h as u32, NSPLIT, 1),
25714                block_dim: (256, 1, 1),
25715                shared_mem_bytes: 0,
25716            };
25717            let __s_b = self.gpu.stream();
25718            let mut b = __s_b.launch_builder(&f);
25719            b.arg(k)
25720                .arg(&gcum)
25721                .arg(beta)
25722                .arg(&u)
25723                .arg(&w)
25724                .arg(&mut y)
25725                .arg(&mut ssnap)
25726                .arg(state_in)
25727                .arg(&mut *state_out)
25728                .arg(&hi)
25729                .arg(&ti)
25730                .arg(&ci);
25731            unsafe {
25732                b.launch(cfg)?;
25733            }
25734        }
25735        {
25736            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
25737            let f = self.func("gdn_chunk_output_f32");
25738            let jt = ((c + 31) / 32) as u32;
25739            let cfg = LaunchConfig {
25740                grid_dim: (nc as u32, h as u32, jt),
25741                block_dim: (256, 1, 1),
25742                shared_mem_bytes: 0,
25743            };
25744            let __s_b = self.gpu.stream();
25745            let mut b = __s_b.launch_builder(&f);
25746            b.arg(q)
25747                .arg(&gcum)
25748                .arg(&p)
25749                .arg(&y)
25750                .arg(&ssnap)
25751                .arg(o)
25752                .arg(&hi)
25753                .arg(&ti)
25754                .arg(&ci)
25755                .arg(&scale);
25756            unsafe {
25757                b.launch(cfg)?;
25758            }
25759        }
25760        Ok(())
25761    }
25762
25763    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
25764    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
25765    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
25766    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
25767    ///
25768    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
25769    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
25770    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
25771    #[allow(clippy::too_many_arguments)]
25772    #[allow(clippy::too_many_arguments)]
25773    pub fn gdn_scan_prefill(
25774        &self,
25775        q: &CudaSlice<f32>,
25776        k: &CudaSlice<f32>,
25777        v: &CudaSlice<f32>,
25778        g: &CudaSlice<f32>,
25779        beta: &CudaSlice<f32>,
25780        kb16_pre: Option<&CudaSlice<u8>>,
25781        qb16_pre: Option<&CudaSlice<u8>>,
25782        state_in: &CudaSlice<f32>,
25783        state_out: &mut CudaSlice<f32>,
25784        o: &mut CudaSlice<f32>,
25785        n_head: usize,
25786        t: usize,
25787        scale: f32,
25788        hk: usize,
25789    ) -> Result<(), Box<dyn std::error::Error>> {
25790        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
25791            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
25792            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
25793        }
25794        if Self::gdn_chunked_enabled() && t >= 16 {
25795            self.gdn_scan_chunked(
25796                q,
25797                k,
25798                v,
25799                g,
25800                beta,
25801                kb16_pre,
25802                qb16_pre,
25803                state_in,
25804                state_out,
25805                o,
25806                n_head,
25807                t,
25808                scale,
25809                Self::gdn_chunk_size(),
25810                hk,
25811            )
25812        } else {
25813            assert!(
25814                hk == n_head,
25815                "s128 scan is broadcast-only (prep guarantees by predicate)"
25816            );
25817            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
25818        }
25819    }
25820
25821    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
25822    #[allow(clippy::too_many_arguments)]
25823    fn gdn_scan_diff(
25824        &self,
25825        q: &CudaSlice<f32>,
25826        k: &CudaSlice<f32>,
25827        v: &CudaSlice<f32>,
25828        g: &CudaSlice<f32>,
25829        beta: &CudaSlice<f32>,
25830        state_in: &CudaSlice<f32>,
25831        state_out: &mut CudaSlice<f32>,
25832        o: &mut CudaSlice<f32>,
25833        n_head: usize,
25834        t: usize,
25835        scale: f32,
25836    ) -> Result<(), Box<dyn std::error::Error>> {
25837        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
25838        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
25839        let mut o_c = self.uninit(o.len())?;
25840        let mut st_c = self.uninit(state_out.len())?;
25841        self.gdn_scan_chunked(
25842            q,
25843            k,
25844            v,
25845            g,
25846            beta,
25847            None,
25848            None,
25849            state_in,
25850            &mut st_c,
25851            &mut o_c,
25852            n_head,
25853            t,
25854            scale,
25855            Self::gdn_chunk_size(),
25856            n_head,
25857        )?;
25858        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
25859        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
25860        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
25861        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
25862            let mut max_abs = 0f32;
25863            let mut max_rel = 0f32;
25864            let mut sum_rel = 0f64;
25865            for (x, y) in a.iter().zip(b) {
25866                let ad = (x - y).abs();
25867                let rel = ad / x.abs().max(y.abs()).max(1e-3);
25868                if ad > max_abs {
25869                    max_abs = ad;
25870                }
25871                if rel > max_rel {
25872                    max_rel = rel;
25873                }
25874                sum_rel += rel as f64;
25875            }
25876            (max_abs, max_rel, sum_rel / a.len() as f64)
25877        };
25878        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
25879        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
25880        println!(
25881            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
25882                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
25883            Self::gdn_chunk_size()
25884        );
25885        Ok(())
25886    }
25887
25888    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
25889    pub fn gdn_glog(
25890        &self,
25891        alpha: &CudaSlice<f32>,
25892        dt_bias: &CudaSlice<f32>,
25893        a: &CudaSlice<f32>,
25894        g_log: &mut CudaSlice<f32>,
25895        n_head: usize,
25896        t: usize,
25897    ) -> Result<(), Box<dyn std::error::Error>> {
25898        let f = self.func("gdn_glog_f32");
25899        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
25900        let (h, ti) = (n_head as i32, t as i32);
25901        let __s_b = self.gpu.stream();
25902        let mut b = __s_b.launch_builder(&f);
25903        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
25904        unsafe {
25905            b.launch(cfg)?;
25906        }
25907        Ok(())
25908    }
25909
25910    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
25911    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
25912    pub fn sigmoid_v(
25913        &self,
25914        x: &cudarc::driver::CudaView<f32>,
25915        y: &mut CudaSlice<f32>,
25916        n: usize,
25917    ) -> Result<(), Box<dyn std::error::Error>> {
25918        let f = self.func("sigmoid_f32");
25919        let cfg = LaunchConfig::for_num_elems(n as u32);
25920        let ni = n as i32;
25921        let __s_b = self.gpu.stream();
25922        let mut b = __s_b.launch_builder(&f);
25923        b.arg(x).arg(y).arg(&ni);
25924        unsafe {
25925            b.launch(cfg)?;
25926        }
25927        Ok(())
25928    }
25929
25930    pub fn gdn_glog_v(
25931        &self,
25932        alpha: &cudarc::driver::CudaView<f32>,
25933        dt_bias: &CudaSlice<f32>,
25934        a: &CudaSlice<f32>,
25935        g_log: &mut CudaSlice<f32>,
25936        n_head: usize,
25937        t: usize,
25938    ) -> Result<(), Box<dyn std::error::Error>> {
25939        let f = self.func("gdn_glog_f32");
25940        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
25941        let (h, ti) = (n_head as i32, t as i32);
25942        let __s_b = self.gpu.stream();
25943        let mut b = __s_b.launch_builder(&f);
25944        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
25945        unsafe {
25946            b.launch(cfg)?;
25947        }
25948        Ok(())
25949    }
25950
25951    pub fn sigmoid(
25952        &self,
25953        x: &CudaSlice<f32>,
25954        y: &mut CudaSlice<f32>,
25955        n: usize,
25956    ) -> Result<(), Box<dyn std::error::Error>> {
25957        let f = self.func("sigmoid_f32");
25958        let cfg = LaunchConfig::for_num_elems(n as u32);
25959        let ni = n as i32;
25960        let __s_b = self.gpu.stream();
25961        let mut b = __s_b.launch_builder(&f);
25962        b.arg(x).arg(y).arg(&ni);
25963        unsafe {
25964            b.launch(cfg)?;
25965        }
25966        Ok(())
25967    }
25968
25969    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
25970    /// (replaces sigmoid + mul + convert). Bit-identical class.
25971    pub fn sig_mul_f16out(
25972        &self,
25973        a: &CudaSlice<f32>,
25974        g: &CudaSlice<f32>,
25975        dst: &mut CudaSlice<f32>,
25976        dst16: &mut CudaSlice<u8>,
25977        n: usize,
25978    ) -> Result<(), Box<dyn std::error::Error>> {
25979        let f = self.func("sig_mul_f16out_f32");
25980        let cfg = LaunchConfig::for_num_elems(n as u32);
25981        let ni = n as i32;
25982        let __s_b = self.gpu.stream();
25983        let mut b = __s_b.launch_builder(&f);
25984        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
25985        unsafe {
25986            b.launch(cfg)?;
25987        }
25988        Ok(())
25989    }
25990
25991    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
25992    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
25993    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
25994    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
25995    ///
25996    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
25997    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
25998    /// applies the wrong number of distinct gate values.
25999    #[allow(clippy::too_many_arguments)]
26000    pub fn attn_head_gate(
26001        &self,
26002        a: &CudaSlice<f32>,
26003        g: &CudaSlice<f32>,
26004        dst: &mut CudaSlice<f32>,
26005        dst16: Option<&mut CudaSlice<u8>>,
26006        head_dim: usize,
26007        n_head: usize,
26008        t: usize,
26009    ) -> Result<(), Box<dyn std::error::Error>> {
26010        let f = self.func("attn_head_gate_f32");
26011        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
26012        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
26013        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
26014        let d16: u64 = match dst16 {
26015            Some(d) => self.addr_u8(d),
26016            None => 0,
26017        };
26018        let __s_b = self.gpu.stream();
26019        let mut b = __s_b.launch_builder(&f);
26020        b.arg(a)
26021            .arg(g)
26022            .arg(dst)
26023            .arg(&d16)
26024            .arg(&hd)
26025            .arg(&nh)
26026            .arg(&ti);
26027        unsafe {
26028            b.launch(cfg)?;
26029        }
26030        Ok(())
26031    }
26032
26033    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
26034    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
26035    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
26036    ///
26037    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
26038    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
26039    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
26040    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
26041    #[allow(clippy::too_many_arguments)]
26042    pub fn swiglu_clamped_mul_scaled(
26043        &self,
26044        gate: &CudaSlice<f32>,
26045        up: &CudaSlice<f32>,
26046        gs: f32,
26047        us: f32,
26048        limit: f32,
26049        dst: &mut CudaSlice<f32>,
26050        n: usize,
26051    ) -> Result<(), Box<dyn std::error::Error>> {
26052        debug_assert!(
26053            limit > 1e-6,
26054            "swiglu_clamped needs a live limit; use silu_mul_scaled"
26055        );
26056        let f = self.func("swiglu_clamped_mul_scaled_f32");
26057        let cfg = LaunchConfig::for_num_elems(n as u32);
26058        let ni = n as i32;
26059        let __s_b = self.gpu.stream();
26060        let mut b = __s_b.launch_builder(&f);
26061        b.arg(gate)
26062            .arg(up)
26063            .arg(&gs)
26064            .arg(&us)
26065            .arg(&limit)
26066            .arg(dst)
26067            .arg(&ni);
26068        unsafe {
26069            b.launch(cfg)?;
26070        }
26071        Ok(())
26072    }
26073
26074    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
26075    pub fn gated_rmsnorm(
26076        &self,
26077        o: &CudaSlice<f32>,
26078        w: &CudaSlice<f32>,
26079        z: &CudaSlice<f32>,
26080        dst: &mut CudaSlice<f32>,
26081        ncols: usize,
26082        nrows: usize,
26083        eps: f32,
26084    ) -> Result<(), Box<dyn std::error::Error>> {
26085        let f = self.func("gated_rmsnorm_f32");
26086        let cfg = LaunchConfig {
26087            grid_dim: (nrows as u32, 1, 1),
26088            block_dim: (128, 1, 1),
26089            shared_mem_bytes: 0,
26090        };
26091        let (nc, e) = (ncols as i32, eps);
26092        let __s_b = self.gpu.stream();
26093        let mut b = __s_b.launch_builder(&f);
26094        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
26095        unsafe {
26096            b.launch(cfg)?;
26097        }
26098        Ok(())
26099    }
26100
26101    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
26102    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
26103    pub fn gated_rmsnorm_f16out(
26104        &self,
26105        o: &CudaSlice<f32>,
26106        w: &CudaSlice<f32>,
26107        z: &CudaSlice<f32>,
26108        dst: &mut CudaSlice<f32>,
26109        dst16: &mut CudaSlice<u8>,
26110        ncols: usize,
26111        nrows: usize,
26112        eps: f32,
26113    ) -> Result<(), Box<dyn std::error::Error>> {
26114        let f = self.func("gated_rmsnorm_f16out_f32");
26115        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
26116        let cfg = LaunchConfig {
26117            grid_dim: (nrows as u32, 1, 1),
26118            block_dim: (128, 1, 1),
26119            shared_mem_bytes: 0,
26120        };
26121        let (nc, e) = (ncols as i32, eps);
26122        let __s_b = self.gpu.stream();
26123        let mut b = __s_b.launch_builder(&f);
26124        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
26125        unsafe {
26126            b.launch(cfg)?;
26127        }
26128        Ok(())
26129    }
26130
26131    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
26132    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
26133    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
26134    #[allow(clippy::too_many_arguments)]
26135    pub fn add_rms_norm_zq8(
26136        &self,
26137        a: &CudaSlice<f32>,
26138        b_in: &CudaSlice<f32>,
26139        w: &CudaSlice<f32>,
26140        res: &mut CudaSlice<f32>,
26141        z: &mut CudaSlice<f32>,
26142        ncols: usize,
26143        nrows: usize,
26144        eps: f32,
26145    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
26146        assert!(ncols % 32 == 0);
26147        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
26148        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
26149        let f = self.func("add_rms_norm_zq8");
26150        let cfg = LaunchConfig {
26151            grid_dim: (nrows as u32, 1, 1),
26152            block_dim: (1024, 1, 1),
26153            shared_mem_bytes: 0,
26154        };
26155        let (nc, ep) = (ncols as i32, eps);
26156        let __s_b = self.gpu.stream();
26157        let mut b = __s_b.launch_builder(&f);
26158        b.arg(a)
26159            .arg(b_in)
26160            .arg(w)
26161            .arg(res)
26162            .arg(z)
26163            .arg(&mut q)
26164            .arg(&mut d)
26165            .arg(&nc)
26166            .arg(&ep);
26167        unsafe {
26168            b.launch(cfg)?;
26169        }
26170        Ok((q, d))
26171    }
26172
26173    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
26174    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
26175    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
26176    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
26177    pub fn gated_rmsnorm_zv(
26178        &self,
26179        o: &CudaSlice<f32>,
26180        w: &CudaSlice<f32>,
26181        z: &cudarc::driver::CudaView<f32>,
26182        dst: &mut CudaSlice<f32>,
26183        ncols: usize,
26184        nrows: usize,
26185        eps: f32,
26186    ) -> Result<(), Box<dyn std::error::Error>> {
26187        let f = self.func("gated_rmsnorm_f32");
26188        let cfg = LaunchConfig {
26189            grid_dim: (nrows as u32, 1, 1),
26190            block_dim: (128, 1, 1),
26191            shared_mem_bytes: 0,
26192        };
26193        let (nc, e) = (ncols as i32, eps);
26194        let __s_b = self.gpu.stream();
26195        let mut b = __s_b.launch_builder(&f);
26196        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
26197        unsafe {
26198            b.launch(cfg)?;
26199        }
26200        Ok(())
26201    }
26202
26203    pub fn gated_rmsnorm_f16out_zv(
26204        &self,
26205        o: &CudaSlice<f32>,
26206        w: &CudaSlice<f32>,
26207        z: &cudarc::driver::CudaView<f32>,
26208        dst: &mut CudaSlice<f32>,
26209        dst16: &mut CudaSlice<u8>,
26210        ncols: usize,
26211        nrows: usize,
26212        eps: f32,
26213    ) -> Result<(), Box<dyn std::error::Error>> {
26214        let f = self.func("gated_rmsnorm_f16out_f32");
26215        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
26216        let cfg = LaunchConfig {
26217            grid_dim: (nrows as u32, 1, 1),
26218            block_dim: (128, 1, 1),
26219            shared_mem_bytes: 0,
26220        };
26221        let (nc, e) = (ncols as i32, eps);
26222        let __s_b = self.gpu.stream();
26223        let mut b = __s_b.launch_builder(&f);
26224        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
26225        unsafe {
26226            b.launch(cfg)?;
26227        }
26228        Ok(())
26229    }
26230
26231    pub fn gated_rmsnorm_q8_1(
26232        &self,
26233        o: &CudaSlice<f32>,
26234        w: &CudaSlice<f32>,
26235        z: &CudaSlice<f32>,
26236        ncols: usize,
26237        nrows: usize,
26238        eps: f32,
26239    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
26240        assert!(ncols % 32 == 0);
26241        let f = self.func("gated_rmsnorm_q8_1");
26242        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
26243        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
26244        let cfg = LaunchConfig {
26245            grid_dim: (nrows as u32, 1, 1),
26246            block_dim: (128, 1, 1),
26247            shared_mem_bytes: 0,
26248        };
26249        let (nc, ep) = (ncols as i32, eps);
26250        let __s_b = self.gpu.stream();
26251        let mut b = __s_b.launch_builder(&f);
26252        b.arg(o)
26253            .arg(w)
26254            .arg(z)
26255            .arg(&mut out_q)
26256            .arg(&mut out_d)
26257            .arg(&nc)
26258            .arg(&ep);
26259        unsafe {
26260            b.launch(cfg)?;
26261        }
26262        Ok((out_q, out_d))
26263    }
26264
26265    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
26266    pub fn transpose(
26267        &self,
26268        inp: &CudaSlice<f32>,
26269        rows: usize,
26270        cols: usize,
26271    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
26272        let f = self.func("transpose_f32");
26273        let mut out = self.zeros(rows * cols)?;
26274        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
26275        let (r, c) = (rows as i32, cols as i32);
26276        let __s_b = self.gpu.stream();
26277        let mut b = __s_b.launch_builder(&f);
26278        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
26279        unsafe {
26280            b.launch(cfg)?;
26281        }
26282        Ok(out)
26283    }
26284
26285    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
26286    pub fn repeat_heads(
26287        &self,
26288        inp: &CudaSlice<f32>,
26289        out: &mut CudaSlice<f32>,
26290        head_dim: usize,
26291        n_in: usize,
26292        n_out: usize,
26293        t: usize,
26294    ) -> Result<(), Box<dyn std::error::Error>> {
26295        let f = self.func("repeat_heads_f32");
26296        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
26297        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
26298        let __s_b = self.gpu.stream();
26299        let mut b = __s_b.launch_builder(&f);
26300        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
26301        unsafe {
26302            b.launch(cfg)?;
26303        }
26304        Ok(())
26305    }
26306
26307    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
26308    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
26309    ///
26310    /// Bounds-checked against `qf` before the launch: the kernel reads `2*head_dim*n_head*t`
26311    /// floats, and running it on a `wq` output that carries no fused gate reads 2x off the end
26312    /// (silently, on the device). A layout mismatch is a typed `FusedQGateExtent` here instead.
26313    pub fn q_gate_split(
26314        &self,
26315        qf: &CudaSlice<f32>,
26316        q_out: &mut CudaSlice<f32>,
26317        gate_out: &mut CudaSlice<f32>,
26318        head_dim: usize,
26319        n_head: usize,
26320        t: usize,
26321    ) -> Result<(), Box<dyn std::error::Error>> {
26322        memra_gguf::config::check_fused_q_gate_extent(qf.len(), head_dim, n_head, t)?;
26323        let out_need = head_dim * n_head * t;
26324        if q_out.len() < out_need || gate_out.len() < out_need {
26325            return Err(format!(
26326                "q_gate_split destinations too small: need {out_need} each, have q={} gate={}",
26327                q_out.len(),
26328                gate_out.len()
26329            )
26330            .into());
26331        }
26332        let f = self.func("q_gate_split_f32");
26333        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
26334        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
26335        let __s_b = self.gpu.stream();
26336        let mut b = __s_b.launch_builder(&f);
26337        b.arg(qf)
26338            .arg(q_out)
26339            .arg(gate_out)
26340            .arg(&hd)
26341            .arg(&nh)
26342            .arg(&ti);
26343        unsafe {
26344            b.launch(cfg)?;
26345        }
26346        Ok(())
26347    }
26348
26349    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
26350    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
26351    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
26352    pub fn qkv_to_gdn_repack(
26353        &self,
26354        conv_out: &CudaSlice<f32>,
26355        q_g: &mut CudaSlice<f32>,
26356        k_g: &mut CudaSlice<f32>,
26357        v_g: &mut CudaSlice<f32>,
26358        d_state: usize,
26359        num_v: usize,
26360        num_k: usize,
26361        key_dim: usize,
26362        t: usize,
26363    ) -> Result<(), Box<dyn std::error::Error>> {
26364        let f = self.func("qkv_to_gdn_repack_f32");
26365        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
26366        let (ds, nv, nk, kd, ti) = (
26367            d_state as i32,
26368            num_v as i32,
26369            num_k as i32,
26370            key_dim as i32,
26371            t as i32,
26372        );
26373        let __s_b = self.gpu.stream();
26374        let mut b = __s_b.launch_builder(&f);
26375        b.arg(conv_out)
26376            .arg(q_g)
26377            .arg(k_g)
26378            .arg(v_g)
26379            .arg(&ds)
26380            .arg(&nv)
26381            .arg(&nk)
26382            .arg(&kd)
26383            .arg(&ti);
26384        unsafe {
26385            b.launch(cfg)?;
26386        }
26387        Ok(())
26388    }
26389
26390    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
26391    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
26392    pub fn conv_left_pad(
26393        &self,
26394        src: &CudaSlice<f32>,
26395        dst: &mut CudaSlice<f32>,
26396        conv_dim: usize,
26397        t: usize,
26398        pad: usize,
26399    ) -> Result<(), Box<dyn std::error::Error>> {
26400        let f = self.func("conv_left_pad_f32");
26401        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
26402        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
26403        let __s_b = self.gpu.stream();
26404        let mut b = __s_b.launch_builder(&f);
26405        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
26406        unsafe {
26407            b.launch(cfg)?;
26408        }
26409        Ok(())
26410    }
26411
26412    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
26413    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
26414    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
26415    pub fn conv_assemble_and_roll(
26416        &self,
26417        qkv_col: &CudaSlice<f32>,
26418        conv_state: &mut CudaSlice<f32>,
26419        conv_in: &mut CudaSlice<f32>,
26420        conv_dim: usize,
26421        pad: usize,
26422    ) -> Result<(), Box<dyn std::error::Error>> {
26423        let f = self.func("conv_assemble_and_roll_f32");
26424        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
26425        let (cd, p) = (conv_dim as i32, pad as i32);
26426        let __s_b = self.gpu.stream();
26427        let mut b = __s_b.launch_builder(&f);
26428        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
26429        unsafe {
26430            b.launch(cfg)?;
26431        }
26432        Ok(())
26433    }
26434
26435    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
26436    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
26437    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
26438    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
26439    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
26440    pub fn ssm_conv1d_fused_decode(
26441        &self,
26442        qkv_col: &CudaSlice<f32>,
26443        conv_state: &mut CudaSlice<f32>,
26444        w: &CudaSlice<f32>,
26445        conv_out: &mut CudaSlice<f32>,
26446        conv_dim: usize,
26447        d_conv: usize,
26448    ) -> Result<(), Box<dyn std::error::Error>> {
26449        let f = self.func("ssm_conv1d_fused_decode_f32");
26450        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
26451        let (cd, dc) = (conv_dim as i32, d_conv as i32);
26452        let __s_b = self.gpu.stream();
26453        let mut b = __s_b.launch_builder(&f);
26454        b.arg(qkv_col)
26455            .arg(conv_state)
26456            .arg(w)
26457            .arg(conv_out)
26458            .arg(&cd)
26459            .arg(&dc);
26460        unsafe {
26461            b.launch(cfg)?;
26462        }
26463        Ok(())
26464    }
26465
26466    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
26467    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
26468    pub fn slice_range(
26469        &self,
26470        src: &CudaSlice<f32>,
26471        start: usize,
26472        len: usize,
26473    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
26474        let host = self.gpu.stream().clone_dtoh(src)?;
26475        self.gpu.stream().synchronize()?;
26476        Ok(self.htod(&host[start..start + len])?)
26477    }
26478}
26479
26480#[cfg(test)]
26481mod target_dispatch_tests {
26482    use super::legacy_quant_gemm_allowed;
26483
26484    #[test]
26485    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
26486        // sm_120a native lane
26487        assert!(legacy_quant_gemm_allowed(false, false, false));
26488        assert!(!legacy_quant_gemm_allowed(false, false, true));
26489        // pure portable lane (sm_89): gated
26490        assert!(!legacy_quant_gemm_allowed(true, false, false));
26491        assert!(!legacy_quant_gemm_allowed(true, false, true));
26492        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
26493        assert!(legacy_quant_gemm_allowed(true, true, false));
26494        assert!(!legacy_quant_gemm_allowed(true, true, true));
26495    }
26496
26497    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
26498    #[test]
26499    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
26500        assert!(!legacy_quant_gemm_allowed(
26501            cfg!(memra_portable_cuda),
26502            cfg!(memra_hopper_mma),
26503            false
26504        ));
26505    }
26506
26507    #[cfg(memra_hopper_mma)]
26508    #[test]
26509    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
26510        assert!(legacy_quant_gemm_allowed(
26511            cfg!(memra_portable_cuda),
26512            cfg!(memra_hopper_mma),
26513            false
26514        ));
26515        assert!(super::portable_mma_gated() == false);
26516    }
26517}
26518
26519/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
26520/// inherent methods (inherent methods win name resolution, so no recursion).
26521impl memra_kv::KvDev for Engine {
26522    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
26523        Engine::zeros(self, n)
26524    }
26525    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
26526        Engine::uninit(self, n)
26527    }
26528    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
26529        Engine::alloc_u8(self, n)
26530    }
26531    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
26532        Engine::htod_i32(self, v)
26533    }
26534    fn clone_dtod(
26535        &self,
26536        src: &CudaSlice<f32>,
26537    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
26538        Engine::clone_dtod(self, src)
26539    }
26540    fn copy_into(
26541        &self,
26542        dst: &mut CudaSlice<f32>,
26543        off: usize,
26544        src: &CudaSlice<f32>,
26545        len: usize,
26546    ) -> Result<(), Box<dyn std::error::Error>> {
26547        Engine::copy_into(self, dst, off, src, len)
26548    }
26549    fn set_i32_one(
26550        &self,
26551        d: &mut CudaSlice<i32>,
26552        v: i32,
26553    ) -> Result<(), Box<dyn std::error::Error>> {
26554        Engine::set_i32_one(self, d, v)
26555    }
26556}
26557
26558#[cfg(test)]
26559mod fused_gate_bounds_tests {
26560    use super::*;
26561
26562    /// The fused `[q|gate]` split's read-site guard, on the device.
26563    ///
26564    /// `q_gate_split_f32` reads `2*head_dim*n_head*T` floats out of `qf`. A checkpoint whose gate
26565    /// is a SEPARATE tensor produces a `wq` output of exactly half that, so before 2026-08-19 the
26566    /// kernel launched and read 2x past the end of the allocation — an out-of-bounds DEVICE read:
26567    /// no panic, no error, just whatever memory follows. The guard turns it into a typed
26568    /// `FusedQGateExtent` before the launch.
26569    ///
26570    /// Catch demonstration for this test (guard temporarily removed, then restored):
26571    /// `compute-sanitizer --tool memcheck` on the half-width case reported invalid `__global__`
26572    /// reads of size 4 in `q_gate_split_f32`; with the guard in place the same run is clean and
26573    /// the call returns `Err`. Receipt in the lane report.
26574    #[test]
26575    #[ignore = "requires a CUDA GPU"]
26576    fn q_gate_split_refuses_a_separate_gate_wq_instead_of_reading_past_it() {
26577        let e = Engine::new(0).unwrap();
26578        let (head_dim, n_head, t) = (8usize, 4usize, 2usize);
26579        let fused = 2 * head_dim * n_head * t;
26580        let out_n = head_dim * n_head * t;
26581
26582        // half-width `qf` = the separate-gate / ungated layout. MUST be refused.
26583        let narrow = e.htod(&vec![1.0f32; out_n]).unwrap();
26584        let mut q = e.uninit(out_n).unwrap();
26585        let mut gate = e.uninit(out_n).unwrap();
26586        let err = e
26587            .q_gate_split(&narrow, &mut q, &mut gate, head_dim, n_head, t)
26588            .expect_err("half-width wq must be refused, not read past")
26589            .to_string();
26590        assert!(err.contains("NO fused gate"), "{err}");
26591        assert!(err.contains(&format!("{fused}")), "{err}");
26592
26593        // full-width `qf` = a real qwen3.5 fused layout. MUST still run, and split correctly:
26594        // per head hh the block is [q(head_dim) | gate(head_dim)] at stride 2*head_dim.
26595        let host: Vec<f32> = (0..fused).map(|i| i as f32).collect();
26596        let wide = e.htod(&host).unwrap();
26597        e.q_gate_split(&wide, &mut q, &mut gate, head_dim, n_head, t)
26598            .expect("full-width wq splits");
26599        let (qh, gh) = (e.dtoh(&q).unwrap(), e.dtoh(&gate).unwrap());
26600        for tok in 0..t {
26601            for hh in 0..n_head {
26602                for d in 0..head_dim {
26603                    let base = tok * (n_head * 2 * head_dim) + hh * (2 * head_dim);
26604                    let idx = tok * (n_head * head_dim) + hh * head_dim + d;
26605                    assert_eq!(qh[idx], host[base + d], "q t{tok} h{hh} d{d}");
26606                    assert_eq!(gh[idx], host[base + head_dim + d], "gate t{tok} h{hh} d{d}");
26607                }
26608            }
26609        }
26610
26611        // undersized destinations are refused too (the other half of the extent contract)
26612        let mut small = e.uninit(out_n - 1).unwrap();
26613        assert!(
26614            e.q_gate_split(&wide, &mut small, &mut gate, head_dim, n_head, t)
26615                .is_err()
26616        );
26617    }
26618}
26619
26620/// FULL-WIDTH-ROPE CONTRACT on the fused rms_norm+qkv+rope kernels
26621/// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up). CPU-only: the guard runs before
26622/// any launch, so the refusal is testable without a device.
26623#[cfg(test)]
26624mod fused_rope_width_tests {
26625    use super::Engine;
26626
26627    /// gemma-4: rotary width == head width on both classes (GGUF 256/256 and 512/512, and the
26628    /// safetensors route derives the same), which is why the fusion is legal there today.
26629    #[test]
26630    fn full_width_is_accepted() {
26631        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 256).is_ok());
26632        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_cat", 512, 512).is_ok());
26633        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append", 128, 128).is_ok());
26634    }
26635
26636    /// The widths the gemma-4 31B OFFICIAL artifact declares, read from its own GGUF header
26637    /// (`gemma-4-31B-it-official-Q8_0-MTP.gguf`, box3, 2026-08-19):
26638    ///
26639    /// ```text
26640    /// attention.key_length     512   rope.dimension_count     512   (global class)
26641    /// attention.key_length_swa 256   rope.dimension_count_swa 256   (SWA class)
26642    /// ```
26643    ///
26644    /// Both classes satisfy `n_rot == head_dim`, which is why the fusion is legal for gemma and
26645    /// why `HybridModel::gemma4_rope_dims` can feed this guard without refusing what we serve.
26646    /// An artifact that ever declares otherwise gets a loud refusal at the first fused launch
26647    /// instead of a silently over-rotated head.
26648    #[test]
26649    fn gemma4_official_artifact_widths_pass() {
26650        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 512, 512).is_ok());
26651        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 256, 256).is_ok());
26652    }
26653
26654    /// The bug this guard exists to make impossible: a partial-rotary arch fused onto a kernel
26655    /// with no `n_dims`, silently rotating the pass-through band.
26656    #[test]
26657    fn partial_rotary_is_refused_with_the_geometry_named() {
26658        // qwen3.5: n_rot 64 of head_dim 256 (the shape probe O-10 pinned in the split path).
26659        let err = Engine::full_width_rope_only("rms_norm_qkv_rope", 64, 256)
26660            .expect_err("partial rotary must refuse");
26661        let msg = err.to_string();
26662        assert!(msg.contains("PARTIAL ROTARY REFUSED"), "{msg}");
26663        assert!(msg.contains("n_rot 64"), "{msg}");
26664        assert!(msg.contains("head_dim 256"), "{msg}");
26665        assert!(
26666            msg.contains("64..256"),
26667            "names the band it would corrupt: {msg}"
26668        );
26669        // step35 full-attn: 64 of 128 (upstream halves n_rot_full).
26670        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 64, 128).is_err());
26671        // and the reverse mismatch (a wider rope than the head) is not "close enough" either.
26672        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 128).is_err());
26673    }
26674}