Skip to main content

memra_engine/
lib.rs

1//! memra engine: Stage-1 correctness-first forward-pass kernels + ops, on sm_120 via cudarc.
2
3use cudarc::driver::{
4    CudaContext, CudaFunction, CudaModule, CudaSlice, CudaStream, LaunchConfig, PushKernelArg,
5};
6use cudarc::nvrtc::Ptx;
7use std::sync::{Arc, Mutex};
8
9#[cfg(debug_assertions)]
10pub(crate) fn debug_assert_tensor_stream_device<T>(
11    tensor: &CudaSlice<T>,
12    stream: &CudaStream,
13    site: &str,
14) {
15    let tensor_dev = tensor.ordinal();
16    let stream_dev = stream.context().ordinal();
17    assert_eq!(
18        tensor_dev, stream_dev,
19        "PP cross-device tensor read at {site}: tensor on dev{tensor_dev}, stream on dev{stream_dev}"
20    );
21}
22
23pub use memra_gguf;
24pub use memra_runtime;
25
26pub mod forward;
27pub mod hybrid;
28pub mod hybrid_forward;
29pub mod model;
30pub mod sigrouter_contract;
31pub mod vision;
32pub mod vision_pre;
33/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
34/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
35pub mod cache {
36    pub use memra_kv::*;
37}
38pub mod decode;
39pub mod decode_batch;
40pub mod dflash;
41pub mod eagle;
42pub mod gemma_spec;
43pub mod graph_update;
44/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
45/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
46/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
47pub mod mla;
48pub mod moesd;
49pub mod parallel;
50pub mod pp;
51pub mod round_stream;
52pub mod spec;
53pub use memra_sampling as sampler;
54
55/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
56/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
57/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
58/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
59/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
60///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
61///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
62///                     stream sync per projection (round-47 ledgered defect).
63///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
64///                     construction, zero syncs, f32 C with the act row-scale folded in.
65/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
66/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
67/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
68/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
69/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
70/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
71///
72/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
73/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
74/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
75/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
76/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
77/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
78/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
79/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
80///
81/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
82/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
83/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
84/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
85/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
86/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
87/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
88///
89/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
90/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
91/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
92/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
93/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
94/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
95/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
96/// the k-quant-only admission survives as the rollback seam, not the default.
97/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
98pub fn moe_f16g_mode() -> u8 {
99    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
100    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
101        Ok("0") => 0,
102        Ok("2") => 2,
103        Ok("3") => 3,
104        Ok(_) => 1,
105        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
106        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
107        Err(_) => 2,
108    })
109}
110/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
111/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
112/// (shape_sel, cross) for the FFI:
113///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
114///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
115///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
116///                         back to 32x64 in-launcher when the device/in_f can't take it).
117///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
118///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
119///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
120///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
121///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
122///                         verdict was stale).
123pub fn moe_f16g_sk_params() -> (i32, i32) {
124    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
125    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
126        Ok("0") => (-1, 0),
127        Ok("32") => (0, i32::MAX),
128        Ok("128") => (0, 1),
129        _ => {
130            let cross = std::env::var("MEMRA_F16G_SK_CROSS")
131                .ok()
132                .and_then(|v| v.parse().ok())
133                .unwrap_or(64);
134            (0, cross)
135        }
136    })
137}
138/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
139/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
140/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
141/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
142/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
143/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
144/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
145/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
146/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
147/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
148pub fn moe_f16g_direct_on(qtype: i32) -> bool {
149    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
150    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
151        Ok("0") => 0,
152        Ok("kq") => 1,
153        _ => 2,
154    });
155    match m {
156        0 => false,
157        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
158        _ => true,
159    }
160}
161/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
162/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
163/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
164/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
165/// stage under q35's routing skew. Bit-identical to every other sk form by construction
166/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
167/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
168/// tail. in_f % 64 != 0 falls back in-launcher.
169pub fn moe_f16g_tail_on() -> bool {
170    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
171    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
172}
173
174/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
175/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
176/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
177/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
178/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
179/// still opens this door for A/B.
180pub fn moe_f16g_gemma_on() -> bool {
181    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
182    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
183}
184
185/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
186/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
187/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
188pub fn moe_fuse_actq_on() -> bool {
189    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
190    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
191}
192
193/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
194/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
195/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
196/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
197/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
198/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
199/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
200/// verify already use (dispatch parity, one router kernel for every t).
201/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
202pub fn router_prefill_exact_on() -> bool {
203    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
204    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
205}
206
207pub fn router_kernel_on() -> bool {
208    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
209    *ON.get_or_init(|| {
210        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
211        if !on {
212            eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)");
213        }
214        on
215    })
216}
217
218/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
219/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
220/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
221/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
222/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
223/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
224/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
225/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
226/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
227/// seam, perf-only: bits are equal by the kernel-check gate).
228/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
229/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
230/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
231/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
232pub const ROUTER_BATCH_MIN_T: usize = 8;
233pub fn router_batch_on() -> bool {
234    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
235    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
236}
237mod cpu_experts;
238#[cfg(memra_cutlass)]
239pub mod cutlass_ffi;
240pub mod f16_ffi;
241pub mod fp8_ffi;
242pub mod mmq_ffi;
243pub mod moe_cache;
244pub mod prime_graph;
245pub mod spill;
246mod spill_pread;
247
248// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
249// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
250// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
251// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
252// broke every machine that wasn't the build machine. Same bytes, same module image;
253// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
254const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
255const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
256const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
257const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
258const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
259const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
260/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
261const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
262
263/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
264/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
265/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
266/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
267/// compile-time default (zero behavior change).
268fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
269    assert!(
270        !(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
271        "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane"
272    );
273    match std::env::var("MEMRA_GEMM_FATBIN") {
274        Ok(path) => std::borrow::Cow::Owned(
275            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}")),
276        ),
277        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
278    }
279}
280
281/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
282/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
283/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
284/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
285/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
286/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
287pub(crate) const fn portable_mma_gated() -> bool {
288    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
289}
290
291/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
292/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
293/// in a pure helper so the dispatch guard can be regression-tested without constructing an
294/// Engine or allocating a GPU tensor.
295const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
296    (!portable_cuda || hopper_mma) && !no_gemm
297}
298
299// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
300// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
301// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
302// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
303// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
304// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
305// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
306const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
307const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
308const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
309const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
310const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
311
312/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
313/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
314pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
315
316/// The flash_attn fatbin matching the selected KV formats.
317fn flash_fatbin_bytes() -> &'static [u8] {
318    match kv_cache_formats() {
319        ("q8_0", "q5_1") => FLASH_FATBIN,
320        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
321        ("q8_0", "fp8") => FLASH_FATBIN_VF8,
322        ("fp8", "q5_1") => FLASH_FATBIN_KF8,
323        ("fp8", "q4_0") => FLASH_FATBIN_KF8VQ4,
324        ("fp8", "fp8") => FLASH_FATBIN_KF8VF8,
325        other => unreachable!("kv_cache_formats returned {other:?}"),
326    }
327}
328
329/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
330/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
331/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
332/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
333/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
334/// defaults (zero behavior change).
335fn k1_launch_override() -> Option<(u32, u32, u32)> {
336    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
337    *K1.get_or_init(|| {
338        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
339        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
340        match p.as_slice() {
341            [bm, bn, w] => Some((*bm, *bn, *w)),
342            _ => None,
343        }
344    })
345}
346
347/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
348/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
349/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
350/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
351/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
352/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
353pub(crate) fn wgmma_gemm_enabled() -> bool {
354    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
355    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
356}
357
358/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
359/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
360/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
361/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
362/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
363/// the split count changes the combine's FP summation order, and the spec verify's batched forward
364/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
365/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
366/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
367/// adaptive retries (any retry MUST pass run-spec self-consistency first).
368/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
369/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
370/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
371/// between eager decode and the verify (the spec-exactness law).
372pub const FA_VEC_MIN_TKV: usize = 96;
373/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
374/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
375/// which moves the crossover — sweep per model, adopt per the battery.
376pub fn fa_vec_min_tkv() -> usize {
377    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
378    *V.get_or_init(|| {
379        std::env::var("MEMRA_FA_VEC_MIN")
380            .ok()
381            .and_then(|v| v.parse().ok())
382            .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
383    })
384}
385
386/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
387/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
388/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
389///
390/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
391/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
392/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
393/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
394/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
395/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
396pub fn fa_f16pv_on() -> bool {
397    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
398    *ON.get_or_init(|| {
399        std::env::var("MEMRA_FA_F16PV")
400            .map(|v| v != "0")
401            .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
402    })
403}
404
405/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
406/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
407/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
408pub fn fa512_hp_on() -> bool {
409    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
410    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
411}
412
413/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
414/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
415/// accumulation. Even n_head and even GQA group required (guarded per call).
416pub fn faw_hp_on() -> bool {
417    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
418    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
419}
420
421/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
422/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
423/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
424pub fn fa512_wide_warps() -> usize {
425    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
426    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
427        Ok("1") => 4,
428        _ => 2,
429    })
430}
431
432/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
433/// and the gemma global-layer rows/parity call sites.
434pub fn fa512_min_tkv() -> usize {
435    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
436    *FA512_MIN.get_or_init(|| {
437        std::env::var("MEMRA_FA512_MIN")
438            .ok()
439            .and_then(|v| v.parse().ok())
440            .unwrap_or(512)
441    })
442}
443/// Per-model crossover default, set at model load BEFORE the first decode (per-model
444/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
445/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
446pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
447    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
448/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
449/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
450/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
451pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(32);
452/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
453/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
454/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
455/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
456/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
457pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
458    std::sync::atomic::AtomicBool::new(false);
459/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
460/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
461/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
462/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
463/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
464/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
465pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
466    std::sync::atomic::AtomicBool::new(true);
467pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
468    std::sync::atomic::AtomicUsize::new(16);
469/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
470/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
471/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
472/// latency-bound at 256 threads — 7us/launch measured).
473pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
474/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
475pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
476/// Per-model stream-k entry override for SPEC serving (-1 = unset → env/default;
477/// 0 = force tiling; 1 = admit the deterministic form selector). The former timing
478/// selector made identical boots choose different fold orders; `MEMRA_MMQ_SK_FORM` is the
479/// explicit numerical-form seam. mmq_ffi reads this before the env.
480pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
481/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
482/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
483pub use memra_kv::KV_FP8_FORCE;
484pub(crate) fn rms_block() -> u32 {
485    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
486    *V.get_or_init(|| {
487        std::env::var("MEMRA_RMS_BLOCK")
488            .ok()
489            .and_then(|v| v.parse().ok())
490            .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
491    })
492}
493
494pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
495    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
496    if let Some(forced) = *S.get_or_init(|| {
497        std::env::var("MEMRA_FA_SPLIT")
498            .ok()
499            .and_then(|v| v.parse().ok())
500            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
501    }) {
502        return forced;
503    }
504    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
505    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
506    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
507    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
508    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
509    //
510    // SM-AWARE SHORT-CTX RUNG (2026-07-06 g7e): the 32-key rung was tuned on the 82-SM 5090.
511    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
512    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on g7e (N=1 sweep + N=3
513    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
514    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
515    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
516    // rig-divergence law: this branch is measured on 188 SMs only).
517    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
518    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
519    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
520    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
521    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
522        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1")
523    {
524        return if t_kv <= 8192 {
525            16
526        } else if t_kv <= 16384 {
527            64
528        } else {
529            128
530        };
531    }
532    let big_rig = fa_sm_count() >= 128;
533    if big_rig {
534        let _ = n_head_kv;
535        if t_kv <= 2048 {
536            16
537        } else if t_kv <= 16384 {
538            64
539        } else {
540            128
541        }
542    } else if n_head_kv <= 4 {
543        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
544        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
545        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
546        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
547        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
548        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
549        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
550        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
551        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
552        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
553        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
554        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
555        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
556        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
557        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
558        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
559        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
560        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
561        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
562        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
563        if t_kv <= 512 {
564            8
565        } else if t_kv <= 16384 {
566            64
567        } else {
568            128
569        }
570    } else {
571        if t_kv <= 8192 {
572            32
573        } else if t_kv <= 16384 {
574            64
575        } else {
576            128
577        }
578    }
579}
580
581/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
582/// same attribute Engine::batched_variant reads).
583fn fa_sm_count() -> i32 {
584    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
585    *N.get_or_init(|| {
586        cudarc::driver::result::init().ok();
587        cudarc::driver::result::device::get(0)
588            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
589                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
590            .unwrap_or(82)
591    })
592}
593
594/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
595/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
596/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
597fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
598    match head_dim {
599        256 => Ok(""),
600        128 => Ok("_hd128"),
601        d => Err(format!(
602            "fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
603                          callers must gate to sdpa_naive"
604        )
605        .into()),
606    }
607}
608
609/// Quant type codes matching qmatvec.cu QType enum.
610pub const QT_Q8_0: i32 = 0;
611pub const QT_Q4_K: i32 = 1;
612pub const QT_Q6_K: i32 = 2;
613pub const QT_Q5_K: i32 = 3;
614pub const QT_Q3_K: i32 = 4;
615pub const QT_IQ4_XS: i32 = 5;
616pub const QT_IQ3_S: i32 = 6;
617pub const QT_NVFP4: i32 = 7;
618/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
619/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
620/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
621/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
622/// — ONE weight copy total, no Q8_0 re-encode duplicate.
623pub const QT_F8_E4M3: i32 = 10;
624/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
625/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
626pub const QT_NVFP4_RP: i32 = 9;
627/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
628pub const QT_F32: i32 = 8;
629pub const QT_BF16: i32 = 11;
630pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
631/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
632/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
633/// dp4a/MMQ implementation exists.
634pub const QT_Q2_K: i32 = 13;
635/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
636/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
637/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
638/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
639/// scalar `scale` field is 1.0 by the layout contract.
640///
641/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
642/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
643/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
644/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
645/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
646/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
647/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
648/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
649/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
650pub const QT_F8_E4M3_BLK: i32 = 14;
651
652/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
653pub struct Engine {
654    pub gpu: memra_runtime::Gpu,
655    module: Arc<CudaModule>,
656    hybrid: Arc<CudaModule>,
657    qmatvec: Arc<CudaModule>,
658    flash: Arc<CudaModule>,
659    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
660    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
661    /// Lazy: loaded on first global-format use; None until then.
662    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
663    gemm: Arc<CudaModule>,
664    router: Arc<CudaModule>,
665    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
666    sample: Arc<CudaModule>,
667    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
668    /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
669    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
670    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
671    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
672    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
673    /// the single largest block. The cache still owns every address for its full lifetime.
674    moe_cache_layout: Mutex<Option<Vec<usize>>>,
675    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
676    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
677    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
678    /// verify between replays) reuse their addresses and the replay reads/writes live memory
679    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
680    capture_keep_on: std::sync::atomic::AtomicBool,
681    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
682    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
683    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
684    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
685    verify_exact: std::sync::atomic::AtomicBool,
686    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
687    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
688    pub copy_stream: Arc<CudaStream>,
689    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
690    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
691    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
692    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
693    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
694    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
695    #[cfg(memra_cutlass)]
696    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
697    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
698    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
699    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
700    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
701    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
702    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
703    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
704    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
705    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
706    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
707    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
708    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
709    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
710    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
711    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
712    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
713    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
714    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
715    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
716    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
717    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
718    /// before capture under the generate_graph tracking-off window so it carries no events).
719    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
720    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
721    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
722    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
723    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
724    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
725    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
726    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
727    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
728    router_stage: Mutex<Option<PinnedStage>>,
729}
730
731/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
732/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
733/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
734/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
735/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
736/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
737/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
738/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
739/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
740fn fa_v2_on() -> bool {
741    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
742    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
743    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
744    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
745    // + graph bit-identity green on all three models.
746    std::env::var("MEMRA_FA_V2")
747        .map(|v| v != "0")
748        .unwrap_or(true)
749}
750
751/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
752/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
753/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
754/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
755/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
756/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
757/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
758fn fa_v3_on() -> bool {
759    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
760    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
761    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
762    std::env::var("MEMRA_FA_V3")
763        .map(|v| v != "0")
764        .unwrap_or(true)
765}
766
767/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
768/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
769/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
770/// predicate so the twins can never diverge.
771fn fa_v4_mode() -> &'static str {
772    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
773    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
774}
775fn fa_v4_on() -> bool {
776    fa_v4_mode() != "0"
777} // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
778/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
779/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
780/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
781/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
782/// stays kernel-family-identical to decode at the same t_kv.
783/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
784/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
785pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
786    std::sync::atomic::AtomicUsize::new(1024);
787pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
788    std::sync::atomic::AtomicUsize::new(usize::MAX);
789pub fn fa_v4_at_pub(t_kv: usize) -> bool {
790    fa_v4_at(t_kv)
791}
792fn fa_v4_at(t_kv: usize) -> bool {
793    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
794    let mx = *M.get_or_init(|| {
795        std::env::var("MEMRA_FA_V4_MAX")
796            .ok()
797            .and_then(|v| v.parse().ok())
798            .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
799    });
800    fa_v4_on() && t_kv < mx
801}
802/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
803/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
804/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
805/// (same split partition, same softmax/accumulation order, same partials/combine) and only
806/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
807/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
808/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
809/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
810/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
811/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
812/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
813/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
814/// within one process (the v2/v3 pattern).
815pub const FA_DEEP_MIN_DEFAULT: usize = 0;
816fn fa_deep_at(t_kv: usize) -> bool {
817    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") {
818        return false;
819    }
820    let min = std::env::var("MEMRA_FA_DEEP_MIN")
821        .ok()
822        .and_then(|v| v.parse().ok())
823        .unwrap_or(FA_DEEP_MIN_DEFAULT);
824    t_kv >= min
825}
826/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
827pub fn fa_deep_at_pub(t_kv: usize) -> bool {
828    fa_deep_at(t_kv)
829}
830
831fn fa_v3_active(head_dim: usize) -> bool {
832    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
833    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
834    fa_v3_on()
835        && head_dim % 128 == 0
836        && kv_cache_formats() == ("q8_0", "q5_1")
837        && !Engine::kv_fp8_on()
838}
839
840/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
841/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
842/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
843/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
844/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
845/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
846/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
847pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
848    std::env::var("MEMRA_NO_FA_VEC").is_err()
849        && t_kv >= fa_vec_min_tkv()
850        && head_dim == 256
851        && fa_v4_at(t_kv)
852        && !matches!(fa_v4_mode(), "noB3" | "stage")
853        && !Engine::kv_fp8_on()
854}
855/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
856pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize {
857    fa_split_keys(t_kv, n_head_kv)
858}
859
860/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
861/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
862/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
863/// so we allocate through `result::malloc_host` with flags=0 directly.
864struct PinnedStage {
865    ptr: *mut u8,
866    cap: usize,
867}
868unsafe impl Send for PinnedStage {}
869impl PinnedStage {
870    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
871        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
872        Ok(PinnedStage { ptr, cap })
873    }
874}
875impl Drop for PinnedStage {
876    fn drop(&mut self) {
877        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
878    }
879}
880
881/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
882/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
883pub const ARGMAX_NB: usize = 256;
884
885/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
886pub(crate) use memra_fa3_vl as fa3_vl_raw;
887
888unsafe extern "C" {
889    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
890    fn memra_fa3_prefill(
891        q16: *const core::ffi::c_void,
892        k16: *const core::ffi::c_void,
893        v16: *const core::ffi::c_void,
894        o: *mut f32,
895        t: i32,
896        h: i32,
897        hkv: i32,
898        d: i32,
899        scale: f32,
900        stream: *mut core::ffi::c_void,
901    ) -> i32;
902    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
903    pub(crate) fn memra_fa3_vl(
904        q16s: *const *const core::ffi::c_void,
905        k16s: *const *const core::ffi::c_void,
906        v16s: *const *const core::ffi::c_void,
907        os: *const *mut f32,
908        ts: *const i32,
909        b: i32,
910        h: i32,
911        hkv: i32,
912        d: i32,
913        scale: f32,
914        stream: *mut core::ffi::c_void,
915    ) -> i32;
916}
917
918/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
919/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
920/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
921/// (slots are never re-allocated), so passing raw values is stable across the launch.
922#[repr(C)]
923#[derive(Clone, Copy)]
924pub struct WPtr8(pub [u64; 8]);
925unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
926
927/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
928/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
929/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
930/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
931#[repr(C)]
932#[derive(Clone, Copy, Default)]
933pub struct GdnSeqVl {
934    pub kb16: u64,
935    pub gcum: u64,
936    pub beta: u64,
937    pub u: u64,
938    pub wb16: u64,
939    pub y: u64,
940    pub ssnap: u64,
941    pub state_in: u64,
942    pub state_out: u64,
943    pub q: u64,
944    pub p: u64,
945    pub o: u64,
946    pub k: u64,
947    pub v: u64,
948    pub g: u64,
949    pub a: u64,
950    pub w: u64,
951    pub t: i32,
952    pub nc: i32,
953}
954unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
955#[repr(C)]
956#[derive(Clone, Copy)]
957pub struct GdnVl8(pub [GdnSeqVl; 8]);
958unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
959
960/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
961/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
962#[repr(C)]
963#[derive(Clone, Copy, Default)]
964pub struct GdnWVl {
965    pub qb16: u64,
966    pub pb16: u64,
967}
968unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
969#[repr(C)]
970#[derive(Clone, Copy)]
971pub struct GdnWVl8(pub [GdnWVl; 8]);
972unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
973
974/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
975#[repr(C)]
976#[derive(Clone, Copy, Default)]
977pub struct GdnPrepVl {
978    pub qkv: u64,
979    pub conv_state: u64,
980    pub conv_out: u64,
981    pub q_g: u64,
982    pub k_g: u64,
983    pub v_g: u64,
984    pub q_l2: u64,
985    pub k_l2: u64,
986    pub beta_raw: u64,
987    pub alpha: u64,
988    pub beta: u64,
989    pub g_log: u64,
990    pub o: u64,
991    pub z: u64,
992    pub gn: u64,
993    pub gn16: u64,
994    pub kb16: u64,
995    pub qb16: u64,
996    pub t: i32,
997    pub pad: i32,
998}
999unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
1000#[repr(C)]
1001#[derive(Clone, Copy)]
1002pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
1003unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
1004
1005/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
1006#[repr(C)]
1007#[derive(Clone, Copy, Default)]
1008pub struct FaSeqVl {
1009    pub q: u64,
1010    pub k16: u64,
1011    pub v16: u64,
1012    pub o: u64,
1013    pub kf: u64,
1014    pub vf: u64,
1015    pub t: i32,
1016    pub pad: i32,
1017}
1018unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
1019#[repr(C)]
1020#[derive(Clone, Copy)]
1021pub struct FaVl8(pub [FaSeqVl; 8]);
1022unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
1023
1024/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
1025#[repr(C)]
1026#[derive(Clone, Copy, Default)]
1027pub struct AttnPreVl {
1028    pub qf: u64,
1029    pub kf: u64,
1030    pub vf: u64,
1031    pub q: u64,
1032    pub gate: u64,
1033    pub qn: u64,
1034    pub kn: u64,
1035    pub kc: u64,
1036    pub vc: u64,
1037    pub t: i32,
1038    pub pad: i32,
1039}
1040unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
1041#[repr(C)]
1042#[derive(Clone, Copy)]
1043pub struct AttnPreVl8(pub [AttnPreVl; 8]);
1044unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
1045
1046/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
1047/// varlen K1-K5 chain fills them).
1048pub struct GdnChunkBufs {
1049    pub gcum: CudaSlice<f32>,
1050    pub a: CudaSlice<f32>,
1051    pub p: CudaSlice<f32>,
1052    pub u: CudaSlice<f32>,
1053    pub w: CudaSlice<f32>,
1054    pub kb16: CudaSlice<u8>,
1055    pub wb16: CudaSlice<u8>,
1056    pub y16: CudaSlice<u8>,
1057    pub ssnap16: CudaSlice<u8>,
1058    pub qb16: CudaSlice<u8>,
1059    pub pb16: CudaSlice<u8>,
1060    pub o: CudaSlice<f32>,
1061    pub t: usize,
1062    pub nc: usize,
1063}
1064
1065/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
1066#[repr(C)]
1067#[derive(Clone, Copy)]
1068pub struct F32x8(pub [f32; 8]);
1069unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
1070
1071/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
1072/// process. Bench binaries read it right after the call to print gen-only throughput without the
1073/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
1074pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1075
1076impl Engine {
1077    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
1078        let gpu = memra_runtime::Gpu::new(ordinal)?;
1079        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
1080        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
1081        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
1082        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
1083            use cudarc::driver::sys::CUdevice_attribute_enum as A;
1084            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
1085                .and_then(|d| unsafe {
1086                    Ok((
1087                        cudarc::driver::result::device::get_attribute(
1088                            d,
1089                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
1090                        )?,
1091                        cudarc::driver::result::device::get_attribute(
1092                            d,
1093                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
1094                        )?,
1095                    ))
1096                })
1097                .unwrap_or((0, 0));
1098            let built = env!("MEMRA_BUILT_CUDA_ARCH");
1099            let ok = matches!(
1100                (built, maj, min),
1101                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9)
1102            );
1103            if !ok {
1104                return Err(format!(
1105                    "memra was built for sm_{built} but device {ordinal} reports compute \
1106                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
1107                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass."
1108                )
1109                .into());
1110            }
1111        }
1112        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
1113        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
1114        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
1115        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
1116        unsafe {
1117            use cudarc::driver::sys;
1118            let dev: sys::CUdevice = ordinal as sys::CUdevice;
1119            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1120            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
1121                let mut thresh: u64 = u64::MAX;
1122                let _ = sys::cuMemPoolSetAttribute(
1123                    pool,
1124                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
1125                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
1126                );
1127            }
1128        }
1129        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
1130        let hybrid = gpu
1131            .ctx
1132            .load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
1133        let qmatvec = gpu
1134            .ctx
1135            .load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
1136        let flash = gpu
1137            .ctx
1138            .load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
1139        let gemm = gpu
1140            .ctx
1141            .load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
1142        let router = gpu
1143            .ctx
1144            .load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
1145        let sample = gpu
1146            .ctx
1147            .load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
1148        let copy_stream = gpu.ctx.new_stream()?;
1149        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
1150        // cudarc is in multi-stream mode (main stream +
1151        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
1152        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
1153        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
1154        // (~7 ms/tok host time, measured nsys 2026-07-04 g7e), and +4.6% measured on 27B decode —
1155        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
1156        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
1157        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
1158        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
1159        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
1160        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
1161        // implicit event tracking.
1162        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
1163        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
1164        if std::env::var("MEMRA_EVT")
1165            .map(|v| v == "1")
1166            .unwrap_or(false)
1167        {
1168            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1169        } else {
1170            unsafe {
1171                gpu.ctx.disable_event_tracking();
1172            }
1173        }
1174        Ok(Self {
1175            gpu,
1176            module,
1177            hybrid,
1178            qmatvec,
1179            flash,
1180            flash_g: std::sync::OnceLock::new(),
1181            gemm,
1182            router,
1183            sample,
1184            moe_cache: Mutex::new(None),
1185            moe_cache_layout: Mutex::new(None),
1186            copy_stream,
1187            capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1188            verify_exact: std::sync::atomic::AtomicBool::new(false),
1189            capture_keep: Mutex::new(Vec::new()),
1190            argmax_partials: Mutex::new(None),
1191            prime_deqw_ws: Mutex::new(None),
1192            router_stage: Mutex::new(None),
1193            fp8_scratch: Mutex::new(None),
1194            fa_vf16_scratch: Mutex::new(None),
1195            fa_part_pool: Mutex::new(None),
1196            fa_part_retired: Mutex::new(Vec::new()),
1197            fn_cache: Mutex::new(Default::default()),
1198            f16_scratch: Mutex::new(None),
1199            #[cfg(memra_cutlass)]
1200            cutlass_scratch: Mutex::new(None),
1201        })
1202    }
1203
1204    pub fn ctx(&self) -> &Arc<CudaContext> {
1205        &self.gpu.ctx
1206    }
1207
1208    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1209    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1210    ///
1211    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1212    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1213    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1214    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1215    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1216    ///
1217    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1218    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1219    /// under-count headroom does not belong in a gate that queues real work, but the honest
1220    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1221    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1222    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1223    ///
1224    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1225    pub fn pool_cached_bytes(&self) -> usize {
1226        let (reserved, used) = self.pool_reserved_used();
1227        reserved.saturating_sub(used)
1228    }
1229
1230    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1231    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1232    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1233    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1234    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1235    /// (0, 0) if the pool cannot be queried.
1236    pub fn pool_reserved_used(&self) -> (usize, usize) {
1237        use cudarc::driver::sys;
1238        unsafe {
1239            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1240            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1241                != sys::CUresult::CUDA_SUCCESS
1242            {
1243                return (0, 0);
1244            }
1245            let (mut reserved, mut used) = (0u64, 0u64);
1246            if sys::cuMemPoolGetAttribute(
1247                pool,
1248                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1249                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1250            ) != sys::CUresult::CUDA_SUCCESS
1251            {
1252                return (0, 0);
1253            }
1254            if sys::cuMemPoolGetAttribute(
1255                pool,
1256                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1257                &mut used as *mut u64 as *mut core::ffi::c_void,
1258            ) != sys::CUresult::CUDA_SUCCESS
1259            {
1260                return (0, 0);
1261            }
1262            (reserved as usize, used as usize)
1263        }
1264    }
1265
1266    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1267    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1268    pub fn stream(&self) -> Arc<CudaStream> {
1269        self.gpu.stream()
1270    }
1271    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1272    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1273    pub fn gkv_on() -> bool {
1274        memra_kv::gkv_on()
1275    }
1276
1277    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1278    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1279    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1280    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1281    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1282    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1283    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1284    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1285    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1286    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1287    /// ON for both — no acceptance cost measured.
1288    pub fn wkv_on() -> bool {
1289        memra_kv::wkv_on()
1290    }
1291
1292    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1293    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1294    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1295    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1296    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1297    pub fn kv_fp8_on() -> bool {
1298        memra_kv::kv_fp8_on()
1299    }
1300
1301    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1302    /// when the fp8-globals arm is on; everything else from the default flash module.
1303    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1304        if head_dim == 512 && Self::gkv_on() {
1305            self.func_g(name)
1306        } else {
1307            self.func(name)
1308        }
1309    }
1310
1311    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1312    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1313    /// per-format fatbins; fall back to the base modules for those.
1314    fn func_g(&self, name: &str) -> CudaFunction {
1315        let m = self.flash_g.get_or_init(|| {
1316            self.gpu
1317                .ctx
1318                .load_module(cudarc::nvrtc::Ptx::from_binary(
1319                    FLASH_FATBIN_KF8VF8.to_vec(),
1320                ))
1321                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1322        });
1323        let key = format!("g:{name}");
1324        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) {
1325            return f.clone();
1326        }
1327        let f = match m.load_function(name) {
1328            Ok(f) => f,
1329            Err(_) => self.func(name),
1330        };
1331        self.fn_cache.lock().unwrap().insert(key, f.clone());
1332        f
1333    }
1334
1335    fn func(&self, name: &str) -> CudaFunction {
1336        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1337        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1338        if let Some(f) = self.fn_cache.lock().unwrap().get(name) {
1339            return f.clone();
1340        }
1341        let f = self
1342            .module
1343            .load_function(name)
1344            .or_else(|_| self.hybrid.load_function(name))
1345            .or_else(|_| self.qmatvec.load_function(name))
1346            .or_else(|_| self.flash.load_function(name))
1347            .or_else(|_| self.gemm.load_function(name))
1348            .or_else(|_| self.router.load_function(name))
1349            .or_else(|_| self.sample.load_function(name))
1350            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1351        self.fn_cache
1352            .lock()
1353            .unwrap()
1354            .insert(name.to_string(), f.clone());
1355        f
1356    }
1357
1358    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1359    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1360    pub fn scatter_trim_logits(
1361        &self,
1362        src: &CudaSlice<f32>,
1363        d2t: &CudaSlice<u32>,
1364        dst: &mut CudaSlice<f32>,
1365        d_vocab: usize,
1366        n_vocab: usize,
1367    ) -> Result<(), Box<dyn std::error::Error>> {
1368        let f1 = self.func("scatter_trim_logits_f32");
1369        let f2 = self.func("scatter_trim_logits_pass2_f32");
1370        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1371        let cfg1 = LaunchConfig {
1372            grid_dim: (256, 1, 1),
1373            block_dim: (256, 1, 1),
1374            shared_mem_bytes: 0,
1375        };
1376        let __s_b1 = self.gpu.stream();
1377        let mut b1 = __s_b1.launch_builder(&f1);
1378        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1379        unsafe {
1380            b1.launch(cfg1)?;
1381        }
1382        let cfg2 = LaunchConfig {
1383            grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1),
1384            block_dim: (256, 1, 1),
1385            shared_mem_bytes: 0,
1386        };
1387        let __s_b2 = self.gpu.stream();
1388        let mut b2 = __s_b2.launch_builder(&f2);
1389        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1390        unsafe {
1391            b2.launch(cfg2)?;
1392        }
1393        Ok(())
1394    }
1395
1396    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1397    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1398
1399    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1400    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1401    #[allow(clippy::too_many_arguments)]
1402    pub fn filter_stats(
1403        &self,
1404        x: &CudaSlice<f32>,
1405        row_stride: usize,
1406        rows: &CudaSlice<i32>,
1407        out_th: &mut CudaSlice<f32>,
1408        out_z: &mut CudaSlice<f32>,
1409        out_max: &mut CudaSlice<f32>,
1410        n: usize,
1411        nrow: usize,
1412        temp: f32,
1413        top_k: i32,
1414        top_p: f32,
1415        min_p: f32,
1416    ) -> Result<(), Box<dyn std::error::Error>> {
1417        let f = self.func("filter_stats_f32");
1418        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1419        let cfg = LaunchConfig {
1420            grid_dim: (nrow as u32, 1, 1),
1421            block_dim: (1024, 1, 1),
1422            shared_mem_bytes: 0,
1423        };
1424        let __s_b = self.gpu.stream();
1425        let mut b = __s_b.launch_builder(&f);
1426        b.arg(x)
1427            .arg(&rs)
1428            .arg(rows)
1429            .arg(&mut *out_th)
1430            .arg(&mut *out_z)
1431            .arg(&mut *out_max)
1432            .arg(&ni)
1433            .arg(&nr)
1434            .arg(&temp)
1435            .arg(&top_k)
1436            .arg(&top_p)
1437            .arg(&min_p);
1438        unsafe {
1439            b.launch(cfg)?;
1440        }
1441        Ok(())
1442    }
1443
1444    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1445    #[allow(clippy::too_many_arguments)]
1446    pub fn softmax_gather_filtered(
1447        &self,
1448        x: &CudaSlice<f32>,
1449        row_stride: usize,
1450        ids: &CudaSlice<u32>,
1451        rows: &CudaSlice<i32>,
1452        th: &CudaSlice<f32>,
1453        z: &CudaSlice<f32>,
1454        out: &mut CudaSlice<f32>,
1455        n: usize,
1456        npair: usize,
1457        temp: f32,
1458    ) -> Result<(), Box<dyn std::error::Error>> {
1459        let f = self.func("softmax_gather_filtered_f32");
1460        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1461        let cfg = LaunchConfig {
1462            grid_dim: (npair as u32, 1, 1),
1463            block_dim: (256, 1, 1),
1464            shared_mem_bytes: 0,
1465        };
1466        let __s_b = self.gpu.stream();
1467        let mut b = __s_b.launch_builder(&f);
1468        b.arg(x)
1469            .arg(&rs)
1470            .arg(ids)
1471            .arg(rows)
1472            .arg(th)
1473            .arg(z)
1474            .arg(&mut *out)
1475            .arg(&ni)
1476            .arg(&np)
1477            .arg(&temp);
1478        unsafe {
1479            b.launch(cfg)?;
1480        }
1481        Ok(())
1482    }
1483
1484    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1485    #[allow(clippy::too_many_arguments)]
1486    pub fn residual_sample_filtered(
1487        &self,
1488        p: &CudaSlice<f32>,
1489        q: Option<&CudaSlice<f32>>,
1490        n: usize,
1491        temp: f32,
1492        seed: u64,
1493        stream_pos: u32,
1494        p_stats: (f32, f32, f32),
1495        q_stats: (f32, f32, f32),
1496        out_tok: &mut CudaSlice<u32>,
1497    ) -> Result<(), Box<dyn std::error::Error>> {
1498        let f = self.func("residual_sample_filtered_f32");
1499        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1500        let has_q: i32 = q.is_some() as i32;
1501        let qbuf = q.unwrap_or(p);
1502        let (pm, pth, pz) = p_stats;
1503        let (qm, qth, qz) = q_stats;
1504        let cfg = LaunchConfig {
1505            grid_dim: (1, 1, 1),
1506            block_dim: (1024, 1, 1),
1507            shared_mem_bytes: 0,
1508        };
1509        let __s_b = self.gpu.stream();
1510        let mut b = __s_b.launch_builder(&f);
1511        b.arg(p)
1512            .arg(qbuf)
1513            .arg(&has_q)
1514            .arg(&ni)
1515            .arg(&temp)
1516            .arg(&slo)
1517            .arg(&shi)
1518            .arg(&stream_pos)
1519            .arg(&pm)
1520            .arg(&pth)
1521            .arg(&pz)
1522            .arg(&qm)
1523            .arg(&qth)
1524            .arg(&qz)
1525            .arg(&mut *out_tok);
1526        unsafe {
1527            b.launch(cfg)?;
1528        }
1529        Ok(())
1530    }
1531
1532    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1533    #[allow(clippy::too_many_arguments)]
1534    pub fn gumbel_perturb_filtered(
1535        &self,
1536        x: &CudaSlice<f32>,
1537        y: &mut CudaSlice<f32>,
1538        n: usize,
1539        seed: u64,
1540        stream_pos: u32,
1541        temp: f32,
1542        row_max: f32,
1543        th: f32,
1544    ) -> Result<(), Box<dyn std::error::Error>> {
1545        let f = self.func("gumbel_perturb_filtered_f32");
1546        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1547        let cfg = LaunchConfig {
1548            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1549            block_dim: (256, 1, 1),
1550            shared_mem_bytes: 0,
1551        };
1552        let __s_b = self.gpu.stream();
1553        let mut b = __s_b.launch_builder(&f);
1554        b.arg(x)
1555            .arg(&mut *y)
1556            .arg(&ni)
1557            .arg(&slo)
1558            .arg(&shi)
1559            .arg(&stream_pos)
1560            .arg(&temp)
1561            .arg(&row_max)
1562            .arg(&th);
1563        unsafe {
1564            b.launch(cfg)?;
1565        }
1566        Ok(())
1567    }
1568
1569    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1570    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1571    /// filtered rejection sampling exact for the penalized target.
1572    #[allow(clippy::too_many_arguments)]
1573    pub fn penalize_logits(
1574        &self,
1575        x: &mut CudaSlice<f32>,
1576        hist: &CudaSlice<u32>,
1577        n_hist: usize,
1578        rep: f32,
1579        freq: f32,
1580        present: f32,
1581        n: usize,
1582    ) -> Result<(), Box<dyn std::error::Error>> {
1583        if n_hist == 0 {
1584            return Ok(());
1585        }
1586        let f = self.func("penalize_logits_f32");
1587        let (nh, ni) = (n_hist as i32, n as i32);
1588        let cfg = LaunchConfig {
1589            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1590            block_dim: (128, 1, 1),
1591            shared_mem_bytes: 0,
1592        };
1593        let __s_b = self.gpu.stream();
1594        let mut b = __s_b.launch_builder(&f);
1595        b.arg(&mut *x)
1596            .arg(hist)
1597            .arg(&nh)
1598            .arg(&rep)
1599            .arg(&freq)
1600            .arg(&present)
1601            .arg(&ni);
1602        unsafe {
1603            b.launch(cfg)?;
1604        }
1605        Ok(())
1606    }
1607
1608    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1609    #[allow(clippy::too_many_arguments)]
1610    pub fn penalize_logits_rows(
1611        &self,
1612        x: &mut CudaSlice<f32>,
1613        hist: &CudaSlice<u32>,
1614        n_hist: usize,
1615        rep: f32,
1616        freq: f32,
1617        present: f32,
1618        n: usize,
1619        nrow: usize,
1620    ) -> Result<(), Box<dyn std::error::Error>> {
1621        if n_hist == 0 || nrow == 0 {
1622            return Ok(());
1623        }
1624        let f = self.func("penalize_logits_rows_f32");
1625        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1626        let cfg = LaunchConfig {
1627            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1628            block_dim: (128, 1, 1),
1629            shared_mem_bytes: 0,
1630        };
1631        let __s_b = self.gpu.stream();
1632        let mut b = __s_b.launch_builder(&f);
1633        b.arg(&mut *x)
1634            .arg(hist)
1635            .arg(&nh)
1636            .arg(&rep)
1637            .arg(&freq)
1638            .arg(&present)
1639            .arg(&ni)
1640            .arg(&nr);
1641        unsafe {
1642            b.launch(cfg)?;
1643        }
1644        Ok(())
1645    }
1646
1647    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1648    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1649    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1650    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1651    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1652    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1653    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1654    pub fn wpf_level() -> u32 {
1655        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1656        *ON.get_or_init(|| {
1657            std::env::var("MEMRA_WPF")
1658                .ok()
1659                .and_then(|v| v.parse().ok())
1660                .unwrap_or(1)
1661        })
1662    }
1663
1664    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1665    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1666    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1667    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1668    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1669    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1670    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1671    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1672    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1673    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1674    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1675    pub fn set_verify_exact(&self, on: bool) {
1676        self.verify_exact
1677            .store(on, std::sync::atomic::Ordering::Relaxed);
1678    }
1679    pub(crate) fn verify_exact_on(&self) -> bool {
1680        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1681    }
1682
1683    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1684    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1685    pub fn qkv_append_on() -> bool {
1686        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1687        *ON.get_or_init(|| {
1688            std::env::var("MEMRA_QKV_APPEND")
1689                .map(|v| v != "0")
1690                .unwrap_or(true)
1691        })
1692    }
1693
1694    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1695    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1696    pub fn pdl_wb_on() -> bool {
1697        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1698        *ON.get_or_init(|| {
1699            std::env::var("MEMRA_PDL_WB")
1700                .map(|v| v != "0")
1701                .unwrap_or(true)
1702        })
1703    }
1704
1705    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1706    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1707    /// per-model no-harm bisect knob.
1708    pub fn pdl_mmvq_on() -> bool {
1709        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1710        *ON.get_or_init(|| {
1711            std::env::var("MEMRA_PDL_MMVQ")
1712                .map(|v| v != "0")
1713                .unwrap_or(true)
1714        })
1715    }
1716
1717    pub fn pdl_on() -> bool {
1718        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1719        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1720    }
1721
1722    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1723    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1724    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1725    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1726    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1727    fn q40_mr1_on() -> bool {
1728        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1729        match *Q40MR.get_or_init(|| {
1730            std::env::var("MEMRA_Q40_MR")
1731                .ok()
1732                .and_then(|v| v.parse().ok())
1733        }) {
1734            Some(v) => v == 1,
1735            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1736        }
1737    }
1738
1739    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1740    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1741    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1742    /// writes wrong bytes silently.
1743    fn pdl_func_flash(
1744        &self,
1745        g: bool,
1746        name: &'static str,
1747    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1748        use cudarc::driver::sys as cu;
1749        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1750        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1751        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1752        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1753        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1754        // this engine's CUcontext; single-context runs behave exactly as before.
1755        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1756            std::sync::Mutex::new(None);
1757        static FNS: std::sync::Mutex<
1758            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
1759        > = std::sync::Mutex::new(None);
1760        let ctx_key = self.ctx().cu_ctx() as usize;
1761        if let Some(&f) = FNS
1762            .lock()
1763            .unwrap()
1764            .get_or_insert_with(Default::default)
1765            .get(&(ctx_key, g, name))
1766        {
1767            return Ok(f as cu::CUfunction);
1768        }
1769        let module = {
1770            let mut mods = MODS.lock().unwrap();
1771            let map = mods.get_or_insert_with(Default::default);
1772            match map.get(&(ctx_key, g)) {
1773                Some(&m) => m,
1774                None => {
1775                    let m = self.pdl_load_module_in_ctx(if g {
1776                        FLASH_FATBIN_KF8VF8
1777                    } else {
1778                        FLASH_FATBIN
1779                    })?;
1780                    map.insert((ctx_key, g), m);
1781                    m
1782                }
1783            }
1784        };
1785        let cname = std::ffi::CString::new(name)?;
1786        let mut f: cu::CUfunction = std::ptr::null_mut();
1787        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1788        if r != cu::CUresult::CUDA_SUCCESS {
1789            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
1790        }
1791        FNS.lock()
1792            .unwrap()
1793            .get_or_insert_with(Default::default)
1794            .insert((ctx_key, g, name), f as usize);
1795        Ok(f)
1796    }
1797
1798    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1799    /// the module to the thread's CURRENT context — a remote-stage engine must not
1800    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1801    /// current context before returning.
1802    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1803        use cudarc::driver::sys as cu;
1804        let mut prev: cu::CUcontext = std::ptr::null_mut();
1805        unsafe {
1806            cu::cuCtxGetCurrent(&mut prev).result()?;
1807        }
1808        self.ctx().bind_to_thread()?;
1809        let mut m: cu::CUmodule = std::ptr::null_mut();
1810        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1811        let restore = if prev.is_null() {
1812            cu::CUresult::CUDA_SUCCESS
1813        } else {
1814            unsafe { cu::cuCtxSetCurrent(prev) }
1815        };
1816        if r != cu::CUresult::CUDA_SUCCESS {
1817            return Err(format!("pdl module load: {r:?}").into());
1818        }
1819        if restore != cu::CUresult::CUDA_SUCCESS {
1820            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1821        }
1822        Ok(m as usize)
1823    }
1824
1825    fn pdl_func(
1826        &self,
1827        name: &'static str,
1828    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1829        use cudarc::driver::sys as cu;
1830        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1831        // are context-scoped; key everything by this engine's CUcontext).
1832        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1833            std::sync::Mutex::new(None);
1834        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1835        // duplicate module, loaded lazily on the first kernels-module miss.
1836        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1837            std::sync::Mutex::new(None);
1838        static FNS: std::sync::Mutex<
1839            Option<std::collections::HashMap<(usize, &'static str), usize>>,
1840        > = std::sync::Mutex::new(None);
1841        let ctx_key = self.ctx().cu_ctx() as usize;
1842        if let Some(&f) = FNS
1843            .lock()
1844            .unwrap()
1845            .get_or_insert_with(Default::default)
1846            .get(&(ctx_key, name))
1847        {
1848            return Ok(f as cu::CUfunction);
1849        }
1850        let module = {
1851            let mut mods = MODULES.lock().unwrap();
1852            let map = mods.get_or_insert_with(Default::default);
1853            match map.get(&ctx_key) {
1854                Some(&m) => m,
1855                None => {
1856                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1857                    map.insert(ctx_key, m);
1858                    m
1859                }
1860            }
1861        };
1862        let cname = std::ffi::CString::new(name)?;
1863        let mut f: cu::CUfunction = std::ptr::null_mut();
1864        let mut r =
1865            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1866        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1867            let qmodule = {
1868                let mut mods = QMODULES.lock().unwrap();
1869                let map = mods.get_or_insert_with(Default::default);
1870                match map.get(&ctx_key) {
1871                    Some(&m) => m,
1872                    None => {
1873                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1874                        map.insert(ctx_key, m);
1875                        m
1876                    }
1877                }
1878            };
1879            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1880        }
1881        if r != cu::CUresult::CUDA_SUCCESS {
1882            return Err(format!("pdl_func {name}: {r:?}").into());
1883        }
1884        FNS.lock()
1885            .unwrap()
1886            .get_or_insert_with(Default::default)
1887            .insert((ctx_key, name), f as usize);
1888        Ok(f)
1889    }
1890
1891    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1892    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1893    ///
1894    /// # Safety
1895    /// `params` must match the kernel's exact parameter list (order, types, count) —
1896    /// a mismatch corrupts the launch silently.
1897    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1898    /// builder path's fa_func/func_g choice exactly).
1899    ///
1900    /// # Safety
1901    /// Same contract as `launch_pdl`.
1902    unsafe fn launch_pdl_flash(
1903        &self,
1904        g: bool,
1905        name: &'static str,
1906        grid: (u32, u32, u32),
1907        block: (u32, u32, u32),
1908        smem: u32,
1909        params: &mut [*mut std::ffi::c_void],
1910    ) -> Result<(), Box<dyn std::error::Error>> {
1911        use cudarc::driver::sys as cu;
1912        let f = self.pdl_func_flash(g, name)?;
1913        if smem > 0 {
1914            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1915            let r =
1916                unsafe {
1917                    cu::cuFuncSetAttribute(f,
1918                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1919                smem as i32)
1920                };
1921            if r != cu::CUresult::CUDA_SUCCESS {
1922                return Err(format!("pdl smem attr {name}: {r:?}").into());
1923            }
1924        }
1925        let mut attr = cu::CUlaunchAttribute {
1926            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1927            pad: [0; 4],
1928            value: cu::CUlaunchAttributeValue {
1929                programmaticStreamSerializationAllowed: 1,
1930            },
1931        };
1932        let cfg = cu::CUlaunchConfig {
1933            gridDimX: grid.0,
1934            gridDimY: grid.1,
1935            gridDimZ: grid.2,
1936            blockDimX: block.0,
1937            blockDimY: block.1,
1938            blockDimZ: block.2,
1939            sharedMemBytes: smem,
1940            hStream: self.gpu.stream().cu_stream(),
1941            attrs: &mut attr,
1942            numAttrs: 1,
1943        };
1944        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1945        if r != cu::CUresult::CUDA_SUCCESS {
1946            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
1947        }
1948        Ok(())
1949    }
1950
1951    unsafe fn launch_pdl(
1952        &self,
1953        name: &'static str,
1954        grid: (u32, u32, u32),
1955        block: (u32, u32, u32),
1956        params: &mut [*mut std::ffi::c_void],
1957    ) -> Result<(), Box<dyn std::error::Error>> {
1958        use cudarc::driver::sys as cu;
1959        let f = self.pdl_func(name)?;
1960        let mut attr = cu::CUlaunchAttribute {
1961            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1962            pad: [0; 4],
1963            value: cu::CUlaunchAttributeValue {
1964                programmaticStreamSerializationAllowed: 1,
1965            },
1966        };
1967        let cfg = cu::CUlaunchConfig {
1968            gridDimX: grid.0,
1969            gridDimY: grid.1,
1970            gridDimZ: grid.2,
1971            blockDimX: block.0,
1972            blockDimY: block.1,
1973            blockDimZ: block.2,
1974            sharedMemBytes: 0,
1975            hStream: self.gpu.stream().cu_stream(),
1976            attrs: &mut attr,
1977            numAttrs: 1,
1978        };
1979        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1980        if r != cu::CUresult::CUDA_SUCCESS {
1981            return Err(format!("launch_pdl {name}: {r:?}").into());
1982        }
1983        Ok(())
1984    }
1985
1986    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
1987    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
1988    pub fn prefetch_weight_l2(
1989        &self,
1990        w: &crate::model::GpuTensor,
1991    ) -> Result<(), Box<dyn std::error::Error>> {
1992        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
1993            let p = rp4.as_ref().unwrap_or(bytes);
1994            self.prefetch_l2(p, p.len())?;
1995        }
1996        Ok(())
1997    }
1998
1999    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
2000    /// by the DEVICE token id at tok[idx] into f32.
2001    pub fn gather_row_bf16(
2002        &self,
2003        table: &CudaSlice<u8>,
2004        tok: &CudaSlice<u32>,
2005        idx: usize,
2006        dst: &mut CudaSlice<f32>,
2007        ncols: usize,
2008    ) -> Result<(), Box<dyn std::error::Error>> {
2009        let f = self.func("gather_row_bf16_f32");
2010        let cfg = LaunchConfig {
2011            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2012            block_dim: (256, 1, 1),
2013            shared_mem_bytes: 0,
2014        };
2015        let (nc, ix) = (ncols as i32, idx as i32);
2016        let __s_b = self.gpu.stream();
2017        let mut b = __s_b.launch_builder(&f);
2018        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2019        unsafe {
2020            b.launch(cfg)?;
2021        }
2022        Ok(())
2023    }
2024
2025    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2026    pub fn add_row_inplace(
2027        &self,
2028        logits: &mut CudaSlice<f32>,
2029        bias: &CudaSlice<f32>,
2030        n: usize,
2031        row_off: usize,
2032    ) -> Result<(), Box<dyn std::error::Error>> {
2033        let f = self.func("add_row_inplace_f32");
2034        let cfg = LaunchConfig {
2035            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2036            block_dim: (256, 1, 1),
2037            shared_mem_bytes: 0,
2038        };
2039        let (ni, off) = (n as i32, row_off as i64);
2040        let __s_b = self.gpu.stream();
2041        let mut b = __s_b.launch_builder(&f);
2042        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2043        unsafe {
2044            b.launch(cfg)?;
2045        }
2046        Ok(())
2047    }
2048
2049    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2050    pub fn prefetch_l2(
2051        &self,
2052        p: &CudaSlice<u8>,
2053        n: usize,
2054    ) -> Result<(), Box<dyn std::error::Error>> {
2055        let f = self.func("prefetch_l2_bytes");
2056        let lines = n.div_ceil(128);
2057        let ni = n as i64;
2058        let cfg = LaunchConfig {
2059            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2060            block_dim: (256, 1, 1),
2061            shared_mem_bytes: 0,
2062        };
2063        let __s_b = self.gpu.stream();
2064        let mut b = __s_b.launch_builder(&f);
2065        b.arg(p).arg(&ni);
2066        unsafe {
2067            b.launch(cfg)?;
2068        }
2069        Ok(())
2070    }
2071
2072    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2073    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2074    pub fn router_gemv(
2075        &self,
2076        w: &CudaSlice<f32>,
2077        x: &CudaSlice<f32>,
2078        n_embd: usize,
2079        n_experts: usize,
2080        t: usize,
2081    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2082        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2083        // stream differs) — too small to justify a numeric config change; deleted.
2084        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2085        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2086        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2087        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2088            Ok("0") => false,
2089            Ok(_) => true,
2090            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2091        };
2092        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2093        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2094        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2095        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2096        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2097        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2098        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2099        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2100        // (perf-only, bits equal).
2101        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2102        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2103    }
2104
2105    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2106    /// force both forms; `batch` requires `w8`).
2107    pub fn router_gemv_form(
2108        &self,
2109        w: &CudaSlice<f32>,
2110        x: &CudaSlice<f32>,
2111        n_embd: usize,
2112        n_experts: usize,
2113        t: usize,
2114        w8: bool,
2115        batch: bool,
2116    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2117        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2118        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2119        let f = if batch {
2120            self.func("router_gemv_f32_w8_batch")
2121        } else if w8 {
2122            self.func("router_gemv_f32_w8")
2123        } else {
2124            self.func("router_gemv_f32")
2125        };
2126        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2127        let cfg = if batch {
2128            LaunchConfig {
2129                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2130                block_dim: (32, 8, 1),
2131                shared_mem_bytes: 0,
2132            }
2133        } else {
2134            LaunchConfig {
2135                grid_dim: (n_experts as u32, t as u32, 1),
2136                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2137                shared_mem_bytes: 0,
2138            }
2139        };
2140        let __s_b = self.gpu.stream();
2141        let mut b = __s_b.launch_builder(&f);
2142        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2143        unsafe {
2144            b.launch(cfg)?;
2145        }
2146        Ok(y)
2147    }
2148
2149    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2150    pub fn rows_permute(
2151        &self,
2152        src: &CudaSlice<f32>,
2153        idx: &CudaSlice<i32>,
2154        nrows: usize,
2155        ncols: usize,
2156    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2157        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2158        let f = self.func("rows_permute_f32");
2159        let (nc, nr) = (ncols as i32, nrows as i32);
2160        let cfg = LaunchConfig {
2161            grid_dim: (nrows as u32, 1, 1),
2162            block_dim: (256, 1, 1),
2163            shared_mem_bytes: 0,
2164        };
2165        let __s_b = self.gpu.stream();
2166        let mut b = __s_b.launch_builder(&f);
2167        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2168        unsafe {
2169            b.launch(cfg)?;
2170        }
2171        Ok(dst)
2172    }
2173
2174    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2175    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2176    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2177    /// decode chain and the small-t spec-verify chain match per row by construction.
2178    pub fn sigmoid_dot_rows(
2179        &self,
2180        x: &CudaSlice<f32>,
2181        w: &CudaSlice<f32>,
2182        n_embd: usize,
2183        t: usize,
2184    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2185        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2186        // config; same class as MEMRA_ROUTER_V2).
2187        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2188        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2189            let gs = self.linear(x, w, t, n_embd, 1)?;
2190            let mut g = self.uninit(t)?;
2191            self.sigmoid(&gs, &mut g, t)?;
2192            return Ok(g);
2193        }
2194        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2195        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2196        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2197        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2198        // flags doctrine; this per-token form serves every t.
2199        let mut g = self.alloc_uninit::<f32>(t)?;
2200        let f = self.func("sigmoid_dot_rows_f32");
2201        let (ne, ti) = (n_embd as i32, t as i32);
2202        let cfg = LaunchConfig {
2203            grid_dim: (t as u32, 1, 1),
2204            block_dim: (32, 8, 1),
2205            shared_mem_bytes: 0,
2206        };
2207        let __s_b = self.gpu.stream();
2208        let mut b = __s_b.launch_builder(&f);
2209        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2210        unsafe {
2211            b.launch(cfg)?;
2212        }
2213        Ok(g)
2214    }
2215
2216    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2217    pub fn spec_rollback_stream(
2218        &self,
2219        len_ptrs: &CudaSlice<u64>,
2220        pos_start: &CudaSlice<i32>,
2221        acc: &CudaSlice<u32>,
2222        base: usize,
2223        n_rows: usize,
2224    ) -> Result<(), Box<dyn std::error::Error>> {
2225        let f = self.func("spec_rollback_stream");
2226        let (b, nr) = (base as i32, n_rows as i32);
2227        let cfg = LaunchConfig {
2228            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2229            block_dim: (64, 1, 1),
2230            shared_mem_bytes: 0,
2231        };
2232        let __s_bl = self.gpu.stream();
2233        let mut bl = __s_bl.launch_builder(&f);
2234        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2235        unsafe {
2236            bl.launch(cfg)?;
2237        }
2238        Ok(())
2239    }
2240
2241    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2242    pub fn plain_tok_ring(
2243        &self,
2244        vam: &CudaSlice<u32>,
2245        pos_start: &CudaSlice<i32>,
2246        base: usize,
2247        ring: &mut CudaSlice<u32>,
2248    ) -> Result<(), Box<dyn std::error::Error>> {
2249        let f = self.func("plain_tok_ring");
2250        let (b, cap) = (base as i32, ring.len() as i32);
2251        let cfg = LaunchConfig {
2252            grid_dim: (1, 1, 1),
2253            block_dim: (32, 1, 1),
2254            shared_mem_bytes: 0,
2255        };
2256        let __s_bl = self.gpu.stream();
2257        let mut bl = __s_bl.launch_builder(&f);
2258        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2259        unsafe {
2260            bl.launch(cfg)?;
2261        }
2262        Ok(())
2263    }
2264
2265    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2266    pub fn spec_ring_commit(
2267        &self,
2268        vtok: &CudaSlice<u32>,
2269        acc: &CudaSlice<u32>,
2270        brk: &CudaSlice<u32>,
2271        ring: &mut CudaSlice<u32>,
2272        pend: &mut CudaSlice<u32>,
2273    ) -> Result<(), Box<dyn std::error::Error>> {
2274        let f = self.func("spec_ring_commit");
2275        let cfg = LaunchConfig {
2276            grid_dim: (1, 1, 1),
2277            block_dim: (32, 1, 1),
2278            shared_mem_bytes: 0,
2279        };
2280        let __s_b = self.gpu.stream();
2281        let mut b = __s_b.launch_builder(&f);
2282        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2283        unsafe {
2284            b.launch(cfg)?;
2285        }
2286        Ok(())
2287    }
2288    pub fn i32_copy_add(
2289        &self,
2290        src: &CudaSlice<i32>,
2291        dst: &mut CudaSlice<i32>,
2292        delta: i32,
2293    ) -> Result<(), Box<dyn std::error::Error>> {
2294        let f = self.func("i32_copy_add");
2295        let cfg = LaunchConfig {
2296            grid_dim: (1, 1, 1),
2297            block_dim: (32, 1, 1),
2298            shared_mem_bytes: 0,
2299        };
2300        let __s_b = self.gpu.stream();
2301        let mut b = __s_b.launch_builder(&f);
2302        b.arg(src).arg(dst).arg(&delta);
2303        unsafe {
2304            b.launch(cfg)?;
2305        }
2306        Ok(())
2307    }
2308    pub fn u32_copy(
2309        &self,
2310        src: &CudaSlice<u32>,
2311        dst: &mut CudaSlice<u32>,
2312    ) -> Result<(), Box<dyn std::error::Error>> {
2313        let f = self.func("u32_copy");
2314        let cfg = LaunchConfig {
2315            grid_dim: (1, 1, 1),
2316            block_dim: (32, 1, 1),
2317            shared_mem_bytes: 0,
2318        };
2319        let __s_b = self.gpu.stream();
2320        let mut b = __s_b.launch_builder(&f);
2321        b.arg(src).arg(dst);
2322        unsafe {
2323            b.launch(cfg)?;
2324        }
2325        Ok(())
2326    }
2327
2328    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2329    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2330    /// caps acceptance exactly like drafting fewer tokens).
2331    pub fn spec_adapt_k(
2332        &self,
2333        acc: &CudaSlice<u32>,
2334        brk: &mut CudaSlice<u32>,
2335        floor: usize,
2336        cap: usize,
2337    ) -> Result<(), Box<dyn std::error::Error>> {
2338        let f = self.func("spec_adapt_k");
2339        let (fl, cp) = (floor as i32, cap as i32);
2340        let cfg = LaunchConfig {
2341            grid_dim: (1, 1, 1),
2342            block_dim: (32, 1, 1),
2343            shared_mem_bytes: 0,
2344        };
2345        let __s_b = self.gpu.stream();
2346        let mut b = __s_b.launch_builder(&f);
2347        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2348        unsafe {
2349            b.launch(cfg)?;
2350        }
2351        Ok(())
2352    }
2353
2354    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2355    pub fn spec_accept_greedy_dc(
2356        &self,
2357        preds: &CudaSlice<u32>,
2358        vtok: &CudaSlice<u32>,
2359        last_pred: &CudaSlice<u32>,
2360        brk: &CudaSlice<u32>,
2361        out: &mut CudaSlice<u32>,
2362    ) -> Result<(), Box<dyn std::error::Error>> {
2363        let f = self.func("spec_accept_greedy_dc");
2364        let cfg = LaunchConfig {
2365            grid_dim: (1, 1, 1),
2366            block_dim: (32, 1, 1),
2367            shared_mem_bytes: 0,
2368        };
2369        let __s_b = self.gpu.stream();
2370        let mut b = __s_b.launch_builder(&f);
2371        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2372        unsafe {
2373            b.launch(cfg)?;
2374        }
2375        Ok(())
2376    }
2377
2378    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2379    pub fn pos_iota(
2380        &self,
2381        pos0: &CudaSlice<i32>,
2382        out: &mut CudaSlice<i32>,
2383        t: usize,
2384    ) -> Result<(), Box<dyn std::error::Error>> {
2385        let f = self.func("pos_iota_i32");
2386        let ti = t as i32;
2387        let cfg = LaunchConfig {
2388            grid_dim: (1, 1, 1),
2389            block_dim: (t.max(1) as u32, 1, 1),
2390            shared_mem_bytes: 0,
2391        };
2392        let __s_b = self.gpu.stream();
2393        let mut b = __s_b.launch_builder(&f);
2394        b.arg(pos0).arg(out).arg(&ti);
2395        unsafe {
2396            b.launch(cfg)?;
2397        }
2398        Ok(())
2399    }
2400    #[allow(clippy::too_many_arguments)]
2401    pub fn append_kv_quantized_rows_dc(
2402        &self,
2403        k_rows: &CudaSlice<f32>,
2404        v_rows: &CudaSlice<f32>,
2405        kc: &mut CudaSlice<u8>,
2406        vc: &mut CudaSlice<u8>,
2407        t0_dev: &CudaSlice<i32>,
2408        t: usize,
2409        kv_dim_k: usize,
2410        kv_dim_v: usize,
2411        k_tok_bytes: usize,
2412        v_tok_bytes: usize,
2413        g: bool,
2414    ) -> Result<(), Box<dyn std::error::Error>> {
2415        let f = if g {
2416            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2417        } else {
2418            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2419        };
2420        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2421        let cfg = LaunchConfig {
2422            grid_dim: (nblk, t as u32, 1),
2423            block_dim: (32, 1, 1),
2424            shared_mem_bytes: 0,
2425        };
2426        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2427        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2428        let __s_b = self.gpu.stream();
2429        let mut b = __s_b.launch_builder(&f);
2430        b.arg(k_rows)
2431            .arg(v_rows)
2432            .arg(kc)
2433            .arg(vc)
2434            .arg(t0_dev)
2435            .arg(&kdk)
2436            .arg(&kdv)
2437            .arg(&ktb)
2438            .arg(&vtb);
2439        unsafe {
2440            b.launch(cfg)?;
2441        }
2442        Ok(())
2443    }
2444
2445    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2446    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2447    #[allow(clippy::too_many_arguments)]
2448    pub fn append_kv_quantized_row_dc_inc(
2449        &self,
2450        k_row: &CudaSlice<f32>,
2451        v_row: &CudaSlice<f32>,
2452        kc: &mut CudaSlice<u8>,
2453        vc: &mut CudaSlice<u8>,
2454        t0_dev: &mut CudaSlice<i32>,
2455        kv_dim_k: usize,
2456        kv_dim_v: usize,
2457        k_tok_bytes: usize,
2458        v_tok_bytes: usize,
2459        g: bool,
2460    ) -> Result<(), Box<dyn std::error::Error>> {
2461        let f = if g {
2462            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2463        } else {
2464            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2465        };
2466        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2467        let cfg = LaunchConfig {
2468            grid_dim: (1, 1, 1),
2469            block_dim: (nthreads, 1, 1),
2470            shared_mem_bytes: 0,
2471        };
2472        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2473        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2474        let __s_b = self.gpu.stream();
2475        let mut b = __s_b.launch_builder(&f);
2476        b.arg(k_row)
2477            .arg(v_row)
2478            .arg(kc)
2479            .arg(vc)
2480            .arg(t0_dev)
2481            .arg(&kdk)
2482            .arg(&kdv)
2483            .arg(&ktb)
2484            .arg(&vtb);
2485        unsafe {
2486            b.launch(cfg)?;
2487        }
2488        Ok(())
2489    }
2490
2491    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
2492    pub fn pack_tok_p(
2493        &self,
2494        tok: &CudaSlice<u32>,
2495        p: &CudaSlice<f32>,
2496        out: &mut CudaSlice<u32>,
2497        slot: usize,
2498    ) -> Result<(), Box<dyn std::error::Error>> {
2499        let f = self.func("pack_tok_p");
2500        let sl = slot as i32;
2501        let cfg = LaunchConfig {
2502            grid_dim: (1, 1, 1),
2503            block_dim: (32, 1, 1),
2504            shared_mem_bytes: 0,
2505        };
2506        let __s_b = self.gpu.stream();
2507        let mut b = __s_b.launch_builder(&f);
2508        b.arg(tok).arg(p).arg(out).arg(&sl);
2509        unsafe {
2510            b.launch(cfg)?;
2511        }
2512        Ok(())
2513    }
2514    pub fn tok_map_u32(
2515        &self,
2516        tok: &mut CudaSlice<u32>,
2517        map: &CudaSlice<u32>,
2518    ) -> Result<(), Box<dyn std::error::Error>> {
2519        let f = self.func("tok_map_u32");
2520        let cfg = LaunchConfig {
2521            grid_dim: (1, 1, 1),
2522            block_dim: (32, 1, 1),
2523            shared_mem_bytes: 0,
2524        };
2525        let __s_b = self.gpu.stream();
2526        let mut b = __s_b.launch_builder(&f);
2527        b.arg(tok).arg(map);
2528        unsafe {
2529            b.launch(cfg)?;
2530        }
2531        Ok(())
2532    }
2533
2534    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
2535    #[allow(clippy::too_many_arguments)]
2536    pub fn spec_assemble_verify(
2537        &self,
2538        tokp: &CudaSlice<u32>,
2539        pend: &CudaSlice<u32>,
2540        d2t: Option<&CudaSlice<u32>>,
2541        vtok: &mut CudaSlice<u32>,
2542        brk: &mut CudaSlice<u32>,
2543        p_min: f32,
2544        k: usize,
2545        pmin0: bool,
2546    ) -> Result<(), Box<dyn std::error::Error>> {
2547        let f = self.func("spec_assemble_verify");
2548        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
2549        let cfg = LaunchConfig {
2550            grid_dim: (1, 1, 1),
2551            block_dim: (32, 1, 1),
2552            shared_mem_bytes: 0,
2553        };
2554        let __s_b = self.gpu.stream();
2555        let mut b = __s_b.launch_builder(&f);
2556        match d2t {
2557            Some(m) => {
2558                b.arg(tokp)
2559                    .arg(pend)
2560                    .arg(m)
2561                    .arg(vtok)
2562                    .arg(brk)
2563                    .arg(&p_min)
2564                    .arg(&ki)
2565                    .arg(&pm);
2566                unsafe {
2567                    b.launch(cfg)?;
2568                }
2569            }
2570            None => {
2571                let null: u64 = 0;
2572                b.arg(tokp)
2573                    .arg(pend)
2574                    .arg(&null)
2575                    .arg(vtok)
2576                    .arg(brk)
2577                    .arg(&p_min)
2578                    .arg(&ki)
2579                    .arg(&pm);
2580                unsafe {
2581                    b.launch(cfg)?;
2582                }
2583            }
2584        }
2585        Ok(())
2586    }
2587
2588    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
2589    #[allow(clippy::too_many_arguments)]
2590    pub fn ssm_conv_ring_rebuild_dc(
2591        &self,
2592        qkv_tm: &CudaSlice<f32>,
2593        ring_old: &CudaSlice<f32>,
2594        conv_state: &mut CudaSlice<f32>,
2595        conv_dim: usize,
2596        acc: &CudaSlice<u32>,
2597        base: usize,
2598        t_v: usize,
2599        d_conv: usize,
2600    ) -> Result<(), Box<dyn std::error::Error>> {
2601        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
2602        let n = conv_dim * (d_conv - 1);
2603        let cfg = LaunchConfig::for_num_elems(n as u32);
2604        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
2605        let __s_b = self.gpu.stream();
2606        let mut b = __s_b.launch_builder(&f);
2607        b.arg(qkv_tm)
2608            .arg(ring_old)
2609            .arg(conv_state)
2610            .arg(&cd)
2611            .arg(acc)
2612            .arg(&b0)
2613            .arg(&tv)
2614            .arg(&dc);
2615        unsafe {
2616            b.launch(cfg)?;
2617        }
2618        Ok(())
2619    }
2620    #[allow(clippy::too_many_arguments)]
2621    pub fn gdn_scan_s128_dc(
2622        &self,
2623        q: &CudaSlice<f32>,
2624        k: &CudaSlice<f32>,
2625        v: &CudaSlice<f32>,
2626        g: &CudaSlice<f32>,
2627        beta: &CudaSlice<f32>,
2628        state_in: &CudaSlice<f32>,
2629        state_out: &mut CudaSlice<f32>,
2630        o: &mut CudaSlice<f32>,
2631        n_head: usize,
2632        acc: &CudaSlice<u32>,
2633        base: usize,
2634        t_v: usize,
2635        scale: f32,
2636    ) -> Result<(), Box<dyn std::error::Error>> {
2637        let f = self.func("gdn_scan_s128_dc");
2638        const S_V: u32 = 128;
2639        const WARP: u32 = 32;
2640        const COLS_PER_BLOCK: u32 = 4;
2641        let cfg = LaunchConfig {
2642            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
2643            block_dim: (WARP, COLS_PER_BLOCK, 1),
2644            shared_mem_bytes: 0,
2645        };
2646        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
2647        let __s_b = self.gpu.stream();
2648        let mut b = __s_b.launch_builder(&f);
2649        b.arg(q)
2650            .arg(k)
2651            .arg(v)
2652            .arg(g)
2653            .arg(beta)
2654            .arg(state_in)
2655            .arg(state_out)
2656            .arg(o)
2657            .arg(&h)
2658            .arg(acc)
2659            .arg(&b0)
2660            .arg(&tv)
2661            .arg(&scale);
2662        unsafe {
2663            b.launch(cfg)?;
2664        }
2665        Ok(())
2666    }
2667
2668    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
2669    pub fn spec_rollback_kv(
2670        &self,
2671        len_ptrs: &CudaSlice<u64>,
2672        saved: &CudaSlice<i32>,
2673        acc: &CudaSlice<u32>,
2674        base: usize,
2675        n_layer: usize,
2676    ) -> Result<(), Box<dyn std::error::Error>> {
2677        let f = self.func("spec_rollback_kv");
2678        let (b, nl) = (base as i32, n_layer as i32);
2679        let cfg = LaunchConfig {
2680            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2681            block_dim: (64, 1, 1),
2682            shared_mem_bytes: 0,
2683        };
2684        let __s_bl = self.gpu.stream();
2685        let mut bl = __s_bl.launch_builder(&f);
2686        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
2687        unsafe {
2688            bl.launch(cfg)?;
2689        }
2690        Ok(())
2691    }
2692
2693    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
2694    pub fn spec_fork_valid(
2695        &self,
2696        acc: &CudaSlice<u32>,
2697        optimistic_pending: u32,
2698        valid: &mut CudaSlice<u32>,
2699    ) -> Result<(), Box<dyn std::error::Error>> {
2700        let f = self.func("spec_fork_valid");
2701        let cfg = LaunchConfig {
2702            grid_dim: (1, 1, 1),
2703            block_dim: (1, 1, 1),
2704            shared_mem_bytes: 0,
2705        };
2706        let __s_bl = self.gpu.stream();
2707        let mut bl = __s_bl.launch_builder(&f);
2708        bl.arg(acc).arg(&optimistic_pending).arg(valid);
2709        unsafe {
2710            bl.launch(cfg)?;
2711        }
2712        Ok(())
2713    }
2714
2715    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
2716    pub fn spec_fork_reconcile_kv(
2717        &self,
2718        len_ptrs: &CudaSlice<u64>,
2719        saved: &CudaSlice<i32>,
2720        acc: &CudaSlice<u32>,
2721        valid: &CudaSlice<u32>,
2722        base: usize,
2723        n_layer: usize,
2724    ) -> Result<(), Box<dyn std::error::Error>> {
2725        let f = self.func("spec_fork_reconcile_kv");
2726        let (b, nl) = (base as i32, n_layer as i32);
2727        let cfg = LaunchConfig {
2728            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2729            block_dim: (64, 1, 1),
2730            shared_mem_bytes: 0,
2731        };
2732        let __s_bl = self.gpu.stream();
2733        let mut bl = __s_bl.launch_builder(&f);
2734        bl.arg(len_ptrs)
2735            .arg(saved)
2736            .arg(acc)
2737            .arg(valid)
2738            .arg(&b)
2739            .arg(&nl);
2740        unsafe {
2741            bl.launch(cfg)?;
2742        }
2743        Ok(())
2744    }
2745
2746    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
2747    pub fn spec_fork_restore_f32(
2748        &self,
2749        snapshot: &CudaSlice<f32>,
2750        state: &mut CudaSlice<f32>,
2751        valid: &CudaSlice<u32>,
2752    ) -> Result<(), Box<dyn std::error::Error>> {
2753        assert_eq!(
2754            snapshot.len(),
2755            state.len(),
2756            "fork recurrent snapshot shape mismatch"
2757        );
2758        let f = self.func("spec_fork_restore_f32");
2759        let n = state.len() as i32;
2760        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
2761        let cfg = LaunchConfig {
2762            grid_dim: (blocks, 1, 1),
2763            block_dim: (256, 1, 1),
2764            shared_mem_bytes: 0,
2765        };
2766        let __s_bl = self.gpu.stream();
2767        let mut bl = __s_bl.launch_builder(&f);
2768        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
2769        unsafe {
2770            bl.launch(cfg)?;
2771        }
2772        Ok(())
2773    }
2774
2775    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
2776    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
2777    pub fn spec_seed_gather(
2778        &self,
2779        vx: &CudaSlice<f32>,
2780        fill_prev: &CudaSlice<f32>,
2781        acc: &CudaSlice<u32>,
2782        h_seed: &mut CudaSlice<f32>,
2783        base: usize,
2784        n_embd: usize,
2785    ) -> Result<(), Box<dyn std::error::Error>> {
2786        let f = self.func("spec_seed_gather");
2787        let (b, ne) = (base as i32, n_embd as i32);
2788        let cfg = LaunchConfig {
2789            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
2790            block_dim: (256, 1, 1),
2791            shared_mem_bytes: 0,
2792        };
2793        let __s_bl = self.gpu.stream();
2794        let mut bl = __s_bl.launch_builder(&f);
2795        bl.arg(vx)
2796            .arg(fill_prev)
2797            .arg(acc)
2798            .arg(h_seed)
2799            .arg(&b)
2800            .arg(&ne);
2801        unsafe {
2802            bl.launch(cfg)?;
2803        }
2804        Ok(())
2805    }
2806
2807    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2808    pub fn spec_accept_greedy(
2809        &self,
2810        preds: &CudaSlice<u32>,
2811        draft: &CudaSlice<u32>,
2812        last_pred: u32,
2813        base: usize,
2814        k_round: usize,
2815        out: &mut CudaSlice<u32>,
2816    ) -> Result<(), Box<dyn std::error::Error>> {
2817        let f = self.func("spec_accept_greedy");
2818        let (b, k) = (base as i32, k_round as i32);
2819        let cfg = LaunchConfig {
2820            grid_dim: (1, 1, 1),
2821            block_dim: (32, 1, 1),
2822            shared_mem_bytes: 0,
2823        };
2824        let __s_bl = self.gpu.stream();
2825        let mut bl = __s_bl.launch_builder(&f);
2826        bl.arg(preds)
2827            .arg(draft)
2828            .arg(&last_pred)
2829            .arg(&b)
2830            .arg(&k)
2831            .arg(out);
2832        unsafe {
2833            bl.launch(cfg)?;
2834        }
2835        Ok(())
2836    }
2837
2838    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2839    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2840    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2841
2842    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2843    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2844    pub fn gumbel_perturb(
2845        &self,
2846        x: &CudaSlice<f32>,
2847        y: &mut CudaSlice<f32>,
2848        n: usize,
2849        seed: u64,
2850        stream_pos: u32,
2851        temp: f32,
2852    ) -> Result<(), Box<dyn std::error::Error>> {
2853        let f = self.func("gumbel_perturb_f32");
2854        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2855        let cfg = LaunchConfig {
2856            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2857            block_dim: (256, 1, 1),
2858            shared_mem_bytes: 0,
2859        };
2860        let __s_b = self.gpu.stream();
2861        let mut b = __s_b.launch_builder(&f);
2862        b.arg(x)
2863            .arg(&mut *y)
2864            .arg(&ni)
2865            .arg(&slo)
2866            .arg(&shi)
2867            .arg(&stream_pos)
2868            .arg(&temp);
2869        unsafe {
2870            b.launch(cfg)?;
2871        }
2872        Ok(())
2873    }
2874
2875    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2876    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2877    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2878    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2879    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2880    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2881    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2882    pub fn mask_logits_col(
2883        &self,
2884        logits: &mut CudaSlice<f32>,
2885        mask: &CudaSlice<u32>,
2886        col: usize,
2887        n: usize,
2888        mask_words: usize,
2889    ) -> Result<(), Box<dyn std::error::Error>> {
2890        let f = self.func("mask_logits_f32");
2891        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2892        let cfg = LaunchConfig {
2893            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2894            block_dim: (256, 1, 1),
2895            shared_mem_bytes: 0,
2896        };
2897        let __s_b = self.gpu.stream();
2898        let mut b = __s_b.launch_builder(&f);
2899        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2900        unsafe {
2901            b.launch(cfg)?;
2902        }
2903        Ok(())
2904    }
2905
2906    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2907    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2908    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2909    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2910    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2911    /// pointer-invariance IS the serving isolation contract for sampled rows.
2912    pub fn gumbel_perturb_col(
2913        &self,
2914        x: &CudaSlice<f32>,
2915        col: usize,
2916        y: &mut CudaSlice<f32>,
2917        n: usize,
2918        seed: u64,
2919        stream_pos: u32,
2920        temp: f32,
2921    ) -> Result<(), Box<dyn std::error::Error>> {
2922        let f = self.func("gumbel_perturb_f32");
2923        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2924        let col_view = x.slice(col * n..(col + 1) * n);
2925        let cfg = LaunchConfig {
2926            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2927            block_dim: (256, 1, 1),
2928            shared_mem_bytes: 0,
2929        };
2930        let __s_b = self.gpu.stream();
2931        let mut b = __s_b.launch_builder(&f);
2932        b.arg(&col_view)
2933            .arg(&mut *y)
2934            .arg(&ni)
2935            .arg(&slo)
2936            .arg(&shi)
2937            .arg(&stream_pos)
2938            .arg(&temp);
2939        unsafe {
2940            b.launch(cfg)?;
2941        }
2942        Ok(())
2943    }
2944
2945    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
2946    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
2947    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
2948    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
2949    /// the serving isolation contract for sampled rows).
2950    #[allow(clippy::too_many_arguments)]
2951    pub fn gumbel_perturb_filtered_col(
2952        &self,
2953        x: &CudaSlice<f32>,
2954        col: usize,
2955        y: &mut CudaSlice<f32>,
2956        n: usize,
2957        seed: u64,
2958        stream_pos: u32,
2959        temp: f32,
2960        stat_max: &CudaSlice<f32>,
2961        stat_th: &CudaSlice<f32>,
2962        stat_idx: usize,
2963    ) -> Result<(), Box<dyn std::error::Error>> {
2964        let f = self.func("gumbel_perturb_filtered_col_f32");
2965        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2966        let (ci, si) = (col as i32, stat_idx as i32);
2967        let cfg = LaunchConfig {
2968            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2969            block_dim: (256, 1, 1),
2970            shared_mem_bytes: 0,
2971        };
2972        let __s_b = self.gpu.stream();
2973        let mut b = __s_b.launch_builder(&f);
2974        b.arg(x)
2975            .arg(&ci)
2976            .arg(&mut *y)
2977            .arg(&ni)
2978            .arg(&slo)
2979            .arg(&shi)
2980            .arg(&stream_pos)
2981            .arg(&temp)
2982            .arg(stat_max)
2983            .arg(stat_th)
2984            .arg(&si);
2985        unsafe {
2986            b.launch(cfg)?;
2987        }
2988        Ok(())
2989    }
2990
2991    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
2992    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
2993    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
2994    /// reads it (counter is data, not state — graph-replay-safe).
2995    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
2996        let f = self.func("memra_sctr_inc");
2997        let cfg = LaunchConfig {
2998            grid_dim: (1, 1, 1),
2999            block_dim: (1, 1, 1),
3000            shared_mem_bytes: 0,
3001        };
3002        let __s_b = self.gpu.stream();
3003        let mut b = __s_b.launch_builder(&f);
3004        b.arg(&mut *ctr);
3005        unsafe {
3006            b.launch(cfg)?;
3007        }
3008        Ok(())
3009    }
3010
3011    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3012    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3013    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3014    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3015    pub fn gumbel_perturb_ctr(
3016        &self,
3017        x: &CudaSlice<f32>,
3018        y: &mut CudaSlice<f32>,
3019        n: usize,
3020        seed: u64,
3021        ctr: &CudaSlice<u32>,
3022        temp: f32,
3023    ) -> Result<(), Box<dyn std::error::Error>> {
3024        let f = self.func("gumbel_perturb_ctr_f32");
3025        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3026        let cfg = LaunchConfig {
3027            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3028            block_dim: (256, 1, 1),
3029            shared_mem_bytes: 0,
3030        };
3031        let __s_b = self.gpu.stream();
3032        let mut b = __s_b.launch_builder(&f);
3033        b.arg(x)
3034            .arg(&mut *y)
3035            .arg(&ni)
3036            .arg(&slo)
3037            .arg(&shi)
3038            .arg(ctr)
3039            .arg(&temp);
3040        unsafe {
3041            b.launch(cfg)?;
3042        }
3043        Ok(())
3044    }
3045
3046    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3047    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3048    /// (smallest-index tie-break — matches the argmax-gate contract).
3049    pub fn softmax_gather(
3050        &self,
3051        x: &CudaSlice<f32>,
3052        row_stride: usize,
3053        ids: &CudaSlice<u32>,
3054        rows: &CudaSlice<i32>,
3055        out: &mut CudaSlice<f32>,
3056        n: usize,
3057        npair: usize,
3058        temp: f32,
3059    ) -> Result<(), Box<dyn std::error::Error>> {
3060        let f = self.func("softmax_gather_f32");
3061        let (ni, rs) = (n as i32, row_stride as i64);
3062        let np = npair as i32;
3063        let cfg = LaunchConfig {
3064            grid_dim: (npair as u32, 1, 1),
3065            block_dim: (256, 1, 1),
3066            shared_mem_bytes: 0,
3067        };
3068        let __s_b = self.gpu.stream();
3069        let mut b = __s_b.launch_builder(&f);
3070        b.arg(x)
3071            .arg(&rs)
3072            .arg(ids)
3073            .arg(rows)
3074            .arg(&mut *out)
3075            .arg(&ni)
3076            .arg(&np)
3077            .arg(&temp);
3078        unsafe {
3079            b.launch(cfg)?;
3080        }
3081        Ok(())
3082    }
3083
3084    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3085    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3086    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3087    pub fn residual_sample(
3088        &self,
3089        p: &CudaSlice<f32>,
3090        q: Option<&CudaSlice<f32>>,
3091        n: usize,
3092        temp: f32,
3093        seed: u64,
3094        stream_pos: u32,
3095        out_tok: &mut CudaSlice<u32>,
3096    ) -> Result<(), Box<dyn std::error::Error>> {
3097        let f = self.func("residual_sample_f32");
3098        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3099        let nth = 1024u32;
3100        let cfg = LaunchConfig {
3101            grid_dim: (1, 1, 1),
3102            block_dim: (nth, 1, 1),
3103            shared_mem_bytes: 0,
3104        };
3105        let has_q: i32 = q.is_some() as i32;
3106        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3107        let __s_b = self.gpu.stream();
3108        let mut b = __s_b.launch_builder(&f);
3109        b.arg(p)
3110            .arg(qbuf)
3111            .arg(&has_q)
3112            .arg(&ni)
3113            .arg(&temp)
3114            .arg(&slo)
3115            .arg(&shi)
3116            .arg(&stream_pos)
3117            .arg(&mut *out_tok);
3118        unsafe {
3119            b.launch(cfg)?;
3120        }
3121        Ok(())
3122    }
3123
3124    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3125    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3126    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3127    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3128    pub fn with_moe_cache<R>(
3129        &self,
3130        max_block_bytes: usize,
3131        f: impl FnOnce(
3132            &mut crate::moe_cache::MoeSlotCache,
3133            &Engine,
3134        ) -> Result<R, Box<dyn std::error::Error>>,
3135    ) -> Result<R, Box<dyn std::error::Error>> {
3136        let mut guard = self.moe_cache.lock().unwrap();
3137        if guard.is_none() {
3138            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3139        }
3140        let cache = guard.as_mut().unwrap();
3141        f(cache, self)
3142    }
3143
3144    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3145    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3146    pub fn freeze_moe_cache(&self) {
3147        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3148            cache.freeze();
3149        }
3150    }
3151
3152    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3153    /// Never constructs a cache.
3154    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3155        self.moe_cache
3156            .lock()
3157            .unwrap()
3158            .as_ref()
3159            .map(crate::moe_cache::MoeSlotCache::export_residency)
3160    }
3161
3162    pub(crate) fn moe_cache_frozen(&self) -> bool {
3163        self.moe_cache
3164            .lock()
3165            .unwrap()
3166            .as_ref()
3167            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3168    }
3169
3170    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3171    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3172    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3173    /// while leaving the profiling warmup's established batched behavior untouched.
3174    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3175    /// tokenwise arm anyway.)
3176    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3177        crate::cpu_experts::configured()
3178            && self.moe_cache_frozen()
3179            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3180    }
3181
3182    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3183    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3184        assert!(
3185            self.moe_cache.lock().unwrap().is_none(),
3186            "MoE cache layout configured after cache construction"
3187        );
3188        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3189    }
3190
3191    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3192        self.moe_cache_layout.lock().unwrap().clone()
3193    }
3194
3195    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3196    pub fn moe_cache_enabled() -> bool {
3197        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3198    }
3199
3200    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3201    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3202    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3203        let guard = self.moe_cache.lock().unwrap();
3204        guard
3205            .as_ref()
3206            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3207    }
3208
3209    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3210    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3211    /// callers compare a before/after snapshot around a decode window.
3212    pub fn cpu_expert_stats(
3213        &self,
3214    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3215        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3216    }
3217
3218    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3219    /// the backend tail that resident-GPU expert work did not hide.
3220    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3221        crate::cpu_experts::predictor_stats()
3222    }
3223
3224    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3225        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3226    }
3227
3228    /// CPU-routed expert selections grouped by how many of their three projections were already
3229    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3230    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3231        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3232    }
3233
3234    /// Positioned-read proof-backend counters:
3235    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3236    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3237        let guard = self.moe_cache.lock().unwrap();
3238        guard
3239            .as_ref()
3240            .and_then(|cache| cache.pread_stats())
3241            .map(|stats| {
3242                (
3243                    stats.reads,
3244                    stats.bytes,
3245                    stats.read_errors,
3246                    stats.short_reads,
3247                    stats.fallbacks,
3248                    stats.buffer_waits,
3249                    stats.ring_full,
3250                )
3251            })
3252    }
3253
3254    /// Spill configuration values that warned and substituted their documented defaults.
3255    pub fn spill_config_fallbacks(&self) -> u64 {
3256        crate::spill_pread::config_fallbacks()
3257    }
3258
3259    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3260    pub fn moe_cache_reset_counters(&self) {
3261        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3262            c.reset_counters();
3263        }
3264    }
3265
3266    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3267        Ok(self.gpu.stream().clone_htod(v)?)
3268    }
3269
3270    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3271    /// past the final q4_0 block through their aligned window — the bytes never reach a
3272    /// result (funnelshift discards them) but must be mapped memory.
3273    pub fn htod_bytes_padded(
3274        &self,
3275        v: &[u8],
3276        pad: usize,
3277    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3278        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3279        {
3280            let mut view = d.slice_mut(0..v.len());
3281            self.gpu.stream().memcpy_htod(v, &mut view)?;
3282        }
3283        Ok(d)
3284    }
3285
3286    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3287    pub fn copy_into(
3288        &self,
3289        dst: &mut CudaSlice<f32>,
3290        off: usize,
3291        src: &CudaSlice<f32>,
3292        len: usize,
3293    ) -> Result<(), Box<dyn std::error::Error>> {
3294        let mut view = dst.slice_mut(off..off + len);
3295        self.gpu
3296            .stream()
3297            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3298        Ok(())
3299    }
3300
3301    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3302    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3303    pub fn copy_u8_into(
3304        &self,
3305        dst: &mut CudaSlice<u8>,
3306        off: usize,
3307        src: &CudaSlice<u8>,
3308        len: usize,
3309    ) -> Result<(), Box<dyn std::error::Error>> {
3310        let mut view = dst.slice_mut(off..off + len);
3311        self.gpu
3312            .stream()
3313            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3314        Ok(())
3315    }
3316
3317    /// D2D byte-range copy with explicit source and destination offsets.
3318    pub fn copy_u8_range_into(
3319        &self,
3320        dst: &mut CudaSlice<u8>,
3321        dst_off: usize,
3322        src: &CudaSlice<u8>,
3323        src_off: usize,
3324        len: usize,
3325    ) -> Result<(), Box<dyn std::error::Error>> {
3326        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3327        self.gpu
3328            .stream()
3329            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3330        Ok(())
3331    }
3332
3333    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3334    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3335    /// keeping the audited attention range contiguous without changing its absolute start.
3336    pub fn prepare_kv_append(
3337        &self,
3338        kv: &mut crate::cache::KvLayer,
3339        retain_from: usize,
3340        append_rows: usize,
3341    ) -> Result<usize, Box<dyn std::error::Error>> {
3342        let Some(plan) = kv
3343            .ring
3344            .as_ref()
3345            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3346            .transpose()?
3347        else {
3348            return Ok(kv.len);
3349        };
3350        match plan {
3351            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3352            crate::cache::KvRingAppend::Rebase {
3353                src_row,
3354                keep_rows,
3355                new_base,
3356                write_row,
3357            } => {
3358                if keep_rows > 0 {
3359                    let k_len = keep_rows * kv.k_tok_bytes;
3360                    let v_len = keep_rows * kv.v_tok_bytes;
3361                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3362                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3363                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3364                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3365                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3366                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3367                }
3368                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3369                Ok(write_row)
3370            }
3371        }
3372    }
3373
3374    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3375    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3376    pub fn htod_u8_into(
3377        &self,
3378        dst: &mut CudaSlice<u8>,
3379        off: usize,
3380        src: &[u8],
3381    ) -> Result<(), Box<dyn std::error::Error>> {
3382        let mut view = dst.slice_mut(off..off + src.len());
3383        self.gpu.stream().memcpy_htod(src, &mut view)?;
3384        Ok(())
3385    }
3386
3387    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3388        b.slice(0..len)
3389    }
3390
3391    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3392    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3393    pub fn view_u8_range<'a>(
3394        &self,
3395        b: &'a CudaSlice<u8>,
3396        start: usize,
3397        end: usize,
3398    ) -> cudarc::driver::CudaView<'a, u8> {
3399        b.slice(start..end)
3400    }
3401    pub fn view_u8<'a>(
3402        &self,
3403        b: &'a CudaSlice<u8>,
3404        len: usize,
3405    ) -> cudarc::driver::CudaView<'a, u8> {
3406        b.slice(0..len)
3407    }
3408
3409    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3410    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3411    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3412    pub fn append_kv_quantized(
3413        &self,
3414        k_row: &CudaSlice<f32>,
3415        v_row: &CudaSlice<f32>,
3416        kc: &mut CudaSlice<u8>,
3417        vc: &mut CudaSlice<u8>,
3418        t: usize,
3419        kv_dim_k: usize,
3420        kv_dim_v: usize,
3421        k_tok_bytes: usize,
3422        v_tok_bytes: usize,
3423        g: bool,
3424    ) -> Result<(), Box<dyn std::error::Error>> {
3425        let f = if g {
3426            self.func_g("append_quantize_kv_q8_0_q5_1")
3427        } else {
3428            self.func("append_quantize_kv_q8_0_q5_1")
3429        };
3430        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3431        let cfg = LaunchConfig {
3432            grid_dim: (nblk, 1, 1),
3433            block_dim: (32, 1, 1),
3434            shared_mem_bytes: 0,
3435        };
3436        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3437        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3438        let __s_b = self.gpu.stream();
3439        let mut b = __s_b.launch_builder(&f);
3440        b.arg(k_row)
3441            .arg(v_row)
3442            .arg(kc)
3443            .arg(vc)
3444            .arg(&ti)
3445            .arg(&kdk)
3446            .arg(&kdv)
3447            .arg(&ktb)
3448            .arg(&vtb);
3449        unsafe {
3450            b.launch(cfg)?;
3451        }
3452        Ok(())
3453    }
3454
3455    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3456    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3457    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3458    pub fn append_kv_quantized_dc(
3459        &self,
3460        k_row: &CudaSlice<f32>,
3461        v_row: &CudaSlice<f32>,
3462        kc: &mut CudaSlice<u8>,
3463        vc: &mut CudaSlice<u8>,
3464        t_dev: &CudaSlice<i32>,
3465        kv_dim_k: usize,
3466        kv_dim_v: usize,
3467        k_tok_bytes: usize,
3468        v_tok_bytes: usize,
3469        g: bool,
3470    ) -> Result<(), Box<dyn std::error::Error>> {
3471        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3472        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3473        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3474        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3475        if Self::pdl_on() && Self::pdl_wb_on() {
3476            use cudarc::driver::{DevicePtr, DevicePtrMut};
3477            let s = &self.gpu.stream();
3478            let (pk, _g0) = k_row.device_ptr(s);
3479            let (pv, _g1) = v_row.device_ptr(s);
3480            let (pkc, _g2) = kc.device_ptr_mut(s);
3481            let (pvc, _g3) = vc.device_ptr_mut(s);
3482            let (pt, _g4) = t_dev.device_ptr(s);
3483            let mut ps = [
3484                &pk as *const _ as *mut std::ffi::c_void,
3485                &pv as *const _ as *mut _,
3486                &pkc as *const _ as *mut _,
3487                &pvc as *const _ as *mut _,
3488                &pt as *const _ as *mut _,
3489                &kdk as *const _ as *mut _,
3490                &kdv as *const _ as *mut _,
3491                &ktb as *const _ as *mut _,
3492                &vtb as *const _ as *mut _,
3493            ];
3494            unsafe {
3495                self.launch_pdl_flash(
3496                    g,
3497                    "append_quantize_kv_q8_0_q5_1_dc",
3498                    (nblk, 1, 1),
3499                    (32, 1, 1),
3500                    0,
3501                    &mut ps,
3502                )?;
3503            }
3504            return Ok(());
3505        }
3506        let f = if g {
3507            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3508        } else {
3509            self.func("append_quantize_kv_q8_0_q5_1_dc")
3510        };
3511        let cfg = LaunchConfig {
3512            grid_dim: (nblk, 1, 1),
3513            block_dim: (32, 1, 1),
3514            shared_mem_bytes: 0,
3515        };
3516        let __s_b = self.gpu.stream();
3517        let mut b = __s_b.launch_builder(&f);
3518        b.arg(k_row)
3519            .arg(v_row)
3520            .arg(kc)
3521            .arg(vc)
3522            .arg(t_dev)
3523            .arg(&kdk)
3524            .arg(&kdv)
3525            .arg(&ktb)
3526            .arg(&vtb);
3527        unsafe {
3528            b.launch(cfg)?;
3529        }
3530        Ok(())
3531    }
3532
3533    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3534    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3535    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3536    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3537    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3538    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3539    #[allow(clippy::too_many_arguments)]
3540    pub fn append_kv_quantized_rows(
3541        &self,
3542        k_rows: &CudaSlice<f32>,
3543        v_rows: &CudaSlice<f32>,
3544        kc: &mut CudaSlice<u8>,
3545        vc: &mut CudaSlice<u8>,
3546        t0: usize,
3547        t: usize,
3548        kv_dim_k: usize,
3549        kv_dim_v: usize,
3550        k_tok_bytes: usize,
3551        v_tok_bytes: usize,
3552        g: bool,
3553    ) -> Result<(), Box<dyn std::error::Error>> {
3554        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3555            for i in 0..t {
3556                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3557                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3558                self.append_kv_quantized_view(
3559                    &k_row,
3560                    &v_row,
3561                    kc,
3562                    vc,
3563                    t0 + i,
3564                    kv_dim_k,
3565                    kv_dim_v,
3566                    k_tok_bytes,
3567                    v_tok_bytes,
3568                    g,
3569                )?;
3570            }
3571            return Ok(());
3572        }
3573        let f = if g {
3574            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3575        } else {
3576            self.func("append_quantize_kv_q8_0_q5_1_rows")
3577        };
3578        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3579        let cfg = LaunchConfig {
3580            grid_dim: (nblk, t as u32, 1),
3581            block_dim: (32, 1, 1),
3582            shared_mem_bytes: 0,
3583        };
3584        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3585        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3586        let __s_b = self.gpu.stream();
3587        let mut b = __s_b.launch_builder(&f);
3588        b.arg(k_rows)
3589            .arg(v_rows)
3590            .arg(kc)
3591            .arg(vc)
3592            .arg(&t0i)
3593            .arg(&kdk)
3594            .arg(&kdv)
3595            .arg(&ktb)
3596            .arg(&vtb);
3597        unsafe {
3598            b.launch(cfg)?;
3599        }
3600        Ok(())
3601    }
3602
3603    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3604    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3605    /// later, inside a captured graph) without a host round-trip.
3606    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3607        let f = self.func("inc_i32");
3608        let cfg = LaunchConfig {
3609            grid_dim: (1, 1, 1),
3610            block_dim: (1, 1, 1),
3611            shared_mem_bytes: 0,
3612        };
3613        let __s_b = self.gpu.stream();
3614        let mut b = __s_b.launch_builder(&f);
3615        b.arg(p);
3616        unsafe {
3617            b.launch(cfg)?;
3618        }
3619        Ok(())
3620    }
3621
3622    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3623    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3624    pub fn append_kv_quantized_view(
3625        &self,
3626        k_row: &cudarc::driver::CudaView<f32>,
3627        v_row: &cudarc::driver::CudaView<f32>,
3628        kc: &mut CudaSlice<u8>,
3629        vc: &mut CudaSlice<u8>,
3630        t: usize,
3631        kv_dim_k: usize,
3632        kv_dim_v: usize,
3633        k_tok_bytes: usize,
3634        v_tok_bytes: usize,
3635        g: bool,
3636    ) -> Result<(), Box<dyn std::error::Error>> {
3637        let f = if g {
3638            self.func_g("append_quantize_kv_q8_0_q5_1")
3639        } else {
3640            self.func("append_quantize_kv_q8_0_q5_1")
3641        };
3642        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3643        let cfg = LaunchConfig {
3644            grid_dim: (nblk, 1, 1),
3645            block_dim: (32, 1, 1),
3646            shared_mem_bytes: 0,
3647        };
3648        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3649        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3650        let __s_b = self.gpu.stream();
3651        let mut b = __s_b.launch_builder(&f);
3652        b.arg(k_row)
3653            .arg(v_row)
3654            .arg(kc)
3655            .arg(vc)
3656            .arg(&ti)
3657            .arg(&kdk)
3658            .arg(&kdv)
3659            .arg(&ktb)
3660            .arg(&vtb);
3661        unsafe {
3662            b.launch(cfg)?;
3663        }
3664        Ok(())
3665    }
3666
3667    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3668    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3669    pub fn copy_view_into(
3670        &self,
3671        dst: &mut CudaSlice<f32>,
3672        off: usize,
3673        src: &cudarc::driver::CudaView<f32>,
3674        len: usize,
3675    ) -> Result<(), Box<dyn std::error::Error>> {
3676        let mut view = dst.slice_mut(off..off + len);
3677        self.gpu
3678            .stream()
3679            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3680        Ok(())
3681    }
3682
3683    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3684    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3685    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3686    pub fn clone_dtod(
3687        &self,
3688        src: &CudaSlice<f32>,
3689    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3690        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3691        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3692        Ok(dst)
3693    }
3694
3695    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3696    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3697    pub fn dtod_copy_view(
3698        &self,
3699        src: &cudarc::driver::CudaView<f32>,
3700        dst: &mut CudaSlice<f32>,
3701    ) -> Result<(), Box<dyn std::error::Error>> {
3702        self.gpu.stream().memcpy_dtod(src, dst)?;
3703        Ok(())
3704    }
3705
3706    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3707    pub fn dtod_copy_view_i8(
3708        &self,
3709        src: &cudarc::driver::CudaView<i8>,
3710        dst: &mut CudaSlice<i8>,
3711    ) -> Result<(), Box<dyn std::error::Error>> {
3712        self.gpu.stream().memcpy_dtod(src, dst)?;
3713        Ok(())
3714    }
3715
3716    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3717    pub fn dtod_copy_into(
3718        &self,
3719        src: &CudaSlice<f32>,
3720        dst: &mut CudaSlice<f32>,
3721        offset: usize,
3722    ) -> Result<(), Box<dyn std::error::Error>> {
3723        let n = src.len();
3724        let mut dv = dst.slice_mut(offset..offset + n);
3725        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3726        Ok(())
3727    }
3728
3729    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3730    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3731        self.alloc_uninit::<i8>(n)
3732    }
3733
3734    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3735    pub fn qmatvec(
3736        &self,
3737        w: &CudaSlice<u8>,
3738        x: &CudaSlice<f32>,
3739        m: usize,
3740        in_f: usize,
3741        out_f: usize,
3742        qtype: i32,
3743        row_bytes: usize,
3744    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3745        let f = self.func("qmatvec_f32");
3746        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3747        let cfg = LaunchConfig {
3748            grid_dim: (out_f as u32, m as u32, 1),
3749            block_dim: (256, 1, 1),
3750            shared_mem_bytes: 0,
3751        };
3752        let (inf, outf, mi, qt, rb) =
3753            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3754        let __s_b = self.gpu.stream();
3755        let mut b = __s_b.launch_builder(&f);
3756        b.arg(w)
3757            .arg(x)
3758            .arg(&mut y)
3759            .arg(&inf)
3760            .arg(&outf)
3761            .arg(&mi)
3762            .arg(&qt)
3763            .arg(&rb);
3764        unsafe {
3765            b.launch(cfg)?;
3766        }
3767        Ok(y)
3768    }
3769
3770    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3771    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3772        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3773        self.keep_if_capturing(&s);
3774        Ok(s)
3775    }
3776
3777    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3778    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3779    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3780    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3781        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3782        self.keep_if_capturing(&s);
3783        Ok(s)
3784    }
3785
3786    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3787    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3788    pub fn memset_zeros_view(
3789        &self,
3790        dst: &mut cudarc::driver::CudaViewMut<f32>,
3791    ) -> Result<(), Box<dyn std::error::Error>> {
3792        self.gpu.stream().memset_zeros(dst)?;
3793        Ok(())
3794    }
3795
3796    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3797    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3798    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3799    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3800    /// stream would require an event).
3801    pub fn stage_expert(
3802        &self,
3803        host_bytes: &[u8],
3804        scratch: &mut CudaSlice<u8>,
3805        off: usize,
3806    ) -> Result<(), Box<dyn std::error::Error>> {
3807        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3808        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3809        Ok(())
3810    }
3811
3812    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3813    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3814    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3815    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3816    /// One CTA per token row, 256 threads (one per expert).
3817    pub fn moe_router_topk(
3818        &self,
3819        logits: &CudaSlice<f32>,
3820        t: usize,
3821        n_expert: usize,
3822        n_used: usize,
3823    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3824        let f = self.func("moe_router_topk_f32");
3825        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3826        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3827        let cfg = LaunchConfig {
3828            grid_dim: (t as u32, 1, 1),
3829            block_dim: (n_expert as u32, 1, 1),
3830            shared_mem_bytes: 0,
3831        };
3832        let (ne, nu) = (n_expert as i32, n_used as i32);
3833        let __s_b = self.gpu.stream();
3834        let mut b = __s_b.launch_builder(&f);
3835        b.arg(logits)
3836            .arg(&mut sel_idx)
3837            .arg(&mut sel_w)
3838            .arg(&ne)
3839            .arg(&nu);
3840        unsafe {
3841            b.launch(cfg)?;
3842        }
3843        Ok((sel_idx, sel_w))
3844    }
3845
3846    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3847    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3848    pub fn moe_router_topk_scaled(
3849        &self,
3850        logits: &CudaSlice<f32>,
3851        t: usize,
3852        n_expert: usize,
3853        n_used: usize,
3854        ex_scale: &CudaSlice<f32>,
3855    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3856        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3857        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3858        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3859        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3860        let f = self.func("moe_router_topk_scaled_f32");
3861        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3862        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3863        let cfg = LaunchConfig {
3864            grid_dim: (t as u32, 1, 1),
3865            block_dim: (n_expert as u32, 1, 1),
3866            shared_mem_bytes: 0,
3867        };
3868        let (ne, nu) = (n_expert as i32, n_used as i32);
3869        let __s_b = self.gpu.stream();
3870        let mut b = __s_b.launch_builder(&f);
3871        b.arg(logits)
3872            .arg(&mut sel_idx)
3873            .arg(&mut sel_w)
3874            .arg(&ne)
3875            .arg(&nu)
3876            .arg(ex_scale);
3877        unsafe {
3878            b.launch(cfg)?;
3879        }
3880        Ok((sel_idx, sel_w))
3881    }
3882
3883    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3884    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3885    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3886    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3887    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3888    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3889    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3890    pub fn moe_router_topk_host(
3891        &self,
3892        logits: &CudaSlice<f32>,
3893        t: usize,
3894        n_expert: usize,
3895        n_used: usize,
3896    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3897        let f = self.func("moe_router_topk_f32");
3898        let n = t * n_used;
3899        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
3900        let mut sel_w = self.alloc_uninit::<f32>(n)?;
3901        let cfg = LaunchConfig {
3902            grid_dim: (t as u32, 1, 1),
3903            block_dim: (n_expert as u32, 1, 1),
3904            shared_mem_bytes: 0,
3905        };
3906        let (ne, nu) = (n_expert as i32, n_used as i32);
3907        let __s_b = self.gpu.stream();
3908        let mut b = __s_b.launch_builder(&f);
3909        b.arg(logits)
3910            .arg(&mut sel_idx)
3911            .arg(&mut sel_w)
3912            .arg(&ne)
3913            .arg(&nu);
3914        unsafe {
3915            b.launch(cfg)?;
3916        }
3917        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
3918        let bytes = n * 8;
3919        let mut guard = self.router_stage.lock().unwrap();
3920        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
3921            *guard = Some(PinnedStage::new(bytes.max(4096))?);
3922        }
3923        let stage = guard.as_mut().unwrap();
3924        let (si, sw) = unsafe {
3925            (
3926                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
3927                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
3928            )
3929        };
3930        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
3931        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
3932        self.gpu.stream().synchronize()?; // ONE sync for both
3933        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
3934    }
3935
3936    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
3937    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
3938    /// original expert ids before top-k. Exact key ties choose the smaller original id.
3939    #[allow(clippy::too_many_arguments)]
3940    pub fn moe_router_sigmoid_topk(
3941        &self,
3942        logits: &CudaSlice<f32>,
3943        t: usize,
3944        n_expert: usize,
3945        n_used: usize,
3946        active_count: usize,
3947        correction_bias: &CudaSlice<f32>,
3948        active: &CudaSlice<u8>,
3949        scaling_factor: f32,
3950        route_norm: bool,
3951    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3952        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
3953        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
3954            return Err(format!(
3955                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
3956            )
3957            .into());
3958        }
3959        if logits.len() < t * n_expert
3960            || correction_bias.len() != n_expert
3961            || active.len() != n_expert
3962        {
3963            return Err(format!(
3964                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
3965                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
3966            ).into());
3967        }
3968        let f = self.func("moe_router_sigmoid_topk_f32");
3969        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3970        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3971        let threads = n_expert.div_ceil(32) * 32;
3972        let cfg = LaunchConfig {
3973            grid_dim: (t as u32, 1, 1),
3974            block_dim: (threads as u32, 1, 1),
3975            shared_mem_bytes: 0,
3976        };
3977        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
3978        let __s_b = self.gpu.stream();
3979        let mut b = __s_b.launch_builder(&f);
3980        b.arg(logits)
3981            .arg(correction_bias)
3982            .arg(active)
3983            .arg(&mut sel_idx)
3984            .arg(&mut sel_w)
3985            .arg(&ne)
3986            .arg(&nu)
3987            .arg(&scaling_factor)
3988            .arg(&rn);
3989        unsafe {
3990            b.launch(cfg)?;
3991        }
3992        Ok((sel_idx, sel_w))
3993    }
3994
3995    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
3996    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
3997    #[allow(clippy::too_many_arguments)]
3998    pub fn moe_router_sigmoid_topk_host(
3999        &self,
4000        logits: &CudaSlice<f32>,
4001        t: usize,
4002        n_expert: usize,
4003        n_used: usize,
4004        active_count: usize,
4005        correction_bias: &CudaSlice<f32>,
4006        active: &CudaSlice<u8>,
4007        scaling_factor: f32,
4008        route_norm: bool,
4009    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4010        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4011            logits,
4012            t,
4013            n_expert,
4014            n_used,
4015            active_count,
4016            correction_bias,
4017            active,
4018            scaling_factor,
4019            route_norm,
4020        )?;
4021        let n = t * n_used;
4022        let bytes = n * 8;
4023        let mut guard = self.router_stage.lock().unwrap();
4024        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4025            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4026        }
4027        let stage = guard.as_mut().unwrap();
4028        let (si, sw) = unsafe {
4029            (
4030                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4031                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4032            )
4033        };
4034        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4035        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4036        self.gpu.stream().synchronize()?;
4037        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4038    }
4039
4040    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4041    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4042    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4043    pub fn stage_expert_async(
4044        &self,
4045        host_bytes: &[u8],
4046        scratch: &mut CudaSlice<u8>,
4047        off: usize,
4048    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4049        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4050        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4051        Ok(self.copy_stream.record_event(None)?)
4052    }
4053
4054    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4055    pub fn compute_wait(
4056        &self,
4057        ev: &cudarc::driver::CudaEvent,
4058    ) -> Result<(), Box<dyn std::error::Error>> {
4059        self.gpu.stream().wait(ev)?;
4060        Ok(())
4061    }
4062
4063    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4064    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4065    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4066    /// CudaView base+offset pointer is honored by the launch arg.
4067    pub fn qmatvec_view(
4068        &self,
4069        w: &CudaSlice<u8>,
4070        range: std::ops::Range<usize>,
4071        x: &cudarc::driver::CudaView<f32>,
4072        m: usize,
4073        in_f: usize,
4074        out_f: usize,
4075        qtype: i32,
4076        row_bytes: usize,
4077    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4078        let f = self.func("qmatvec_f32");
4079        let wv = w.slice(range); // CudaView<u8>, offset honored
4080        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4081        let cfg = LaunchConfig {
4082            grid_dim: (out_f as u32, m as u32, 1),
4083            block_dim: (256, 1, 1),
4084            shared_mem_bytes: 0,
4085        };
4086        let (inf, outf, mi, qt, rb) =
4087            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4088        let __s_b = self.gpu.stream();
4089        let mut b = __s_b.launch_builder(&f);
4090        b.arg(&wv)
4091            .arg(x)
4092            .arg(&mut y)
4093            .arg(&inf)
4094            .arg(&outf)
4095            .arg(&mi)
4096            .arg(&qt)
4097            .arg(&rb);
4098        unsafe {
4099            b.launch(cfg)?;
4100        }
4101        Ok(y)
4102    }
4103
4104    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4105    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4106    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4107    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4108    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4109    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4110    #[allow(clippy::too_many_arguments)]
4111    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4112    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4113    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4114    pub fn moe_gate_up_silu8_q8(
4115        &self,
4116        gp: WPtr8,
4117        up: WPtr8,
4118        aq: &CudaSlice<i8>,
4119        ad: &CudaSlice<f32>,
4120        in_f: usize,
4121        n_ff: usize,
4122        n_used: usize,
4123        qt_g: i32,
4124        qt_u: i32,
4125        rb_g: usize,
4126        rb_u: usize,
4127    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4128        let f = self.func("moe_gate_up_silu8_q8");
4129        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4130        let cfg = LaunchConfig {
4131            grid_dim: (n_ff as u32, n_used as u32, 1),
4132            block_dim: (32, 1, 1),
4133            shared_mem_bytes: 0,
4134        };
4135        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4136        let __s_b = self.gpu.stream();
4137        let mut b = __s_b.launch_builder(&f);
4138        b.arg(&gp)
4139            .arg(&up)
4140            .arg(aq)
4141            .arg(ad)
4142            .arg(&mut act)
4143            .arg(&inf)
4144            .arg(&nff)
4145            .arg(&qt_g)
4146            .arg(&qt_u)
4147            .arg(&rbg)
4148            .arg(&rbu);
4149        unsafe {
4150            b.launch(cfg)?;
4151        }
4152        Ok(act)
4153    }
4154
4155    #[allow(clippy::too_many_arguments)]
4156    pub fn moe_down8_fma_q8(
4157        &self,
4158        dp: WPtr8,
4159        w: F32x8,
4160        aq2: &CudaSlice<i8>,
4161        ad2: &CudaSlice<f32>,
4162        dst: &mut cudarc::driver::CudaViewMut<f32>,
4163        in_f: usize,
4164        out_f: usize,
4165        n_used: usize,
4166        qt: i32,
4167        rb: usize,
4168    ) -> Result<(), Box<dyn std::error::Error>> {
4169        let f = self.func("moe_down8_fma_q8");
4170        let cfg = LaunchConfig {
4171            grid_dim: (out_f as u32, 1, 1),
4172            block_dim: (32, 1, 1),
4173            shared_mem_bytes: 0,
4174        };
4175        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4176        let __s_b = self.gpu.stream();
4177        let mut b = __s_b.launch_builder(&f);
4178        b.arg(&dp)
4179            .arg(&w)
4180            .arg(aq2)
4181            .arg(ad2)
4182            .arg(dst)
4183            .arg(&inf)
4184            .arg(&outf)
4185            .arg(&nu)
4186            .arg(&qt)
4187            .arg(&rbi);
4188        unsafe {
4189            b.launch(cfg)?;
4190        }
4191        Ok(())
4192    }
4193
4194    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4195    pub fn qmatvec_expert_q8(
4196        &self,
4197        w: &CudaSlice<u8>,
4198        range: std::ops::Range<usize>,
4199        aq: &CudaSlice<i8>,
4200        ad: &CudaSlice<f32>,
4201        m: usize,
4202        in_f: usize,
4203        out_f: usize,
4204        qtype: i32,
4205        row_bytes: usize,
4206    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4207        let f = self.func("qmatvec_expert_q8");
4208        let wv = w.slice(range);
4209        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4210        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4211        let cfg = LaunchConfig {
4212            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4213            block_dim: (32, ROWS, 1),
4214            shared_mem_bytes: 0,
4215        };
4216        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4217        let __s_b = self.gpu.stream();
4218        let mut b = __s_b.launch_builder(&f);
4219        b.arg(&wv)
4220            .arg(aq)
4221            .arg(ad)
4222            .arg(&mut y)
4223            .arg(&inf)
4224            .arg(&outf)
4225            .arg(&mi)
4226            .arg(&qtype)
4227            .arg(&rbi);
4228        unsafe {
4229            b.launch(cfg)?;
4230        }
4231        Ok(y)
4232    }
4233
4234    pub fn moe_gate_up_silu8(
4235        &self,
4236        gp: WPtr8,
4237        up: WPtr8,
4238        x: &cudarc::driver::CudaView<f32>,
4239        in_f: usize,
4240        n_ff: usize,
4241        n_used: usize,
4242        qt_g: i32,
4243        qt_u: i32,
4244        rb_g: usize,
4245        rb_u: usize,
4246    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4247        let f = self.func("moe_gate_up_silu8_f32");
4248        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4249        let cfg = LaunchConfig {
4250            grid_dim: (n_ff as u32, n_used as u32, 1),
4251            block_dim: (256, 1, 1),
4252            shared_mem_bytes: 0,
4253        };
4254        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4255        let __s_b = self.gpu.stream();
4256        let mut b = __s_b.launch_builder(&f);
4257        b.arg(&gp)
4258            .arg(&up)
4259            .arg(x)
4260            .arg(&mut act)
4261            .arg(&inf)
4262            .arg(&nff)
4263            .arg(&qt_g)
4264            .arg(&qt_u)
4265            .arg(&rbg)
4266            .arg(&rbu);
4267        unsafe {
4268            b.launch(cfg)?;
4269        }
4270        Ok(act)
4271    }
4272
4273    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4274    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4275    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4276    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4277    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4278    #[allow(clippy::too_many_arguments)]
4279    pub fn moe_down8_fma_into(
4280        &self,
4281        dp: WPtr8,
4282        w: F32x8,
4283        act: &CudaSlice<f32>,
4284        dst: &mut cudarc::driver::CudaViewMut<f32>,
4285        in_f: usize,
4286        out_f: usize,
4287        n_used: usize,
4288        qt: i32,
4289        rb: usize,
4290    ) -> Result<(), Box<dyn std::error::Error>> {
4291        let f = self.func("moe_down8_fma_f32");
4292        let cfg = LaunchConfig {
4293            grid_dim: (out_f as u32, 1, 1),
4294            block_dim: (256, 1, 1),
4295            shared_mem_bytes: 0,
4296        };
4297        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4298        let __s_b = self.gpu.stream();
4299        let mut b = __s_b.launch_builder(&f);
4300        b.arg(&dp)
4301            .arg(&w)
4302            .arg(act)
4303            .arg(dst)
4304            .arg(&inf)
4305            .arg(&outf)
4306            .arg(&nu)
4307            .arg(&qt)
4308            .arg(&rbv);
4309        unsafe {
4310            b.launch(cfg)?;
4311        }
4312        Ok(())
4313    }
4314
4315    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4316    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4317    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4318    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4319    #[allow(clippy::too_many_arguments)]
4320    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4321    ///
4322    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4323    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4324    /// down's FMA chain stays slot-ordered serial). Seams:
4325    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4326    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4327    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4328    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4329    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4330    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4331    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4332    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4333    ///                       only) | w8h2 (h2 x slot-parallel)
4334    #[allow(clippy::too_many_arguments)]
4335    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4336    #[allow(clippy::too_many_arguments)]
4337    pub fn moe_pairs_matvec_q8(
4338        &self,
4339        table: &CudaSlice<u64>,
4340        proj: i32,
4341        pair_tok: &CudaSlice<i32>,
4342        pair_ex: &CudaSlice<i32>,
4343        aq: &CudaSlice<i8>,
4344        ad: &CudaSlice<f32>,
4345        in_f: usize,
4346        out_f: usize,
4347        n_expert: usize,
4348        n_pairs: usize,
4349        qtype: i32,
4350        row_bytes: usize,
4351    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4352        let f = self.func("moe_pairs_matvec_q8");
4353        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4354        const ROWS: u32 = 4;
4355        let cfg = LaunchConfig {
4356            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4357            block_dim: (32, ROWS, 1),
4358            shared_mem_bytes: 0,
4359        };
4360        let (inf, outf, ne, np, rbi) = (
4361            in_f as i32,
4362            out_f as i32,
4363            n_expert as i32,
4364            n_pairs as i32,
4365            row_bytes as i64,
4366        );
4367        let __s_b = self.gpu.stream();
4368        let mut b = __s_b.launch_builder(&f);
4369        b.arg(table)
4370            .arg(&proj)
4371            .arg(pair_tok)
4372            .arg(pair_ex)
4373            .arg(aq)
4374            .arg(ad)
4375            .arg(&mut y)
4376            .arg(&inf)
4377            .arg(&outf)
4378            .arg(&ne)
4379            .arg(&np)
4380            .arg(&qtype)
4381            .arg(&rbi);
4382        unsafe {
4383            b.launch(cfg)?;
4384        }
4385        Ok(y)
4386    }
4387
4388    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4389    #[allow(clippy::too_many_arguments)]
4390    pub fn moe_pairs_matvec_q8_em(
4391        &self,
4392        table: &CudaSlice<u64>,
4393        proj: i32,
4394        ex_ids: &CudaSlice<i32>,
4395        ex_off: &CudaSlice<i32>,
4396        ex_pairs: &CudaSlice<i32>,
4397        pair_tok: &CudaSlice<i32>,
4398        aq: &CudaSlice<i8>,
4399        ad: &CudaSlice<f32>,
4400        in_f: usize,
4401        out_f: usize,
4402        n_expert: usize,
4403        n_active: usize,
4404        n_pairs: usize,
4405        qtype: i32,
4406        row_bytes: usize,
4407    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4408        let f = self.func("moe_pairs_matvec_q8_em");
4409        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4410        const ROWS: u32 = 4;
4411        let cfg = LaunchConfig {
4412            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4413            block_dim: (32, ROWS, 1),
4414            shared_mem_bytes: 0,
4415        };
4416        let (inf, outf, ne, na, rbi) = (
4417            in_f as i32,
4418            out_f as i32,
4419            n_expert as i32,
4420            n_active as i32,
4421            row_bytes as i64,
4422        );
4423        let __s_b = self.gpu.stream();
4424        let mut b = __s_b.launch_builder(&f);
4425        b.arg(table)
4426            .arg(&proj)
4427            .arg(ex_ids)
4428            .arg(ex_off)
4429            .arg(ex_pairs)
4430            .arg(pair_tok)
4431            .arg(aq)
4432            .arg(ad)
4433            .arg(&mut y)
4434            .arg(&inf)
4435            .arg(&outf)
4436            .arg(&ne)
4437            .arg(&na)
4438            .arg(&qtype)
4439            .arg(&rbi);
4440        unsafe {
4441            b.launch(cfg)?;
4442        }
4443        Ok(y)
4444    }
4445
4446    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4447    // weight group once per (row,group) then dp4a's across the expert's token group.
4448    #[allow(clippy::too_many_arguments)]
4449    pub fn moe_pairs_matvec_q8_dec(
4450        &self,
4451        table: &CudaSlice<u64>,
4452        proj: i32,
4453        ex_ids: &CudaSlice<i32>,
4454        ex_off: &CudaSlice<i32>,
4455        ex_pairs: &CudaSlice<i32>,
4456        pair_tok: &CudaSlice<i32>,
4457        aq: &CudaSlice<i8>,
4458        ad: &CudaSlice<f32>,
4459        in_f: usize,
4460        out_f: usize,
4461        n_expert: usize,
4462        n_active: usize,
4463        n_pairs: usize,
4464        qtype: i32,
4465        row_bytes: usize,
4466    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4467        let f = self.func("moe_pairs_matvec_q8_dec");
4468        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4469        const ROWS: u32 = 4;
4470        let cfg = LaunchConfig {
4471            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4472            block_dim: (32, ROWS, 1),
4473            shared_mem_bytes: 0,
4474        };
4475        let (inf, outf, ne, na, rbi) = (
4476            in_f as i32,
4477            out_f as i32,
4478            n_expert as i32,
4479            n_active as i32,
4480            row_bytes as i64,
4481        );
4482        let __s_b = self.gpu.stream();
4483        let mut b = __s_b.launch_builder(&f);
4484        b.arg(table)
4485            .arg(&proj)
4486            .arg(ex_ids)
4487            .arg(ex_off)
4488            .arg(ex_pairs)
4489            .arg(pair_tok)
4490            .arg(aq)
4491            .arg(ad)
4492            .arg(&mut y)
4493            .arg(&inf)
4494            .arg(&outf)
4495            .arg(&ne)
4496            .arg(&na)
4497            .arg(&qtype)
4498            .arg(&rbi);
4499        unsafe {
4500            b.launch(cfg)?;
4501        }
4502        Ok(y)
4503    }
4504
4505    pub fn moe_pairs_gelu_mul(
4506        &self,
4507        gate: &CudaSlice<f32>,
4508        up: &CudaSlice<f32>,
4509        n: usize,
4510    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4511        let f = self.func("moe_pairs_gelu_mul");
4512        let mut act = self.alloc_uninit::<f32>(n)?;
4513        let cfg = LaunchConfig::for_num_elems(n as u32);
4514        let nl = n as i64;
4515        let __s_b = self.gpu.stream();
4516        let mut b = __s_b.launch_builder(&f);
4517        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4518        unsafe {
4519            b.launch(cfg)?;
4520        }
4521        Ok(act)
4522    }
4523
4524    pub fn moe_pairs_silu_mul(
4525        &self,
4526        gate: &CudaSlice<f32>,
4527        up: &CudaSlice<f32>,
4528        n: usize,
4529    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4530        let f = self.func("moe_pairs_silu_mul");
4531        let mut act = self.alloc_uninit::<f32>(n)?;
4532        let cfg = LaunchConfig::for_num_elems(n as u32);
4533        let nl = n as i64;
4534        let __s_b = self.gpu.stream();
4535        let mut b = __s_b.launch_builder(&f);
4536        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4537        unsafe {
4538            b.launch(cfg)?;
4539        }
4540        Ok(act)
4541    }
4542
4543    #[allow(clippy::too_many_arguments)]
4544    pub fn moe_pairs_scatter(
4545        &self,
4546        y_down: &CudaSlice<f32>,
4547        pair_w: &CudaSlice<f32>,
4548        tok_pair_off: &CudaSlice<i32>,
4549        tok_pair_ids: &CudaSlice<i32>,
4550        moe_out: &mut CudaSlice<f32>,
4551        t: usize,
4552        n_embd: usize,
4553    ) -> Result<(), Box<dyn std::error::Error>> {
4554        let f = self.func("moe_pairs_scatter");
4555        let cfg = LaunchConfig {
4556            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4557            block_dim: (256, 1, 1),
4558            shared_mem_bytes: 0,
4559        };
4560        let ne = n_embd as i32;
4561        let __s_b = self.gpu.stream();
4562        let mut b = __s_b.launch_builder(&f);
4563        b.arg(y_down)
4564            .arg(pair_w)
4565            .arg(tok_pair_off)
4566            .arg(tok_pair_ids)
4567            .arg(moe_out)
4568            .arg(&ne);
4569        unsafe {
4570            b.launch(cfg)?;
4571        }
4572        Ok(())
4573    }
4574
4575    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4576    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4577    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4578    #[allow(clippy::too_many_arguments)]
4579    pub fn moe_gate_up_gelu8_dev_q8(
4580        &self,
4581        table: &CudaSlice<u64>,
4582        sel: &cudarc::driver::CudaView<i32>,
4583        aq: &CudaSlice<i8>,
4584        ad: &CudaSlice<f32>,
4585        in_f: usize,
4586        n_ff: usize,
4587        n_used: usize,
4588        n_expert: usize,
4589        qt_g: i32,
4590        qt_u: i32,
4591        rb_g: usize,
4592        rb_u: usize,
4593    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4594        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4595        let (inf, nff, ne, rbg, rbu) = (
4596            in_f as i32,
4597            n_ff as i32,
4598            n_expert as i32,
4599            rb_g as i64,
4600            rb_u as i64,
4601        );
4602        let f = self.func("moe_gate_up_gelu8_dev_q8");
4603        let cfg = LaunchConfig {
4604            grid_dim: (n_ff as u32, n_used as u32, 1),
4605            block_dim: (32, 1, 1),
4606            shared_mem_bytes: 0,
4607        };
4608        let __s_b = self.gpu.stream();
4609        let mut b = __s_b.launch_builder(&f);
4610        b.arg(table)
4611            .arg(sel)
4612            .arg(aq)
4613            .arg(ad)
4614            .arg(&mut act)
4615            .arg(&inf)
4616            .arg(&nff)
4617            .arg(&ne)
4618            .arg(&qt_g)
4619            .arg(&qt_u)
4620            .arg(&rbg)
4621            .arg(&rbu);
4622        unsafe {
4623            b.launch(cfg)?;
4624        }
4625        Ok(act)
4626    }
4627
4628    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4629    #[allow(clippy::too_many_arguments)]
4630    pub fn moe_gate_up_gelu8_dev_q8_rows(
4631        &self,
4632        table: &CudaSlice<u64>,
4633        sel: &CudaSlice<i32>,
4634        aq: &CudaSlice<i8>,
4635        ad: &CudaSlice<f32>,
4636        t: usize,
4637        in_f: usize,
4638        n_ff: usize,
4639        n_used: usize,
4640        n_expert: usize,
4641        qt_g: i32,
4642        qt_u: i32,
4643        rb_g: usize,
4644        rb_u: usize,
4645    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4646        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4647        let (inf, nff, ne, rbg, rbu, nu) = (
4648            in_f as i32,
4649            n_ff as i32,
4650            n_expert as i32,
4651            rb_g as i64,
4652            rb_u as i64,
4653            n_used as i32,
4654        );
4655        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4656        let cfg = LaunchConfig {
4657            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4658            block_dim: (32, 1, 1),
4659            shared_mem_bytes: 0,
4660        };
4661        let __s_b = self.gpu.stream();
4662        let mut b = __s_b.launch_builder(&f);
4663        b.arg(table)
4664            .arg(sel)
4665            .arg(aq)
4666            .arg(ad)
4667            .arg(&mut act)
4668            .arg(&inf)
4669            .arg(&nff)
4670            .arg(&ne)
4671            .arg(&qt_g)
4672            .arg(&qt_u)
4673            .arg(&rbg)
4674            .arg(&rbu)
4675            .arg(&nu);
4676        unsafe {
4677            b.launch(cfg)?;
4678        }
4679        Ok(act)
4680    }
4681
4682    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4683    #[allow(clippy::too_many_arguments)]
4684    pub fn moe_gate_up_gelu8_dev_q8_csr(
4685        &self,
4686        table: &CudaSlice<u64>,
4687        sel: &CudaSlice<i32>,
4688        aq: &CudaSlice<i8>,
4689        ad: &CudaSlice<f32>,
4690        n_pairs: usize,
4691        in_f: usize,
4692        n_ff: usize,
4693        n_used: usize,
4694        n_expert: usize,
4695        qt_g: i32,
4696        qt_u: i32,
4697        rb_g: usize,
4698        rb_u: usize,
4699    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4700        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4701        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4702            in_f as i32,
4703            n_ff as i32,
4704            n_expert as i32,
4705            rb_g as i64,
4706            rb_u as i64,
4707            n_used as i32,
4708            n_pairs as i32,
4709        );
4710        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4711        let cfg = LaunchConfig {
4712            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4713            block_dim: (32, 1, 1),
4714            shared_mem_bytes: 0,
4715        };
4716        let __s_b = self.gpu.stream();
4717        let mut b = __s_b.launch_builder(&f);
4718        b.arg(table)
4719            .arg(sel)
4720            .arg(aq)
4721            .arg(ad)
4722            .arg(&mut act)
4723            .arg(&inf)
4724            .arg(&nff)
4725            .arg(&ne)
4726            .arg(&qt_g)
4727            .arg(&qt_u)
4728            .arg(&rbg)
4729            .arg(&rbu)
4730            .arg(&nu)
4731            .arg(&npi);
4732        unsafe {
4733            b.launch(cfg)?;
4734        }
4735        Ok(act)
4736    }
4737
4738    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4739    #[allow(clippy::too_many_arguments)]
4740    pub fn moe_down8_fma_dev_q8_rows_g(
4741        &self,
4742        table: &CudaSlice<u64>,
4743        sel: &CudaSlice<i32>,
4744        w: &CudaSlice<f32>,
4745        aq2: &CudaSlice<i8>,
4746        ad2: &CudaSlice<f32>,
4747        dst: &mut CudaSlice<f32>,
4748        t: usize,
4749        in_f: usize,
4750        out_f: usize,
4751        n_used: usize,
4752        n_expert: usize,
4753        qt: i32,
4754        rb: usize,
4755    ) -> Result<(), Box<dyn std::error::Error>> {
4756        let (inf, outf, nu, ne, rbi) = (
4757            in_f as i32,
4758            out_f as i32,
4759            n_used as i32,
4760            n_expert as i32,
4761            rb as i64,
4762        );
4763        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4764        // eight warps, then replay the original slot-ordered FMA chain. Every
4765        // other shape retains the generic one-warp rows kernel.
4766        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4767        let f = self.func(if step_b1_w8 {
4768            "moe_down8_fma_dev_q8_rows_w8"
4769        } else {
4770            "moe_down8_fma_dev_q8_rows_g"
4771        });
4772        let cfg = LaunchConfig {
4773            grid_dim: (out_f as u32, 1, t as u32),
4774            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4775            shared_mem_bytes: 0,
4776        };
4777        let __s_b = self.gpu.stream();
4778        let mut b = __s_b.launch_builder(&f);
4779        b.arg(table)
4780            .arg(sel)
4781            .arg(w)
4782            .arg(aq2)
4783            .arg(ad2)
4784            .arg(dst)
4785            .arg(&inf)
4786            .arg(&outf)
4787            .arg(&nu)
4788            .arg(&ne)
4789            .arg(&qt)
4790            .arg(&rbi);
4791        unsafe {
4792            b.launch(cfg)?;
4793        }
4794        Ok(())
4795    }
4796
4797    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4798    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4799    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4800    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4801        let (out_f, in_f) = (2048usize, 2816usize);
4802        let nblk = in_f / 32;
4803        let mut seed = 0x9E3779B97F4A7C15u64;
4804        let mut rng = move || {
4805            seed = seed
4806                .wrapping_mul(6364136223846793005)
4807                .wrapping_add(1442695040888963407);
4808            (seed >> 33) as u8
4809        };
4810        let mut w = vec![0u8; out_f * nblk * 18];
4811        for b in w.iter_mut() {
4812            *b = rng();
4813        }
4814        for r in 0..out_f {
4815            for g in 0..nblk {
4816                let off = (r * nblk + g) * 18;
4817                w[off] = 0x00;
4818                w[off + 1] = 0x2C; // sane half d
4819            }
4820        }
4821        let qplane = out_f * nblk * 16;
4822        let mut wrp = vec![0u8; w.len()];
4823        for r in 0..out_f {
4824            for g in 0..nblk {
4825                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4826                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4827                    .copy_from_slice(&src[0..2]);
4828                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4829            }
4830        }
4831        let w_d = self.htod_bytes(&w)?;
4832        let wrp_d = self.htod_bytes(&wrp)?;
4833        let mut aq = vec![0i8; m * in_f];
4834        for v in aq.iter_mut() {
4835            *v = rng() as i8;
4836        }
4837        let aq_d = self.htod_i8(&aq)?;
4838        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4839        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4840        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4841        const RPB: u32 = 4;
4842        let cfg = LaunchConfig {
4843            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4844            block_dim: (32, RPB, 1),
4845            shared_mem_bytes: 0,
4846        };
4847        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4848        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4849        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4850        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4851        {
4852            let __s_b = self.gpu.stream();
4853            let mut b = __s_b.launch_builder(&fb);
4854            b.arg(&w_d)
4855                .arg(&aq_d)
4856                .arg(&ad_d)
4857                .arg(&mut y0)
4858                .arg(&inf)
4859                .arg(&outf)
4860                .arg(&mi)
4861                .arg(&rb);
4862            unsafe {
4863                b.launch(cfg)?;
4864            }
4865            let __s_b = self.gpu.stream();
4866            let mut b = __s_b.launch_builder(&fr);
4867            b.arg(&wrp_d)
4868                .arg(&aq_d)
4869                .arg(&ad_d)
4870                .arg(&mut y1)
4871                .arg(&inf)
4872                .arg(&outf)
4873                .arg(&mi)
4874                .arg(&qp);
4875            unsafe {
4876                b.launch(cfg)?;
4877            }
4878        }
4879        self.gpu.stream().synchronize()?;
4880        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4881        let nd = h0
4882            .iter()
4883            .zip(&h1)
4884            .filter(|(a, b)| a.to_bits() != b.to_bits())
4885            .count();
4886        if nd != 0 {
4887            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4888        }
4889        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4890            self.gpu.stream().synchronize()?;
4891            let t0 = std::time::Instant::now();
4892            for _ in 0..500 {
4893                if rp {
4894                    let __s_b = self.gpu.stream();
4895                    let mut b = __s_b.launch_builder(&fr);
4896                    b.arg(&wrp_d)
4897                        .arg(&aq_d)
4898                        .arg(&ad_d)
4899                        .arg(&mut y1)
4900                        .arg(&inf)
4901                        .arg(&outf)
4902                        .arg(&mi)
4903                        .arg(&qp);
4904                    unsafe {
4905                        b.launch(cfg)?;
4906                    }
4907                } else {
4908                    let __s_b = self.gpu.stream();
4909                    let mut b = __s_b.launch_builder(&fb);
4910                    b.arg(&w_d)
4911                        .arg(&aq_d)
4912                        .arg(&ad_d)
4913                        .arg(&mut y0)
4914                        .arg(&inf)
4915                        .arg(&outf)
4916                        .arg(&mi)
4917                        .arg(&rb);
4918                    unsafe {
4919                        b.launch(cfg)?;
4920                    }
4921                }
4922            }
4923            self.gpu.stream().synchronize()?;
4924            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
4925        };
4926        let _ = time(false)?;
4927        let _ = time(true)?; // warm
4928        Ok((time(false)?, time(true)?))
4929    }
4930
4931    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
4932    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
4933    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
4934    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
4935    pub fn build_q4_rp4(
4936        &self,
4937        t: &mut crate::model::GpuTensor,
4938    ) -> Result<(), Box<dyn std::error::Error>> {
4939        use crate::model::GpuTensor;
4940        let GpuTensor::Quant {
4941            bytes,
4942            qtype,
4943            row_bytes,
4944            ne,
4945            rp4,
4946            ..
4947        } = t
4948        else {
4949            return Ok(());
4950        };
4951        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
4952            return Ok(());
4953        }
4954        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
4955        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
4956            return Ok(());
4957        }
4958        let nblk = in_f / 32;
4959        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
4960        let f = self.func("q4_0_split_rp_build");
4961        let n = (out_f * nblk) as i32;
4962        let cfg = LaunchConfig {
4963            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
4964            block_dim: (256, 1, 1),
4965            shared_mem_bytes: 0,
4966        };
4967        let (of, nb) = (out_f as i32, nblk as i32);
4968        let _ = n;
4969        let __s_b = self.gpu.stream();
4970        let mut b = __s_b.launch_builder(&f);
4971        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
4972        unsafe {
4973            b.launch(cfg)?;
4974        }
4975        *rp4 = Some(dst);
4976        Ok(())
4977    }
4978
4979    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
4980    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
4981    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
4982    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
4983    pub fn build_q8_rp4(
4984        &self,
4985        t: &mut crate::model::GpuTensor,
4986    ) -> Result<(), Box<dyn std::error::Error>> {
4987        use crate::model::GpuTensor;
4988        let GpuTensor::Quant {
4989            bytes,
4990            qtype,
4991            row_bytes,
4992            ne,
4993            rp4,
4994            ..
4995        } = t
4996        else {
4997            return Ok(());
4998        };
4999        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
5000            return Ok(());
5001        }
5002        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5003        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5004            return Ok(());
5005        }
5006        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5007        Ok(())
5008    }
5009
5010    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5011    /// mirror without a GpuTensor (same kernel the loader path above uses).
5012    pub fn build_q8_rp4_raw(
5013        &self,
5014        bytes: &CudaSlice<u8>,
5015        in_f: usize,
5016        out_f: usize,
5017    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5018        assert!(in_f % 32 == 0);
5019        let nblk = in_f / 32;
5020        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5021        let f = self.func("q8_0_split_rp_build");
5022        let cfg = LaunchConfig {
5023            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5024            block_dim: (256, 1, 1),
5025            shared_mem_bytes: 0,
5026        };
5027        let (of, nb) = (out_f as i32, nblk as i32);
5028        let __s_b = self.gpu.stream();
5029        let mut b = __s_b.launch_builder(&f);
5030        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5031        unsafe {
5032            b.launch(cfg)?;
5033        }
5034        Ok(dst)
5035    }
5036
5037    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5038    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5039    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5040    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5041    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5042    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5043    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5044    pub fn build_q4k_rp4(
5045        &self,
5046        t: &mut crate::model::GpuTensor,
5047    ) -> Result<(), Box<dyn std::error::Error>> {
5048        use crate::model::GpuTensor;
5049        let GpuTensor::Quant {
5050            bytes,
5051            qtype,
5052            row_bytes,
5053            ne,
5054            rp4,
5055            ..
5056        } = t
5057        else {
5058            return Ok(());
5059        };
5060        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5061            return Ok(());
5062        }
5063        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5064        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5065            return Ok(());
5066        }
5067        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5068        Ok(())
5069    }
5070
5071    pub fn build_q6k_rp4(
5072        &self,
5073        t: &mut crate::model::GpuTensor,
5074    ) -> Result<(), Box<dyn std::error::Error>> {
5075        use crate::model::GpuTensor;
5076        let GpuTensor::Quant {
5077            bytes,
5078            qtype,
5079            row_bytes,
5080            ne,
5081            rp4,
5082            ..
5083        } = t
5084        else {
5085            return Ok(());
5086        };
5087        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5088            return Ok(());
5089        }
5090        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5091        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5092            return Ok(());
5093        }
5094        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5095        Ok(())
5096    }
5097
5098    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5099    pub fn build_kq_rp4_raw(
5100        &self,
5101        bytes: &CudaSlice<u8>,
5102        in_f: usize,
5103        out_f: usize,
5104        qtype: i32,
5105    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5106        assert!(in_f % 256 == 0);
5107        let nsbk = in_f / 256;
5108        let (sb_bytes, kname) = match qtype {
5109            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5110            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5111            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5112        };
5113        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5114        let f = self.func(kname);
5115        let cfg = LaunchConfig {
5116            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5117            block_dim: (256, 1, 1),
5118            shared_mem_bytes: 0,
5119        };
5120        let (of, nb) = (out_f as i32, nsbk as i32);
5121        let __s_b = self.gpu.stream();
5122        let mut b = __s_b.launch_builder(&f);
5123        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5124        unsafe {
5125            b.launch(cfg)?;
5126        }
5127        Ok(dst)
5128    }
5129
5130    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5131    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5132    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5133    pub fn kqrp_enabled() -> bool {
5134        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5135        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5136            Ok("0") => false,
5137            Ok(_) => true,
5138            Err(_) => cfg!(memra_hopper_mma),
5139        })
5140    }
5141
5142    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5143    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5144    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5145    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5146    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5147    pub fn build_q4_rp_swap(
5148        &self,
5149        t: &mut crate::model::GpuTensor,
5150    ) -> Result<bool, Box<dyn std::error::Error>> {
5151        self.build_q4_rp4(t)?;
5152        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5153        use crate::model::GpuTensor;
5154        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5155            return Ok(false);
5156        };
5157        match rp4.take() {
5158            Some(split) => {
5159                *bytes = split; // the GGUF-layout buffer drops here
5160                *rp = true;
5161                Ok(true)
5162            }
5163            None => Ok(false),
5164        }
5165    }
5166
5167    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5168    pub fn q4rp_enabled() -> bool {
5169        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5170        *ON.get_or_init(|| {
5171            std::env::var("MEMRA_Q4RP")
5172                .map(|v| v != "0")
5173                .unwrap_or(true)
5174        })
5175    }
5176
5177    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5178    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5179    pub fn copy_rows_strided(
5180        &self,
5181        src: &CudaSlice<f32>,
5182        dst: &mut CudaSlice<f32>,
5183        row_elems: usize,
5184        n_rows: usize,
5185        src_stride: usize,
5186        src_off: usize,
5187    ) -> Result<(), Box<dyn std::error::Error>> {
5188        let f = self.func("copy_rows_strided_f32");
5189        let cfg = LaunchConfig {
5190            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5191            block_dim: (256, 1, 1),
5192            shared_mem_bytes: 0,
5193        };
5194        let (re, nr) = (row_elems as i32, n_rows as i32);
5195        let (st, off) = (src_stride as i64, src_off as i64);
5196        let __s_b = self.gpu.stream();
5197        let mut b = __s_b.launch_builder(&f);
5198        b.arg(src)
5199            .arg(&mut *dst)
5200            .arg(&re)
5201            .arg(&nr)
5202            .arg(&st)
5203            .arg(&off);
5204        unsafe {
5205            b.launch(cfg)?;
5206        }
5207        Ok(())
5208    }
5209
5210    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5211    pub fn u32_set_k(
5212        &self,
5213        dst: &mut CudaSlice<u32>,
5214        v: u32,
5215        idx: usize,
5216    ) -> Result<(), Box<dyn std::error::Error>> {
5217        let f = self.func("u32_set_k");
5218        let cfg = LaunchConfig {
5219            grid_dim: (1, 1, 1),
5220            block_dim: (1, 1, 1),
5221            shared_mem_bytes: 0,
5222        };
5223        let ii = idx as i32;
5224        let __s_b = self.gpu.stream();
5225        let mut b = __s_b.launch_builder(&f);
5226        b.arg(dst).arg(&v).arg(&ii);
5227        unsafe {
5228            b.launch(cfg)?;
5229        }
5230        Ok(())
5231    }
5232
5233    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5234    pub fn i32_add_k(
5235        &self,
5236        d: &mut CudaSlice<i32>,
5237        v: i32,
5238    ) -> Result<(), Box<dyn std::error::Error>> {
5239        let f = self.func("i32_add_k");
5240        let cfg = LaunchConfig {
5241            grid_dim: (1, 1, 1),
5242            block_dim: (32, 1, 1),
5243            shared_mem_bytes: 0,
5244        };
5245        let __s_b = self.gpu.stream();
5246        let mut b = __s_b.launch_builder(&f);
5247        b.arg(d).arg(&v);
5248        unsafe {
5249            b.launch(cfg)?;
5250        }
5251        Ok(())
5252    }
5253
5254    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5255    pub fn i32_iota_from(
5256        &self,
5257        ctr: &CudaSlice<i32>,
5258        dst: &mut CudaSlice<i32>,
5259        n: usize,
5260    ) -> Result<(), Box<dyn std::error::Error>> {
5261        let f = self.func("i32_iota_from");
5262        let cfg = LaunchConfig::for_num_elems(n as u32);
5263        let ni = n as i32;
5264        let __s_b = self.gpu.stream();
5265        let mut b = __s_b.launch_builder(&f);
5266        b.arg(ctr).arg(dst).arg(&ni);
5267        unsafe {
5268            b.launch(cfg)?;
5269        }
5270        Ok(())
5271    }
5272
5273    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5274    pub fn u32_map_k(
5275        &self,
5276        buf: &mut CudaSlice<u32>,
5277        map: &CudaSlice<u32>,
5278        idx: usize,
5279    ) -> Result<(), Box<dyn std::error::Error>> {
5280        let f = self.func("u32_map_k");
5281        let cfg = LaunchConfig {
5282            grid_dim: (1, 1, 1),
5283            block_dim: (1, 1, 1),
5284            shared_mem_bytes: 0,
5285        };
5286        let ii = idx as i32;
5287        let __s_b = self.gpu.stream();
5288        let mut b = __s_b.launch_builder(&f);
5289        b.arg(buf).arg(map).arg(&ii);
5290        unsafe {
5291            b.launch(cfg)?;
5292        }
5293        Ok(())
5294    }
5295
5296    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5297    #[allow(clippy::too_many_arguments)]
5298    pub fn u32_pack2(
5299        &self,
5300        a: &CudaSlice<u32>,
5301        off_a: usize,
5302        n1: usize,
5303        b_in: &CudaSlice<u32>,
5304        n2: usize,
5305        out: &mut CudaSlice<u32>,
5306    ) -> Result<(), Box<dyn std::error::Error>> {
5307        let f = self.func("u32_pack2");
5308        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5309        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5310        let __s_b = self.gpu.stream();
5311        let mut b = __s_b.launch_builder(&f);
5312        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5313        unsafe {
5314            b.launch(cfg)?;
5315        }
5316        Ok(())
5317    }
5318
5319    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5320    pub fn moe_w_exscale(
5321        &self,
5322        w: &mut CudaSlice<f32>,
5323        sel: &CudaSlice<i32>,
5324        s: &CudaSlice<f32>,
5325        n: usize,
5326    ) -> Result<(), Box<dyn std::error::Error>> {
5327        let f = self.func("moe_w_exscale");
5328        let cfg = LaunchConfig::for_num_elems(n as u32);
5329        let ni = n as i32;
5330        let __s_b = self.gpu.stream();
5331        let mut b = __s_b.launch_builder(&f);
5332        b.arg(w).arg(sel).arg(s).arg(&ni);
5333        unsafe {
5334            b.launch(cfg)?;
5335        }
5336        Ok(())
5337    }
5338
5339    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5340    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5341    pub fn moe_w_scale_by_expert(
5342        &self,
5343        w: &mut CudaSlice<f32>,
5344        sel: &CudaSlice<i32>,
5345        macros: &CudaSlice<f32>,
5346        n_expert: usize,
5347        n: usize,
5348    ) -> Result<(), Box<dyn std::error::Error>> {
5349        let f = self.func("moe_w_scale_by_expert");
5350        let cfg = LaunchConfig {
5351            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5352            block_dim: (64, 1, 1),
5353            shared_mem_bytes: 0,
5354        };
5355        let (ne, nn) = (n_expert as i32, n as i32);
5356        let __s_b = self.gpu.stream();
5357        let mut b = __s_b.launch_builder(&f);
5358        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5359        unsafe {
5360            b.launch(cfg)?;
5361        }
5362        Ok(())
5363    }
5364
5365    pub fn moe_gate_up_silu8_dev_q8(
5366        &self,
5367        table: &CudaSlice<u64>,
5368        sel: &cudarc::driver::CudaView<i32>,
5369        aq: &CudaSlice<i8>,
5370        ad: &CudaSlice<f32>,
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        macros: &CudaSlice<f32>,
5380    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5381        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5382        let (mode, wpb) = GU.get_or_init(|| {
5383            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5384            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5385                .ok()
5386                .and_then(|v| v.parse().ok())
5387                .unwrap_or(4u32)
5388                .clamp(1, 16);
5389            (mode, wpb)
5390        });
5391        let (mode, wpb) = (mode.as_str(), *wpb);
5392        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5393        let (inf, nff, ne, rbg, rbu) = (
5394            in_f as i32,
5395            n_ff as i32,
5396            n_expert as i32,
5397            rb_g as i64,
5398            rb_u as i64,
5399        );
5400        let (f, cfg) = match mode {
5401            "1" | "2" | "4" => {
5402                let rpw: u32 = mode.parse().unwrap();
5403                let f = self.func(match rpw {
5404                    1 => "moe_gate_up_silu8_dev_q8_r1",
5405                    2 => "moe_gate_up_silu8_dev_q8_r2",
5406                    _ => "moe_gate_up_silu8_dev_q8_r4",
5407                });
5408                let rows_per_block = (rpw * wpb) as usize;
5409                let gx = n_ff.div_ceil(rows_per_block) as u32;
5410                (
5411                    f,
5412                    LaunchConfig {
5413                        grid_dim: (gx, n_used as u32, 1),
5414                        block_dim: (32, wpb, 1),
5415                        shared_mem_bytes: 0,
5416                    },
5417                )
5418            }
5419            "j8" if n_used <= 32 => (
5420                self.func("moe_gate_up_silu8_dev_q8_j8"),
5421                LaunchConfig {
5422                    grid_dim: (n_ff as u32, 1, 1),
5423                    block_dim: (32, n_used as u32, 1),
5424                    shared_mem_bytes: 0,
5425                },
5426            ),
5427            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5428            "vsm2" => {
5429                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5430                let sh = (rb_g + rb_u) as u32;
5431                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5432                f.set_attribute(
5433                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5434                    sh as i32,
5435                )?;
5436                (
5437                    f,
5438                    LaunchConfig {
5439                        grid_dim: (n_ff as u32, n_used as u32, 1),
5440                        block_dim: (32, 1, 1),
5441                        shared_mem_bytes: sh,
5442                    },
5443                )
5444            }
5445            "vsm" => {
5446                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5447                let sh = (rb_g + rb_u) as u32;
5448                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5449                f.set_attribute(
5450                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5451                    sh as i32,
5452                )?;
5453                (
5454                    f,
5455                    LaunchConfig {
5456                        grid_dim: (n_ff as u32, n_used as u32, 1),
5457                        block_dim: (32, 1, 1),
5458                        shared_mem_bytes: sh,
5459                    },
5460                )
5461            }
5462            "sg" => (
5463                self.func("moe_gate_up_silu8_dev_q8_sg"),
5464                LaunchConfig {
5465                    grid_dim: (n_ff as u32, n_used as u32, 1),
5466                    block_dim: (32, 1, 1),
5467                    shared_mem_bytes: 0,
5468                },
5469            ),
5470            "j8sg" if n_used <= 32 => (
5471                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5472                LaunchConfig {
5473                    grid_dim: (n_ff as u32, 1, 1),
5474                    block_dim: (32, n_used as u32, 1),
5475                    shared_mem_bytes: 0,
5476                },
5477            ),
5478            "u64" if in_f == 2048 => (
5479                self.func("moe_gate_up_silu8_dev_q8_u64"),
5480                LaunchConfig {
5481                    grid_dim: (n_ff as u32, n_used as u32, 1),
5482                    block_dim: (32, 1, 1),
5483                    shared_mem_bytes: 0,
5484                },
5485            ),
5486            "gs4" if in_f == 2048 => (
5487                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5488                LaunchConfig {
5489                    grid_dim: (n_ff as u32, n_used as u32, 1),
5490                    block_dim: (32, 4, 1),
5491                    shared_mem_bytes: 0,
5492                },
5493            ),
5494            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5495            "v" | "" => (
5496                self.func("moe_gate_up_silu8_dev_q8_v"),
5497                LaunchConfig {
5498                    grid_dim: (n_ff as u32, n_used as u32, 1),
5499                    block_dim: (32, 1, 1),
5500                    shared_mem_bytes: 0,
5501                },
5502            ),
5503            "s2" => (
5504                self.func("moe_gate_up_silu8_dev_q8_s2"),
5505                LaunchConfig {
5506                    grid_dim: (n_ff as u32, n_used as u32, 1),
5507                    block_dim: (32, 2, 1),
5508                    shared_mem_bytes: 0,
5509                },
5510            ),
5511            "s2z" => {
5512                let rz = wpb.min(16); // s2z smem tile is [16][2]
5513                (
5514                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5515                    LaunchConfig {
5516                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5517                        block_dim: (32, 2, rz),
5518                        shared_mem_bytes: 0,
5519                    },
5520                )
5521            }
5522            _ => (
5523                self.func("moe_gate_up_silu8_dev_q8"),
5524                LaunchConfig {
5525                    grid_dim: (n_ff as u32, n_used as u32, 1),
5526                    block_dim: (32, 1, 1),
5527                    shared_mem_bytes: 0,
5528                },
5529            ),
5530        };
5531        let __s_b = self.gpu.stream();
5532        let mut b = __s_b.launch_builder(&f);
5533        b.arg(table)
5534            .arg(sel)
5535            .arg(aq)
5536            .arg(ad)
5537            .arg(&mut act)
5538            .arg(&inf)
5539            .arg(&nff)
5540            .arg(&ne)
5541            .arg(&qt_g)
5542            .arg(&qt_u)
5543            .arg(&rbg)
5544            .arg(&rbu)
5545            .arg(macros);
5546        unsafe {
5547            b.launch(cfg)?;
5548        }
5549        Ok(act)
5550    }
5551
5552    #[allow(clippy::too_many_arguments)]
5553    pub fn moe_down8_fma_dev_q8(
5554        &self,
5555        table: &CudaSlice<u64>,
5556        sel: &cudarc::driver::CudaView<i32>,
5557        w: &cudarc::driver::CudaView<f32>,
5558        aq2: &CudaSlice<i8>,
5559        ad2: &CudaSlice<f32>,
5560        dst: &mut cudarc::driver::CudaViewMut<f32>,
5561        in_f: usize,
5562        out_f: usize,
5563        n_used: usize,
5564        n_expert: usize,
5565        qt: i32,
5566        rb: usize,
5567    ) -> Result<(), Box<dyn std::error::Error>> {
5568        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5569        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5570        let (inf, outf, nu, ne, rbi) = (
5571            in_f as i32,
5572            out_f as i32,
5573            n_used as i32,
5574            n_expert as i32,
5575            rb as i64,
5576        );
5577        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5578        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5579        let (f, cfg) = match mode.as_str() {
5580            m @ ("1" | "2" | "4") if n_used <= 8 => {
5581                let rpw: usize = m.parse().unwrap();
5582                let f = self.func(match rpw {
5583                    1 => "moe_down8_fma_dev_q8_w8r1",
5584                    2 => "moe_down8_fma_dev_q8_w8r2",
5585                    _ => "moe_down8_fma_dev_q8_w8r4",
5586                });
5587                (
5588                    f,
5589                    LaunchConfig {
5590                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5591                        block_dim: (32, n_used as u32, 1),
5592                        shared_mem_bytes: 0,
5593                    },
5594                )
5595            }
5596            "h2" if in_f == 512 => (
5597                self.func("moe_down8_fma_dev_q8_h2"),
5598                LaunchConfig {
5599                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5600                    block_dim: (32, 1, 1),
5601                    shared_mem_bytes: 0,
5602                },
5603            ),
5604            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5605            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5606            "" if in_f == 704 && n_used <= 8 => (
5607                self.func("moe_down8_fma_dev_q8_w8r2"),
5608                LaunchConfig {
5609                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5610                    block_dim: (32, n_used as u32, 1),
5611                    shared_mem_bytes: 0,
5612                },
5613            ),
5614            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5615            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5616            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5617            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5618                self.func("moe_down8_fma_dev_q8_w8h2v"),
5619                LaunchConfig {
5620                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5621                    block_dim: (32, n_used as u32, 1),
5622                    shared_mem_bytes: 0,
5623                },
5624            ),
5625            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5626                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5627                LaunchConfig {
5628                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5629                    block_dim: (32, n_used as u32, 1),
5630                    shared_mem_bytes: 0,
5631                },
5632            ),
5633            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5634                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5635                LaunchConfig {
5636                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5637                    block_dim: (32, n_used as u32, 1),
5638                    shared_mem_bytes: 0,
5639                },
5640            ),
5641            "w8h2" if in_f == 512 && n_used <= 8 => (
5642                self.func("moe_down8_fma_dev_q8_w8h2"),
5643                LaunchConfig {
5644                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5645                    block_dim: (32, n_used as u32, 1),
5646                    shared_mem_bytes: 0,
5647                },
5648            ),
5649            _ => (
5650                self.func("moe_down8_fma_dev_q8"),
5651                LaunchConfig {
5652                    grid_dim: (out_f as u32, 1, 1),
5653                    block_dim: (32, 1, 1),
5654                    shared_mem_bytes: 0,
5655                },
5656            ),
5657        };
5658        let __s_b = self.gpu.stream();
5659        let mut b = __s_b.launch_builder(&f);
5660        b.arg(table)
5661            .arg(sel)
5662            .arg(w)
5663            .arg(aq2)
5664            .arg(ad2)
5665            .arg(dst)
5666            .arg(&inf)
5667            .arg(&outf)
5668            .arg(&nu)
5669            .arg(&ne)
5670            .arg(&qt)
5671            .arg(&rbi);
5672        unsafe {
5673            b.launch(cfg)?;
5674        }
5675        Ok(())
5676    }
5677
5678    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5679    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5680    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5681    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5682    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5683    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5684    #[allow(clippy::too_many_arguments)]
5685    pub fn moe_gate_up_silu8_dev_q8_rows(
5686        &self,
5687        table: &CudaSlice<u64>,
5688        sel: &CudaSlice<i32>,
5689        aq: &CudaSlice<i8>,
5690        ad: &CudaSlice<f32>,
5691        t: usize,
5692        in_f: usize,
5693        n_ff: usize,
5694        n_used: usize,
5695        n_expert: usize,
5696        qt_g: i32,
5697        qt_u: i32,
5698        rb_g: usize,
5699        rb_u: usize,
5700        macros: &CudaSlice<f32>,
5701    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5702        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5703        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5704        let cfg = LaunchConfig {
5705            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5706            block_dim: (32, 1, 1),
5707            shared_mem_bytes: 0,
5708        };
5709        let (inf, nff, ne, nu, rbg, rbu) = (
5710            in_f as i32,
5711            n_ff as i32,
5712            n_expert as i32,
5713            n_used as i32,
5714            rb_g as i64,
5715            rb_u as i64,
5716        );
5717        let __s_b = self.gpu.stream();
5718        let mut b = __s_b.launch_builder(&f);
5719        b.arg(table)
5720            .arg(sel)
5721            .arg(aq)
5722            .arg(ad)
5723            .arg(&mut act)
5724            .arg(&inf)
5725            .arg(&nff)
5726            .arg(&ne)
5727            .arg(&qt_g)
5728            .arg(&qt_u)
5729            .arg(&rbg)
5730            .arg(&rbu)
5731            .arg(&nu)
5732            .arg(macros);
5733        unsafe {
5734            b.launch(cfg)?;
5735        }
5736        Ok(act)
5737    }
5738
5739    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5740    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5741    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5742    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5743    #[allow(clippy::too_many_arguments)]
5744    pub fn moe_down8_fma_dev_q8_rows(
5745        &self,
5746        table: &CudaSlice<u64>,
5747        sel: &CudaSlice<i32>,
5748        w: &CudaSlice<f32>,
5749        aq2: &CudaSlice<i8>,
5750        ad2: &CudaSlice<f32>,
5751        dst: &mut CudaSlice<f32>,
5752        t: usize,
5753        in_f: usize,
5754        out_f: usize,
5755        n_used: usize,
5756        n_expert: usize,
5757        qt: i32,
5758        rb: usize,
5759    ) -> Result<(), Box<dyn std::error::Error>> {
5760        assert!(
5761            in_f == 512 && n_used <= 8,
5762            "down rows twin is w8h2v shape-gated"
5763        );
5764        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5765        let cfg = LaunchConfig {
5766            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5767            block_dim: (32, n_used as u32, 1),
5768            shared_mem_bytes: 0,
5769        };
5770        let (inf, outf, nu, ne, rbi) = (
5771            in_f as i32,
5772            out_f as i32,
5773            n_used as i32,
5774            n_expert as i32,
5775            rb as i64,
5776        );
5777        let __s_b = self.gpu.stream();
5778        let mut b = __s_b.launch_builder(&f);
5779        b.arg(table)
5780            .arg(sel)
5781            .arg(w)
5782            .arg(aq2)
5783            .arg(ad2)
5784            .arg(dst)
5785            .arg(&inf)
5786            .arg(&outf)
5787            .arg(&nu)
5788            .arg(&ne)
5789            .arg(&qt)
5790            .arg(&rbi);
5791        unsafe {
5792            b.launch(cfg)?;
5793        }
5794        Ok(())
5795    }
5796
5797    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5798    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5799    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5800    #[allow(clippy::too_many_arguments)]
5801    pub fn moe_gate_up_silu8_dev_q8_csr(
5802        &self,
5803        table: &CudaSlice<u64>,
5804        sel: &CudaSlice<i32>,
5805        aq: &CudaSlice<i8>,
5806        ad: &CudaSlice<f32>,
5807        n_pairs: usize,
5808        in_f: usize,
5809        n_ff: usize,
5810        n_used: usize,
5811        n_expert: usize,
5812        qt_g: i32,
5813        qt_u: i32,
5814        rb_g: usize,
5815        rb_u: usize,
5816    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5817        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
5818        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5819        let cfg = LaunchConfig {
5820            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5821            block_dim: (32, 1, 1),
5822            shared_mem_bytes: 0,
5823        };
5824        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5825            in_f as i32,
5826            n_ff as i32,
5827            n_expert as i32,
5828            n_used as i32,
5829            n_pairs as i32,
5830            rb_g as i64,
5831            rb_u as i64,
5832        );
5833        let __s_b = self.gpu.stream();
5834        let mut b = __s_b.launch_builder(&f);
5835        b.arg(table)
5836            .arg(sel)
5837            .arg(aq)
5838            .arg(ad)
5839            .arg(&mut act)
5840            .arg(&inf)
5841            .arg(&nff)
5842            .arg(&ne)
5843            .arg(&qt_g)
5844            .arg(&qt_u)
5845            .arg(&rbg)
5846            .arg(&rbu)
5847            .arg(&nu)
5848            .arg(&npi);
5849        unsafe {
5850            b.launch(cfg)?;
5851        }
5852        Ok(act)
5853    }
5854
5855    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5856    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5857    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5858    #[allow(clippy::too_many_arguments)]
5859    pub fn moe_down8_fma_dev_q8_variant(
5860        &self,
5861        variant: &str,
5862        table: &CudaSlice<u64>,
5863        sel: &cudarc::driver::CudaView<i32>,
5864        w: &cudarc::driver::CudaView<f32>,
5865        aq2: &CudaSlice<i8>,
5866        ad2: &CudaSlice<f32>,
5867        dst: &mut cudarc::driver::CudaViewMut<f32>,
5868        in_f: usize,
5869        out_f: usize,
5870        n_used: usize,
5871        n_expert: usize,
5872        qt: i32,
5873        rb: usize,
5874    ) -> Result<(), Box<dyn std::error::Error>> {
5875        let (inf, outf, nu, ne, rbi) = (
5876            in_f as i32,
5877            out_f as i32,
5878            n_used as i32,
5879            n_expert as i32,
5880            rb as i64,
5881        );
5882        let (f, cfg) = match variant {
5883            "w8h2" | "w8h2v" => (
5884                self.func(if variant == "w8h2" {
5885                    "moe_down8_fma_dev_q8_w8h2"
5886                } else {
5887                    "moe_down8_fma_dev_q8_w8h2v"
5888                }),
5889                LaunchConfig {
5890                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5891                    block_dim: (32, n_used as u32, 1),
5892                    shared_mem_bytes: 0,
5893                },
5894            ),
5895            "w8h2r2" | "w8h2r2v" => (
5896                self.func(if variant == "w8h2r2" {
5897                    "moe_down8_fma_dev_q8_w8h2r2"
5898                } else {
5899                    "moe_down8_fma_dev_q8_w8h2r2v"
5900                }),
5901                LaunchConfig {
5902                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5903                    block_dim: (32, n_used as u32, 1),
5904                    shared_mem_bytes: 0,
5905                },
5906            ),
5907            _ => (
5908                self.func("moe_down8_fma_dev_q8"),
5909                LaunchConfig {
5910                    grid_dim: (out_f as u32, 1, 1),
5911                    block_dim: (32, 1, 1),
5912                    shared_mem_bytes: 0,
5913                },
5914            ),
5915        };
5916        let __s_b = self.gpu.stream();
5917        let mut b = __s_b.launch_builder(&f);
5918        b.arg(table)
5919            .arg(sel)
5920            .arg(w)
5921            .arg(aq2)
5922            .arg(ad2)
5923            .arg(dst)
5924            .arg(&inf)
5925            .arg(&outf)
5926            .arg(&nu)
5927            .arg(&ne)
5928            .arg(&qt)
5929            .arg(&rbi);
5930        unsafe {
5931            b.launch(cfg)?;
5932        }
5933        Ok(())
5934    }
5935
5936    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
5937    #[allow(clippy::too_many_arguments)]
5938    pub fn moe_gate_up_silu8_dev_q8_variant(
5939        &self,
5940        variant: &str,
5941        table: &CudaSlice<u64>,
5942        sel: &cudarc::driver::CudaView<i32>,
5943        aq: &CudaSlice<i8>,
5944        ad: &CudaSlice<f32>,
5945        in_f: usize,
5946        n_ff: usize,
5947        n_used: usize,
5948        n_expert: usize,
5949        qt_g: i32,
5950        qt_u: i32,
5951        rb_g: usize,
5952        rb_u: usize,
5953    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5954        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5955        let (inf, nff, ne, rbg, rbu) = (
5956            in_f as i32,
5957            n_ff as i32,
5958            n_expert as i32,
5959            rb_g as i64,
5960            rb_u as i64,
5961        );
5962        let f = self.func(if variant == "v" {
5963            "moe_gate_up_silu8_dev_q8_v"
5964        } else {
5965            "moe_gate_up_silu8_dev_q8"
5966        });
5967        let cfg = LaunchConfig {
5968            grid_dim: (n_ff as u32, n_used as u32, 1),
5969            block_dim: (32, 1, 1),
5970            shared_mem_bytes: 0,
5971        };
5972        let __s_b = self.gpu.stream();
5973        let mut b = __s_b.launch_builder(&f);
5974        b.arg(table)
5975            .arg(sel)
5976            .arg(aq)
5977            .arg(ad)
5978            .arg(&mut act)
5979            .arg(&inf)
5980            .arg(&nff)
5981            .arg(&ne)
5982            .arg(&qt_g)
5983            .arg(&qt_u)
5984            .arg(&rbg)
5985            .arg(&rbu);
5986        unsafe {
5987            b.launch(cfg)?;
5988        }
5989        Ok(act)
5990    }
5991
5992    pub fn moe_gate_up_silu8_dev(
5993        &self,
5994        table: &CudaSlice<u64>,
5995        sel: &cudarc::driver::CudaView<i32>,
5996        x: &cudarc::driver::CudaView<f32>,
5997        in_f: usize,
5998        n_ff: usize,
5999        n_used: usize,
6000        n_expert: usize,
6001        qt_g: i32,
6002        qt_u: i32,
6003        rb_g: usize,
6004        rb_u: usize,
6005        macros: &CudaSlice<f32>,
6006    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6007        let f = self.func("moe_gate_up_silu8_dev");
6008        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6009        let cfg = LaunchConfig {
6010            grid_dim: (n_ff as u32, n_used as u32, 1),
6011            block_dim: (256, 1, 1),
6012            shared_mem_bytes: 0,
6013        };
6014        let (inf, nff, ne, rbg, rbu) = (
6015            in_f as i32,
6016            n_ff as i32,
6017            n_expert as i32,
6018            rb_g as i64,
6019            rb_u as i64,
6020        );
6021        let __s_b = self.gpu.stream();
6022        let mut b = __s_b.launch_builder(&f);
6023        b.arg(table)
6024            .arg(sel)
6025            .arg(x)
6026            .arg(&mut act)
6027            .arg(&inf)
6028            .arg(&nff)
6029            .arg(&ne)
6030            .arg(&qt_g)
6031            .arg(&qt_u)
6032            .arg(&rbg)
6033            .arg(&rbu)
6034            .arg(macros);
6035        unsafe {
6036            b.launch(cfg)?;
6037        }
6038        Ok(act)
6039    }
6040
6041    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6042    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6043    #[allow(clippy::too_many_arguments)]
6044    pub fn moe_down8_fma_dev(
6045        &self,
6046        table: &CudaSlice<u64>,
6047        sel: &cudarc::driver::CudaView<i32>,
6048        w: &cudarc::driver::CudaView<f32>,
6049        act: &CudaSlice<f32>,
6050        dst: &mut cudarc::driver::CudaViewMut<f32>,
6051        in_f: usize,
6052        out_f: usize,
6053        n_used: usize,
6054        n_expert: usize,
6055        qt: i32,
6056        rb: usize,
6057    ) -> Result<(), Box<dyn std::error::Error>> {
6058        let f = self.func("moe_down8_fma_dev");
6059        let cfg = LaunchConfig {
6060            grid_dim: (out_f as u32, 1, 1),
6061            block_dim: (256, 1, 1),
6062            shared_mem_bytes: 0,
6063        };
6064        let (inf, outf, nu, ne, rbv) = (
6065            in_f as i32,
6066            out_f as i32,
6067            n_used as i32,
6068            n_expert as i32,
6069            rb as i64,
6070        );
6071        let __s_b = self.gpu.stream();
6072        let mut b = __s_b.launch_builder(&f);
6073        b.arg(table)
6074            .arg(sel)
6075            .arg(w)
6076            .arg(act)
6077            .arg(dst)
6078            .arg(&inf)
6079            .arg(&outf)
6080            .arg(&nu)
6081            .arg(&ne)
6082            .arg(&qt)
6083            .arg(&rbv);
6084        unsafe {
6085            b.launch(cfg)?;
6086        }
6087        Ok(())
6088    }
6089
6090    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6091    pub fn axpy_into(
6092        &self,
6093        src: &CudaSlice<f32>,
6094        alpha: f32,
6095        dst: &mut cudarc::driver::CudaViewMut<f32>,
6096        n: usize,
6097    ) -> Result<(), Box<dyn std::error::Error>> {
6098        let f = self.func("axpy_f32");
6099        let cfg = LaunchConfig::for_num_elems(n as u32);
6100        let (a, ni) = (alpha, n as i32);
6101        let __s_b = self.gpu.stream();
6102        let mut b = __s_b.launch_builder(&f);
6103        b.arg(src).arg(dst).arg(&a).arg(&ni);
6104        unsafe {
6105            b.launch(cfg)?;
6106        }
6107        Ok(())
6108    }
6109
6110    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6111    pub fn add_scaled_rows(
6112        &self,
6113        src: &CudaSlice<f32>,
6114        scale: &CudaSlice<f32>,
6115        dst: &mut CudaSlice<f32>,
6116        ncols: usize,
6117        nrows: usize,
6118    ) -> Result<(), Box<dyn std::error::Error>> {
6119        let f = self.func("add_scaled_rows_f32");
6120        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6121        let (nc, nr) = (ncols as i32, nrows as i32);
6122        let __s_b = self.gpu.stream();
6123        let mut b = __s_b.launch_builder(&f);
6124        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6125        unsafe {
6126            b.launch(cfg)?;
6127        }
6128        Ok(())
6129    }
6130
6131    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6132
6133    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6134    pub fn gather_rows(
6135        &self,
6136        src: &CudaSlice<f32>,
6137        idx: &CudaSlice<i32>,
6138        dst: &mut CudaSlice<f32>,
6139        ncols: usize,
6140        m_e: usize,
6141    ) -> Result<(), Box<dyn std::error::Error>> {
6142        let f = self.func("gather_rows_f32");
6143        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6144        let (nc, me) = (ncols as i32, m_e as i32);
6145        let __s_b = self.gpu.stream();
6146        let mut b = __s_b.launch_builder(&f);
6147        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6148        unsafe {
6149            b.launch(cfg)?;
6150        }
6151        Ok(())
6152    }
6153
6154    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6155    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6156    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6157    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6158    pub fn scatter_slot(
6159        &self,
6160        src: &CudaSlice<f32>,
6161        tok_idx: &CudaSlice<i32>,
6162        slot_idx: &CudaSlice<i32>,
6163        weight: &CudaSlice<f32>,
6164        dst: &mut CudaSlice<f32>,
6165        wbuf: &mut CudaSlice<f32>,
6166        ncols: usize,
6167        n_used: usize,
6168        m_e: usize,
6169    ) -> Result<(), Box<dyn std::error::Error>> {
6170        let f = self.func("scatter_add_slot_f32");
6171        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6172        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6173        let __s_b = self.gpu.stream();
6174        let mut b = __s_b.launch_builder(&f);
6175        b.arg(src)
6176            .arg(tok_idx)
6177            .arg(slot_idx)
6178            .arg(weight)
6179            .arg(dst)
6180            .arg(wbuf)
6181            .arg(&nc)
6182            .arg(&nu)
6183            .arg(&me);
6184        unsafe {
6185            b.launch(cfg)?;
6186        }
6187        Ok(())
6188    }
6189
6190    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6191    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6192    /// Uses FMA for bit-identity with the sequential axpy path.
6193    pub fn reduce_slots(
6194        &self,
6195        slots: &CudaSlice<f32>,
6196        wbuf: &CudaSlice<f32>,
6197        dst: &mut CudaSlice<f32>,
6198        ncols: usize,
6199        n_used: usize,
6200        t: usize,
6201    ) -> Result<(), Box<dyn std::error::Error>> {
6202        let f = self.func("reduce_slots_f32");
6203        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6204        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6205        let __s_b = self.gpu.stream();
6206        let mut b = __s_b.launch_builder(&f);
6207        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6208        unsafe {
6209            b.launch(cfg)?;
6210        }
6211        Ok(())
6212    }
6213
6214    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6215    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6216    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6217    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6218    /// GPU time, ~half of it redundant re-quantization of the same row.
6219    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6220    pub fn quantize_q8_1_view(
6221        &self,
6222        x: &cudarc::driver::CudaView<f32>,
6223        m: usize,
6224        in_f: usize,
6225    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6226        let f = self.func("quantize_q8_1");
6227        let nblk = in_f / 32;
6228        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6229        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6230        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6231        let (inf, mi) = (in_f as i32, m as i32);
6232        let __s_b = self.gpu.stream();
6233        let mut b = __s_b.launch_builder(&f);
6234        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6235        unsafe {
6236            b.launch(cfg)?;
6237        }
6238        Ok((q, d))
6239    }
6240
6241    pub fn quantize_q8_1(
6242        &self,
6243        x: &CudaSlice<f32>,
6244        m: usize,
6245        in_f: usize,
6246    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6247        let nblk = in_f / 32;
6248        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6249        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6250        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6251        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6252        let (inf, mi) = (in_f as i32, m as i32);
6253        if Self::pdl_on() && Self::pdl_wb_on() {
6254            {
6255                use cudarc::driver::{DevicePtr, DevicePtrMut};
6256                let s = &self.gpu.stream();
6257                let (px, _g0) = x.device_ptr(s);
6258                let (pq, _g1) = q.device_ptr_mut(s);
6259                let (pd, _g2) = d.device_ptr_mut(s);
6260                let mut ps = [
6261                    &px as *const _ as *mut std::ffi::c_void,
6262                    &pq as *const _ as *mut _,
6263                    &pd as *const _ as *mut _,
6264                    &inf as *const _ as *mut _,
6265                    &mi as *const _ as *mut _,
6266                ];
6267                unsafe {
6268                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6269                }
6270            }
6271            return Ok((q, d));
6272        }
6273        let f = self.func("quantize_q8_1");
6274        let __s_b = self.gpu.stream();
6275        let mut b = __s_b.launch_builder(&f);
6276        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6277        unsafe {
6278            b.launch(cfg)?;
6279        }
6280        Ok((q, d))
6281    }
6282
6283    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6284    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6285    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6286    pub fn quantize_fp4_act(
6287        &self,
6288        x: &CudaSlice<f32>,
6289        m: usize,
6290        in_f: usize,
6291    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6292        let f = self.func("quantize_fp4_act");
6293        let nb16 = in_f / 16;
6294        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6295        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6296        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6297        let (inf, mi) = (in_f as i32, m as i32);
6298        let __s_b = self.gpu.stream();
6299        let mut b = __s_b.launch_builder(&f);
6300        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6301        unsafe {
6302            b.launch(cfg)?;
6303        }
6304        Ok((aq4, ad4))
6305    }
6306
6307    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6308    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6309    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6310    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6311    pub fn qmatvec_gemm_nvfp4_fp4(
6312        &self,
6313        bytes: &CudaSlice<u8>,
6314        x: &CudaSlice<f32>,
6315        m: usize,
6316        in_f: usize,
6317        out_f: usize,
6318        row_bytes: usize,
6319        scale: f32,
6320    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6321        assert!(
6322            in_f % 64 == 0,
6323            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6324        );
6325        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6326        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6327        if scale != 1.0 {
6328            self.scale_inplace(&mut y, scale, m * out_f)?;
6329        }
6330        Ok(y)
6331    }
6332
6333    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6334    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6335    fn fp4_gemm_launch(
6336        &self,
6337        bytes: &CudaSlice<u8>,
6338        aq4: &CudaSlice<u32>,
6339        ad4: &CudaSlice<u8>,
6340        m: usize,
6341        in_f: usize,
6342        out_f: usize,
6343        row_bytes: usize,
6344    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6345        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6346        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6347        const BM: u32 = 64;
6348        const BN: u32 = 256;
6349        let cfg = LaunchConfig {
6350            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6351            block_dim: (32, 4, 1),
6352            shared_mem_bytes: 0,
6353        };
6354        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6355        let __s_b = self.gpu.stream();
6356        let mut b = __s_b.launch_builder(&f);
6357        b.arg(bytes)
6358            .arg(aq4)
6359            .arg(ad4)
6360            .arg(&mut y)
6361            .arg(&inf)
6362            .arg(&outf)
6363            .arg(&mi)
6364            .arg(&rb);
6365        unsafe {
6366            b.launch(cfg)?;
6367        }
6368        Ok(y)
6369    }
6370
6371    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6372    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6373        &self,
6374        bytes: &CudaSlice<u8>,
6375        x: &CudaSlice<f32>,
6376        m: usize,
6377        in_f: usize,
6378        out_f: usize,
6379        row_bytes: usize,
6380    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6381        assert!(
6382            in_f % 64 == 0,
6383            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6384        );
6385        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6386        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6387    }
6388
6389    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6390    pub fn qmatvec_q8_0_fast(
6391        &self,
6392        w: &CudaSlice<u8>,
6393        x: &CudaSlice<f32>,
6394        m: usize,
6395        in_f: usize,
6396        out_f: usize,
6397        row_bytes: usize,
6398    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6399        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6400        let f = self.func("qmatvec_q8_0_dp4a");
6401        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6402        let cfg = LaunchConfig {
6403            grid_dim: (out_f as u32, m as u32, 1),
6404            block_dim: (128, 1, 1),
6405            shared_mem_bytes: 0,
6406        };
6407        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6408        let __s_b = self.gpu.stream();
6409        let mut b = __s_b.launch_builder(&f);
6410        b.arg(w)
6411            .arg(&aq)
6412            .arg(&ad)
6413            .arg(&mut y)
6414            .arg(&inf)
6415            .arg(&outf)
6416            .arg(&mi)
6417            .arg(&rb);
6418        unsafe {
6419            b.launch(cfg)?;
6420        }
6421        Ok(y)
6422    }
6423
6424    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6425    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6426    pub fn qmatvec_q4_K_fast(
6427        &self,
6428        w: &CudaSlice<u8>,
6429        x: &CudaSlice<f32>,
6430        m: usize,
6431        in_f: usize,
6432        out_f: usize,
6433        row_bytes: usize,
6434    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6435        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6436        let f = self.func("qmatvec_q4_K_dp4a");
6437        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6438        let cfg = LaunchConfig {
6439            grid_dim: (out_f as u32, m as u32, 1),
6440            block_dim: (128, 1, 1),
6441            shared_mem_bytes: 0,
6442        };
6443        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6444        let __s_b = self.gpu.stream();
6445        let mut b = __s_b.launch_builder(&f);
6446        b.arg(w)
6447            .arg(&aq)
6448            .arg(&ad)
6449            .arg(&mut y)
6450            .arg(&inf)
6451            .arg(&outf)
6452            .arg(&mi)
6453            .arg(&rb);
6454        unsafe {
6455            b.launch(cfg)?;
6456        }
6457        Ok(y)
6458    }
6459
6460    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6461    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6462    pub fn qmatvec_q6_K_fast(
6463        &self,
6464        w: &CudaSlice<u8>,
6465        x: &CudaSlice<f32>,
6466        m: usize,
6467        in_f: usize,
6468        out_f: usize,
6469        row_bytes: usize,
6470    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6471        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6472        let f = self.func("qmatvec_q6_K_dp4a");
6473        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6474        let cfg = LaunchConfig {
6475            grid_dim: (out_f as u32, m as u32, 1),
6476            block_dim: (128, 1, 1),
6477            shared_mem_bytes: 0,
6478        };
6479        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6480        let __s_b = self.gpu.stream();
6481        let mut b = __s_b.launch_builder(&f);
6482        b.arg(w)
6483            .arg(&aq)
6484            .arg(&ad)
6485            .arg(&mut y)
6486            .arg(&inf)
6487            .arg(&outf)
6488            .arg(&mi)
6489            .arg(&rb);
6490        unsafe {
6491            b.launch(cfg)?;
6492        }
6493        Ok(y)
6494    }
6495
6496    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6497    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6498    pub fn qmatvec_q5_K_fast(
6499        &self,
6500        w: &CudaSlice<u8>,
6501        x: &CudaSlice<f32>,
6502        m: usize,
6503        in_f: usize,
6504        out_f: usize,
6505        row_bytes: usize,
6506    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6507        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6508    }
6509    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6510    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6511    pub fn qmatvec_q3_K_fast(
6512        &self,
6513        w: &CudaSlice<u8>,
6514        x: &CudaSlice<f32>,
6515        m: usize,
6516        in_f: usize,
6517        out_f: usize,
6518        row_bytes: usize,
6519    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6520        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6521    }
6522    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6523    pub fn qmatvec_nvfp4_fast_rp(
6524        &self,
6525        w: &CudaSlice<u8>,
6526        x: &CudaSlice<f32>,
6527        m: usize,
6528        in_f: usize,
6529        out_f: usize,
6530        row_bytes: usize,
6531    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6532        assert!(
6533            in_f % 64 == 0,
6534            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6535        );
6536        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6537    }
6538    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6539    pub fn qmatvec_nvfp4_fast(
6540        &self,
6541        w: &CudaSlice<u8>,
6542        x: &CudaSlice<f32>,
6543        m: usize,
6544        in_f: usize,
6545        out_f: usize,
6546        row_bytes: usize,
6547    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6548        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6549        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6550        assert!(
6551            in_f % 64 == 0,
6552            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6553        );
6554        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6555    }
6556    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6557    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6558    pub fn qmatvec_iq4_XS_fast(
6559        &self,
6560        w: &CudaSlice<u8>,
6561        x: &CudaSlice<f32>,
6562        m: usize,
6563        in_f: usize,
6564        out_f: usize,
6565        row_bytes: usize,
6566    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6567        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6568    }
6569
6570    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6571    fn qmatvec_dp4a_named(
6572        &self,
6573        name: &str,
6574        w: &CudaSlice<u8>,
6575        x: &CudaSlice<f32>,
6576        m: usize,
6577        in_f: usize,
6578        out_f: usize,
6579        row_bytes: usize,
6580    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6581        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6582        let f = self.func(name);
6583        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6584        let cfg = LaunchConfig {
6585            grid_dim: (out_f as u32, m as u32, 1),
6586            block_dim: (128, 1, 1),
6587            shared_mem_bytes: 0,
6588        };
6589        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6590        let __s_b = self.gpu.stream();
6591        let mut b = __s_b.launch_builder(&f);
6592        b.arg(w)
6593            .arg(&aq)
6594            .arg(&ad)
6595            .arg(&mut y)
6596            .arg(&inf)
6597            .arg(&outf)
6598            .arg(&mi)
6599            .arg(&rb);
6600        unsafe {
6601            b.launch(cfg)?;
6602        }
6603        Ok(y)
6604    }
6605
6606    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6607        Ok(self.gpu.stream().clone_htod(v)?)
6608    }
6609    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6610        Ok(self.gpu.stream().clone_htod(v)?)
6611    }
6612    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6613    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6614        Ok(self.gpu.stream().clone_htod(v)?)
6615    }
6616    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6617        Ok(self.gpu.stream().clone_htod(v)?)
6618    }
6619    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6620    pub fn dtoh_view(
6621        &self,
6622        d: &cudarc::driver::CudaView<f32>,
6623    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6624        let v = self.gpu.stream().clone_dtoh(d)?;
6625        self.gpu.stream().synchronize()?;
6626        Ok(v)
6627    }
6628    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6629        let v = self.gpu.stream().clone_dtoh(d)?;
6630        self.gpu.stream().synchronize()?;
6631        Ok(v)
6632    }
6633    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6634    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6635    /// issuing them together avoids a second stream synchronization in every trunk layer.
6636    pub fn dtoh_pair(
6637        &self,
6638        a: &CudaSlice<f32>,
6639        b: &CudaSlice<f32>,
6640    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6641        let av = self.gpu.stream().clone_dtoh(a)?;
6642        let bv = self.gpu.stream().clone_dtoh(b)?;
6643        self.gpu.stream().synchronize()?;
6644        Ok((av, bv))
6645    }
6646    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6647    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6648        let v = self.gpu.stream().clone_dtoh(d)?;
6649        self.gpu.stream().synchronize()?;
6650        Ok(v)
6651    }
6652    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6653    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6654        let v = self.gpu.stream().clone_dtoh(d)?;
6655        self.gpu.stream().synchronize()?;
6656        Ok(v)
6657    }
6658    pub fn dtoh_u8_view(
6659        &self,
6660        d: &cudarc::driver::CudaView<u8>,
6661    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6662        let v = self.gpu.stream().clone_dtoh(d)?;
6663        self.gpu.stream().synchronize()?;
6664        Ok(v)
6665    }
6666    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6667        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6668        self.keep_if_capturing(&s);
6669        Ok(s)
6670    }
6671
6672    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6673    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6674    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6675    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6676    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6677    /// back (or kept resident for graph replay). Returns the device token buffer.
6678    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6679    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6680    pub fn prob_of_token_device(
6681        &self,
6682        logits: &CudaSlice<f32>,
6683        tok: &CudaSlice<u32>,
6684        n_vocab: usize,
6685    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6686        let nb = ARGMAX_NB;
6687        let mut part = self.alloc_uninit::<f32>(nb)?;
6688        let mut p = self.alloc_uninit::<f32>(1)?;
6689        let f1 = self.func("prob_of_token_partial_f32");
6690        let cfg1 = LaunchConfig {
6691            grid_dim: (nb as u32, 1, 1),
6692            block_dim: (256, 1, 1),
6693            shared_mem_bytes: 0,
6694        };
6695        let nv = n_vocab as i32;
6696        let __s_b1 = self.gpu.stream();
6697        let mut b1 = __s_b1.launch_builder(&f1);
6698        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6699        unsafe {
6700            b1.launch(cfg1)?;
6701        }
6702        let f2 = self.func("prob_of_token_final_f32");
6703        let cfg2 = LaunchConfig {
6704            grid_dim: (1, 1, 1),
6705            block_dim: (256, 1, 1),
6706            shared_mem_bytes: 0,
6707        };
6708        let nbi = nb as i32;
6709        let __s_b2 = self.gpu.stream();
6710        let mut b2 = __s_b2.launch_builder(&f2);
6711        b2.arg(&part).arg(&mut p).arg(&nbi);
6712        unsafe {
6713            b2.launch(cfg2)?;
6714        }
6715        Ok(p)
6716    }
6717
6718    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6719    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6720    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6721    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6722    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6723    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6724    pub fn prob_of_token_device_col(
6725        &self,
6726        logits: &CudaSlice<f32>,
6727        tok_all: &CudaSlice<u32>,
6728        tok_idx: usize,
6729        p_out: &mut CudaSlice<f32>,
6730        p_idx: usize,
6731        n_vocab: usize,
6732    ) -> Result<(), Box<dyn std::error::Error>> {
6733        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6734        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6735        let nb = ARGMAX_NB;
6736        let mut part = self.alloc_uninit::<f32>(nb)?;
6737        let f1 = self.func("prob_of_token_partial_f32");
6738        let cfg1 = LaunchConfig {
6739            grid_dim: (nb as u32, 1, 1),
6740            block_dim: (256, 1, 1),
6741            shared_mem_bytes: 0,
6742        };
6743        let nv = n_vocab as i32;
6744        let __s_b1 = self.gpu.stream();
6745        let mut b1 = __s_b1.launch_builder(&f1);
6746        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6747        unsafe {
6748            b1.launch(cfg1)?;
6749        }
6750        let f2 = self.func("prob_of_token_final_f32");
6751        let cfg2 = LaunchConfig {
6752            grid_dim: (1, 1, 1),
6753            block_dim: (256, 1, 1),
6754            shared_mem_bytes: 0,
6755        };
6756        let nbi = nb as i32;
6757        let __s_b2 = self.gpu.stream();
6758        let mut b2 = __s_b2.launch_builder(&f2);
6759        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6760        unsafe {
6761            b2.launch(cfg2)?;
6762        }
6763        Ok(())
6764    }
6765
6766    pub fn prob_of_token_device_into(
6767        &self,
6768        logits: &CudaSlice<f32>,
6769        tok: &CudaSlice<u32>,
6770        p_out: &mut CudaSlice<f32>,
6771        n_vocab: usize,
6772    ) -> Result<(), Box<dyn std::error::Error>> {
6773        let nb = ARGMAX_NB;
6774        let mut part = self.alloc_uninit::<f32>(nb)?;
6775        let f1 = self.func("prob_of_token_partial_f32");
6776        let cfg1 = LaunchConfig {
6777            grid_dim: (nb as u32, 1, 1),
6778            block_dim: (256, 1, 1),
6779            shared_mem_bytes: 0,
6780        };
6781        let nv = n_vocab as i32;
6782        let __s_b1 = self.gpu.stream();
6783        let mut b1 = __s_b1.launch_builder(&f1);
6784        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6785        unsafe {
6786            b1.launch(cfg1)?;
6787        }
6788        let f2 = self.func("prob_of_token_final_f32");
6789        let cfg2 = LaunchConfig {
6790            grid_dim: (1, 1, 1),
6791            block_dim: (256, 1, 1),
6792            shared_mem_bytes: 0,
6793        };
6794        let nbi = nb as i32;
6795        let __s_b2 = self.gpu.stream();
6796        let mut b2 = __s_b2.launch_builder(&f2);
6797        b2.arg(&part).arg(p_out).arg(&nbi);
6798        unsafe {
6799            b2.launch(cfg2)?;
6800        }
6801        Ok(())
6802    }
6803
6804    pub fn argmax_token_device(
6805        &self,
6806        logits: &CudaSlice<f32>,
6807        n_vocab: usize,
6808    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6809        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6810        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6811        Ok(tok)
6812    }
6813    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6814    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6815    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6816    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6817    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6818    /// captured passes bake fixed addresses.
6819    pub fn argmax_token_device_into(
6820        &self,
6821        logits: &CudaSlice<f32>,
6822        tok: &mut CudaSlice<u32>,
6823        n_vocab: usize,
6824    ) -> Result<(), Box<dyn std::error::Error>> {
6825        let nb = ARGMAX_NB;
6826        let f1 = self.func("argmax_partial_f32");
6827        let f2 = self.func("argmax_final_f32");
6828        let mut guard = self.argmax_partials.lock().unwrap();
6829        if guard.is_none() {
6830            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6831            // buffers carry no cudarc events (illegal inside capture).
6832            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6833            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6834            *guard = Some((pv, pi));
6835        }
6836        let (part_v, part_i) = guard.as_mut().unwrap();
6837        let nv = n_vocab as i32;
6838        let nbi = nb as i32;
6839        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6840        let cfg1 = LaunchConfig {
6841            grid_dim: (nb as u32, 1, 1),
6842            block_dim: (256, 1, 1),
6843            shared_mem_bytes: 0,
6844        };
6845        let __s_b1 = self.gpu.stream();
6846        let mut b1 = __s_b1.launch_builder(&f1);
6847        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6848        unsafe {
6849            b1.launch(cfg1)?;
6850        }
6851        // pass 2: one block reduces NB partials -> token_out[0].
6852        let cfg2 = LaunchConfig {
6853            grid_dim: (1, 1, 1),
6854            block_dim: (256, 1, 1),
6855            shared_mem_bytes: 0,
6856        };
6857        let __s_b2 = self.gpu.stream();
6858        let mut b2 = __s_b2.launch_builder(&f2);
6859        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6860        unsafe {
6861            b2.launch(cfg2)?;
6862        }
6863        Ok(())
6864    }
6865    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6866    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6867    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6868    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6869    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6870    pub fn argmax_token_device_col(
6871        &self,
6872        logits: &CudaSlice<f32>,
6873        col: usize,
6874        n_vocab: usize,
6875        toks: &mut CudaSlice<u32>,
6876        out_idx: usize,
6877    ) -> Result<(), Box<dyn std::error::Error>> {
6878        let nb = ARGMAX_NB;
6879        let f1 = self.func("argmax_partial_f32");
6880        let f2 = self.func("argmax_final_f32");
6881        let mut guard = self.argmax_partials.lock().unwrap();
6882        if guard.is_none() {
6883            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6884            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6885            *guard = Some((pv, pi));
6886        }
6887        let (part_v, part_i) = guard.as_mut().unwrap();
6888        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
6889        let nv = n_vocab as i32;
6890        let nbi = nb as i32;
6891        let cfg1 = LaunchConfig {
6892            grid_dim: (nb as u32, 1, 1),
6893            block_dim: (256, 1, 1),
6894            shared_mem_bytes: 0,
6895        };
6896        let __s_b1 = self.gpu.stream();
6897        let mut b1 = __s_b1.launch_builder(&f1);
6898        b1.arg(&col_view)
6899            .arg(&mut *part_v)
6900            .arg(&mut *part_i)
6901            .arg(&nv);
6902        unsafe {
6903            b1.launch(cfg1)?;
6904        }
6905        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
6906        let cfg2 = LaunchConfig {
6907            grid_dim: (1, 1, 1),
6908            block_dim: (256, 1, 1),
6909            shared_mem_bytes: 0,
6910        };
6911        let __s_b2 = self.gpu.stream();
6912        let mut b2 = __s_b2.launch_builder(&f2);
6913        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
6914        unsafe {
6915            b2.launch(cfg2)?;
6916        }
6917        Ok(())
6918    }
6919    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
6920    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6921        Ok(self.gpu.stream().clone_htod(v)?)
6922    }
6923    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
6924        let v = self.gpu.stream().clone_dtoh(d)?;
6925        self.gpu.stream().synchronize()?;
6926        Ok(v)
6927    }
6928    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
6929    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
6930    /// contents change every step, the address must not, so a captured graph can read it).
6931    pub fn htod_u32_into(
6932        &self,
6933        dst: &mut CudaSlice<u32>,
6934        src: &[u32],
6935    ) -> Result<(), Box<dyn std::error::Error>> {
6936        let mut view = dst.slice_mut(0..src.len());
6937        self.gpu.stream().memcpy_htod(src, &mut view)?;
6938        Ok(())
6939    }
6940
6941    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
6942    /// table without changing the device address its reconcile kernel consumes.
6943    pub fn htod_i32_into(
6944        &self,
6945        dst: &mut CudaSlice<i32>,
6946        src: &[i32],
6947    ) -> Result<(), Box<dyn std::error::Error>> {
6948        let mut view = dst.slice_mut(0..src.len());
6949        self.gpu.stream().memcpy_htod(src, &mut view)?;
6950        Ok(())
6951    }
6952
6953    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6954        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
6955        self.keep_if_capturing(&s);
6956        Ok(s)
6957    }
6958    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
6959    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
6960    pub fn embed_gather_device_into(
6961        &self,
6962        embd: &CudaSlice<u8>,
6963        token_d: &CudaSlice<u32>,
6964        x_out: &mut CudaSlice<f32>,
6965        n_embd: usize,
6966        qtype: i32,
6967        row_bytes: usize,
6968    ) -> Result<(), Box<dyn std::error::Error>> {
6969        let f = self.func("embed_gather_u32");
6970        let cfg = LaunchConfig {
6971            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
6972            block_dim: (256, 1, 1),
6973            shared_mem_bytes: 0,
6974        };
6975        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
6976        let __s_b = self.gpu.stream();
6977        let mut b = __s_b.launch_builder(&f);
6978        b.arg(embd)
6979            .arg(token_d)
6980            .arg(x_out)
6981            .arg(&ne)
6982            .arg(&qt)
6983            .arg(&rb);
6984        unsafe {
6985            b.launch(cfg)?;
6986        }
6987        Ok(())
6988    }
6989    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
6990    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
6991        let v = self.gpu.stream().clone_dtoh(d)?;
6992        self.gpu.stream().synchronize()?;
6993        Ok(v[0])
6994    }
6995    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
6996    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
6997    /// the counter value after the throwaway capture warmups corrupt it.
6998    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
6999    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
7000    /// copy (fine at stream-idle boundaries, poison mid-round).
7001    pub fn i32_set_k(
7002        &self,
7003        dst: &mut CudaSlice<i32>,
7004        v: i32,
7005    ) -> Result<(), Box<dyn std::error::Error>> {
7006        let f = self.func("i32_set_k");
7007        let cfg = LaunchConfig {
7008            grid_dim: (1, 1, 1),
7009            block_dim: (1, 1, 1),
7010            shared_mem_bytes: 0,
7011        };
7012        let idx = 0i32;
7013        let __s_b = self.gpu.stream();
7014        let mut b = __s_b.launch_builder(&f);
7015        b.arg(dst).arg(&v).arg(&idx);
7016        unsafe {
7017            b.launch(cfg)?;
7018        }
7019        Ok(())
7020    }
7021
7022    pub fn set_i32_one(
7023        &self,
7024        d: &mut CudaSlice<i32>,
7025        v: i32,
7026    ) -> Result<(), Box<dyn std::error::Error>> {
7027        self.gpu.stream().memcpy_htod(&[v], d)?;
7028        Ok(())
7029    }
7030    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
7031    /// during priming / capture-state restore.
7032    pub fn set_u32_one(
7033        &self,
7034        d: &mut CudaSlice<u32>,
7035        v: u32,
7036    ) -> Result<(), Box<dyn std::error::Error>> {
7037        self.gpu.stream().memcpy_htod(&[v], d)?;
7038        Ok(())
7039    }
7040    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
7041    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
7042        let v = self.gpu.stream().clone_dtoh(d)?;
7043        self.gpu.stream().synchronize()?;
7044        Ok(v[0])
7045    }
7046    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
7047    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
7048        Ok(self.gpu.stream().clone_htod(bytes)?)
7049    }
7050    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7051    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7052    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7053    pub fn embed_gather_device(
7054        &self,
7055        embd: &CudaSlice<u8>,
7056        token_d: &CudaSlice<u32>,
7057        n_embd: usize,
7058        qtype: i32,
7059        row_bytes: usize,
7060    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7061        let f = self.func("embed_gather_u32");
7062        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7063        let cfg = LaunchConfig {
7064            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7065            block_dim: (256, 1, 1),
7066            shared_mem_bytes: 0,
7067        };
7068        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7069        let __s_b = self.gpu.stream();
7070        let mut b = __s_b.launch_builder(&f);
7071        b.arg(embd)
7072            .arg(token_d)
7073            .arg(&mut x)
7074            .arg(&ne)
7075            .arg(&qt)
7076            .arg(&rb);
7077        unsafe {
7078            b.launch(cfg)?;
7079        }
7080        Ok(x)
7081    }
7082
7083    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7084    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7085    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7086    pub fn embed_gather_device_t(
7087        &self,
7088        embd: &CudaSlice<u8>,
7089        tokens: &[u32],
7090        n_embd: usize,
7091        qtype: i32,
7092        row_bytes: usize,
7093    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7094        let t = tokens.len();
7095        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7096        let f = self.func("embed_gather_u32_t");
7097        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7098        let cfg = LaunchConfig {
7099            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7100            block_dim: (256, 1, 1),
7101            shared_mem_bytes: 0,
7102        };
7103        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7104        let __s_b = self.gpu.stream();
7105        let mut b = __s_b.launch_builder(&f);
7106        b.arg(embd)
7107            .arg(&tok_d)
7108            .arg(&mut x)
7109            .arg(&ne)
7110            .arg(&qt)
7111            .arg(&rb)
7112            .arg(&ti);
7113        unsafe {
7114            b.launch(cfg)?;
7115        }
7116        Ok(x)
7117    }
7118
7119    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7120    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7121    /// as embed_gather_device_t — bit-identical rows.
7122    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7123    pub fn embed_gather_device_tv(
7124        &self,
7125        embd: &CudaSlice<u8>,
7126        tok_v: &cudarc::driver::CudaView<u32>,
7127        t: usize,
7128        n_embd: usize,
7129        qtype: i32,
7130        row_bytes: usize,
7131    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7132        let f = self.func("embed_gather_u32_t");
7133        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7134        let cfg = LaunchConfig {
7135            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7136            block_dim: (256, 1, 1),
7137            shared_mem_bytes: 0,
7138        };
7139        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7140        let __s_b = self.gpu.stream();
7141        let mut b = __s_b.launch_builder(&f);
7142        b.arg(embd)
7143            .arg(tok_v)
7144            .arg(&mut x)
7145            .arg(&ne)
7146            .arg(&qt)
7147            .arg(&rb)
7148            .arg(&ti);
7149        unsafe {
7150            b.launch(cfg)?;
7151        }
7152        Ok(x)
7153    }
7154
7155    pub fn embed_gather_device_td(
7156        &self,
7157        embd: &CudaSlice<u8>,
7158        tok_d: &CudaSlice<u32>,
7159        t: usize,
7160        n_embd: usize,
7161        qtype: i32,
7162        row_bytes: usize,
7163    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7164        let f = self.func("embed_gather_u32_t");
7165        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7166        let cfg = LaunchConfig {
7167            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7168            block_dim: (256, 1, 1),
7169            shared_mem_bytes: 0,
7170        };
7171        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7172        let __s_b = self.gpu.stream();
7173        let mut b = __s_b.launch_builder(&f);
7174        b.arg(embd)
7175            .arg(tok_d)
7176            .arg(&mut x)
7177            .arg(&ne)
7178            .arg(&qt)
7179            .arg(&rb)
7180            .arg(&ti);
7181        unsafe {
7182            b.launch(cfg)?;
7183        }
7184        Ok(x)
7185    }
7186
7187    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7188    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7189    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7190    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7191    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7192    #[inline]
7193    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7194    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7195        if self
7196            .capture_keep_on
7197            .load(std::sync::atomic::Ordering::Relaxed)
7198        {
7199            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7200        }
7201    }
7202
7203    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7204        &self,
7205        n: usize,
7206    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7207        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7208        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7209        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7210        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7211        {
7212            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7213            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7214                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7215                use cudarc::driver::DevicePtrMut;
7216                let n_bytes = s.len() * std::mem::size_of::<T>();
7217                let stream = self.gpu.stream();
7218                let (p_, _g) = s.device_ptr_mut(&stream);
7219                unsafe {
7220                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7221                        .result()?;
7222                }
7223            }
7224        }
7225        self.keep_if_capturing(&s);
7226        Ok(s)
7227    }
7228
7229    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7230    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7231    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7232    /// consumers alloc through this (m=1 decode arms).
7233    pub fn uninit_q8_pair(
7234        &self,
7235        n: usize,
7236    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7237        Ok((
7238            self.alloc_uninit::<i8>(n)?,
7239            self.alloc_uninit::<f32>(n / 32)?,
7240        ))
7241    }
7242
7243    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7244        self.alloc_uninit::<f32>(n)
7245    }
7246
7247    /// i8 uninitialized scratch (same contract as `uninit`).
7248    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7249        self.alloc_uninit::<i8>(n)
7250    }
7251
7252    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7253    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7254    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7255    #[allow(clippy::too_many_arguments)]
7256    pub fn rms_norm3(
7257        &self,
7258        x: &CudaSlice<f32>,
7259        w0: &CudaSlice<f32>,
7260        w1: &CudaSlice<f32>,
7261        w2: &CudaSlice<f32>,
7262        d0: &mut CudaSlice<f32>,
7263        d1: &mut CudaSlice<f32>,
7264        d2: &mut CudaSlice<f32>,
7265        ncols: usize,
7266        nrows: usize,
7267        eps: f32,
7268    ) -> Result<(), Box<dyn std::error::Error>> {
7269        let f = self.func("rms_norm3_f32");
7270        let cfg = LaunchConfig {
7271            grid_dim: (nrows as u32, 1, 1),
7272            block_dim: (rms_block(), 1, 1),
7273            shared_mem_bytes: 0,
7274        };
7275        let (nc, e) = (ncols as i32, eps);
7276        let __s_b = self.gpu.stream();
7277        let mut b = __s_b.launch_builder(&f);
7278        b.arg(x)
7279            .arg(w0)
7280            .arg(w1)
7281            .arg(w2)
7282            .arg(d0)
7283            .arg(d1)
7284            .arg(d2)
7285            .arg(&nc)
7286            .arg(&e);
7287        unsafe {
7288            b.launch(cfg)?;
7289        }
7290        Ok(())
7291    }
7292
7293    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7294    #[allow(clippy::too_many_arguments)]
7295    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7296    /// piggybacks on the same conditions.
7297    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7298        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7299        *WARP_ON.get_or_init(|| {
7300            std::env::var("MEMRA_QKVNORM_W")
7301                .map(|v| v != "0")
7302                .unwrap_or(true)
7303        }) && ncols % 4 == 0
7304            && rows >= 64
7305    }
7306
7307    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7308    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7309    #[allow(clippy::too_many_arguments)]
7310    pub fn rms_norm_qkv_w4b(
7311        &self,
7312        q: &CudaSlice<f32>,
7313        k: &CudaSlice<f32>,
7314        v: &CudaSlice<f32>,
7315        wq: &CudaSlice<f32>,
7316        wk: &CudaSlice<f32>,
7317        wv: &CudaSlice<f32>,
7318        dq: &mut CudaSlice<f32>,
7319        dk: &mut CudaSlice<f32>,
7320        dv: &mut CudaSlice<f32>,
7321        dvb: &mut CudaSlice<u8>,
7322        ncols: usize,
7323        rq: usize,
7324        rk: usize,
7325        eps: f32,
7326        vf16: bool,
7327    ) -> Result<(), Box<dyn std::error::Error>> {
7328        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7329        let f = self.func("rms_norm_qkv_w4b_f32");
7330        let rows = (rq + 2 * rk) as u32;
7331        let cfg = LaunchConfig {
7332            grid_dim: (rows.div_ceil(8), 1, 1),
7333            block_dim: (256, 1, 1),
7334            shared_mem_bytes: 0,
7335        };
7336        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7337        let vf = vf16 as i32;
7338        let __s_b = self.gpu.stream();
7339        let mut b = __s_b.launch_builder(&f);
7340        b.arg(q)
7341            .arg(k)
7342            .arg(v)
7343            .arg(wq)
7344            .arg(wk)
7345            .arg(wv)
7346            .arg(dq)
7347            .arg(dk)
7348            .arg(dv)
7349            .arg(&mut *dvb)
7350            .arg(&nc)
7351            .arg(&rqi)
7352            .arg(&rki)
7353            .arg(&rvi)
7354            .arg(&e)
7355            .arg(&vf);
7356        unsafe {
7357            b.launch(cfg)?;
7358        }
7359        Ok(())
7360    }
7361
7362    pub fn rms_norm_qkv(
7363        &self,
7364        q: &CudaSlice<f32>,
7365        k: &CudaSlice<f32>,
7366        v: &CudaSlice<f32>,
7367        wq: &CudaSlice<f32>,
7368        wk: &CudaSlice<f32>,
7369        wv: &CudaSlice<f32>,
7370        dq: &mut CudaSlice<f32>,
7371        dk: &mut CudaSlice<f32>,
7372        dv: &mut CudaSlice<f32>,
7373        ncols: usize,
7374        rq: usize,
7375        rk: usize,
7376        eps: f32,
7377    ) -> Result<(), Box<dyn std::error::Error>> {
7378        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7379        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7380        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7381        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7382        let warp_on = *WARP_ON.get_or_init(|| {
7383            std::env::var("MEMRA_QKVNORM_W")
7384                .map(|v| v != "0")
7385                .unwrap_or(true)
7386        });
7387        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7388        // replay numerics are untouched on every model; only prefill depth takes the new config.
7389        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7390            let f = self.func("rms_norm_qkv_w4_f32");
7391            let rows = (rq + 2 * rk) as u32;
7392            let cfg = LaunchConfig {
7393                grid_dim: (rows.div_ceil(8), 1, 1),
7394                block_dim: (256, 1, 1),
7395                shared_mem_bytes: 0,
7396            };
7397            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7398            let __s_b = self.gpu.stream();
7399            let mut b = __s_b.launch_builder(&f);
7400            b.arg(q)
7401                .arg(k)
7402                .arg(v)
7403                .arg(wq)
7404                .arg(wk)
7405                .arg(wv)
7406                .arg(dq)
7407                .arg(dk)
7408                .arg(dv)
7409                .arg(&nc)
7410                .arg(&rqi)
7411                .arg(&rki)
7412                .arg(&rvi)
7413                .arg(&e);
7414            unsafe {
7415                b.launch(cfg)?;
7416            }
7417            return Ok(());
7418        }
7419        let f = self.func("rms_norm_qkv_f32");
7420        let grid = (rq + 2 * rk) as u32;
7421        let cfg = LaunchConfig {
7422            grid_dim: (grid, 1, 1),
7423            block_dim: (rms_block(), 1, 1),
7424            shared_mem_bytes: 0,
7425        };
7426        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7427        let __s_b = self.gpu.stream();
7428        let mut b = __s_b.launch_builder(&f);
7429        b.arg(q)
7430            .arg(k)
7431            .arg(v)
7432            .arg(wq)
7433            .arg(wk)
7434            .arg(wv)
7435            .arg(dq)
7436            .arg(dk)
7437            .arg(dv)
7438            .arg(&nc)
7439            .arg(&rqi)
7440            .arg(&rki)
7441            .arg(&e);
7442        unsafe {
7443            b.launch(cfg)?;
7444        }
7445        Ok(())
7446    }
7447
7448    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7449    #[allow(clippy::too_many_arguments)]
7450    pub fn rms_norm2x(
7451        &self,
7452        a: &CudaSlice<f32>,
7453        bb: &CudaSlice<f32>,
7454        wa: &CudaSlice<f32>,
7455        wb: &CudaSlice<f32>,
7456        da: &mut CudaSlice<f32>,
7457        db: &mut CudaSlice<f32>,
7458        ncols: usize,
7459        nrows: usize,
7460        eps: f32,
7461    ) -> Result<(), Box<dyn std::error::Error>> {
7462        let f = self.func("rms_norm2x_f32");
7463        let cfg = LaunchConfig {
7464            grid_dim: (2 * nrows as u32, 1, 1),
7465            block_dim: (rms_block(), 1, 1),
7466            shared_mem_bytes: 0,
7467        };
7468        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7469        let __s_b = self.gpu.stream();
7470        let mut b = __s_b.launch_builder(&f);
7471        b.arg(a)
7472            .arg(bb)
7473            .arg(wa)
7474            .arg(wb)
7475            .arg(da)
7476            .arg(db)
7477            .arg(&nc)
7478            .arg(&nr)
7479            .arg(&e);
7480        unsafe {
7481            b.launch(cfg)?;
7482        }
7483        Ok(())
7484    }
7485
7486    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7487    pub fn softcap(
7488        &self,
7489        y: &mut CudaSlice<f32>,
7490        cap: f32,
7491        n: usize,
7492    ) -> Result<(), Box<dyn std::error::Error>> {
7493        let f = self.func("softcap_f32");
7494        let cfg = LaunchConfig::for_num_elems(n as u32);
7495        let ni = n as i32;
7496        let __s_b = self.gpu.stream();
7497        let mut b = __s_b.launch_builder(&f);
7498        b.arg(y).arg(&cap).arg(&ni);
7499        unsafe {
7500            b.launch(cfg)?;
7501        }
7502        Ok(())
7503    }
7504
7505    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7506    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7507    pub fn mask_ids_rows(
7508        &self,
7509        y: &mut CudaSlice<f32>,
7510        ids: &CudaSlice<i32>,
7511        n_ids: usize,
7512        n_vocab: usize,
7513        t: usize,
7514    ) -> Result<(), Box<dyn std::error::Error>> {
7515        let f = self.func("mask_ids_rows_f32");
7516        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7517        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7518        let __s_b = self.gpu.stream();
7519        let mut b = __s_b.launch_builder(&f);
7520        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7521        unsafe {
7522            b.launch(cfg)?;
7523        }
7524        Ok(())
7525    }
7526
7527    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7528    #[allow(clippy::too_many_arguments)]
7529    pub fn add_scale_rms_norm(
7530        &self,
7531        a: &CudaSlice<f32>,
7532        b_in: &CudaSlice<f32>,
7533        c: f32,
7534        w: &CudaSlice<f32>,
7535        res: &mut CudaSlice<f32>,
7536        dst: &mut CudaSlice<f32>,
7537        ncols: usize,
7538        nrows: usize,
7539        eps: f32,
7540    ) -> Result<(), Box<dyn std::error::Error>> {
7541        let f = self.func("add_scale_rms_norm_f32");
7542        let cfg = LaunchConfig {
7543            grid_dim: (nrows as u32, 1, 1),
7544            block_dim: (rms_block(), 1, 1),
7545            shared_mem_bytes: 0,
7546        };
7547        let (nc, e2) = (ncols as i32, eps);
7548        let __s_b = self.gpu.stream();
7549        let mut b = __s_b.launch_builder(&f);
7550        b.arg(a)
7551            .arg(b_in)
7552            .arg(&c)
7553            .arg(w)
7554            .arg(res)
7555            .arg(dst)
7556            .arg(&nc)
7557            .arg(&e2);
7558        unsafe {
7559            b.launch(cfg)?;
7560        }
7561        Ok(())
7562    }
7563
7564    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7565    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7566    #[allow(clippy::too_many_arguments)]
7567    pub fn add_scale_rms_norm_q8_1(
7568        &self,
7569        a: &CudaSlice<f32>,
7570        b_in: &CudaSlice<f32>,
7571        c: f32,
7572        w: &CudaSlice<f32>,
7573        res: &mut CudaSlice<f32>,
7574        ncols: usize,
7575        nrows: usize,
7576        eps: f32,
7577    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7578        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7579        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7580        let (nc, e2) = (ncols as i32, eps);
7581        if Self::pdl_on() && Self::pdl_wb_on() {
7582            {
7583                use cudarc::driver::{DevicePtr, DevicePtrMut};
7584                let s = &self.gpu.stream();
7585                let (pa, _g0) = a.device_ptr(s);
7586                let (pb, _g1) = b_in.device_ptr(s);
7587                let (pw, _g2) = w.device_ptr(s);
7588                let (pr, _g3) = res.device_ptr_mut(s);
7589                let (pq, _g4) = out_q.device_ptr_mut(s);
7590                let (pd, _g5) = out_d.device_ptr_mut(s);
7591                let mut ps = [
7592                    &pa as *const _ as *mut std::ffi::c_void,
7593                    &pb as *const _ as *mut _,
7594                    &c as *const _ as *mut _,
7595                    &pw as *const _ as *mut _,
7596                    &pr as *const _ as *mut _,
7597                    &pq as *const _ as *mut _,
7598                    &pd as *const _ as *mut _,
7599                    &nc as *const _ as *mut _,
7600                    &e2 as *const _ as *mut _,
7601                ];
7602                unsafe {
7603                    self.launch_pdl(
7604                        "add_scale_rms_norm_q8_1",
7605                        (nrows as u32, 1, 1),
7606                        (rms_block(), 1, 1),
7607                        &mut ps,
7608                    )?;
7609                }
7610            }
7611            return Ok((out_q, out_d));
7612        }
7613        let f = self.func("add_scale_rms_norm_q8_1");
7614        let cfg = LaunchConfig {
7615            grid_dim: (nrows as u32, 1, 1),
7616            block_dim: (rms_block(), 1, 1),
7617            shared_mem_bytes: 0,
7618        };
7619        let __s_b = self.gpu.stream();
7620        let mut b = __s_b.launch_builder(&f);
7621        b.arg(a)
7622            .arg(b_in)
7623            .arg(&c)
7624            .arg(w)
7625            .arg(res)
7626            .arg(&mut out_q)
7627            .arg(&mut out_d)
7628            .arg(&nc)
7629            .arg(&e2);
7630        unsafe {
7631            b.launch(cfg)?;
7632        }
7633        Ok((out_q, out_d))
7634    }
7635
7636    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7637    #[allow(clippy::too_many_arguments)]
7638    pub fn add_scale_rms_norm_q8_1_into(
7639        &self,
7640        a: &CudaSlice<f32>,
7641        b_in: &CudaSlice<f32>,
7642        c: f32,
7643        w: &CudaSlice<f32>,
7644        res: &mut CudaSlice<f32>,
7645        ncols: usize,
7646        nrows: usize,
7647        eps: f32,
7648        out_q: &mut CudaSlice<i8>,
7649        out_d: &mut CudaSlice<f32>,
7650    ) -> Result<(), Box<dyn std::error::Error>> {
7651        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7652        let (nc, e2) = (ncols as i32, eps);
7653        if Self::pdl_on() && Self::pdl_wb_on() {
7654            use cudarc::driver::{DevicePtr, DevicePtrMut};
7655            let s = &self.gpu.stream();
7656            let (pa, _g0) = a.device_ptr(s);
7657            let (pb, _g1) = b_in.device_ptr(s);
7658            let (pw, _g2) = w.device_ptr(s);
7659            let (pr, _g3) = res.device_ptr_mut(s);
7660            let (pq, _g4) = out_q.device_ptr_mut(s);
7661            let (pd, _g5) = out_d.device_ptr_mut(s);
7662            let mut ps = [
7663                &pa as *const _ as *mut std::ffi::c_void,
7664                &pb as *const _ as *mut _,
7665                &c as *const _ as *mut _,
7666                &pw as *const _ as *mut _,
7667                &pr as *const _ as *mut _,
7668                &pq as *const _ as *mut _,
7669                &pd as *const _ as *mut _,
7670                &nc as *const _ as *mut _,
7671                &e2 as *const _ as *mut _,
7672            ];
7673            unsafe {
7674                self.launch_pdl(
7675                    "add_scale_rms_norm_q8_1",
7676                    (nrows as u32, 1, 1),
7677                    (rms_block(), 1, 1),
7678                    &mut ps,
7679                )?;
7680            }
7681            return Ok(());
7682        }
7683        let f = self.func("add_scale_rms_norm_q8_1");
7684        let cfg = LaunchConfig {
7685            grid_dim: (nrows as u32, 1, 1),
7686            block_dim: (rms_block(), 1, 1),
7687            shared_mem_bytes: 0,
7688        };
7689        let __s_b = self.gpu.stream();
7690        let mut b = __s_b.launch_builder(&f);
7691        b.arg(a)
7692            .arg(b_in)
7693            .arg(&c)
7694            .arg(w)
7695            .arg(res)
7696            .arg(&mut *out_q)
7697            .arg(&mut *out_d)
7698            .arg(&nc)
7699            .arg(&e2);
7700        unsafe {
7701            b.launch(cfg)?;
7702        }
7703        Ok(())
7704    }
7705
7706    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7707    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7708    #[allow(clippy::too_many_arguments)]
7709    pub fn rms_pre_add_scale_rms_norm_q8_1(
7710        &self,
7711        a: &CudaSlice<f32>,
7712        wa: &CudaSlice<f32>,
7713        b_in: &CudaSlice<f32>,
7714        c: f32,
7715        w: &CudaSlice<f32>,
7716        res: &mut CudaSlice<f32>,
7717        ncols: usize,
7718        nrows: usize,
7719        eps: f32,
7720    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7721        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7722        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7723        let (nc, e2) = (ncols as i32, eps);
7724        if Self::pdl_on() {
7725            {
7726                use cudarc::driver::{DevicePtr, DevicePtrMut};
7727                let s = &self.gpu.stream();
7728                let (pa, _g0) = a.device_ptr(s);
7729                let (pwa, _g1) = wa.device_ptr(s);
7730                let (pb, _g2) = b_in.device_ptr(s);
7731                let (pw, _g3) = w.device_ptr(s);
7732                let (pr, _g4) = res.device_ptr_mut(s);
7733                let (pq, _g5) = out_q.device_ptr_mut(s);
7734                let (pd, _g6) = out_d.device_ptr_mut(s);
7735                let mut ps = [
7736                    &pa as *const _ as *mut std::ffi::c_void,
7737                    &pwa as *const _ as *mut _,
7738                    &pb as *const _ as *mut _,
7739                    &c as *const _ as *mut _,
7740                    &pw as *const _ as *mut _,
7741                    &pr as *const _ as *mut _,
7742                    &pq as *const _ as *mut _,
7743                    &pd as *const _ as *mut _,
7744                    &nc as *const _ as *mut _,
7745                    &e2 as *const _ as *mut _,
7746                ];
7747                unsafe {
7748                    self.launch_pdl(
7749                        "rms_pre_add_scale_rms_norm_q8_1",
7750                        (nrows as u32, 1, 1),
7751                        (rms_block(), 1, 1),
7752                        &mut ps,
7753                    )?;
7754                }
7755            }
7756            return Ok((out_q, out_d));
7757        }
7758        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7759        let cfg = LaunchConfig {
7760            grid_dim: (nrows as u32, 1, 1),
7761            block_dim: (rms_block(), 1, 1),
7762            shared_mem_bytes: 0,
7763        };
7764        let __s_b = self.gpu.stream();
7765        let mut b = __s_b.launch_builder(&f);
7766        b.arg(a)
7767            .arg(wa)
7768            .arg(b_in)
7769            .arg(&c)
7770            .arg(w)
7771            .arg(res)
7772            .arg(&mut out_q)
7773            .arg(&mut out_d)
7774            .arg(&nc)
7775            .arg(&e2);
7776        unsafe {
7777            b.launch(cfg)?;
7778        }
7779        Ok((out_q, out_d))
7780    }
7781
7782    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7783    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7784    pub fn gelu_tanh_mul_q8_1(
7785        &self,
7786        gate: &CudaSlice<f32>,
7787        up: &cudarc::driver::CudaView<f32>,
7788        act: &mut CudaSlice<f32>,
7789        ncols: usize,
7790        nrows: usize,
7791    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7792        debug_assert!(ncols % 128 == 0);
7793        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7794        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7795        let nc = ncols as i32;
7796        if Self::pdl_on() {
7797            {
7798                use cudarc::driver::{DevicePtr, DevicePtrMut};
7799                let s = &self.gpu.stream();
7800                let (pg, _g0) = gate.device_ptr(s);
7801                let (pu, _g1) = up.device_ptr(s);
7802                let (pact, _g2) = act.device_ptr_mut(s);
7803                let (pq, _g3) = out_q.device_ptr_mut(s);
7804                let (pd, _g4) = out_d.device_ptr_mut(s);
7805                let mut ps = [
7806                    &pg as *const _ as *mut std::ffi::c_void,
7807                    &pu as *const _ as *mut _,
7808                    &pact as *const _ as *mut _,
7809                    &pq as *const _ as *mut _,
7810                    &pd as *const _ as *mut _,
7811                    &nc as *const _ as *mut _,
7812                ];
7813                unsafe {
7814                    self.launch_pdl(
7815                        "gelu_tanh_mul_q8_1",
7816                        (nrows as u32, 1, 1),
7817                        (rms_block(), 1, 1),
7818                        &mut ps,
7819                    )?;
7820                }
7821            }
7822            return Ok((out_q, out_d));
7823        }
7824        let f = self.func("gelu_tanh_mul_q8_1");
7825        let cfg = LaunchConfig {
7826            grid_dim: (nrows as u32, 1, 1),
7827            block_dim: (rms_block(), 1, 1),
7828            shared_mem_bytes: 0,
7829        };
7830        let __s_b = self.gpu.stream();
7831        let mut b = __s_b.launch_builder(&f);
7832        b.arg(gate)
7833            .arg(up)
7834            .arg(act)
7835            .arg(&mut out_q)
7836            .arg(&mut out_d)
7837            .arg(&nc);
7838        unsafe {
7839            b.launch(cfg)?;
7840        }
7841        Ok((out_q, out_d))
7842    }
7843
7844    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7845    #[allow(clippy::too_many_arguments)]
7846    pub fn gelu_tanh_mul_q8_1_into(
7847        &self,
7848        gate: &CudaSlice<f32>,
7849        up: &cudarc::driver::CudaView<f32>,
7850        act: &mut CudaSlice<f32>,
7851        ncols: usize,
7852        nrows: usize,
7853        out_q: &mut CudaSlice<i8>,
7854        out_d: &mut CudaSlice<f32>,
7855    ) -> Result<(), Box<dyn std::error::Error>> {
7856        debug_assert!(ncols % 128 == 0);
7857        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7858        let nc = ncols as i32;
7859        if Self::pdl_on() {
7860            use cudarc::driver::{DevicePtr, DevicePtrMut};
7861            let s = &self.gpu.stream();
7862            let (pg, _g0) = gate.device_ptr(s);
7863            let (pu, _g1) = up.device_ptr(s);
7864            let (pact, _g2) = act.device_ptr_mut(s);
7865            let (pq, _g3) = out_q.device_ptr_mut(s);
7866            let (pd, _g4) = out_d.device_ptr_mut(s);
7867            let mut ps = [
7868                &pg as *const _ as *mut std::ffi::c_void,
7869                &pu as *const _ as *mut _,
7870                &pact as *const _ as *mut _,
7871                &pq as *const _ as *mut _,
7872                &pd as *const _ as *mut _,
7873                &nc as *const _ as *mut _,
7874            ];
7875            unsafe {
7876                self.launch_pdl(
7877                    "gelu_tanh_mul_q8_1",
7878                    (nrows as u32, 1, 1),
7879                    (rms_block(), 1, 1),
7880                    &mut ps,
7881                )?;
7882            }
7883            return Ok(());
7884        }
7885        let f = self.func("gelu_tanh_mul_q8_1");
7886        let cfg = LaunchConfig {
7887            grid_dim: (nrows as u32, 1, 1),
7888            block_dim: (rms_block(), 1, 1),
7889            shared_mem_bytes: 0,
7890        };
7891        let __s_b = self.gpu.stream();
7892        let mut b = __s_b.launch_builder(&f);
7893        b.arg(gate)
7894            .arg(up)
7895            .arg(&mut *act)
7896            .arg(&mut *out_q)
7897            .arg(&mut *out_d)
7898            .arg(&nc);
7899        unsafe {
7900            b.launch(cfg)?;
7901        }
7902        Ok(())
7903    }
7904
7905    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
7906    #[allow(clippy::too_many_arguments)]
7907    pub fn add_rms_norm3_q8z(
7908        &self,
7909        a: &CudaSlice<f32>,
7910        b_in: &CudaSlice<f32>,
7911        w0: &CudaSlice<f32>,
7912        w1: &CudaSlice<f32>,
7913        w2: &CudaSlice<f32>,
7914        res: &mut CudaSlice<f32>,
7915        out1: &mut CudaSlice<f32>,
7916        ncols: usize,
7917        nrows: usize,
7918        eps: f32,
7919    ) -> Result<
7920        (
7921            (CudaSlice<i8>, CudaSlice<f32>),
7922            (CudaSlice<i8>, CudaSlice<f32>),
7923        ),
7924        Box<dyn std::error::Error>,
7925    > {
7926        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
7927        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7928        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
7929        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7930        let f = self.func("add_rms_norm3_q8z_f32");
7931        let cfg = LaunchConfig {
7932            grid_dim: (nrows as u32, 1, 1),
7933            block_dim: (rms_block(), 1, 1),
7934            shared_mem_bytes: 0,
7935        };
7936        let (nc, e2) = (ncols as i32, eps);
7937        let __s_b = self.gpu.stream();
7938        let mut b = __s_b.launch_builder(&f);
7939        b.arg(a)
7940            .arg(b_in)
7941            .arg(w0)
7942            .arg(w1)
7943            .arg(w2)
7944            .arg(res)
7945            .arg(&mut q0)
7946            .arg(&mut d0)
7947            .arg(out1)
7948            .arg(&mut q2)
7949            .arg(&mut d2)
7950            .arg(&nc)
7951            .arg(&e2);
7952        unsafe {
7953            b.launch(cfg)?;
7954        }
7955        Ok(((q0, d0), (q2, d2)))
7956    }
7957
7958    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
7959    #[allow(clippy::too_many_arguments)]
7960    pub fn add_rms_norm3(
7961        &self,
7962        a: &CudaSlice<f32>,
7963        b_in: &CudaSlice<f32>,
7964        w0: &CudaSlice<f32>,
7965        w1: &CudaSlice<f32>,
7966        w2: &CudaSlice<f32>,
7967        res: &mut CudaSlice<f32>,
7968        d0: &mut CudaSlice<f32>,
7969        d1: &mut CudaSlice<f32>,
7970        d2: &mut CudaSlice<f32>,
7971        ncols: usize,
7972        nrows: usize,
7973        eps: f32,
7974    ) -> Result<(), Box<dyn std::error::Error>> {
7975        let f = self.func("add_rms_norm3_f32");
7976        let cfg = LaunchConfig {
7977            grid_dim: (nrows as u32, 1, 1),
7978            block_dim: (rms_block(), 1, 1),
7979            shared_mem_bytes: 0,
7980        };
7981        let (nc, e2) = (ncols as i32, eps);
7982        let __s_b = self.gpu.stream();
7983        let mut b = __s_b.launch_builder(&f);
7984        b.arg(a)
7985            .arg(b_in)
7986            .arg(w0)
7987            .arg(w1)
7988            .arg(w2)
7989            .arg(res)
7990            .arg(d0)
7991            .arg(d1)
7992            .arg(d2)
7993            .arg(&nc)
7994            .arg(&e2);
7995        unsafe {
7996            b.launch(cfg)?;
7997        }
7998        Ok(())
7999    }
8000
8001    /// dst = (a + b) * c (residual add + layer scale, one launch).
8002    pub fn add_scale(
8003        &self,
8004        a: &CudaSlice<f32>,
8005        b_in: &CudaSlice<f32>,
8006        c: f32,
8007        dst: &mut CudaSlice<f32>,
8008        n: usize,
8009    ) -> Result<(), Box<dyn std::error::Error>> {
8010        let f = self.func("add_scale_f32");
8011        let cfg = LaunchConfig::for_num_elems(n as u32);
8012        let ni = n as i32;
8013        let __s_b = self.gpu.stream();
8014        let mut b = __s_b.launch_builder(&f);
8015        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
8016        unsafe {
8017            b.launch(cfg)?;
8018        }
8019        Ok(())
8020    }
8021
8022    /// Vision-tower LayerNorm (with bias) over [nrows, ncols] — lane/vision.
8023    pub fn layer_norm_bias(
8024        &self,
8025        x: &CudaSlice<f32>,
8026        w: &CudaSlice<f32>,
8027        b: &CudaSlice<f32>,
8028        dst: &mut CudaSlice<f32>,
8029        ncols: usize,
8030        nrows: usize,
8031        eps: f32,
8032    ) -> Result<(), Box<dyn std::error::Error>> {
8033        let f = self.func("layer_norm_bias_f32");
8034        let (nc, e) = (ncols as i32, eps);
8035        let cfg = LaunchConfig {
8036            grid_dim: (nrows as u32, 1, 1),
8037            block_dim: (256, 1, 1),
8038            shared_mem_bytes: 0,
8039        };
8040        let __s_b = self.gpu.stream();
8041        let mut lb = __s_b.launch_builder(&f);
8042        lb.arg(x).arg(w).arg(b).arg(&mut *dst).arg(&nc).arg(&e);
8043        unsafe {
8044            lb.launch(cfg)?;
8045        }
8046        Ok(())
8047    }
8048
8049    /// gelu_pytorch_tanh elementwise (vision tower MLP activation).
8050    pub fn gelu_tanh(
8051        &self,
8052        x: &CudaSlice<f32>,
8053        dst: &mut CudaSlice<f32>,
8054        n: usize,
8055    ) -> Result<(), Box<dyn std::error::Error>> {
8056        let f = self.func("gelu_tanh_f32");
8057        let ni = n as i64;
8058        let cfg = LaunchConfig {
8059            grid_dim: (n.div_ceil(256) as u32, 1, 1),
8060            block_dim: (256, 1, 1),
8061            shared_mem_bytes: 0,
8062        };
8063        let __s_b = self.gpu.stream();
8064        let mut lb = __s_b.launch_builder(&f);
8065        lb.arg(x).arg(&mut *dst).arg(&ni);
8066        unsafe {
8067            lb.launch(cfg)?;
8068        }
8069        Ok(())
8070    }
8071
8072    /// In-place row softmax over [nrows, ncols] (bidirectional vision attention).
8073    pub fn row_softmax(
8074        &self,
8075        x: &mut CudaSlice<f32>,
8076        ncols: usize,
8077        nrows: usize,
8078    ) -> Result<(), Box<dyn std::error::Error>> {
8079        let f = self.func("row_softmax_f32");
8080        let nc = ncols as i32;
8081        let cfg = LaunchConfig {
8082            grid_dim: (nrows as u32, 1, 1),
8083            block_dim: (256, 1, 1),
8084            shared_mem_bytes: 0,
8085        };
8086        let __s_b = self.gpu.stream();
8087        let mut lb = __s_b.launch_builder(&f);
8088        lb.arg(&mut *x).arg(&nc);
8089        unsafe {
8090            lb.launch(cfg)?;
8091        }
8092        Ok(())
8093    }
8094
8095    pub fn rms_norm(
8096        &self,
8097        x: &CudaSlice<f32>,
8098        w: &CudaSlice<f32>,
8099        dst: &mut CudaSlice<f32>,
8100        ncols: usize,
8101        nrows: usize,
8102        eps: f32,
8103    ) -> Result<(), Box<dyn std::error::Error>> {
8104        let (nc, e) = (ncols as i32, eps);
8105        if Self::pdl_on() && Self::pdl_wb_on() {
8106            use cudarc::driver::{DevicePtr, DevicePtrMut};
8107            let s = &self.gpu.stream();
8108            let (px, _g0) = x.device_ptr(s);
8109            let (pw, _g1) = w.device_ptr(s);
8110            let (pd, _g2) = dst.device_ptr_mut(s);
8111            let mut ps = [
8112                &px as *const _ as *mut std::ffi::c_void,
8113                &pw as *const _ as *mut _,
8114                &pd as *const _ as *mut _,
8115                &nc as *const _ as *mut _,
8116                &e as *const _ as *mut _,
8117            ];
8118            unsafe {
8119                self.launch_pdl(
8120                    "rms_norm_f32",
8121                    (nrows as u32, 1, 1),
8122                    (rms_block(), 1, 1),
8123                    &mut ps,
8124                )?;
8125            }
8126            return Ok(());
8127        }
8128        let f = self.func("rms_norm_f32");
8129        let cfg = LaunchConfig {
8130            grid_dim: (nrows as u32, 1, 1),
8131            block_dim: (rms_block(), 1, 1),
8132            shared_mem_bytes: 0,
8133        };
8134        let __s_b = self.gpu.stream();
8135        let mut b = __s_b.launch_builder(&f);
8136        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8137        unsafe {
8138            b.launch(cfg)?;
8139        }
8140        Ok(())
8141    }
8142
8143    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8144    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8145    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8146    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8147    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8148    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8149    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8150    pub fn rms_norm_decode(
8151        &self,
8152        x: &CudaSlice<f32>,
8153        w: &CudaSlice<f32>,
8154        dst: &mut CudaSlice<f32>,
8155        ncols: usize,
8156        nrows: usize,
8157        eps: f32,
8158    ) -> Result<(), Box<dyn std::error::Error>> {
8159        let f = self.func("rms_norm_f32");
8160        let cfg = LaunchConfig {
8161            grid_dim: (nrows as u32, 1, 1),
8162            block_dim: (1024, 1, 1),
8163            shared_mem_bytes: 0,
8164        };
8165        let (nc, e) = (ncols as i32, eps);
8166        let __s_b = self.gpu.stream();
8167        let mut b = __s_b.launch_builder(&f);
8168        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8169        unsafe {
8170            b.launch(cfg)?;
8171        }
8172        Ok(())
8173    }
8174
8175    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8176    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8177    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8178    pub fn rms_norm_q8_1(
8179        &self,
8180        x: &CudaSlice<f32>,
8181        w: &CudaSlice<f32>,
8182        ncols: usize,
8183        nrows: usize,
8184        eps: f32,
8185    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8186        let nblk = ncols / 32;
8187        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8188        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8189        let (nc, e) = (ncols as i32, eps);
8190        if Self::pdl_on() {
8191            {
8192                use cudarc::driver::{DevicePtr, DevicePtrMut};
8193                let s = &self.gpu.stream();
8194                let (px, _g0) = x.device_ptr(s);
8195                let (pw, _g1) = w.device_ptr(s);
8196                let (pq, _g2) = q.device_ptr_mut(s);
8197                let (pd, _g3) = d.device_ptr_mut(s);
8198                let mut ps = [
8199                    &px as *const _ as *mut std::ffi::c_void,
8200                    &pw as *const _ as *mut _,
8201                    &pq as *const _ as *mut _,
8202                    &pd as *const _ as *mut _,
8203                    &nc as *const _ as *mut _,
8204                    &e as *const _ as *mut _,
8205                ];
8206                unsafe {
8207                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8208                }
8209            }
8210            return Ok((q, d));
8211        }
8212        let f = self.func("rms_norm_q8_1");
8213        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8214        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8215        let cfg = LaunchConfig {
8216            grid_dim: (nrows as u32, 1, 1),
8217            block_dim: (1024, 1, 1),
8218            shared_mem_bytes: 0,
8219        };
8220        let __s_b = self.gpu.stream();
8221        let mut b = __s_b.launch_builder(&f);
8222        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8223        unsafe {
8224            b.launch(cfg)?;
8225        }
8226        Ok((q, d))
8227    }
8228
8229    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8230    /// PDL arm), caller-owned outputs.
8231    pub fn rms_norm_q8_1_into(
8232        &self,
8233        x: &CudaSlice<f32>,
8234        w: &CudaSlice<f32>,
8235        ncols: usize,
8236        nrows: usize,
8237        eps: f32,
8238        q: &mut CudaSlice<i8>,
8239        d: &mut CudaSlice<f32>,
8240    ) -> Result<(), Box<dyn std::error::Error>> {
8241        let nblk = ncols / 32;
8242        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8243        let (nc, e) = (ncols as i32, eps);
8244        if Self::pdl_on() {
8245            use cudarc::driver::{DevicePtr, DevicePtrMut};
8246            let s = &self.gpu.stream();
8247            let (px, _g0) = x.device_ptr(s);
8248            let (pw, _g1) = w.device_ptr(s);
8249            let (pq, _g2) = q.device_ptr_mut(s);
8250            let (pd, _g3) = d.device_ptr_mut(s);
8251            let mut ps = [
8252                &px as *const _ as *mut std::ffi::c_void,
8253                &pw as *const _ as *mut _,
8254                &pq as *const _ as *mut _,
8255                &pd as *const _ as *mut _,
8256                &nc as *const _ as *mut _,
8257                &e as *const _ as *mut _,
8258            ];
8259            unsafe {
8260                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8261            }
8262            return Ok(());
8263        }
8264        let f = self.func("rms_norm_q8_1");
8265        let cfg = LaunchConfig {
8266            grid_dim: (nrows as u32, 1, 1),
8267            block_dim: (1024, 1, 1),
8268            shared_mem_bytes: 0,
8269        };
8270        let __s_b = self.gpu.stream();
8271        let mut b = __s_b.launch_builder(&f);
8272        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8273        unsafe {
8274            b.launch(cfg)?;
8275        }
8276        Ok(())
8277    }
8278
8279    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8280    pub fn quantize_q8_1_into(
8281        &self,
8282        x: &CudaSlice<f32>,
8283        m: usize,
8284        in_f: usize,
8285        q: &mut CudaSlice<i8>,
8286        d: &mut CudaSlice<f32>,
8287    ) -> Result<(), Box<dyn std::error::Error>> {
8288        let nblk = in_f / 32;
8289        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8290        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8291        let (inf, mi) = (in_f as i32, m as i32);
8292        if Self::pdl_on() && Self::pdl_wb_on() {
8293            use cudarc::driver::{DevicePtr, DevicePtrMut};
8294            let s = &self.gpu.stream();
8295            let (px, _g0) = x.device_ptr(s);
8296            let (pq, _g1) = q.device_ptr_mut(s);
8297            let (pd, _g2) = d.device_ptr_mut(s);
8298            let mut ps = [
8299                &px as *const _ as *mut std::ffi::c_void,
8300                &pq as *const _ as *mut _,
8301                &pd as *const _ as *mut _,
8302                &inf as *const _ as *mut _,
8303                &mi as *const _ as *mut _,
8304            ];
8305            unsafe {
8306                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8307            }
8308            return Ok(());
8309        }
8310        let f = self.func("quantize_q8_1");
8311        let __s_b = self.gpu.stream();
8312        let mut b = __s_b.launch_builder(&f);
8313        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8314        unsafe {
8315            b.launch(cfg)?;
8316        }
8317        Ok(())
8318    }
8319
8320    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8321    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8322    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8323    pub fn add_rms_norm_q8_1(
8324        &self,
8325        a: &CudaSlice<f32>,
8326        b_in: &CudaSlice<f32>,
8327        w: &CudaSlice<f32>,
8328        res: &mut CudaSlice<f32>,
8329        ncols: usize,
8330        nrows: usize,
8331        eps: f32,
8332    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8333        let nblk = ncols / 32;
8334        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8335        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8336        let f = self.func("add_rms_norm_q8_1");
8337        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8338        let cfg = LaunchConfig {
8339            grid_dim: (nrows as u32, 1, 1),
8340            block_dim: (1024, 1, 1),
8341            shared_mem_bytes: 0,
8342        };
8343        let (nc, e) = (ncols as i32, eps);
8344        let __s_bld = self.gpu.stream();
8345        let mut bld = __s_bld.launch_builder(&f);
8346        bld.arg(a)
8347            .arg(b_in)
8348            .arg(w)
8349            .arg(res)
8350            .arg(&mut q)
8351            .arg(&mut d)
8352            .arg(&nc)
8353            .arg(&e);
8354        unsafe {
8355            bld.launch(cfg)?;
8356        }
8357        Ok((q, d))
8358    }
8359
8360    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8361    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8362    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8363    pub fn add_rms_norm(
8364        &self,
8365        a: &CudaSlice<f32>,
8366        b: &CudaSlice<f32>,
8367        w: &CudaSlice<f32>,
8368        res: &mut CudaSlice<f32>,
8369        dst: &mut CudaSlice<f32>,
8370        ncols: usize,
8371        nrows: usize,
8372        eps: f32,
8373    ) -> Result<(), Box<dyn std::error::Error>> {
8374        let (nc, e) = (ncols as i32, eps);
8375        if Self::pdl_on() && Self::pdl_wb_on() {
8376            use cudarc::driver::{DevicePtr, DevicePtrMut};
8377            let s = &self.gpu.stream();
8378            let (pa, _g0) = a.device_ptr(s);
8379            let (pb, _g1) = b.device_ptr(s);
8380            let (pw, _g2) = w.device_ptr(s);
8381            let (pr, _g3) = res.device_ptr_mut(s);
8382            let (pd, _g4) = dst.device_ptr_mut(s);
8383            let mut ps = [
8384                &pa as *const _ as *mut std::ffi::c_void,
8385                &pb as *const _ as *mut _,
8386                &pw as *const _ as *mut _,
8387                &pr as *const _ as *mut _,
8388                &pd as *const _ as *mut _,
8389                &nc as *const _ as *mut _,
8390                &e as *const _ as *mut _,
8391            ];
8392            unsafe {
8393                self.launch_pdl(
8394                    "add_rms_norm_f32",
8395                    (nrows as u32, 1, 1),
8396                    (rms_block(), 1, 1),
8397                    &mut ps,
8398                )?;
8399            }
8400            return Ok(());
8401        }
8402        let f = self.func("add_rms_norm_f32");
8403        let cfg = LaunchConfig {
8404            grid_dim: (nrows as u32, 1, 1),
8405            block_dim: (rms_block(), 1, 1),
8406            shared_mem_bytes: 0,
8407        };
8408        let __s_b2 = self.gpu.stream();
8409        let mut b2 = __s_b2.launch_builder(&f);
8410        b2.arg(a)
8411            .arg(b)
8412            .arg(w)
8413            .arg(&mut *res)
8414            .arg(&mut *dst)
8415            .arg(&nc)
8416            .arg(&e);
8417        unsafe {
8418            b2.launch(cfg)?;
8419        }
8420        Ok(())
8421    }
8422
8423    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8424    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8425    #[allow(clippy::too_many_arguments)]
8426    pub fn rms_pre_add_rms_norm(
8427        &self,
8428        a: &CudaSlice<f32>,
8429        wa: &CudaSlice<f32>,
8430        b: &CudaSlice<f32>,
8431        w: &CudaSlice<f32>,
8432        res: &mut CudaSlice<f32>,
8433        dst: &mut CudaSlice<f32>,
8434        ncols: usize,
8435        nrows: usize,
8436        eps: f32,
8437    ) -> Result<(), Box<dyn std::error::Error>> {
8438        let f = self.func("rms_pre_add_rms_norm_f32");
8439        let cfg = LaunchConfig {
8440            grid_dim: (nrows as u32, 1, 1),
8441            block_dim: (rms_block(), 1, 1),
8442            shared_mem_bytes: 0,
8443        };
8444        let (nc, e) = (ncols as i32, eps);
8445        let __s_b2 = self.gpu.stream();
8446        let mut b2 = __s_b2.launch_builder(&f);
8447        b2.arg(a)
8448            .arg(wa)
8449            .arg(b)
8450            .arg(w)
8451            .arg(&mut *res)
8452            .arg(&mut *dst)
8453            .arg(&nc)
8454            .arg(&e);
8455        unsafe {
8456            b2.launch(cfg)?;
8457        }
8458        Ok(())
8459    }
8460
8461    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8462    #[allow(clippy::too_many_arguments)]
8463    pub fn rms_pre_add_rms_norm_q8z(
8464        &self,
8465        a: &CudaSlice<f32>,
8466        wa: &CudaSlice<f32>,
8467        b: &CudaSlice<f32>,
8468        w: &CudaSlice<f32>,
8469        res: &mut CudaSlice<f32>,
8470        dst: &mut CudaSlice<f32>,
8471        ncols: usize,
8472        nrows: usize,
8473        eps: f32,
8474    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8475        debug_assert!(ncols % 128 == 0);
8476        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8477        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8478        let (nc, e) = (ncols as i32, eps);
8479        if Self::pdl_on() {
8480            {
8481                use cudarc::driver::{DevicePtr, DevicePtrMut};
8482                let s = &self.gpu.stream();
8483                let (pa, _g0) = a.device_ptr(s);
8484                let (pwa, _g1) = wa.device_ptr(s);
8485                let (pb, _g2) = b.device_ptr(s);
8486                let (pw, _g3) = w.device_ptr(s);
8487                let (pr, _g4) = res.device_ptr_mut(s);
8488                let (pdst, _g5) = dst.device_ptr_mut(s);
8489                let (pq, _g6) = out_q.device_ptr_mut(s);
8490                let (pd, _g7) = out_d.device_ptr_mut(s);
8491                let mut ps = [
8492                    &pa as *const _ as *mut std::ffi::c_void,
8493                    &pwa as *const _ as *mut _,
8494                    &pb as *const _ as *mut _,
8495                    &pw as *const _ as *mut _,
8496                    &pr as *const _ as *mut _,
8497                    &pdst as *const _ as *mut _,
8498                    &pq as *const _ as *mut _,
8499                    &pd as *const _ as *mut _,
8500                    &nc as *const _ as *mut _,
8501                    &e as *const _ as *mut _,
8502                ];
8503                unsafe {
8504                    self.launch_pdl(
8505                        "rms_pre_add_rms_norm_q8z_f32",
8506                        (nrows as u32, 1, 1),
8507                        (rms_block(), 1, 1),
8508                        &mut ps,
8509                    )?;
8510                }
8511            }
8512            return Ok((out_q, out_d));
8513        }
8514        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8515        let cfg = LaunchConfig {
8516            grid_dim: (nrows as u32, 1, 1),
8517            block_dim: (rms_block(), 1, 1),
8518            shared_mem_bytes: 0,
8519        };
8520        let __s_b2 = self.gpu.stream();
8521        let mut b2 = __s_b2.launch_builder(&f);
8522        b2.arg(a)
8523            .arg(wa)
8524            .arg(b)
8525            .arg(w)
8526            .arg(&mut *res)
8527            .arg(&mut *dst)
8528            .arg(&mut out_q)
8529            .arg(&mut out_d)
8530            .arg(&nc)
8531            .arg(&e);
8532        unsafe {
8533            b2.launch(cfg)?;
8534        }
8535        Ok((out_q, out_d))
8536    }
8537
8538    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8539    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8540    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8541    pub fn build_q4_out_concat3(
8542        &self,
8543        w0: &crate::model::GpuTensor,
8544        w1: &crate::model::GpuTensor,
8545        w2: &crate::model::GpuTensor,
8546    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8547        use crate::model::GpuTensor;
8548        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8549            match w {
8550                GpuTensor::Quant {
8551                    qtype,
8552                    row_bytes,
8553                    rp,
8554                    ..
8555                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8556                _ => None,
8557            }
8558        };
8559        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8560        else {
8561            return Ok(None);
8562        };
8563        if rb0 != rb1
8564            || rb0 != rb2
8565            || w0.in_features() != w1.in_features()
8566            || w0.in_features() != w2.in_features()
8567        {
8568            return Ok(None);
8569        }
8570        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8571            match w {
8572                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8573                _ => unreachable!(),
8574            }
8575        }
8576        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8577        let total = rb0 * (o0 + o1 + o2);
8578        let mut cat = self.alloc_u8(total)?;
8579        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8580        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8581        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8582        Ok(Some(GpuTensor::Quant {
8583            bytes: cat,
8584            qtype: QT_Q4_0,
8585            row_bytes: rb0,
8586            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8587            scale: 1.0,
8588            rp: false,
8589            #[cfg(memra_cutlass)]
8590            cutlass: None,
8591            fp8: None,
8592            blk: None,
8593            rp4: None,
8594            f16: None,
8595        }))
8596    }
8597
8598    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8599    #[allow(clippy::too_many_arguments)]
8600    pub fn rms_norm_qkv_rope_cat(
8601        &self,
8602        qkv: &CudaSlice<f32>,
8603        wq: &CudaSlice<f32>,
8604        wk: &CudaSlice<f32>,
8605        wv: &CudaSlice<f32>,
8606        q: &mut CudaSlice<f32>,
8607        k: &mut CudaSlice<f32>,
8608        v: &mut CudaSlice<f32>,
8609        head_dim: usize,
8610        rq: usize,
8611        rk: usize,
8612        pos: &CudaSlice<i32>,
8613        nh_q: usize,
8614        nh_k: usize,
8615        base: f32,
8616        freq_scale: f32,
8617        ff: Option<&CudaSlice<f32>>,
8618        eps: f32,
8619    ) -> Result<(), Box<dyn std::error::Error>> {
8620        let rows = rq + rk + rk;
8621        let theta_scale = base.powf(-2.0 / head_dim as f32);
8622        let (nc, rqi, rki, nhq, nhk) = (
8623            head_dim as i32,
8624            rq as i32,
8625            rk as i32,
8626            nh_q as i32,
8627            nh_k as i32,
8628        );
8629        if Self::pdl_on() {
8630            use cudarc::driver::{DevicePtr, DevicePtrMut};
8631            let s = &self.gpu.stream();
8632            let (pqkv, _g0) = qkv.device_ptr(s);
8633            let (pwq, _g1) = wq.device_ptr(s);
8634            let (pwk, _g2) = wk.device_ptr(s);
8635            let (pwv, _g3) = wv.device_ptr(s);
8636            let (pq, _g4) = q.device_ptr_mut(s);
8637            let (pk, _g5) = k.device_ptr_mut(s);
8638            let (pv, _g6) = v.device_ptr_mut(s);
8639            let (ppos, _g7) = pos.device_ptr(s);
8640            let (pff, _g8) = match ff {
8641                Some(t) => {
8642                    let (p, g) = t.device_ptr(s);
8643                    (p, Some(g))
8644                }
8645                None => (0, None),
8646            };
8647            let mut ps = [
8648                &pqkv as *const _ as *mut std::ffi::c_void,
8649                &pwq as *const _ as *mut _,
8650                &pwk as *const _ as *mut _,
8651                &pwv as *const _ as *mut _,
8652                &pq as *const _ as *mut _,
8653                &pk as *const _ as *mut _,
8654                &pv as *const _ as *mut _,
8655                &nc as *const _ as *mut _,
8656                &rqi as *const _ as *mut _,
8657                &rki as *const _ as *mut _,
8658                &ppos as *const _ as *mut _,
8659                &nhq as *const _ as *mut _,
8660                &nhk as *const _ as *mut _,
8661                &theta_scale as *const _ as *mut _,
8662                &freq_scale as *const _ as *mut _,
8663                &pff as *const _ as *mut _,
8664                &eps as *const _ as *mut _,
8665            ];
8666            unsafe {
8667                self.launch_pdl(
8668                    "rms_norm_qkv_rope_cat_f32",
8669                    (rows as u32, 1, 1),
8670                    (rms_block(), 1, 1),
8671                    &mut ps,
8672                )?;
8673            }
8674            return Ok(());
8675        }
8676        let f = self.func("rms_norm_qkv_rope_cat_f32");
8677        let cfg = LaunchConfig {
8678            grid_dim: (rows as u32, 1, 1),
8679            block_dim: (rms_block(), 1, 1),
8680            shared_mem_bytes: 0,
8681        };
8682        let __s_b = self.gpu.stream();
8683        let mut b = __s_b.launch_builder(&f);
8684        match ff {
8685            Some(t) => {
8686                b.arg(qkv)
8687                    .arg(wq)
8688                    .arg(wk)
8689                    .arg(wv)
8690                    .arg(&mut *q)
8691                    .arg(&mut *k)
8692                    .arg(&mut *v)
8693                    .arg(&nc)
8694                    .arg(&rqi)
8695                    .arg(&rki)
8696                    .arg(pos)
8697                    .arg(&nhq)
8698                    .arg(&nhk)
8699                    .arg(&theta_scale)
8700                    .arg(&freq_scale)
8701                    .arg(t)
8702                    .arg(&eps);
8703                unsafe {
8704                    b.launch(cfg)?;
8705                }
8706            }
8707            None => {
8708                let null: u64 = 0;
8709                b.arg(qkv)
8710                    .arg(wq)
8711                    .arg(wk)
8712                    .arg(wv)
8713                    .arg(&mut *q)
8714                    .arg(&mut *k)
8715                    .arg(&mut *v)
8716                    .arg(&nc)
8717                    .arg(&rqi)
8718                    .arg(&rki)
8719                    .arg(pos)
8720                    .arg(&nhq)
8721                    .arg(&nhk)
8722                    .arg(&theta_scale)
8723                    .arg(&freq_scale)
8724                    .arg(&null)
8725                    .arg(&eps);
8726                unsafe {
8727                    b.launch(cfg)?;
8728                }
8729            }
8730        }
8731        Ok(())
8732    }
8733
8734    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8735    #[allow(clippy::too_many_arguments)]
8736    pub fn rms_norm_qkv_rope(
8737        &self,
8738        q0: &CudaSlice<f32>,
8739        k0: &CudaSlice<f32>,
8740        v0: &CudaSlice<f32>,
8741        wq: &CudaSlice<f32>,
8742        wk: &CudaSlice<f32>,
8743        wv: &CudaSlice<f32>,
8744        q: &mut CudaSlice<f32>,
8745        k: &mut CudaSlice<f32>,
8746        v: &mut CudaSlice<f32>,
8747        head_dim: usize,
8748        rq: usize,
8749        rk: usize,
8750        pos: &CudaSlice<i32>,
8751        nh_q: usize,
8752        nh_k: usize,
8753        base: f32,
8754        freq_scale: f32,
8755        ff: Option<&CudaSlice<f32>>,
8756        eps: f32,
8757    ) -> Result<(), Box<dyn std::error::Error>> {
8758        let f = self.func("rms_norm_qkv_rope_f32");
8759        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
8760        let cfg = LaunchConfig {
8761            grid_dim: (rows as u32, 1, 1),
8762            block_dim: (rms_block(), 1, 1),
8763            shared_mem_bytes: 0,
8764        };
8765        let theta_scale = base.powf(-2.0 / head_dim as f32);
8766        let (nc, rqi, rki, nhq, nhk) = (
8767            head_dim as i32,
8768            rq as i32,
8769            rk as i32,
8770            nh_q as i32,
8771            nh_k as i32,
8772        );
8773        let __s_b = self.gpu.stream();
8774        let mut b = __s_b.launch_builder(&f);
8775        match ff {
8776            Some(t) => {
8777                b.arg(q0)
8778                    .arg(k0)
8779                    .arg(v0)
8780                    .arg(wq)
8781                    .arg(wk)
8782                    .arg(wv)
8783                    .arg(&mut *q)
8784                    .arg(&mut *k)
8785                    .arg(&mut *v)
8786                    .arg(&nc)
8787                    .arg(&rqi)
8788                    .arg(&rki)
8789                    .arg(pos)
8790                    .arg(&nhq)
8791                    .arg(&nhk)
8792                    .arg(&theta_scale)
8793                    .arg(&freq_scale)
8794                    .arg(t)
8795                    .arg(&eps);
8796                unsafe {
8797                    b.launch(cfg)?;
8798                }
8799            }
8800            None => {
8801                let null: u64 = 0;
8802                b.arg(q0)
8803                    .arg(k0)
8804                    .arg(v0)
8805                    .arg(wq)
8806                    .arg(wk)
8807                    .arg(wv)
8808                    .arg(&mut *q)
8809                    .arg(&mut *k)
8810                    .arg(&mut *v)
8811                    .arg(&nc)
8812                    .arg(&rqi)
8813                    .arg(&rki)
8814                    .arg(pos)
8815                    .arg(&nhq)
8816                    .arg(&nhk)
8817                    .arg(&theta_scale)
8818                    .arg(&freq_scale)
8819                    .arg(&null)
8820                    .arg(&eps);
8821                unsafe {
8822                    b.launch(cfg)?;
8823                }
8824            }
8825        }
8826        Ok(())
8827    }
8828
8829    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
8830    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
8831    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
8832    #[allow(clippy::too_many_arguments)]
8833    pub fn rms_norm_qkv_rope_append_dc(
8834        &self,
8835        q0: &CudaSlice<f32>,
8836        k0: &CudaSlice<f32>,
8837        v0: &CudaSlice<f32>,
8838        wq: &CudaSlice<f32>,
8839        wk: &CudaSlice<f32>,
8840        wv: &CudaSlice<f32>,
8841        q: &mut CudaSlice<f32>,
8842        k: &mut CudaSlice<f32>,
8843        v: &mut CudaSlice<f32>,
8844        head_dim: usize,
8845        rq: usize,
8846        rk: usize,
8847        pos: &CudaSlice<i32>,
8848        nh_q: usize,
8849        nh_k: usize,
8850        base: f32,
8851        freq_scale: f32,
8852        ff: Option<&CudaSlice<f32>>,
8853        eps: f32,
8854        kc: &mut CudaSlice<u8>,
8855        vc: &mut CudaSlice<u8>,
8856        t_dev: &CudaSlice<i32>,
8857        k_tok_bytes: usize,
8858        v_tok_bytes: usize,
8859        g: bool,
8860    ) -> Result<(), Box<dyn std::error::Error>> {
8861        let rows = rq + rk + rk;
8862        let theta_scale = base.powf(-2.0 / head_dim as f32);
8863        let (nc, rqi, rki, nhq, nhk) = (
8864            head_dim as i32,
8865            rq as i32,
8866            rk as i32,
8867            nh_q as i32,
8868            nh_k as i32,
8869        );
8870        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
8871        if Self::pdl_on() && Self::pdl_wb_on() {
8872            use cudarc::driver::{DevicePtr, DevicePtrMut};
8873            let s = &self.gpu.stream();
8874            let (p0, _a0) = q0.device_ptr(s);
8875            let (p1, _a1) = k0.device_ptr(s);
8876            let (p2, _a2) = v0.device_ptr(s);
8877            let (pwq, _a3) = wq.device_ptr(s);
8878            let (pwk, _a4) = wk.device_ptr(s);
8879            let (pwv, _a5) = wv.device_ptr(s);
8880            let (pq, _a6) = q.device_ptr_mut(s);
8881            let (pk, _a7) = k.device_ptr_mut(s);
8882            let (pv, _a8) = v.device_ptr_mut(s);
8883            let (pp, _a9) = pos.device_ptr(s);
8884            let pff: u64 = match ff {
8885                Some(t) => {
8886                    let (p, _gg) = t.device_ptr(s);
8887                    p as u64
8888                }
8889                None => 0,
8890            };
8891            let (pkc, _a10) = kc.device_ptr_mut(s);
8892            let (pvc, _a11) = vc.device_ptr_mut(s);
8893            let (pt, _a12) = t_dev.device_ptr(s);
8894            let mut ps = [
8895                &p0 as *const _ as *mut std::ffi::c_void,
8896                &p1 as *const _ as *mut _,
8897                &p2 as *const _ as *mut _,
8898                &pwq as *const _ as *mut _,
8899                &pwk as *const _ as *mut _,
8900                &pwv as *const _ as *mut _,
8901                &pq as *const _ as *mut _,
8902                &pk as *const _ as *mut _,
8903                &pv as *const _ as *mut _,
8904                &nc as *const _ as *mut _,
8905                &rqi as *const _ as *mut _,
8906                &rki as *const _ as *mut _,
8907                &pp as *const _ as *mut _,
8908                &nhq as *const _ as *mut _,
8909                &nhk as *const _ as *mut _,
8910                &theta_scale as *const _ as *mut _,
8911                &freq_scale as *const _ as *mut _,
8912                &pff as *const _ as *mut _,
8913                &eps as *const _ as *mut _,
8914                &pkc as *const _ as *mut _,
8915                &pvc as *const _ as *mut _,
8916                &pt as *const _ as *mut _,
8917                &ktb as *const _ as *mut _,
8918                &vtb as *const _ as *mut _,
8919            ];
8920            unsafe {
8921                self.launch_pdl_flash(
8922                    g,
8923                    "rms_norm_qkv_rope_append_dc_f32",
8924                    (rows as u32, 1, 1),
8925                    (rms_block(), 1, 1),
8926                    0,
8927                    &mut ps,
8928                )?;
8929            }
8930            return Ok(());
8931        }
8932        let f = if g {
8933            self.func_g("rms_norm_qkv_rope_append_dc_f32")
8934        } else {
8935            self.func("rms_norm_qkv_rope_append_dc_f32")
8936        };
8937        let cfg = LaunchConfig {
8938            grid_dim: (rows as u32, 1, 1),
8939            block_dim: (rms_block(), 1, 1),
8940            shared_mem_bytes: 0,
8941        };
8942        let __s_b = self.gpu.stream();
8943        let mut b = __s_b.launch_builder(&f);
8944        match ff {
8945            Some(t) => {
8946                b.arg(q0)
8947                    .arg(k0)
8948                    .arg(v0)
8949                    .arg(wq)
8950                    .arg(wk)
8951                    .arg(wv)
8952                    .arg(&mut *q)
8953                    .arg(&mut *k)
8954                    .arg(&mut *v)
8955                    .arg(&nc)
8956                    .arg(&rqi)
8957                    .arg(&rki)
8958                    .arg(pos)
8959                    .arg(&nhq)
8960                    .arg(&nhk)
8961                    .arg(&theta_scale)
8962                    .arg(&freq_scale)
8963                    .arg(t)
8964                    .arg(&eps)
8965                    .arg(&mut *kc)
8966                    .arg(&mut *vc)
8967                    .arg(t_dev)
8968                    .arg(&ktb)
8969                    .arg(&vtb);
8970                unsafe {
8971                    b.launch(cfg)?;
8972                }
8973            }
8974            None => {
8975                let null: u64 = 0;
8976                b.arg(q0)
8977                    .arg(k0)
8978                    .arg(v0)
8979                    .arg(wq)
8980                    .arg(wk)
8981                    .arg(wv)
8982                    .arg(&mut *q)
8983                    .arg(&mut *k)
8984                    .arg(&mut *v)
8985                    .arg(&nc)
8986                    .arg(&rqi)
8987                    .arg(&rki)
8988                    .arg(pos)
8989                    .arg(&nhq)
8990                    .arg(&nhk)
8991                    .arg(&theta_scale)
8992                    .arg(&freq_scale)
8993                    .arg(&null)
8994                    .arg(&eps)
8995                    .arg(&mut *kc)
8996                    .arg(&mut *vc)
8997                    .arg(t_dev)
8998                    .arg(&ktb)
8999                    .arg(&vtb);
9000                unsafe {
9001                    b.launch(cfg)?;
9002                }
9003            }
9004        }
9005        Ok(())
9006    }
9007
9008    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
9009    pub fn add_q8_1(
9010        &self,
9011        a: &CudaSlice<f32>,
9012        b: &CudaSlice<f32>,
9013        res: &mut CudaSlice<f32>,
9014        ncols: usize,
9015        nrows: usize,
9016    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9017        debug_assert!(ncols % 128 == 0);
9018        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9019        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9020        let f = self.func("add_q8_1_f32");
9021        let cfg = LaunchConfig {
9022            grid_dim: (nrows as u32, 1, 1),
9023            block_dim: (rms_block(), 1, 1),
9024            shared_mem_bytes: 0,
9025        };
9026        let nc = ncols as i32;
9027        let __s_b2 = self.gpu.stream();
9028        let mut b2 = __s_b2.launch_builder(&f);
9029        b2.arg(a)
9030            .arg(b)
9031            .arg(&mut *res)
9032            .arg(&mut out_q)
9033            .arg(&mut out_d)
9034            .arg(&nc);
9035        unsafe {
9036            b2.launch(cfg)?;
9037        }
9038        Ok((out_q, out_d))
9039    }
9040
9041    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
9042    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
9043    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
9044    pub fn rms_pre_add_q8_1(
9045        &self,
9046        a: &CudaSlice<f32>,
9047        wa: &CudaSlice<f32>,
9048        b: &CudaSlice<f32>,
9049        res: &mut CudaSlice<f32>,
9050        ncols: usize,
9051        nrows: usize,
9052        eps: f32,
9053    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9054        debug_assert!(ncols % 128 == 0);
9055        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9056        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9057        let f = self.func("rms_pre_add_q8_1_f32");
9058        let cfg = LaunchConfig {
9059            grid_dim: (nrows as u32, 1, 1),
9060            block_dim: (rms_block(), 1, 1),
9061            shared_mem_bytes: 0,
9062        };
9063        let (nc, ep) = (ncols as i32, eps);
9064        let __s_b2 = self.gpu.stream();
9065        let mut b2 = __s_b2.launch_builder(&f);
9066        b2.arg(a)
9067            .arg(wa)
9068            .arg(b)
9069            .arg(&mut *res)
9070            .arg(&mut out_q)
9071            .arg(&mut out_d)
9072            .arg(&nc)
9073            .arg(&ep);
9074        unsafe {
9075            b2.launch(cfg)?;
9076        }
9077        Ok((out_q, out_d))
9078    }
9079
9080    /// L2 norm per row (head_dim), no weight.
9081    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
9082    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
9083    pub fn l2_v2_on(ncols: usize) -> bool {
9084        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
9085    }
9086
9087    pub fn l2_norm_pp(
9088        &self,
9089        x: &CudaSlice<f32>,
9090        dst: &mut CudaSlice<f32>,
9091        dst16: Option<&mut CudaSlice<u8>>,
9092        ncols: usize,
9093        nrows: usize,
9094        eps: f32,
9095    ) -> Result<(), Box<dyn std::error::Error>> {
9096        if Self::l2_v2_on(ncols) {
9097            let f = self.func("l2_norm_pp_v2_f32");
9098            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
9099            let cfg = LaunchConfig {
9100                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
9101                block_dim: (256, 1, 1),
9102                shared_mem_bytes: 0,
9103            };
9104            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9105            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
9106            let d16: u64 = match dst16 {
9107                Some(d) => self.addr_u8(d),
9108                None => 0,
9109            };
9110            let __s_b = self.gpu.stream();
9111            let mut b = __s_b.launch_builder(&f);
9112            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
9113            unsafe {
9114                b.launch(cfg)?;
9115            }
9116            return Ok(());
9117        }
9118        self.l2_norm(x, dst, ncols, nrows, eps)
9119    }
9120
9121    pub fn l2_norm(
9122        &self,
9123        x: &CudaSlice<f32>,
9124        dst: &mut CudaSlice<f32>,
9125        ncols: usize,
9126        nrows: usize,
9127        eps: f32,
9128    ) -> Result<(), Box<dyn std::error::Error>> {
9129        let f = self.func("l2_norm_f32");
9130        let cfg = LaunchConfig {
9131            grid_dim: (nrows as u32, 1, 1),
9132            block_dim: (256, 1, 1),
9133            shared_mem_bytes: 0,
9134        };
9135        let (nc, e) = (ncols as i32, eps);
9136        let __s_b = self.gpu.stream();
9137        let mut b = __s_b.launch_builder(&f);
9138        b.arg(x).arg(dst).arg(&nc).arg(&e);
9139        unsafe {
9140            b.launch(cfg)?;
9141        }
9142        Ok(())
9143    }
9144
9145    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9146    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9147    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9148    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9149    /// propagate through gdn_scan and flip argmax on marginal logits.
9150    pub fn l2_norm_decode(
9151        &self,
9152        x: &CudaSlice<f32>,
9153        dst: &mut CudaSlice<f32>,
9154        ncols: usize,
9155        nrows: usize,
9156        eps: f32,
9157    ) -> Result<(), Box<dyn std::error::Error>> {
9158        let f = self.func("l2_norm_f32");
9159        let cfg = LaunchConfig {
9160            grid_dim: (nrows as u32, 1, 1),
9161            block_dim: (32, 1, 1),
9162            shared_mem_bytes: 0,
9163        };
9164        let (nc, e) = (ncols as i32, eps);
9165        let __s_b = self.gpu.stream();
9166        let mut b = __s_b.launch_builder(&f);
9167        b.arg(x).arg(dst).arg(&nc).arg(&e);
9168        unsafe {
9169            b.launch(cfg)?;
9170        }
9171        Ok(())
9172    }
9173
9174    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9175    pub fn rope_neox(
9176        &self,
9177        x: &mut CudaSlice<f32>,
9178        pos: &CudaSlice<i32>,
9179        head_dim: usize,
9180        n_dims: usize,
9181        n_heads: usize,
9182        n_tokens: usize,
9183        freq_base: f32,
9184        freq_scale: f32,
9185    ) -> Result<(), Box<dyn std::error::Error>> {
9186        let f = self.func("rope_neox_f32");
9187        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9188        let grid = (n_heads * n_tokens) as u32;
9189        let cfg = LaunchConfig {
9190            grid_dim: (grid, 1, 1),
9191            block_dim: ((head_dim / 2) as u32, 1, 1),
9192            shared_mem_bytes: 0,
9193        };
9194        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9195        let __s_b = self.gpu.stream();
9196        let mut b = __s_b.launch_builder(&f);
9197        b.arg(x)
9198            .arg(pos)
9199            .arg(&hd)
9200            .arg(&nd)
9201            .arg(&nh)
9202            .arg(&theta_scale)
9203            .arg(&freq_scale);
9204        unsafe {
9205            b.launch(cfg)?;
9206        }
9207        Ok(())
9208    }
9209
9210    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9211    pub fn rope_neox_ff(
9212        &self,
9213        x: &mut CudaSlice<f32>,
9214        pos: &CudaSlice<i32>,
9215        head_dim: usize,
9216        n_dims: usize,
9217        n_heads: usize,
9218        n_tokens: usize,
9219        freq_base: f32,
9220        freq_scale: f32,
9221        ff: &CudaSlice<f32>,
9222    ) -> Result<(), Box<dyn std::error::Error>> {
9223        let f = self.func("rope_neox_ff_f32");
9224        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9225        let grid = (n_heads * n_tokens) as u32;
9226        let cfg = LaunchConfig {
9227            grid_dim: (grid, 1, 1),
9228            block_dim: ((head_dim / 2) as u32, 1, 1),
9229            shared_mem_bytes: 0,
9230        };
9231        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9232        let __s_b = self.gpu.stream();
9233        let mut b = __s_b.launch_builder(&f);
9234        b.arg(x)
9235            .arg(pos)
9236            .arg(&hd)
9237            .arg(&nd)
9238            .arg(&nh)
9239            .arg(&theta_scale)
9240            .arg(&freq_scale)
9241            .arg(ff);
9242        unsafe {
9243            b.launch(cfg)?;
9244        }
9245        Ok(())
9246    }
9247
9248    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9249    #[allow(clippy::too_many_arguments)]
9250    pub fn rope_neox2(
9251        &self,
9252        q: &mut CudaSlice<f32>,
9253        k: &mut CudaSlice<f32>,
9254        pos: &CudaSlice<i32>,
9255        head_dim: usize,
9256        n_dims: usize,
9257        nh_q: usize,
9258        nh_k: usize,
9259        n_tokens: usize,
9260        freq_base: f32,
9261        freq_scale: f32,
9262        ff: Option<&CudaSlice<f32>>,
9263    ) -> Result<(), Box<dyn std::error::Error>> {
9264        let f = self.func("rope_neox2_f32");
9265        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9266        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9267        let cfg = LaunchConfig {
9268            grid_dim: (grid, 1, 1),
9269            block_dim: ((head_dim / 2) as u32, 1, 1),
9270            shared_mem_bytes: 0,
9271        };
9272        let (hd, nd, nq, nk, nt) = (
9273            head_dim as i32,
9274            n_dims as i32,
9275            nh_q as i32,
9276            nh_k as i32,
9277            n_tokens as i32,
9278        );
9279        let __s_b = self.gpu.stream();
9280        let mut b = __s_b.launch_builder(&f);
9281        b.arg(q)
9282            .arg(k)
9283            .arg(pos)
9284            .arg(&hd)
9285            .arg(&nd)
9286            .arg(&nq)
9287            .arg(&nk)
9288            .arg(&nt)
9289            .arg(&theta_scale)
9290            .arg(&freq_scale);
9291        match ff {
9292            Some(ffv) => {
9293                b.arg(ffv);
9294                unsafe {
9295                    b.launch(cfg)?;
9296                }
9297            }
9298            None => {
9299                let null: u64 = 0;
9300                b.arg(&null);
9301                unsafe {
9302                    b.launch(cfg)?;
9303                }
9304            }
9305        }
9306        Ok(())
9307    }
9308
9309    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9310    pub fn gelu_tanh_mul(
9311        &self,
9312        gate: &CudaSlice<f32>,
9313        up: &CudaSlice<f32>,
9314        dst: &mut CudaSlice<f32>,
9315        n: usize,
9316    ) -> Result<(), Box<dyn std::error::Error>> {
9317        let f = self.func("gelu_tanh_mul_f32");
9318        let cfg = LaunchConfig::for_num_elems(n as u32);
9319        let ni = n as i32;
9320        let __s_b = self.gpu.stream();
9321        let mut b = __s_b.launch_builder(&f);
9322        b.arg(gate).arg(up).arg(dst).arg(&ni);
9323        unsafe {
9324            b.launch(cfg)?;
9325        }
9326        Ok(())
9327    }
9328
9329    pub fn silu_mul(
9330        &self,
9331        gate: &CudaSlice<f32>,
9332        up: &CudaSlice<f32>,
9333        dst: &mut CudaSlice<f32>,
9334        n: usize,
9335    ) -> Result<(), Box<dyn std::error::Error>> {
9336        let f = self.func("silu_mul_f32");
9337        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9338        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9339        let ni = n as i32;
9340        let __s_b = self.gpu.stream();
9341        let mut b = __s_b.launch_builder(&f);
9342        b.arg(gate).arg(up).arg(dst).arg(&ni);
9343        unsafe {
9344            b.launch(cfg)?;
9345        }
9346        Ok(())
9347    }
9348
9349    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9350    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9351    pub fn silu_mul_f16out(
9352        &self,
9353        gate: &CudaSlice<f32>,
9354        up: &CudaSlice<f32>,
9355        dst: &mut CudaSlice<f32>,
9356        dst16: &mut CudaSlice<u8>,
9357        n: usize,
9358    ) -> Result<(), Box<dyn std::error::Error>> {
9359        let f = self.func("silu_mul_f16out_f32");
9360        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9361        let ni = n as i32;
9362        let __s_b = self.gpu.stream();
9363        let mut b = __s_b.launch_builder(&f);
9364        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9365        unsafe {
9366            b.launch(cfg)?;
9367        }
9368        Ok(())
9369    }
9370
9371    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9372    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9373    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9374    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9375    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9376    /// launches per dense FFN layer (the gate+up post-matmul scales).
9377    pub fn silu_mul_scaled(
9378        &self,
9379        gate: &CudaSlice<f32>,
9380        up: &CudaSlice<f32>,
9381        gs: f32,
9382        us: f32,
9383        dst: &mut CudaSlice<f32>,
9384        n: usize,
9385    ) -> Result<(), Box<dyn std::error::Error>> {
9386        let f = self.func("silu_mul_scaled_f32");
9387        let cfg = LaunchConfig::for_num_elems(n as u32);
9388        let ni = n as i32;
9389        let (gsf, usf) = (gs, us);
9390        let __s_b = self.gpu.stream();
9391        let mut b = __s_b.launch_builder(&f);
9392        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9393        unsafe {
9394            b.launch(cfg)?;
9395        }
9396        Ok(())
9397    }
9398
9399    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9400    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9401    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9402    #[allow(clippy::too_many_arguments)]
9403    pub fn swigluoai_mul_scaled(
9404        &self,
9405        gate: &CudaSlice<f32>,
9406        up: &CudaSlice<f32>,
9407        gs: f32,
9408        us: f32,
9409        alpha: f32,
9410        limit: f32,
9411        dst: &mut CudaSlice<f32>,
9412        n: usize,
9413    ) -> Result<(), Box<dyn std::error::Error>> {
9414        let f = self.func("swigluoai_mul_scaled_f32");
9415        let cfg = LaunchConfig::for_num_elems(n as u32);
9416        let ni = n as i32;
9417        let __s_b = self.gpu.stream();
9418        let mut b = __s_b.launch_builder(&f);
9419        b.arg(gate)
9420            .arg(up)
9421            .arg(&gs)
9422            .arg(&us)
9423            .arg(&alpha)
9424            .arg(&limit)
9425            .arg(dst)
9426            .arg(&ni);
9427        unsafe {
9428            b.launch(cfg)?;
9429        }
9430        Ok(())
9431    }
9432
9433    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9434    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9435    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9436    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9437    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9438    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9439    /// n must be a multiple of 32 (n_ff always is).
9440    pub fn silu_mul_scaled_q8_1(
9441        &self,
9442        gate: &CudaSlice<f32>,
9443        up: &CudaSlice<f32>,
9444        gs: f32,
9445        us: f32,
9446        n: usize,
9447    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9448        let f = self.func("silu_mul_scaled_q8_1");
9449        let nblk = n / 32;
9450        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9451        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9452        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9453        let cfg = LaunchConfig::for_num_elems(n as u32);
9454        let (gsf, usf, ni) = (gs, us, n as i32);
9455        let __s_b = self.gpu.stream();
9456        let mut b = __s_b.launch_builder(&f);
9457        b.arg(gate)
9458            .arg(up)
9459            .arg(&gsf)
9460            .arg(&usf)
9461            .arg(&mut aq)
9462            .arg(&mut ad)
9463            .arg(&ni);
9464        unsafe {
9465            b.launch(cfg)?;
9466        }
9467        Ok((aq, ad))
9468    }
9469
9470    pub fn add(
9471        &self,
9472        a: &CudaSlice<f32>,
9473        b_in: &CudaSlice<f32>,
9474        dst: &mut CudaSlice<f32>,
9475        n: usize,
9476    ) -> Result<(), Box<dyn std::error::Error>> {
9477        let f = self.func("add_f32");
9478        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9479        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9480        let ni = n as i32;
9481        let __s_bld = self.gpu.stream();
9482        let mut bld = __s_bld.launch_builder(&f);
9483        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9484        unsafe {
9485            bld.launch(cfg)?;
9486        }
9487        Ok(())
9488    }
9489
9490    pub fn mul(
9491        &self,
9492        a: &CudaSlice<f32>,
9493        b_in: &CudaSlice<f32>,
9494        dst: &mut CudaSlice<f32>,
9495        n: usize,
9496    ) -> Result<(), Box<dyn std::error::Error>> {
9497        let f = self.func("mul_f32");
9498        let cfg = LaunchConfig::for_num_elems(n as u32);
9499        let ni = n as i32;
9500        let __s_bld = self.gpu.stream();
9501        let mut bld = __s_bld.launch_builder(&f);
9502        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9503        unsafe {
9504            bld.launch(cfg)?;
9505        }
9506        Ok(())
9507    }
9508
9509    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9510    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9511    pub fn matmul(
9512        &self,
9513        w: &crate::model::GpuTensor,
9514        x: &CudaSlice<f32>,
9515        m: usize,
9516    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9517        use crate::model::GpuTensor;
9518        let in_f = w.in_features();
9519        let out_f = w.out_features();
9520        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9521        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9522        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9523        // gives nothing). Quantize the activation once here then call the GEMM.
9524        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9525        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9526        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9527        #[allow(non_snake_case)]
9528        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9529        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9530        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9531            usize::MAX
9532        } else {
9533            16usize
9534        };
9535
9536        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9537        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9538        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9539        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9540        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9541        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9542        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9543        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9544        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9545        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9546        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9547        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9548        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9549        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9550        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9551        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9552        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9553        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9554        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9555        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9556        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9557        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9558        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9559        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9560        if m >= GEMM_M_THRESHOLD {
9561            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9562                return Ok(y);
9563            }
9564            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9565            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9566            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9567            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9568            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9569            // tile defaults differently by operand source.
9570            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9571                return Ok(y);
9572            }
9573            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
9574            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
9575            if let Some(y) = self.try_f16_gemm(w, x, m)? {
9576                return Ok(y);
9577            }
9578        }
9579        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
9580        // m threshold the rest of this method uses:
9581        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
9582        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
9583        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
9584        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
9585        //     across every tier by construction with no batched twin needed.
9586        //
9587        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
9588        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
9589        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
9590        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
9591        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
9592        // arms is what makes sure it never gets there.
9593        if let GpuTensor::Quant { qtype, .. } = w {
9594            if *qtype == QT_F8_E4M3_BLK {
9595                if m >= GEMM_M_THRESHOLD {
9596                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
9597                        return Ok(y);
9598                    }
9599                }
9600                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9601                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
9602                    return Ok(y);
9603                }
9604            }
9605        }
9606        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
9607            return self.qmatvec_mmq(w, x, m);
9608        }
9609        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
9610            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9611            return self.qmatvec_gemm(w, &aq, &ad, m);
9612        }
9613        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
9614        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
9615        if m >= GEMM_M_THRESHOLD {
9616            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
9617                return Ok(y);
9618            }
9619        }
9620        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
9621        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
9622        // to Stage-A f32-dequant (the correctness oracle path).
9623        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
9624        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
9625        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
9626        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
9627        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
9628        if m == 1 && fast {
9629            if let GpuTensor::Quant {
9630                bytes,
9631                qtype,
9632                row_bytes,
9633                rp,
9634                rp4,
9635                scale,
9636                ..
9637            } = w
9638            {
9639                if self.mmvq_supports(*qtype) {
9640                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
9641                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
9642                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
9643                    let (bytes, rp) = match rp4 {
9644                        Some(m4) => (m4, true),
9645                        None => (bytes, *rp),
9646                    };
9647                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9648                    return self.qmatvec_mmvq(
9649                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
9650                    );
9651                }
9652            }
9653        }
9654        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
9655        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
9656        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
9657        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
9658        // block below. MEMRA_NO_BATCHED -> per-m path.
9659        //
9660        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
9661        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
9662        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
9663        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
9664        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
9665        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
9666        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
9667        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
9668        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
9669        if (2..=16).contains(&m)
9670            && fast
9671            && std::env::var("MEMRA_NO_BATCHED").is_err()
9672            && (m <= 4 || Self::b8_enabled())
9673        {
9674            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
9675            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
9676            // is present (rp4) — the mirror pick below then routes to the _rp family.
9677            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
9678            // because the native e4m3 row layout is already aligned and needs no mirror.
9679            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
9680            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
9681            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
9682            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
9683            let m_ok = m <= 8
9684                || matches!(w, GpuTensor::Quant { qtype, .. }
9685                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
9686                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
9687            if m_ok {
9688                if let GpuTensor::Quant {
9689                    bytes,
9690                    qtype,
9691                    row_bytes,
9692                    rp,
9693                    rp4,
9694                    ..
9695                } = w
9696                {
9697                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
9698                        let (bytes, rp) = match rp4 {
9699                            Some(m4) => (m4, true),
9700                            None => (bytes, *rp),
9701                        };
9702                        let mcols = Self::batched_mcols(m);
9703                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9704                        let mut y = self.qmatvec_mmvq_batched(
9705                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
9706                        )?;
9707                        if let GpuTensor::Quant { scale, .. } = w {
9708                            if *scale != 1.0 {
9709                                self.scale_inplace(&mut y, *scale, m * out_f)?;
9710                            }
9711                        }
9712                        return Ok(y);
9713                    }
9714                }
9715            }
9716        }
9717        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
9718        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
9719        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
9720        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
9721        // for this dtype, so the generic match below must never see it under `fast`.
9722        if fast {
9723            if let GpuTensor::Quant {
9724                bytes,
9725                qtype,
9726                row_bytes,
9727                scale,
9728                ..
9729            } = w
9730            {
9731                if *qtype == QT_F8_E4M3 {
9732                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9733                    return self.qmatvec_mmvq(
9734                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
9735                    );
9736                }
9737            }
9738        }
9739        let mut y = match w {
9740            GpuTensor::Quant {
9741                bytes,
9742                qtype,
9743                row_bytes,
9744                ..
9745            } if fast && *qtype == QT_Q8_0 => {
9746                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9747            }
9748            GpuTensor::Quant {
9749                bytes,
9750                qtype,
9751                row_bytes,
9752                ..
9753            } if fast && *qtype == QT_Q4_K => {
9754                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9755            }
9756            GpuTensor::Quant {
9757                bytes,
9758                qtype,
9759                row_bytes,
9760                ..
9761            } if fast && *qtype == QT_Q6_K => {
9762                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9763            }
9764            GpuTensor::Quant {
9765                bytes,
9766                qtype,
9767                row_bytes,
9768                ..
9769            } if fast && *qtype == QT_Q5_K => {
9770                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9771            }
9772            GpuTensor::Quant {
9773                bytes,
9774                qtype,
9775                row_bytes,
9776                ..
9777            } if fast && *qtype == QT_Q3_K => {
9778                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9779            }
9780            GpuTensor::Quant {
9781                bytes,
9782                qtype,
9783                row_bytes,
9784                rp,
9785                ..
9786            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
9787                if *rp {
9788                    "qmatvec_nvfp4_dp4a_rp"
9789                } else {
9790                    "qmatvec_nvfp4_dp4a"
9791                },
9792                bytes,
9793                x,
9794                m,
9795                in_f,
9796                out_f,
9797                *row_bytes,
9798            )?,
9799            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
9800            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
9801            // anomaly (research/kat-anomaly-20260802/).
9802            GpuTensor::Quant {
9803                bytes,
9804                qtype,
9805                row_bytes,
9806                ..
9807            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
9808                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9809            }
9810            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
9811            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
9812            // without first writing the matching kernel, or func() will panic
9813            // "kernel ... not in any fatbin".
9814            GpuTensor::Quant {
9815                bytes,
9816                qtype,
9817                row_bytes,
9818                rp,
9819                ..
9820            } =>
9821            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
9822            // deq(row,j) form cannot address the planes; same value/product order).
9823            {
9824                self.qmatvec(
9825                    bytes,
9826                    x,
9827                    m,
9828                    in_f,
9829                    out_f,
9830                    if *rp && *qtype == QT_NVFP4 {
9831                        QT_NVFP4_RP
9832                    } else {
9833                        *qtype
9834                    },
9835                    *row_bytes,
9836                )?
9837            }
9838            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
9839            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
9840            // cuBLASLt f32 GEMV as the Float arm.
9841            GpuTensor::FloatBf16 { data, .. } => {
9842                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
9843            }
9844        };
9845        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
9846        if let GpuTensor::Quant { scale, .. } = w {
9847            if *scale != 1.0 {
9848                self.scale_inplace(&mut y, *scale, m * out_f)?;
9849            }
9850        }
9851        Ok(y)
9852    }
9853
9854    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
9855    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
9856    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
9857        use crate::model::GpuTensor;
9858        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
9859            return false;
9860        }
9861        match w {
9862            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
9863            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
9864            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
9865            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
9866            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
9867            // block class has no fused twin yet, so each of its projections takes its own launch.
9868            GpuTensor::Quant { qtype, .. } => {
9869                matches!(
9870                    *qtype,
9871                    QT_Q8_0
9872                        | QT_Q4_K
9873                        | QT_Q6_K
9874                        | QT_Q5_K
9875                        | QT_Q3_K
9876                        | QT_NVFP4
9877                        | QT_F8_E4M3
9878                        | QT_F8_E4M3_BLK
9879                        | QT_Q4_0
9880                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
9881            }
9882            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
9883        }
9884    }
9885
9886    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
9887    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
9888    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
9889    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
9890    pub fn matmul_pre(
9891        &self,
9892        w: &crate::model::GpuTensor,
9893        aq: &CudaSlice<i8>,
9894        ad: &CudaSlice<f32>,
9895        x_fallback: &CudaSlice<f32>,
9896        m: usize,
9897    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9898        use crate::model::GpuTensor;
9899        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
9900        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
9901        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
9902        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
9903        // rc=30013 dig, 2026-07-31).
9904        let x_raw_ok = x_fallback.len() >= m * w.in_features();
9905        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
9906        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
9907        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9908            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
9909                return Ok(y);
9910            }
9911            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
9912            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
9913            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
9914                return Ok(y);
9915            }
9916            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
9917            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
9918                return Ok(y);
9919            }
9920        }
9921        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
9922        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
9923        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
9924        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
9925        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
9926        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9927            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
9928                return Ok(y);
9929            }
9930        }
9931        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
9932            return Ok(y);
9933        }
9934        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
9935        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
9936        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
9937        // aq/ad.
9938        if m >= 16
9939            && w.out_features() >= 128
9940            && self.mmq_supports(w)
9941            && !self.verify_exact_on()
9942            && x_raw_ok
9943        {
9944            return self.qmatvec_mmq(w, x_fallback, m);
9945        }
9946        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
9947        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
9948        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9949            if let Some(y) =
9950                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
9951            {
9952                return Ok(y);
9953            }
9954        }
9955        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
9956        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
9957        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
9958            return self.qmatvec_gemm(w, aq, ad, m);
9959        }
9960        if !self.uses_q8_1_fast(w) {
9961            return self.matmul(w, x_fallback, m);
9962        }
9963        let in_f = w.in_features();
9964        let out_f = w.out_features();
9965        let (bytes, qtype, row_bytes, scale, rp) = match w {
9966            GpuTensor::Quant {
9967                bytes,
9968                qtype,
9969                row_bytes,
9970                scale,
9971                rp,
9972                ..
9973            } => (bytes, *qtype, *row_bytes, *scale, *rp),
9974            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
9975        };
9976        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
9977        // the dp4a/oracle tails below keep the raw GGUF bytes.
9978        let (mbytes, mrp) = match w {
9979            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
9980            _ => (bytes, rp),
9981        };
9982        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
9983        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
9984        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
9985        if m == 1 && self.mmvq_supports(qtype) {
9986            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
9987        }
9988        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
9989        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
9990        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
9991        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
9992        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
9993        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
9994        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
9995        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
9996        // m=5..8 on the old per-m path (b8-tier-only seam).
9997        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
9998        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
9999        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
10000        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10001            && std::env::var("MEMRA_NO_BATCHED").is_err()
10002            && (m <= 4 || Self::b8_enabled())
10003            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
10004            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
10005            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
10006            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
10007                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
10008        {
10009            let mcols = Self::batched_mcols(m);
10010            return self.qmatvec_mmvq_batched(
10011                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
10012            );
10013        }
10014        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
10015        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
10016        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
10017        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
10018        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
10019        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
10020            let (b2, r2) = if qtype == QT_Q4_0 {
10021                (mbytes, mrp)
10022            } else {
10023                (bytes, rp)
10024            };
10025            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
10026        }
10027        let name = match qtype {
10028            QT_Q8_0 => "qmatvec_q8_0_dp4a",
10029            QT_Q4_K => "qmatvec_q4_K_dp4a",
10030            QT_Q6_K => "qmatvec_q6_K_dp4a",
10031            QT_Q5_K => "qmatvec_q5_K_dp4a",
10032            QT_Q3_K => "qmatvec_q3_K_dp4a",
10033            QT_NVFP4 => {
10034                if rp {
10035                    "qmatvec_nvfp4_dp4a_rp"
10036                } else {
10037                    "qmatvec_nvfp4_dp4a"
10038                }
10039            }
10040            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
10041            _ => unreachable!(),
10042        };
10043        let f = self.func(name);
10044        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
10045        let cfg = LaunchConfig {
10046            grid_dim: (out_f as u32, m as u32, 1),
10047            block_dim: (128, 1, 1),
10048            shared_mem_bytes: 0,
10049        };
10050        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10051        let __s_b = self.gpu.stream();
10052        let mut b = __s_b.launch_builder(&f);
10053        b.arg(bytes)
10054            .arg(aq)
10055            .arg(ad)
10056            .arg(&mut y)
10057            .arg(&inf)
10058            .arg(&outf)
10059            .arg(&mi)
10060            .arg(&rb);
10061        unsafe {
10062            b.launch(cfg)?;
10063        }
10064        if scale != 1.0 {
10065            self.scale_inplace(&mut y, scale, m * out_f)?;
10066        }
10067        Ok(y)
10068    }
10069
10070    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
10071    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
10072    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
10073    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
10074    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
10075    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
10076    /// reduce as m=1); this method just forces that path unconditionally.
10077    pub fn matmul_decode_exact(
10078        &self,
10079        w: &crate::model::GpuTensor,
10080        x: &CudaSlice<f32>,
10081        m: usize,
10082    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10083        use crate::model::GpuTensor;
10084        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
10085        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
10086        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
10087        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
10088        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
10089        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
10090        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
10091        if let GpuTensor::Float { data, .. } = w {
10092            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
10093        }
10094        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
10095        // float linear (same n-independent reduction contract as the Float arm above).
10096        if let GpuTensor::FloatBf16 { data, .. } = w {
10097            let (in_f, out_f) = (w.in_features(), w.out_features());
10098            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
10099        }
10100        if !self.uses_q8_1_fast(w) {
10101            return self.matmul(w, x, m);
10102        }
10103        let in_f = w.in_features();
10104        let out_f = w.out_features();
10105        let (bytes, qtype, row_bytes, scale, rp) = match w {
10106            GpuTensor::Quant {
10107                bytes,
10108                qtype,
10109                row_bytes,
10110                scale,
10111                rp,
10112                ..
10113            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10114            _ => return self.matmul(w, x, m),
10115        };
10116        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
10117        // which does its own mirror pick).
10118        let (bytes, rp) = match w {
10119            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10120            _ => (bytes, rp),
10121        };
10122        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10123        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10124        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10125        // (token,row) by construction, which is exactly what this method exists to guarantee.
10126        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10127            return Ok(y);
10128        }
10129        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10130        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10131        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10132        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10133        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10134        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10135        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10136        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10137        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10138            && std::env::var("MEMRA_NO_BATCHED").is_err()
10139            && (m <= 4 || Self::b8_enabled())
10140            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10141            // no mirror precondition, `rp` selects the layout only.
10142            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10143                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10144        {
10145            let mcols = Self::batched_mcols(m);
10146            return self.qmatvec_mmvq_batched(
10147                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10148            );
10149        }
10150        if self.mmvq_supports(qtype) {
10151            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10152            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10153            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10154        }
10155        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10156        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10157        self.matmul_pre(w, &aq, &ad, x, m)
10158    }
10159
10160    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10161    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10162    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10163    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10164    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10165    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10166    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10167    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10168    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10169    pub fn matmul_decode_exact_pre(
10170        &self,
10171        w: &crate::model::GpuTensor,
10172        aq: &CudaSlice<i8>,
10173        ad: &CudaSlice<f32>,
10174        m: usize,
10175    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10176        use crate::model::GpuTensor;
10177        debug_assert!(
10178            self.uses_q8_1_fast(w),
10179            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10180        );
10181        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10182        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10183            return Ok(y);
10184        }
10185        let in_f = w.in_features();
10186        let out_f = w.out_features();
10187        let (bytes, qtype, row_bytes, scale, rp) = match w {
10188            GpuTensor::Quant {
10189                bytes,
10190                qtype,
10191                row_bytes,
10192                scale,
10193                rp,
10194                ..
10195            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10196            _ => {
10197                return Err(
10198                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10199                );
10200            }
10201        };
10202        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10203        let (bytes, rp) = match w {
10204            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10205            _ => (bytes, rp),
10206        };
10207        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10208        if (2..=16).contains(&m)
10209            && self.batched_supports(qtype)
10210            && self.mmvq_supports(qtype)
10211            && std::env::var("MEMRA_NO_BATCHED").is_err()
10212            && (m <= 4 || Self::b8_enabled())
10213            && (m <= 8
10214                || qtype == QT_Q4_0
10215                || qtype == QT_Q6_K
10216                || qtype == QT_F8_E4M3
10217                || qtype == QT_NVFP4
10218                || qtype == QT_Q4_K
10219                || qtype == QT_Q5_K
10220                || qtype == QT_Q8_0)
10221        {
10222            let mcols = Self::batched_mcols(m);
10223            return self.qmatvec_mmvq_batched(
10224                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10225            );
10226        }
10227        if self.mmvq_supports(qtype) {
10228            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10229        }
10230        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10231        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10232        let x0 = self.zeros(0)?;
10233        self.matmul_pre(w, aq, ad, &x0, m)
10234    }
10235
10236    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10237    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10238    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10239    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10240    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10241    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10242    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10243    /// per-tensor path.
10244    pub fn matmul_decode_exact_dual_pre(
10245        &self,
10246        w0: &crate::model::GpuTensor,
10247        w1: &crate::model::GpuTensor,
10248        aq: &CudaSlice<i8>,
10249        ad: &CudaSlice<f32>,
10250        m: usize,
10251    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10252    {
10253        use crate::model::GpuTensor;
10254        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10255        let on = *ON.get_or_init(|| {
10256            std::env::var("MEMRA_SPEC_DUAL_T")
10257                .map(|v| v != "0")
10258                .unwrap_or(true)
10259        });
10260        if !on
10261            || !(2..=7).contains(&m)
10262            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10263            || !self.uses_q8_1_fast(w0)
10264            || !self.uses_q8_1_fast(w1)
10265        {
10266            return Ok(None);
10267        }
10268        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10269        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10270        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10271        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10272        if !self.mmvq_supports(QT_NVFP4) {
10273            return Ok(None);
10274        }
10275        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10276        if w1.in_features() != in_f || w1.out_features() != out_f {
10277            return Ok(None);
10278        }
10279        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10280            (
10281                GpuTensor::Quant {
10282                    bytes: b0,
10283                    qtype: q0,
10284                    row_bytes: rb0,
10285                    scale: s0,
10286                    rp: rp0,
10287                    rp4: None,
10288                    ..
10289                },
10290                GpuTensor::Quant {
10291                    bytes: b1,
10292                    qtype: q1,
10293                    row_bytes: rb1,
10294                    scale: s1,
10295                    rp: rp1,
10296                    rp4: None,
10297                    ..
10298                },
10299            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10300                (b0, b1, *rb0, *s0, *s1, *rp0)
10301            }
10302            _ => return Ok(None),
10303        };
10304        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10305        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10306        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10307        {
10308            return Ok(None);
10309        }
10310        let (y0, y1) =
10311            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10312        Ok(Some(((y0, s0), (y1, s1))))
10313    }
10314
10315    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10316    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10317    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10318    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10319    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10320    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10321    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10322    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10323    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10324    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10325    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10326    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10327    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10328    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10329    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10330    pub fn matmul_decode_exact_dual(
10331        &self,
10332        w0: &crate::model::GpuTensor,
10333        w1: &crate::model::GpuTensor,
10334        x: &CudaSlice<f32>,
10335        m: usize,
10336    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10337        use crate::model::GpuTensor;
10338        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10339        let on = *ON.get_or_init(|| {
10340            std::env::var("MEMRA_SPEC_DUAL_T")
10341                .map(|v| v != "0")
10342                .unwrap_or(true)
10343        });
10344        if !on
10345            || !(2..=4).contains(&m)
10346            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10347            || !self.uses_q8_1_fast(w0)
10348            || !self.uses_q8_1_fast(w1)
10349        {
10350            return Ok(None);
10351        }
10352        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10353        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10354        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10355        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10356        if !self.mmvq_supports(QT_NVFP4) {
10357            return Ok(None);
10358        }
10359        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10360        if w1.in_features() != in_f || w1.out_features() != out_f {
10361            return Ok(None);
10362        }
10363        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10364            (
10365                GpuTensor::Quant {
10366                    bytes: b0,
10367                    qtype: q0,
10368                    row_bytes: rb0,
10369                    scale: s0,
10370                    rp: rp0,
10371                    rp4: None,
10372                    ..
10373                },
10374                GpuTensor::Quant {
10375                    bytes: b1,
10376                    qtype: q1,
10377                    row_bytes: rb1,
10378                    scale: s1,
10379                    rp: rp1,
10380                    rp4: None,
10381                    ..
10382                },
10383            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10384                (b0, b1, *rb0, *s0, *s1, *rp0)
10385            }
10386            _ => return Ok(None),
10387        };
10388        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10389        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10390        if std::env::var("MEMRA_DEBUG").is_ok() {
10391            static ONCE: std::sync::Once = std::sync::Once::new();
10392            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10393        }
10394        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10395        let (y0, y1) =
10396            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10397        let mut y0 = y0;
10398        let mut y1 = y1;
10399        if s0 != 1.0 {
10400            self.scale_inplace(&mut y0, s0, m * out_f)?;
10401        }
10402        if s1 != 1.0 {
10403            self.scale_inplace(&mut y1, s1, m * out_f)?;
10404        }
10405        Ok(Some((y0, y1)))
10406    }
10407
10408    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10409    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10410    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10411    /// twins (both buffers must be the repacked layout).
10412    #[allow(clippy::too_many_arguments)]
10413    pub fn qmatvec_batched_dual_raw(
10414        &self,
10415        b0: &CudaSlice<u8>,
10416        b1: &CudaSlice<u8>,
10417        aq: &CudaSlice<i8>,
10418        ad: &CudaSlice<f32>,
10419        m: usize,
10420        in_f: usize,
10421        out_f: usize,
10422        row_bytes: usize,
10423        rp: bool,
10424    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10425        const ROWS_PER_BLOCK: u32 = 4;
10426        let mcols = Self::batched_mcols(m);
10427        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10428        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10429        let tiny_rp1 = rp
10430            && mcols == 4
10431            && out_f <= 128
10432            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10433        let (name, rows_per_block) = if tiny_rp1 {
10434            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10435        } else {
10436            match (mcols, rp, m) {
10437                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10438                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10439                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10440                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10441                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10442                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10443                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10444                _ => {
10445                    return Err(
10446                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10447                    );
10448                }
10449            }
10450        };
10451        let f = self.func(name);
10452        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10453        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10454        let cfg = LaunchConfig {
10455            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10456            block_dim: (32, ROWS_PER_BLOCK, 1),
10457            shared_mem_bytes: 0,
10458        };
10459        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10460        let __s_b = self.gpu.stream();
10461        let mut b = __s_b.launch_builder(&f);
10462        b.arg(b0)
10463            .arg(b1)
10464            .arg(aq)
10465            .arg(ad)
10466            .arg(&mut y0)
10467            .arg(&mut y1)
10468            .arg(&inf)
10469            .arg(&outf)
10470            .arg(&mi)
10471            .arg(&rb);
10472        unsafe {
10473            b.launch(cfg)?;
10474        }
10475        Ok((y0, y1))
10476    }
10477
10478    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10479    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10480    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10481    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10482    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10483    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10484    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10485    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10486    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10487    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10488    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10489    pub fn matmul_pre_dual_noscale(
10490        &self,
10491        w0: &crate::model::GpuTensor,
10492        w1: &crate::model::GpuTensor,
10493        aq: &CudaSlice<i8>,
10494        ad: &CudaSlice<f32>,
10495        m: usize,
10496    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10497    {
10498        use crate::model::GpuTensor;
10499        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10500            return Ok(None);
10501        }
10502        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10503        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10504        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10505        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10506        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10507        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10508        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10509        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10510        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10511        if !self.mmvq_supports(QT_NVFP4) {
10512            return Ok(None);
10513        }
10514        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10515        if w1.in_features() != in_f || w1.out_features() != out_f {
10516            return Ok(None);
10517        }
10518        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10519        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10520        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10521        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10522        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10523        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10524        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10525        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10526        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
10527        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
10528        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
10529        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
10530        let no_mirror =
10531            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
10532        if self.q8_ffn_fuse2_on()
10533            && no_mirror(w0)
10534            && no_mirror(w1)
10535            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
10536        {
10537            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
10538            return Ok(Some(((y0, 1.0), (y1, 1.0))));
10539        }
10540        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
10541        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
10542        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
10543        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
10544        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
10545        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
10546        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
10547        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
10548        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
10549        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10550            let (y0, y1) =
10551                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
10552            return Ok(Some(((y0, p0.3), (y1, p1.3))));
10553        }
10554        let (b0, q0, rb0, s0, rp0) = match w0 {
10555            GpuTensor::Quant {
10556                bytes,
10557                qtype,
10558                row_bytes,
10559                scale,
10560                rp,
10561                ..
10562            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10563            _ => return Ok(None),
10564        };
10565        let (b1, q1, rb1, s1, rp1) = match w1 {
10566            GpuTensor::Quant {
10567                bytes,
10568                qtype,
10569                row_bytes,
10570                scale,
10571                rp,
10572                ..
10573            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10574            _ => return Ok(None),
10575        };
10576        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
10577            return Ok(None);
10578        }
10579        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10580        const RPW: u32 = 2;
10581        let rows_per_block = ROWS_PER_BLOCK * RPW;
10582        let f = self.func(if rp0 {
10583            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
10584        } else {
10585            "qmatvec_nvfp4_mmvq_dual_mr2"
10586        });
10587        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
10588        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
10589        let cfg = LaunchConfig {
10590            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10591            block_dim: (32, ROWS_PER_BLOCK, 1),
10592            shared_mem_bytes: 0,
10593        };
10594        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
10595        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
10596        // yscale args stay 1.0 here (they exist for the single-tensor callers).
10597        let one = 1.0f32;
10598        let __s_b = self.gpu.stream();
10599        let mut b = __s_b.launch_builder(&f);
10600        b.arg(b0)
10601            .arg(b1)
10602            .arg(aq)
10603            .arg(ad)
10604            .arg(&mut y0)
10605            .arg(&mut y1)
10606            .arg(&inf)
10607            .arg(&outf)
10608            .arg(&mi)
10609            .arg(&rb)
10610            .arg(&one)
10611            .arg(&one);
10612        unsafe {
10613            b.launch(cfg)?;
10614        }
10615        Ok(Some(((y0, s0), (y1, s1))))
10616    }
10617
10618    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
10619    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
10620    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
10621    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
10622    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
10623    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
10624    /// back to the three singles.
10625    #[allow(clippy::too_many_arguments)]
10626    pub fn matmul_nvfp4_fused3(
10627        &self,
10628        w0: &crate::model::GpuTensor,
10629        w1: &crate::model::GpuTensor,
10630        w2: &crate::model::GpuTensor,
10631        aq: &CudaSlice<i8>,
10632        ad: &CudaSlice<f32>,
10633        m: usize,
10634    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
10635    {
10636        use crate::model::GpuTensor;
10637        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
10638        // read serves all m rows); the fused segments would re-read the weight per row. The
10639        // fusion win is the B=1 decode tick.
10640        if m != 1
10641            || !self.mmvq_supports(QT_NVFP4)
10642            || !self.uses_q8_1_fast(w0)
10643            || !self.uses_q8_1_fast(w1)
10644            || !self.uses_q8_1_fast(w2)
10645        {
10646            return Ok(None);
10647        }
10648        let unpack = |w: &crate::model::GpuTensor| match w {
10649            GpuTensor::Quant {
10650                bytes,
10651                qtype,
10652                scale,
10653                rp,
10654                ..
10655            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
10656            _ => None,
10657        };
10658        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
10659            return Ok(None);
10660        };
10661        let in_f = w0.in_features();
10662        if w1.in_features() != in_f || w2.in_features() != in_f {
10663            return Ok(None);
10664        }
10665        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
10666        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
10667        const RPW: u32 = 2;
10668        let rows_pb = ROWS_PER_BLOCK * RPW;
10669        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
10670        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
10671        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
10672        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
10673        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
10674        let cfg = LaunchConfig {
10675            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
10676            block_dim: (32, ROWS_PER_BLOCK, 1),
10677            shared_mem_bytes: 0,
10678        };
10679        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
10680        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
10681        // only dereferenced for the launch-arg build inside this call.
10682        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
10683        let __s_b = self.gpu.stream();
10684        let mut b = __s_b.launch_builder(&f);
10685        b.arg(b0)
10686            .arg(b1)
10687            .arg(b2)
10688            .arg(aq)
10689            .arg(ad)
10690            .arg(&mut y0)
10691            .arg(&mut y1)
10692            .arg(&mut y2)
10693            .arg(&inf)
10694            .arg(&oi0)
10695            .arg(&oi1)
10696            .arg(&oi2)
10697            .arg(&mi)
10698            .arg(&p0.1)
10699            .arg(&p1.1)
10700            .arg(&p2.1);
10701        unsafe {
10702            b.launch(cfg)?;
10703        }
10704        Ok(Some((y0, y1, y2)))
10705    }
10706
10707    /// fused4 twin of `matmul_nvfp4_fused3`: the Linear-mixer projection quartet
10708    /// (wqkv + wqkv_gate + ssm_beta + ssm_alpha) in one launch, m==1 only. Per
10709    /// (tensor,row) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM — bit-identical
10710    /// to four separate launches (rig-native decode increment 2, RIG-NATIVE-DECODE.md).
10711    #[allow(clippy::type_complexity)]
10712    pub fn matmul_nvfp4_fused4(
10713        &self,
10714        w0: &crate::model::GpuTensor,
10715        w1: &crate::model::GpuTensor,
10716        w2: &crate::model::GpuTensor,
10717        w3: &crate::model::GpuTensor,
10718        aq: &CudaSlice<i8>,
10719        ad: &CudaSlice<f32>,
10720        m: usize,
10721    ) -> Result<
10722        Option<(
10723            CudaSlice<f32>,
10724            CudaSlice<f32>,
10725            CudaSlice<f32>,
10726            CudaSlice<f32>,
10727        )>,
10728        Box<dyn std::error::Error>,
10729    > {
10730        use crate::model::GpuTensor;
10731        // MEMRA_NVFP4_FUSED4=0: rollback seam + the same-binary interleaved A/B arm.
10732        if m != 1
10733            || std::env::var("MEMRA_NVFP4_FUSED4").as_deref() == Ok("0")
10734            || !self.mmvq_supports(QT_NVFP4)
10735            || !self.uses_q8_1_fast(w0)
10736            || !self.uses_q8_1_fast(w1)
10737            || !self.uses_q8_1_fast(w2)
10738            || !self.uses_q8_1_fast(w3)
10739        {
10740            return Ok(None);
10741        }
10742        let unpack = |w: &crate::model::GpuTensor| match w {
10743            GpuTensor::Quant {
10744                bytes,
10745                qtype,
10746                scale,
10747                rp,
10748                ..
10749            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
10750            _ => None,
10751        };
10752        let (Some(p0), Some(p1), Some(p2), Some(p3)) =
10753            (unpack(w0), unpack(w1), unpack(w2), unpack(w3))
10754        else {
10755            return Ok(None);
10756        };
10757        let in_f = w0.in_features();
10758        if w1.in_features() != in_f || w2.in_features() != in_f || w3.in_features() != in_f {
10759            return Ok(None);
10760        }
10761        let (o0, o1, o2, o3) = (
10762            w0.out_features(),
10763            w1.out_features(),
10764            w2.out_features(),
10765            w3.out_features(),
10766        );
10767        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
10768        const RPW: u32 = 2;
10769        let rows_pb = ROWS_PER_BLOCK * RPW;
10770        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
10771        let f = self.func("qmatvec_nvfp4_mmvq_fused4_rp");
10772        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
10773        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
10774        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
10775        let mut y3 = self.alloc_uninit::<f32>(m * o3)?;
10776        let cfg = LaunchConfig {
10777            grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), m as u32, 1),
10778            block_dim: (32, ROWS_PER_BLOCK, 1),
10779            shared_mem_bytes: 0,
10780        };
10781        let (inf, oi0, oi1, oi2, oi3, mi) = (
10782            in_f as i32,
10783            o0 as i32,
10784            o1 as i32,
10785            o2 as i32,
10786            o3 as i32,
10787            m as i32,
10788        );
10789        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
10790        // only dereferenced for the launch-arg build inside this call.
10791        let (b0, b1, b2, b3) = unsafe { (&*p0.0, &*p1.0, &*p2.0, &*p3.0) };
10792        let __s_b = self.gpu.stream();
10793        let mut b = __s_b.launch_builder(&f);
10794        b.arg(b0)
10795            .arg(b1)
10796            .arg(b2)
10797            .arg(b3)
10798            .arg(aq)
10799            .arg(ad)
10800            .arg(&mut y0)
10801            .arg(&mut y1)
10802            .arg(&mut y2)
10803            .arg(&mut y3)
10804            .arg(&inf)
10805            .arg(&oi0)
10806            .arg(&oi1)
10807            .arg(&oi2)
10808            .arg(&oi3)
10809            .arg(&mi)
10810            .arg(&p0.1)
10811            .arg(&p1.1)
10812            .arg(&p2.1)
10813            .arg(&p3.1);
10814        unsafe {
10815            b.launch(cfg)?;
10816        }
10817        Ok(Some((y0, y1, y2, y3)))
10818    }
10819
10820    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
10821    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
10822    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
10823    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
10824    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
10825    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
10826    /// back to the per-tensor path.
10827    pub fn matmul_q8_fused2(
10828        &self,
10829        w0: &crate::model::GpuTensor,
10830        w1: &crate::model::GpuTensor,
10831        aq: &CudaSlice<i8>,
10832        ad: &CudaSlice<f32>,
10833    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10834        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
10835        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
10836        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
10837        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
10838        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
10839        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10840            return Ok(Some(self.e4m3_fused2_core(
10841                p0.0,
10842                p1.0,
10843                aq,
10844                ad,
10845                w0.in_features(),
10846                p0.1,
10847                p1.1,
10848                p0.2,
10849                p0.3,
10850                p1.3,
10851            )?));
10852        }
10853        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10854            return Ok(None);
10855        };
10856        Ok(Some(self.q8_fused2_core(
10857            p0.0,
10858            p1.0,
10859            aq,
10860            ad,
10861            w0.in_features(),
10862            p0.1,
10863            p1.1,
10864            p0.2,
10865        )?))
10866    }
10867
10868    #[allow(clippy::too_many_arguments)]
10869    fn q8_fused2_core(
10870        &self,
10871        b0: &CudaSlice<u8>,
10872        b1: &CudaSlice<u8>,
10873        aq: &CudaSlice<i8>,
10874        ad: &CudaSlice<f32>,
10875        in_f: usize,
10876        out0: usize,
10877        out1: usize,
10878        row_bytes: usize,
10879    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10880        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10881        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
10882        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
10883        let f = self.func("qmatvec_q8_0_mmvq_fused2");
10884        let mut y0 = self.alloc_uninit::<f32>(out0)?;
10885        let mut y1 = self.alloc_uninit::<f32>(out1)?;
10886        let cfg = LaunchConfig {
10887            grid_dim: (nb0 + nb1, 1, 1),
10888            block_dim: (32, ROWS_PER_BLOCK, 1),
10889            shared_mem_bytes: 0,
10890        };
10891        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
10892        let __s_b = self.gpu.stream();
10893        let mut b = __s_b.launch_builder(&f);
10894        b.arg(b0)
10895            .arg(b1)
10896            .arg(aq)
10897            .arg(ad)
10898            .arg(&mut y0)
10899            .arg(&mut y1)
10900            .arg(&inf)
10901            .arg(&o0)
10902            .arg(&o1)
10903            .arg(&rbl);
10904        unsafe {
10905            b.launch(cfg)?;
10906        }
10907        Ok((y0, y1))
10908    }
10909
10910    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
10911    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
10912    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
10913    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
10914    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
10915    pub fn matmul_q8_fused2_x(
10916        &self,
10917        w0: &crate::model::GpuTensor,
10918        w1: &crate::model::GpuTensor,
10919        x: &CudaSlice<f32>,
10920    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10921        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10922            return Ok(None);
10923        }
10924        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10925            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10926            return Ok(Some(self.e4m3_fused2_core(
10927                p0.0,
10928                p1.0,
10929                &aq,
10930                &ad,
10931                w0.in_features(),
10932                p0.1,
10933                p1.1,
10934                p0.2,
10935                p0.3,
10936                p1.3,
10937            )?));
10938        }
10939        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10940            return Ok(None);
10941        };
10942        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10943        Ok(Some(self.q8_fused2_core(
10944            p0.0,
10945            p1.0,
10946            &aq,
10947            &ad,
10948            w0.in_features(),
10949            p0.1,
10950            p1.1,
10951            p0.2,
10952        )?))
10953    }
10954
10955    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
10956    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
10957    #[allow(clippy::too_many_arguments)]
10958    pub fn qmatvec_q8_fused2_raw(
10959        &self,
10960        b0: &CudaSlice<u8>,
10961        b1: &CudaSlice<u8>,
10962        x: &CudaSlice<f32>,
10963        in_f: usize,
10964        out0: usize,
10965        out1: usize,
10966        row_bytes: usize,
10967    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10968        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
10969        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
10970    }
10971
10972    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
10973    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
10974    /// (tensor,row) to three separate m=1 MMVQ launches.
10975    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
10976    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
10977    pub fn matmul_q4_fused3(
10978        &self,
10979        w0: &crate::model::GpuTensor,
10980        w1: &crate::model::GpuTensor,
10981        w2: &crate::model::GpuTensor,
10982        aq: &CudaSlice<i8>,
10983        ad: &CudaSlice<f32>,
10984    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
10985    {
10986        use crate::model::GpuTensor;
10987        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10988            match w {
10989                GpuTensor::Quant {
10990                    qtype, row_bytes, ..
10991                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10992                _ => None,
10993            }
10994        };
10995        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
10996            return Ok(None);
10997        };
10998        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
10999            return Ok(None);
11000        }
11001        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
11002        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
11003        // the separate matvecs (each routes its own rp).
11004        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11005            match w {
11006                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11007                    Some(m) => (m, true),
11008                    None => (bytes, *rp),
11009                },
11010                _ => unreachable!(),
11011            }
11012        }
11013        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11014        if rp0 != rp1 || rp1 != rp2 {
11015            return Ok(None);
11016        }
11017        let rp = rp0;
11018        let rpb: u32 = 4;
11019        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
11020        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
11021        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
11022        let mr1 = rp && Self::q40_mr1_on();
11023        let nb = |o: usize| {
11024            if mr1 {
11025                (o as u32).div_ceil(rpb)
11026            } else {
11027                (o as u32).div_ceil(2).div_ceil(rpb)
11028            }
11029        };
11030        let grid = nb(o0) + nb(o1) + nb(o2);
11031        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11032        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11033        let mut y2 = self.alloc_uninit::<f32>(o2)?;
11034        let f = self.func(if mr1 {
11035            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11036        } else if rp {
11037            "qmatvec_q4_0_mmvq_fused3_rp"
11038        } else {
11039            "qmatvec_q4_0_mmvq_fused3"
11040        });
11041        let cfg = LaunchConfig {
11042            grid_dim: (grid, 1, 1),
11043            block_dim: (32, rpb, 1),
11044            shared_mem_bytes: 0,
11045        };
11046        let inf = w0.in_features() as i32;
11047        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11048        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11049        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
11050        // variant may take the programmatic-serialization launch.
11051        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11052            {
11053                use cudarc::driver::{DevicePtr, DevicePtrMut};
11054                let s = &self.gpu.stream();
11055                let (p0, _g0) = b0.device_ptr(s);
11056                let (p1, _g1) = b1.device_ptr(s);
11057                let (p2, _g2) = b2.device_ptr(s);
11058                let (paq, _g3) = aq.device_ptr(s);
11059                let (pad, _g4) = ad.device_ptr(s);
11060                let (py0, _g5) = y0.device_ptr_mut(s);
11061                let (py1, _g6) = y1.device_ptr_mut(s);
11062                let (py2, _g7) = y2.device_ptr_mut(s);
11063                let mut ps = [
11064                    &p0 as *const _ as *mut std::ffi::c_void,
11065                    &p1 as *const _ as *mut _,
11066                    &p2 as *const _ as *mut _,
11067                    &paq as *const _ as *mut _,
11068                    &pad as *const _ as *mut _,
11069                    &py0 as *const _ as *mut _,
11070                    &py1 as *const _ as *mut _,
11071                    &py2 as *const _ as *mut _,
11072                    &inf as *const _ as *mut _,
11073                    &oo0 as *const _ as *mut _,
11074                    &oo1 as *const _ as *mut _,
11075                    &oo2 as *const _ as *mut _,
11076                    &r0 as *const _ as *mut _,
11077                    &r1 as *const _ as *mut _,
11078                    &r2 as *const _ as *mut _,
11079                ];
11080                unsafe {
11081                    self.launch_pdl(
11082                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11083                        (grid, 1, 1),
11084                        (32, rpb, 1),
11085                        &mut ps,
11086                    )?;
11087                }
11088            }
11089            return Ok(Some((y0, y1, y2)));
11090        }
11091        let __s_b = self.gpu.stream();
11092        let mut b = __s_b.launch_builder(&f);
11093        b.arg(b0)
11094            .arg(b1)
11095            .arg(b2)
11096            .arg(aq)
11097            .arg(ad)
11098            .arg(&mut y0)
11099            .arg(&mut y1)
11100            .arg(&mut y2)
11101            .arg(&inf)
11102            .arg(&oo0)
11103            .arg(&oo1)
11104            .arg(&oo2)
11105            .arg(&r0)
11106            .arg(&r1)
11107            .arg(&r2);
11108        unsafe {
11109            b.launch(cfg)?;
11110        }
11111        Ok(Some((y0, y1, y2)))
11112    }
11113
11114    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11115    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
11116    #[allow(clippy::too_many_arguments)]
11117    pub fn matmul_q4_fused3_into(
11118        &self,
11119        w0: &crate::model::GpuTensor,
11120        w1: &crate::model::GpuTensor,
11121        w2: &crate::model::GpuTensor,
11122        aq: &CudaSlice<i8>,
11123        ad: &CudaSlice<f32>,
11124        y0: &mut CudaSlice<f32>,
11125        y1: &mut CudaSlice<f32>,
11126        y2: &mut CudaSlice<f32>,
11127    ) -> Result<bool, Box<dyn std::error::Error>> {
11128        use crate::model::GpuTensor;
11129        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11130            match w {
11131                GpuTensor::Quant {
11132                    qtype, row_bytes, ..
11133                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11134                _ => None,
11135            }
11136        };
11137        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11138            return Ok(false);
11139        };
11140        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11141            return Ok(false);
11142        }
11143        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11144            match w {
11145                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11146                    Some(m) => (m, true),
11147                    None => (bytes, *rp),
11148                },
11149                _ => unreachable!(),
11150            }
11151        }
11152        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11153        if rp0 != rp1 || rp1 != rp2 {
11154            return Ok(false);
11155        }
11156        let rp = rp0;
11157        let rpb: u32 = 4;
11158        let mr1 = rp && Self::q40_mr1_on();
11159        let nb = |o: usize| {
11160            if mr1 {
11161                (o as u32).div_ceil(rpb)
11162            } else {
11163                (o as u32).div_ceil(2).div_ceil(rpb)
11164            }
11165        };
11166        let grid = nb(o0) + nb(o1) + nb(o2);
11167        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
11168        let f = self.func(if mr1 {
11169            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11170        } else if rp {
11171            "qmatvec_q4_0_mmvq_fused3_rp"
11172        } else {
11173            "qmatvec_q4_0_mmvq_fused3"
11174        });
11175        let cfg = LaunchConfig {
11176            grid_dim: (grid, 1, 1),
11177            block_dim: (32, rpb, 1),
11178            shared_mem_bytes: 0,
11179        };
11180        let inf = w0.in_features() as i32;
11181        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11182        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11183        // PDL wave-A: identical to the owned twin (capture-lane parity).
11184        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11185            use cudarc::driver::{DevicePtr, DevicePtrMut};
11186            let s = &self.gpu.stream();
11187            let (p0, _g0) = b0.device_ptr(s);
11188            let (p1, _g1) = b1.device_ptr(s);
11189            let (p2, _g2) = b2.device_ptr(s);
11190            let (paq, _g3) = aq.device_ptr(s);
11191            let (pad, _g4) = ad.device_ptr(s);
11192            let (py0, _g5) = y0.device_ptr_mut(s);
11193            let (py1, _g6) = y1.device_ptr_mut(s);
11194            let (py2, _g7) = y2.device_ptr_mut(s);
11195            let mut ps = [
11196                &p0 as *const _ as *mut std::ffi::c_void,
11197                &p1 as *const _ as *mut _,
11198                &p2 as *const _ as *mut _,
11199                &paq as *const _ as *mut _,
11200                &pad as *const _ as *mut _,
11201                &py0 as *const _ as *mut _,
11202                &py1 as *const _ as *mut _,
11203                &py2 as *const _ as *mut _,
11204                &inf as *const _ as *mut _,
11205                &oo0 as *const _ as *mut _,
11206                &oo1 as *const _ as *mut _,
11207                &oo2 as *const _ as *mut _,
11208                &r0 as *const _ as *mut _,
11209                &r1 as *const _ as *mut _,
11210                &r2 as *const _ as *mut _,
11211            ];
11212            unsafe {
11213                self.launch_pdl(
11214                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11215                    (grid, 1, 1),
11216                    (32, rpb, 1),
11217                    &mut ps,
11218                )?;
11219            }
11220            return Ok(true);
11221        }
11222        let __s_b = self.gpu.stream();
11223        let mut b = __s_b.launch_builder(&f);
11224        b.arg(b0)
11225            .arg(b1)
11226            .arg(b2)
11227            .arg(aq)
11228            .arg(ad)
11229            .arg(&mut *y0)
11230            .arg(&mut *y1)
11231            .arg(&mut *y2)
11232            .arg(&inf)
11233            .arg(&oo0)
11234            .arg(&oo1)
11235            .arg(&oo2)
11236            .arg(&r0)
11237            .arg(&r1)
11238            .arg(&r2);
11239        unsafe {
11240            b.launch(cfg)?;
11241        }
11242        Ok(true)
11243    }
11244
11245    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
11246    pub fn matmul_q4_fused2(
11247        &self,
11248        w0: &crate::model::GpuTensor,
11249        w1: &crate::model::GpuTensor,
11250        aq: &CudaSlice<i8>,
11251        ad: &CudaSlice<f32>,
11252    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11253        use crate::model::GpuTensor;
11254        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11255            match w {
11256                GpuTensor::Quant {
11257                    qtype, row_bytes, ..
11258                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11259                _ => None,
11260            }
11261        };
11262        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11263            return Ok(None);
11264        };
11265        if w0.in_features() != w1.in_features() {
11266            return Ok(None);
11267        }
11268        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
11269        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11270            match w {
11271                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11272                    Some(m) => (m, true),
11273                    None => (bytes, *rp),
11274                },
11275                _ => unreachable!(),
11276            }
11277        }
11278        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11279        if rp0 != rp1 {
11280            return Ok(None);
11281        }
11282        let rp = rp0;
11283        let rpb: u32 = 4;
11284        // mr1 twin — see matmul_q4_fused3.
11285        let mr1 = rp && Self::q40_mr1_on();
11286        let nb = |o: usize| {
11287            if mr1 {
11288                (o as u32).div_ceil(rpb)
11289            } else {
11290                (o as u32).div_ceil(2).div_ceil(rpb)
11291            }
11292        };
11293        let grid = nb(o0) + nb(o1);
11294        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11295        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11296        let f = self.func(if mr1 {
11297            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11298        } else if rp {
11299            "qmatvec_q4_0_mmvq_fused2_rp"
11300        } else {
11301            "qmatvec_q4_0_mmvq_fused2"
11302        });
11303        let cfg = LaunchConfig {
11304            grid_dim: (grid, 1, 1),
11305            block_dim: (32, rpb, 1),
11306            shared_mem_bytes: 0,
11307        };
11308        let inf = w0.in_features() as i32;
11309        let (oo0, oo1) = (o0 as i32, o1 as i32);
11310        let (r0, r1) = (rb0 as i64, rb1 as i64);
11311        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
11312        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11313            {
11314                use cudarc::driver::{DevicePtr, DevicePtrMut};
11315                let s = &self.gpu.stream();
11316                let (p0, _g0) = b0.device_ptr(s);
11317                let (p1, _g1) = b1.device_ptr(s);
11318                let (paq, _g2) = aq.device_ptr(s);
11319                let (pad, _g3) = ad.device_ptr(s);
11320                let (py0, _g4) = y0.device_ptr_mut(s);
11321                let (py1, _g5) = y1.device_ptr_mut(s);
11322                let mut ps = [
11323                    &p0 as *const _ as *mut std::ffi::c_void,
11324                    &p1 as *const _ as *mut _,
11325                    &paq as *const _ as *mut _,
11326                    &pad as *const _ as *mut _,
11327                    &py0 as *const _ as *mut _,
11328                    &py1 as *const _ as *mut _,
11329                    &inf as *const _ as *mut _,
11330                    &oo0 as *const _ as *mut _,
11331                    &oo1 as *const _ as *mut _,
11332                    &r0 as *const _ as *mut _,
11333                    &r1 as *const _ as *mut _,
11334                ];
11335                unsafe {
11336                    self.launch_pdl(
11337                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11338                        (grid, 1, 1),
11339                        (32, rpb, 1),
11340                        &mut ps,
11341                    )?;
11342                }
11343            }
11344            return Ok(Some((y0, y1)));
11345        }
11346        let __s_b = self.gpu.stream();
11347        let mut b = __s_b.launch_builder(&f);
11348        b.arg(b0)
11349            .arg(b1)
11350            .arg(aq)
11351            .arg(ad)
11352            .arg(&mut y0)
11353            .arg(&mut y1)
11354            .arg(&inf)
11355            .arg(&oo0)
11356            .arg(&oo1)
11357            .arg(&r0)
11358            .arg(&r1);
11359        unsafe {
11360            b.launch(cfg)?;
11361        }
11362        Ok(Some((y0, y1)))
11363    }
11364
11365    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11366    pub fn matmul_q4_fused2_into(
11367        &self,
11368        w0: &crate::model::GpuTensor,
11369        w1: &crate::model::GpuTensor,
11370        aq: &CudaSlice<i8>,
11371        ad: &CudaSlice<f32>,
11372        y0: &mut CudaSlice<f32>,
11373        y1: &mut CudaSlice<f32>,
11374    ) -> Result<bool, Box<dyn std::error::Error>> {
11375        use crate::model::GpuTensor;
11376        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11377            match w {
11378                GpuTensor::Quant {
11379                    qtype, row_bytes, ..
11380                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11381                _ => None,
11382            }
11383        };
11384        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11385            return Ok(false);
11386        };
11387        if w0.in_features() != w1.in_features() {
11388            return Ok(false);
11389        }
11390        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11391            match w {
11392                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11393                    Some(m) => (m, true),
11394                    None => (bytes, *rp),
11395                },
11396                _ => unreachable!(),
11397            }
11398        }
11399        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11400        if rp0 != rp1 {
11401            return Ok(false);
11402        }
11403        let rp = rp0;
11404        let rpb: u32 = 4;
11405        let mr1 = rp && Self::q40_mr1_on();
11406        let nb = |o: usize| {
11407            if mr1 {
11408                (o as u32).div_ceil(rpb)
11409            } else {
11410                (o as u32).div_ceil(2).div_ceil(rpb)
11411            }
11412        };
11413        let grid = nb(o0) + nb(o1);
11414        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
11415        let f = self.func(if mr1 {
11416            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11417        } else if rp {
11418            "qmatvec_q4_0_mmvq_fused2_rp"
11419        } else {
11420            "qmatvec_q4_0_mmvq_fused2"
11421        });
11422        let cfg = LaunchConfig {
11423            grid_dim: (grid, 1, 1),
11424            block_dim: (32, rpb, 1),
11425            shared_mem_bytes: 0,
11426        };
11427        let inf = w0.in_features() as i32;
11428        let (oo0, oo1) = (o0 as i32, o1 as i32);
11429        let (r0, r1) = (rb0 as i64, rb1 as i64);
11430        // PDL wave-A: identical to the owned twin (capture-lane parity).
11431        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11432            use cudarc::driver::{DevicePtr, DevicePtrMut};
11433            let s = &self.gpu.stream();
11434            let (p0, _g0) = b0.device_ptr(s);
11435            let (p1, _g1) = b1.device_ptr(s);
11436            let (paq, _g2) = aq.device_ptr(s);
11437            let (pad, _g3) = ad.device_ptr(s);
11438            let (py0, _g4) = y0.device_ptr_mut(s);
11439            let (py1, _g5) = y1.device_ptr_mut(s);
11440            let mut ps = [
11441                &p0 as *const _ as *mut std::ffi::c_void,
11442                &p1 as *const _ as *mut _,
11443                &paq as *const _ as *mut _,
11444                &pad as *const _ as *mut _,
11445                &py0 as *const _ as *mut _,
11446                &py1 as *const _ as *mut _,
11447                &inf as *const _ as *mut _,
11448                &oo0 as *const _ as *mut _,
11449                &oo1 as *const _ as *mut _,
11450                &r0 as *const _ as *mut _,
11451                &r1 as *const _ as *mut _,
11452            ];
11453            unsafe {
11454                self.launch_pdl(
11455                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11456                    (grid, 1, 1),
11457                    (32, rpb, 1),
11458                    &mut ps,
11459                )?;
11460            }
11461            return Ok(true);
11462        }
11463        let __s_b = self.gpu.stream();
11464        let mut b = __s_b.launch_builder(&f);
11465        b.arg(b0)
11466            .arg(b1)
11467            .arg(aq)
11468            .arg(ad)
11469            .arg(&mut *y0)
11470            .arg(&mut *y1)
11471            .arg(&inf)
11472            .arg(&oo0)
11473            .arg(&oo1)
11474            .arg(&r0)
11475            .arg(&r1);
11476        unsafe {
11477            b.launch(cfg)?;
11478        }
11479        Ok(true)
11480    }
11481
11482    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
11483    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
11484    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
11485    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
11486    pub fn matmul_q4_fused2_batched(
11487        &self,
11488        w0: &crate::model::GpuTensor,
11489        w1: &crate::model::GpuTensor,
11490        aq: &CudaSlice<i8>,
11491        ad: &CudaSlice<f32>,
11492        m: usize,
11493    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11494        use crate::model::GpuTensor;
11495        if m < 2 || m > 8 {
11496            return Ok(None);
11497        }
11498        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11499            match w {
11500                GpuTensor::Quant {
11501                    qtype, row_bytes, ..
11502                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11503                _ => None,
11504            }
11505        };
11506        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
11507            return Ok(None);
11508        };
11509        if w0.in_features() != w1.in_features() {
11510            return Ok(None);
11511        }
11512        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11513            match w {
11514                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11515                    Some(mr) => (mr, true),
11516                    None => (bytes, *rp),
11517                },
11518                _ => unreachable!(),
11519            }
11520        }
11521        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11522        if !rp0 || !rp1 {
11523            return Ok(None);
11524        }
11525        let mcols = Self::batched_mcols(m);
11526        let rpb: u32 = 4;
11527        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11528        let grid = nb(o0) + nb(o1);
11529        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11530        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11531        let f = self.func(match mcols {
11532            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
11533            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
11534            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
11535        });
11536        let cfg = LaunchConfig {
11537            grid_dim: (grid, 1, 1),
11538            block_dim: (32, rpb, 1),
11539            shared_mem_bytes: 0,
11540        };
11541        let inf = w0.in_features() as i32;
11542        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
11543        let rb = rb0 as i64;
11544        let __s_b = self.gpu.stream();
11545        let mut b = __s_b.launch_builder(&f);
11546        b.arg(b0)
11547            .arg(b1)
11548            .arg(aq)
11549            .arg(ad)
11550            .arg(&mut y0)
11551            .arg(&mut y1)
11552            .arg(&inf)
11553            .arg(&oo0)
11554            .arg(&oo1)
11555            .arg(&mi)
11556            .arg(&rb);
11557        unsafe {
11558            b.launch(cfg)?;
11559        }
11560        Ok(Some((y0, y1)))
11561    }
11562
11563    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
11564    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
11565    #[allow(clippy::too_many_arguments)]
11566    pub fn matmul_q4_fused3_batched(
11567        &self,
11568        w0: &crate::model::GpuTensor,
11569        w1: &crate::model::GpuTensor,
11570        w2: &crate::model::GpuTensor,
11571        aq: &CudaSlice<i8>,
11572        ad: &CudaSlice<f32>,
11573        m: usize,
11574    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11575    {
11576        use crate::model::GpuTensor;
11577        if m < 2 || m > 8 {
11578            return Ok(None);
11579        }
11580        let q4 = |w: &GpuTensor| -> Option<usize> {
11581            match w {
11582                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
11583                _ => None,
11584            }
11585        };
11586        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
11587            return Ok(None);
11588        };
11589        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11590            return Ok(None);
11591        }
11592        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11593            match w {
11594                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11595                    Some(mr) => (mr, true),
11596                    None => (bytes, *rp),
11597                },
11598                _ => unreachable!(),
11599            }
11600        }
11601        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11602        if !rp0 || !rp1 || !rp2 {
11603            return Ok(None);
11604        }
11605        let mcols = Self::batched_mcols(m);
11606        let rpb: u32 = 4;
11607        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11608        let grid = nb(o0) + nb(o1) + nb(o2);
11609        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11610        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11611        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11612        let f = self.func(match mcols {
11613            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
11614            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
11615            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
11616        });
11617        let cfg = LaunchConfig {
11618            grid_dim: (grid, 1, 1),
11619            block_dim: (32, rpb, 1),
11620            shared_mem_bytes: 0,
11621        };
11622        let inf = w0.in_features() as i32;
11623        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
11624        let rb = 0i64;
11625        let __s_b = self.gpu.stream();
11626        let mut b = __s_b.launch_builder(&f);
11627        b.arg(b0)
11628            .arg(b1)
11629            .arg(b2)
11630            .arg(aq)
11631            .arg(ad)
11632            .arg(&mut y0)
11633            .arg(&mut y1)
11634            .arg(&mut y2)
11635            .arg(&inf)
11636            .arg(&oo0)
11637            .arg(&oo1)
11638            .arg(&oo2)
11639            .arg(&mi)
11640            .arg(&rb);
11641        unsafe {
11642            b.launch(cfg)?;
11643        }
11644        Ok(Some((y0, y1, y2)))
11645    }
11646
11647    pub fn matmul_q8_fused3(
11648        &self,
11649        w0: &crate::model::GpuTensor,
11650        w1: &crate::model::GpuTensor,
11651        w2: &crate::model::GpuTensor,
11652        aq: &CudaSlice<i8>,
11653        ad: &CudaSlice<f32>,
11654    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11655    {
11656        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
11657        // are per-tensor FP8, so native residency without this arm meant three separate launches.
11658        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11659            return Ok(Some(self.e4m3_fused3_core(
11660                p0.0,
11661                p1.0,
11662                p2.0,
11663                aq,
11664                ad,
11665                w0.in_features(),
11666                p0.1,
11667                p1.1,
11668                p2.1,
11669                p0.2,
11670                p0.3,
11671                p1.3,
11672                p2.3,
11673            )?));
11674        }
11675        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11676            return Ok(None);
11677        };
11678        Ok(Some(self.q8_fused3_core(
11679            p0.0,
11680            p1.0,
11681            p2.0,
11682            aq,
11683            ad,
11684            w0.in_features(),
11685            p0.1,
11686            p1.1,
11687            p2.1,
11688            p0.2,
11689        )?))
11690    }
11691
11692    #[allow(clippy::too_many_arguments)]
11693    fn q8_fused3_core(
11694        &self,
11695        b0: &CudaSlice<u8>,
11696        b1: &CudaSlice<u8>,
11697        b2: &CudaSlice<u8>,
11698        aq: &CudaSlice<i8>,
11699        ad: &CudaSlice<f32>,
11700        in_f: usize,
11701        out0: usize,
11702        out1: usize,
11703        out2: usize,
11704        row_bytes: usize,
11705    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11706        const ROWS_PER_BLOCK: u32 = 4;
11707        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11708        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11709        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11710        let f = self.func("qmatvec_q8_0_mmvq_fused3");
11711        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11712        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11713        let mut y2 = self.alloc_uninit::<f32>(out2)?;
11714        let cfg = LaunchConfig {
11715            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11716            block_dim: (32, ROWS_PER_BLOCK, 1),
11717            shared_mem_bytes: 0,
11718        };
11719        let (inf, o0, o1, o2, rbl) = (
11720            in_f as i32,
11721            out0 as i32,
11722            out1 as i32,
11723            out2 as i32,
11724            row_bytes as i64,
11725        );
11726        let __s_b = self.gpu.stream();
11727        let mut b = __s_b.launch_builder(&f);
11728        b.arg(b0)
11729            .arg(b1)
11730            .arg(b2)
11731            .arg(aq)
11732            .arg(ad)
11733            .arg(&mut y0)
11734            .arg(&mut y1)
11735            .arg(&mut y2)
11736            .arg(&inf)
11737            .arg(&o0)
11738            .arg(&o1)
11739            .arg(&o2)
11740            .arg(&rbl);
11741        unsafe {
11742            b.launch(cfg)?;
11743        }
11744        Ok((y0, y1, y2))
11745    }
11746
11747    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
11748    #[allow(clippy::too_many_arguments)]
11749    pub fn qmatvec_q8_fused3_raw(
11750        &self,
11751        b0: &CudaSlice<u8>,
11752        b1: &CudaSlice<u8>,
11753        b2: &CudaSlice<u8>,
11754        x: &CudaSlice<f32>,
11755        in_f: usize,
11756        out0: usize,
11757        out1: usize,
11758        out2: usize,
11759        row_bytes: usize,
11760    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11761        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11762        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
11763    }
11764
11765    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
11766    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
11767    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
11768    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
11769    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
11770    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
11771    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
11772    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
11773    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
11774    /// twin must not introduce a batched program the reference path would not run).
11775    pub fn matmul_q8_fused2_t(
11776        &self,
11777        w0: &crate::model::GpuTensor,
11778        w1: &crate::model::GpuTensor,
11779        aq: &CudaSlice<i8>,
11780        ad: &CudaSlice<f32>,
11781        m: usize,
11782    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11783        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
11784        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
11785        // fuses too — same template body, still bit-identical to the two _b8 launches.
11786        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11787            return Ok(None);
11788        }
11789        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
11790        // so the fused b8 launch would introduce a batched program the reference path would not run.
11791        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11792            if m > 4 && !Self::b8_enabled() {
11793                return Ok(None);
11794            }
11795            return Ok(Some(self.e4m3_fused2_t_core(
11796                p0.0,
11797                p1.0,
11798                aq,
11799                ad,
11800                m,
11801                w0.in_features(),
11802                p0.1,
11803                p1.1,
11804                p0.2,
11805                p0.3,
11806                p1.3,
11807            )?));
11808        }
11809        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11810            return Ok(None);
11811        };
11812        Ok(Some(self.q8_fused2_t_core(
11813            p0.0,
11814            p1.0,
11815            aq,
11816            ad,
11817            m,
11818            w0.in_features(),
11819            p0.1,
11820            p1.1,
11821            p0.2,
11822        )?))
11823    }
11824
11825    #[allow(clippy::too_many_arguments)]
11826    fn q8_fused2_t_core(
11827        &self,
11828        b0: &CudaSlice<u8>,
11829        b1: &CudaSlice<u8>,
11830        aq: &CudaSlice<i8>,
11831        ad: &CudaSlice<f32>,
11832        m: usize,
11833        in_f: usize,
11834        out0: usize,
11835        out1: usize,
11836        row_bytes: usize,
11837    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11838        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11839        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11840        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11841        let f = self.func(match Self::batched_mcols(m) {
11842            2 => "qmatvec_q8_0_mmvq_fused2_b2",
11843            4 => "qmatvec_q8_0_mmvq_fused2_b4",
11844            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
11845            _ => "qmatvec_q8_0_mmvq_fused2_b8",
11846        });
11847        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11848        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11849        let cfg = LaunchConfig {
11850            grid_dim: (nb0 + nb1, 1, 1),
11851            block_dim: (32, ROWS_PER_BLOCK, 1),
11852            shared_mem_bytes: 0,
11853        };
11854        let (inf, o0, o1, mi, rbl) = (
11855            in_f as i32,
11856            out0 as i32,
11857            out1 as i32,
11858            m as i32,
11859            row_bytes as i64,
11860        );
11861        let __s_b = self.gpu.stream();
11862        let mut b = __s_b.launch_builder(&f);
11863        b.arg(b0)
11864            .arg(b1)
11865            .arg(aq)
11866            .arg(ad)
11867            .arg(&mut y0)
11868            .arg(&mut y1)
11869            .arg(&inf)
11870            .arg(&o0)
11871            .arg(&o1)
11872            .arg(&mi)
11873            .arg(&rbl);
11874        unsafe {
11875            b.launch(cfg)?;
11876        }
11877        Ok((y0, y1))
11878    }
11879
11880    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
11881    /// q8_1 quant of the [m, in_f] activation), no env gating.
11882    #[allow(clippy::too_many_arguments)]
11883    pub fn qmatvec_q8_fused2_t_raw(
11884        &self,
11885        b0: &CudaSlice<u8>,
11886        b1: &CudaSlice<u8>,
11887        x: &CudaSlice<f32>,
11888        m: usize,
11889        in_f: usize,
11890        out0: usize,
11891        out1: usize,
11892        row_bytes: usize,
11893    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11894        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11895        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
11896    }
11897
11898    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
11899    /// `matmul_q8_fused2_t` with three ranges.
11900    #[allow(clippy::too_many_arguments)]
11901    pub fn matmul_q8_fused3_t(
11902        &self,
11903        w0: &crate::model::GpuTensor,
11904        w1: &crate::model::GpuTensor,
11905        w2: &crate::model::GpuTensor,
11906        aq: &CudaSlice<i8>,
11907        ad: &CudaSlice<f32>,
11908        m: usize,
11909    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11910    {
11911        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11912            return Ok(None);
11913        }
11914        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11915            return Ok(Some(self.e4m3_fused3_t_core(
11916                p0.0,
11917                p1.0,
11918                p2.0,
11919                aq,
11920                ad,
11921                m,
11922                w0.in_features(),
11923                p0.1,
11924                p1.1,
11925                p2.1,
11926                p0.2,
11927                p0.3,
11928                p1.3,
11929                p2.3,
11930            )?));
11931        }
11932        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11933            return Ok(None);
11934        };
11935        Ok(Some(self.q8_fused3_t_core(
11936            p0.0,
11937            p1.0,
11938            p2.0,
11939            aq,
11940            ad,
11941            m,
11942            w0.in_features(),
11943            p0.1,
11944            p1.1,
11945            p2.1,
11946            p0.2,
11947        )?))
11948    }
11949
11950    #[allow(clippy::too_many_arguments)]
11951    fn q8_fused3_t_core(
11952        &self,
11953        b0: &CudaSlice<u8>,
11954        b1: &CudaSlice<u8>,
11955        b2: &CudaSlice<u8>,
11956        aq: &CudaSlice<i8>,
11957        ad: &CudaSlice<f32>,
11958        m: usize,
11959        in_f: usize,
11960        out0: usize,
11961        out1: usize,
11962        out2: usize,
11963        row_bytes: usize,
11964    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11965        const ROWS_PER_BLOCK: u32 = 4;
11966        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11967        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11968        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11969        let f = self.func(if Self::batched_mcols(m) == 2 {
11970            "qmatvec_q8_0_mmvq_fused3_b2"
11971        } else {
11972            "qmatvec_q8_0_mmvq_fused3_b4"
11973        });
11974        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11975        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11976        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
11977        let cfg = LaunchConfig {
11978            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11979            block_dim: (32, ROWS_PER_BLOCK, 1),
11980            shared_mem_bytes: 0,
11981        };
11982        let (inf, o0, o1, o2, mi, rbl) = (
11983            in_f as i32,
11984            out0 as i32,
11985            out1 as i32,
11986            out2 as i32,
11987            m as i32,
11988            row_bytes as i64,
11989        );
11990        let __s_b = self.gpu.stream();
11991        let mut b = __s_b.launch_builder(&f);
11992        b.arg(b0)
11993            .arg(b1)
11994            .arg(b2)
11995            .arg(aq)
11996            .arg(ad)
11997            .arg(&mut y0)
11998            .arg(&mut y1)
11999            .arg(&mut y2)
12000            .arg(&inf)
12001            .arg(&o0)
12002            .arg(&o1)
12003            .arg(&o2)
12004            .arg(&mi)
12005            .arg(&rbl);
12006        unsafe {
12007            b.launch(cfg)?;
12008        }
12009        Ok((y0, y1, y2))
12010    }
12011
12012    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
12013    #[allow(clippy::too_many_arguments)]
12014    pub fn qmatvec_q8_fused3_t_raw(
12015        &self,
12016        b0: &CudaSlice<u8>,
12017        b1: &CudaSlice<u8>,
12018        b2: &CudaSlice<u8>,
12019        x: &CudaSlice<f32>,
12020        m: usize,
12021        in_f: usize,
12022        out0: usize,
12023        out1: usize,
12024        out2: usize,
12025        row_bytes: usize,
12026    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12027        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12028        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
12029    }
12030
12031    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
12032    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
12033    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
12034    pub fn q8_ffn_fuse2_on(&self) -> bool {
12035        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12036        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
12037    }
12038
12039    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
12040    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
12041    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
12042    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
12043    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
12044    #[allow(clippy::type_complexity)]
12045    fn q8_fused_params<'w, const N: usize>(
12046        &self,
12047        ws: &[&'w crate::model::GpuTensor; N],
12048    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
12049        use crate::model::GpuTensor;
12050        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12051            return None;
12052        }
12053        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
12054            return None;
12055        }
12056        let in_f = ws[0].in_features();
12057        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
12058        for (i, w) in ws.iter().enumerate() {
12059            match w {
12060                GpuTensor::Quant {
12061                    bytes,
12062                    qtype,
12063                    row_bytes,
12064                    scale,
12065                    ..
12066                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
12067                    out[i] = Some((bytes, w.out_features(), *row_bytes))
12068                }
12069                _ => return None,
12070            }
12071        }
12072        Some(out.map(|o| o.unwrap()))
12073    }
12074
12075    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
12076    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
12077    pub fn e4m3_dual_on(&self) -> bool {
12078        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12079        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
12080    }
12081
12082    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
12083    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
12084    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
12085    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
12086    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
12087    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
12088    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
12089    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
12090    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
12091    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
12092    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
12093    #[allow(clippy::type_complexity)]
12094    fn e4m3_fused_params<'w, const N: usize>(
12095        &self,
12096        ws: &[&'w crate::model::GpuTensor; N],
12097    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
12098        use crate::model::GpuTensor;
12099        if !self.e4m3_dual_on() {
12100            return None;
12101        }
12102        let in_f = ws[0].in_features();
12103        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
12104        for (i, w) in ws.iter().enumerate() {
12105            match w {
12106                GpuTensor::Quant {
12107                    bytes,
12108                    qtype,
12109                    row_bytes,
12110                    scale,
12111                    rp,
12112                    rp4,
12113                    ..
12114                } if *qtype == QT_F8_E4M3
12115                    && w.in_features() == in_f
12116                    && *row_bytes == in_f
12117                    && !*rp
12118                    && rp4.is_none() =>
12119                {
12120                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
12121                }
12122                _ => return None,
12123            }
12124        }
12125        Some(out.map(|o| o.unwrap()))
12126    }
12127
12128    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
12129    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
12130    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
12131    #[allow(clippy::too_many_arguments)]
12132    fn e4m3_fused2_core(
12133        &self,
12134        b0: &CudaSlice<u8>,
12135        b1: &CudaSlice<u8>,
12136        aq: &CudaSlice<i8>,
12137        ad: &CudaSlice<f32>,
12138        in_f: usize,
12139        out0: usize,
12140        out1: usize,
12141        row_bytes: usize,
12142        ws0: f32,
12143        ws1: f32,
12144    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12145        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12146        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12147        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12148        let f = self.func("qmatvec_e4m3_mmvq_fused2");
12149        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12150        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12151        let cfg = LaunchConfig {
12152            grid_dim: (nb0 + nb1, 1, 1),
12153            block_dim: (32, ROWS_PER_BLOCK, 1),
12154            shared_mem_bytes: 0,
12155        };
12156        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
12157        let __s_b = self.gpu.stream();
12158        let mut b = __s_b.launch_builder(&f);
12159        b.arg(b0)
12160            .arg(b1)
12161            .arg(aq)
12162            .arg(ad)
12163            .arg(&mut y0)
12164            .arg(&mut y1)
12165            .arg(&inf)
12166            .arg(&o0)
12167            .arg(&o1)
12168            .arg(&rbl)
12169            .arg(&ws0)
12170            .arg(&ws1);
12171        unsafe {
12172            b.launch(cfg)?;
12173        }
12174        Ok((y0, y1))
12175    }
12176
12177    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
12178    #[allow(clippy::too_many_arguments)]
12179    fn e4m3_fused3_core(
12180        &self,
12181        b0: &CudaSlice<u8>,
12182        b1: &CudaSlice<u8>,
12183        b2: &CudaSlice<u8>,
12184        aq: &CudaSlice<i8>,
12185        ad: &CudaSlice<f32>,
12186        in_f: usize,
12187        out0: usize,
12188        out1: usize,
12189        out2: usize,
12190        row_bytes: usize,
12191        ws0: f32,
12192        ws1: f32,
12193        ws2: f32,
12194    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12195        const ROWS_PER_BLOCK: u32 = 4;
12196        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12197        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12198        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12199        let f = self.func("qmatvec_e4m3_mmvq_fused3");
12200        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12201        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12202        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12203        let cfg = LaunchConfig {
12204            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12205            block_dim: (32, ROWS_PER_BLOCK, 1),
12206            shared_mem_bytes: 0,
12207        };
12208        let (inf, o0, o1, o2, rbl) = (
12209            in_f as i32,
12210            out0 as i32,
12211            out1 as i32,
12212            out2 as i32,
12213            row_bytes as i64,
12214        );
12215        let __s_b = self.gpu.stream();
12216        let mut b = __s_b.launch_builder(&f);
12217        b.arg(b0)
12218            .arg(b1)
12219            .arg(b2)
12220            .arg(aq)
12221            .arg(ad)
12222            .arg(&mut y0)
12223            .arg(&mut y1)
12224            .arg(&mut y2)
12225            .arg(&inf)
12226            .arg(&o0)
12227            .arg(&o1)
12228            .arg(&o2)
12229            .arg(&rbl)
12230            .arg(&ws0)
12231            .arg(&ws1)
12232            .arg(&ws2);
12233        unsafe {
12234            b.launch(cfg)?;
12235        }
12236        Ok((y0, y1, y2))
12237    }
12238
12239    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
12240    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
12241    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
12242    #[allow(clippy::too_many_arguments)]
12243    fn e4m3_fused2_t_core(
12244        &self,
12245        b0: &CudaSlice<u8>,
12246        b1: &CudaSlice<u8>,
12247        aq: &CudaSlice<i8>,
12248        ad: &CudaSlice<f32>,
12249        m: usize,
12250        in_f: usize,
12251        out0: usize,
12252        out1: usize,
12253        row_bytes: usize,
12254        ws0: f32,
12255        ws1: f32,
12256    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12257        const ROWS_PER_BLOCK: u32 = 4;
12258        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12259        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12260        let f = self.func(match Self::batched_mcols(m) {
12261            2 => "qmatvec_e4m3_mmvq_fused2_b2",
12262            4 => "qmatvec_e4m3_mmvq_fused2_b4",
12263            _ => "qmatvec_e4m3_mmvq_fused2_b8",
12264        });
12265        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12266        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12267        let cfg = LaunchConfig {
12268            grid_dim: (nb0 + nb1, 1, 1),
12269            block_dim: (32, ROWS_PER_BLOCK, 1),
12270            shared_mem_bytes: 0,
12271        };
12272        let (inf, o0, o1, mi, rbl) = (
12273            in_f as i32,
12274            out0 as i32,
12275            out1 as i32,
12276            m as i32,
12277            row_bytes as i64,
12278        );
12279        let __s_b = self.gpu.stream();
12280        let mut b = __s_b.launch_builder(&f);
12281        b.arg(b0)
12282            .arg(b1)
12283            .arg(aq)
12284            .arg(ad)
12285            .arg(&mut y0)
12286            .arg(&mut y1)
12287            .arg(&inf)
12288            .arg(&o0)
12289            .arg(&o1)
12290            .arg(&mi)
12291            .arg(&rbl);
12292        unsafe {
12293            b.launch(cfg)?;
12294        }
12295        if ws0 != 1.0 {
12296            self.scale_inplace(&mut y0, ws0, m * out0)?;
12297        }
12298        if ws1 != 1.0 {
12299            self.scale_inplace(&mut y1, ws1, m * out1)?;
12300        }
12301        Ok((y0, y1))
12302    }
12303
12304    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
12305    #[allow(clippy::too_many_arguments)]
12306    fn e4m3_fused3_t_core(
12307        &self,
12308        b0: &CudaSlice<u8>,
12309        b1: &CudaSlice<u8>,
12310        b2: &CudaSlice<u8>,
12311        aq: &CudaSlice<i8>,
12312        ad: &CudaSlice<f32>,
12313        m: usize,
12314        in_f: usize,
12315        out0: usize,
12316        out1: usize,
12317        out2: usize,
12318        row_bytes: usize,
12319        ws0: f32,
12320        ws1: f32,
12321        ws2: f32,
12322    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12323        const ROWS_PER_BLOCK: u32 = 4;
12324        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12325        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12326        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12327        let f = self.func(if Self::batched_mcols(m) == 2 {
12328            "qmatvec_e4m3_mmvq_fused3_b2"
12329        } else {
12330            "qmatvec_e4m3_mmvq_fused3_b4"
12331        });
12332        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12333        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12334        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12335        let cfg = LaunchConfig {
12336            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12337            block_dim: (32, ROWS_PER_BLOCK, 1),
12338            shared_mem_bytes: 0,
12339        };
12340        let (inf, o0, o1, o2, mi, rbl) = (
12341            in_f as i32,
12342            out0 as i32,
12343            out1 as i32,
12344            out2 as i32,
12345            m as i32,
12346            row_bytes as i64,
12347        );
12348        let __s_b = self.gpu.stream();
12349        let mut b = __s_b.launch_builder(&f);
12350        b.arg(b0)
12351            .arg(b1)
12352            .arg(b2)
12353            .arg(aq)
12354            .arg(ad)
12355            .arg(&mut y0)
12356            .arg(&mut y1)
12357            .arg(&mut y2)
12358            .arg(&inf)
12359            .arg(&o0)
12360            .arg(&o1)
12361            .arg(&o2)
12362            .arg(&mi)
12363            .arg(&rbl);
12364        unsafe {
12365            b.launch(cfg)?;
12366        }
12367        if ws0 != 1.0 {
12368            self.scale_inplace(&mut y0, ws0, m * out0)?;
12369        }
12370        if ws1 != 1.0 {
12371            self.scale_inplace(&mut y1, ws1, m * out1)?;
12372        }
12373        if ws2 != 1.0 {
12374            self.scale_inplace(&mut y2, ws2, m * out2)?;
12375        }
12376        Ok((y0, y1, y2))
12377    }
12378
12379    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
12380    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
12381    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
12382    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
12383    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
12384    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
12385    ///
12386    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
12387    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
12388    pub fn qmatvec_e4m3_blk_mmvq(
12389        &self,
12390        bytes: &CudaSlice<u8>,
12391        aq: &CudaSlice<i8>,
12392        ad: &CudaSlice<f32>,
12393        scales: &CudaSlice<f32>,
12394        m: usize,
12395        in_f: usize,
12396        out_f: usize,
12397        row_bytes: usize,
12398        scale_cols: usize,
12399    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12400        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
12401        self.qmatvec_e4m3_blk_mmvq_into(
12402            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
12403        )?;
12404        Ok(y)
12405    }
12406
12407    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
12408    #[allow(clippy::too_many_arguments)]
12409    pub fn qmatvec_e4m3_blk_mmvq_into(
12410        &self,
12411        bytes: &CudaSlice<u8>,
12412        aq: &CudaSlice<i8>,
12413        ad: &CudaSlice<f32>,
12414        scales: &CudaSlice<f32>,
12415        m: usize,
12416        in_f: usize,
12417        out_f: usize,
12418        row_bytes: usize,
12419        scale_cols: usize,
12420        y: &mut CudaSlice<f32>,
12421    ) -> Result<(), Box<dyn std::error::Error>> {
12422        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12423        let f = self.func("qmatvec_e4m3_blk_mmvq");
12424        let cfg = LaunchConfig {
12425            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
12426            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
12427            shared_mem_bytes: 0,                // warp-only reduce
12428        };
12429        let (inf, outf, mi, rb, sc) = (
12430            in_f as i32,
12431            out_f as i32,
12432            m as i32,
12433            row_bytes as i64,
12434            scale_cols as i32,
12435        );
12436        let __s_b = self.gpu.stream();
12437        let mut b = __s_b.launch_builder(&f);
12438        b.arg(bytes)
12439            .arg(aq)
12440            .arg(ad)
12441            .arg(scales)
12442            .arg(&mut *y)
12443            .arg(&inf)
12444            .arg(&outf)
12445            .arg(&mi)
12446            .arg(&rb)
12447            .arg(&sc);
12448        unsafe {
12449            b.launch(cfg)?;
12450        }
12451        Ok(())
12452    }
12453
12454    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
12455    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
12456    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
12457    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
12458    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
12459    #[allow(clippy::too_many_arguments)]
12460    pub fn qmatvec_e4m3_blk_mmvq_batched(
12461        &self,
12462        bytes: &CudaSlice<u8>,
12463        aq: &CudaSlice<i8>,
12464        ad: &CudaSlice<f32>,
12465        scales: &CudaSlice<f32>,
12466        m: usize,
12467        in_f: usize,
12468        out_f: usize,
12469        row_bytes: usize,
12470        scale_cols: usize,
12471        mcols: usize,
12472    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12473        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12474        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
12475        let name = match mcols {
12476            2 => "qmatvec_e4m3_blk_mmvq_b2",
12477            4 => "qmatvec_e4m3_blk_mmvq_b4",
12478            8 => "qmatvec_e4m3_blk_mmvq_b8",
12479            16 => "qmatvec_e4m3_blk_mmvq_b16",
12480            _ => {
12481                return Err(
12482                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
12483                );
12484            }
12485        };
12486        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12487        let f = self.func(name);
12488        let cfg = LaunchConfig {
12489            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
12490            block_dim: (32, ROWS_PER_BLOCK, 1),
12491            shared_mem_bytes: 0,
12492        };
12493        let (inf, outf, mi, rb, sc) = (
12494            in_f as i32,
12495            out_f as i32,
12496            m as i32,
12497            row_bytes as i64,
12498            scale_cols as i32,
12499        );
12500        let __s_b = self.gpu.stream();
12501        let mut b = __s_b.launch_builder(&f);
12502        b.arg(bytes)
12503            .arg(aq)
12504            .arg(ad)
12505            .arg(scales)
12506            .arg(&mut y)
12507            .arg(&inf)
12508            .arg(&outf)
12509            .arg(&mi)
12510            .arg(&rb)
12511            .arg(&sc);
12512        unsafe {
12513            b.launch(cfg)?;
12514        }
12515        Ok(y)
12516    }
12517
12518    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
12519    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
12520    #[allow(clippy::too_many_arguments)]
12521    pub fn qmatvec_e4m3_blk_batched_raw(
12522        &self,
12523        bytes: &CudaSlice<u8>,
12524        x: &CudaSlice<f32>,
12525        scales: &CudaSlice<f32>,
12526        m: usize,
12527        in_f: usize,
12528        out_f: usize,
12529        row_bytes: usize,
12530        scale_cols: usize,
12531        mcols: usize,
12532    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12533        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12534        self.qmatvec_e4m3_blk_mmvq_batched(
12535            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
12536        )
12537    }
12538
12539    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
12540    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
12541    #[allow(clippy::too_many_arguments)]
12542    pub fn qmatvec_e4m3_blk_mmvq_raw(
12543        &self,
12544        bytes: &CudaSlice<u8>,
12545        x: &CudaSlice<f32>,
12546        scales: &CudaSlice<f32>,
12547        m: usize,
12548        in_f: usize,
12549        out_f: usize,
12550        row_bytes: usize,
12551        scale_cols: usize,
12552    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12553        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12554        self.qmatvec_e4m3_blk_mmvq(
12555            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
12556        )
12557    }
12558
12559    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
12560    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
12561    #[allow(clippy::too_many_arguments)]
12562    pub fn qmatvec_e4m3_fused2_raw(
12563        &self,
12564        b0: &CudaSlice<u8>,
12565        b1: &CudaSlice<u8>,
12566        x: &CudaSlice<f32>,
12567        in_f: usize,
12568        out0: usize,
12569        out1: usize,
12570        row_bytes: usize,
12571        ws0: f32,
12572        ws1: f32,
12573    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12574        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12575        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
12576    }
12577
12578    #[allow(clippy::too_many_arguments)]
12579    pub fn qmatvec_e4m3_fused3_raw(
12580        &self,
12581        b0: &CudaSlice<u8>,
12582        b1: &CudaSlice<u8>,
12583        b2: &CudaSlice<u8>,
12584        x: &CudaSlice<f32>,
12585        in_f: usize,
12586        out0: usize,
12587        out1: usize,
12588        out2: usize,
12589        row_bytes: usize,
12590        ws0: f32,
12591        ws1: f32,
12592        ws2: f32,
12593    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12594        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12595        self.e4m3_fused3_core(
12596            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12597        )
12598    }
12599
12600    #[allow(clippy::too_many_arguments)]
12601    pub fn qmatvec_e4m3_fused2_t_raw(
12602        &self,
12603        b0: &CudaSlice<u8>,
12604        b1: &CudaSlice<u8>,
12605        x: &CudaSlice<f32>,
12606        m: usize,
12607        in_f: usize,
12608        out0: usize,
12609        out1: usize,
12610        row_bytes: usize,
12611        ws0: f32,
12612        ws1: f32,
12613    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12614        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12615        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
12616    }
12617
12618    #[allow(clippy::too_many_arguments)]
12619    pub fn qmatvec_e4m3_fused3_t_raw(
12620        &self,
12621        b0: &CudaSlice<u8>,
12622        b1: &CudaSlice<u8>,
12623        b2: &CudaSlice<u8>,
12624        x: &CudaSlice<f32>,
12625        m: usize,
12626        in_f: usize,
12627        out0: usize,
12628        out1: usize,
12629        out2: usize,
12630        row_bytes: usize,
12631        ws0: f32,
12632        ws1: f32,
12633        ws2: f32,
12634    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12635        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12636        self.e4m3_fused3_t_core(
12637            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12638        )
12639    }
12640
12641    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
12642    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
12643    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
12644    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
12645    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
12646    ///
12647    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
12648    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
12649    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
12650    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
12651    fn try_e4m3_blk_pre(
12652        &self,
12653        w: &crate::model::GpuTensor,
12654        aq: &CudaSlice<i8>,
12655        ad: &CudaSlice<f32>,
12656        m: usize,
12657    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12658        use crate::model::GpuTensor;
12659        if let GpuTensor::Quant {
12660            bytes,
12661            qtype,
12662            row_bytes,
12663            blk: Some(g),
12664            ..
12665        } = w
12666        {
12667            if *qtype == QT_F8_E4M3_BLK {
12668                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
12669                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
12670                // below, so the decode-exactness contract is preserved at every width. Gated by
12671                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
12672                // one rollback door covers every dtype's batched tier.
12673                if (2..=16).contains(&m)
12674                    && std::env::var("MEMRA_NO_BATCHED").is_err()
12675                    && (m <= 4 || Self::b8_enabled())
12676                {
12677                    let mcols = Self::batched_mcols(m);
12678                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
12679                        bytes,
12680                        aq,
12681                        ad,
12682                        &g.scales,
12683                        m,
12684                        w.in_features(),
12685                        w.out_features(),
12686                        *row_bytes,
12687                        g.cols,
12688                        mcols,
12689                    )?));
12690                }
12691                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
12692                    bytes,
12693                    aq,
12694                    ad,
12695                    &g.scales,
12696                    m,
12697                    w.in_features(),
12698                    w.out_features(),
12699                    *row_bytes,
12700                    g.cols,
12701                )?));
12702            }
12703        }
12704        Ok(None)
12705    }
12706
12707    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
12708    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
12709    ///
12710    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
12711    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
12712    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
12713    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
12714    /// prefill keeps the floor's arithmetic and the floor's kernels.
12715    ///
12716    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
12717    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
12718    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
12719    /// (projection, prefill call) and frees immediately.
12720    ///
12721    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
12722    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
12723    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
12724    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
12725    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
12726    /// single-variable comparison instead of a two-variable one.
12727    ///
12728    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
12729    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
12730    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
12731    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
12732    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
12733    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
12734    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
12735    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
12736    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
12737    ///
12738    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
12739    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
12740    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
12741    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
12742    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
12743    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
12744    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
12745    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
12746    /// because v2's denominator had its slab already resident while this class's floor must build it
12747    /// every call; same tile, opposite sign, because the question changed.
12748    ///
12749    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
12750    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
12751    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
12752    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
12753    fn try_e4m3_blk_prefill(
12754        &self,
12755        w: &crate::model::GpuTensor,
12756        x: &CudaSlice<f32>,
12757        m: usize,
12758    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12759        use crate::model::GpuTensor;
12760        let GpuTensor::Quant {
12761            bytes,
12762            qtype,
12763            blk: Some(g),
12764            ..
12765        } = w
12766        else {
12767            return Ok(None);
12768        };
12769        if *qtype != QT_F8_E4M3_BLK {
12770            return Ok(None);
12771        }
12772        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
12773        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
12774        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
12775        // through to the dequant below when they do, never silently produce nothing.
12776        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
12777            return Ok(Some(y));
12778        }
12779        let (in_f, out_f) = (w.in_features(), w.out_features());
12780        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
12781        let tmp = GpuTensor::Quant {
12782            bytes: slab,
12783            qtype: QT_Q8_0,
12784            row_bytes: in_f / 32 * 34,
12785            ne: vec![in_f as u64, out_f as u64],
12786            scale: 1.0,
12787            rp: false,
12788            #[cfg(memra_cutlass)]
12789            cutlass: None,
12790            fp8: None,
12791            blk: None,
12792            f16: None,
12793            rp4: None,
12794        };
12795        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
12796        Ok(Some(self.matmul(&tmp, x, m)?))
12797    }
12798
12799    pub fn matmul_pre_noscale(
12800        &self,
12801        w: &crate::model::GpuTensor,
12802        aq: &CudaSlice<i8>,
12803        ad: &CudaSlice<f32>,
12804        m: usize,
12805    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
12806        use crate::model::GpuTensor;
12807        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
12808        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
12809        // rather than let the tail below refuse and cost the caller a re-dispatch.
12810        if m == 1 {
12811            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
12812                return Ok(Some((y, 1.0)));
12813            }
12814        }
12815        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
12816        if m != 1 || !self.uses_q8_1_fast(w) {
12817            return Ok(None);
12818        }
12819        let in_f = w.in_features();
12820        let out_f = w.out_features();
12821        let (bytes, qtype, row_bytes, scale, rp) = match w {
12822            GpuTensor::Quant {
12823                bytes,
12824                qtype,
12825                row_bytes,
12826                scale,
12827                rp,
12828                ..
12829            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12830            _ => return Ok(None),
12831        };
12832        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
12833        if self.mmvq_supports(qtype) {
12834            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
12835            let (mbytes, mrp) = match w {
12836                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12837                _ => (bytes, rp),
12838            };
12839            let y = self.qmatvec_mmvq(
12840                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
12841            )?;
12842            return Ok(Some((y, scale)));
12843        }
12844        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
12845        let name = match qtype {
12846            QT_Q8_0 => "qmatvec_q8_0_dp4a",
12847            QT_Q4_K => "qmatvec_q4_K_dp4a",
12848            QT_Q6_K => "qmatvec_q6_K_dp4a",
12849            QT_Q5_K => "qmatvec_q5_K_dp4a",
12850            QT_Q3_K => "qmatvec_q3_K_dp4a",
12851            QT_NVFP4 => {
12852                if rp {
12853                    "qmatvec_nvfp4_dp4a_rp"
12854                } else {
12855                    "qmatvec_nvfp4_dp4a"
12856                }
12857            }
12858            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
12859            _ => return Ok(None),
12860        };
12861        let f = self.func(name);
12862        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12863        let cfg = LaunchConfig {
12864            grid_dim: (out_f as u32, m as u32, 1),
12865            block_dim: (128, 1, 1),
12866            shared_mem_bytes: 0,
12867        };
12868        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12869        let __s_b = self.gpu.stream();
12870        let mut b = __s_b.launch_builder(&f);
12871        b.arg(bytes)
12872            .arg(aq)
12873            .arg(ad)
12874            .arg(&mut y)
12875            .arg(&inf)
12876            .arg(&outf)
12877            .arg(&mi)
12878            .arg(&rb);
12879        unsafe {
12880            b.launch(cfg)?;
12881        }
12882        Ok(Some((y, scale)))
12883    }
12884
12885    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
12886    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
12887    pub fn mmvq_supports(&self, qtype: i32) -> bool {
12888        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
12889        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
12890        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
12891        // is a pure function of the dtype — the decode-parity law holds under every env.
12892        if qtype == QT_F8_E4M3 {
12893            return true;
12894        }
12895        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12896            return false;
12897        }
12898        matches!(
12899            qtype,
12900            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
12901        )
12902    }
12903
12904    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
12905    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
12906    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
12907    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
12908    pub fn qmatvec_mmvq(
12909        &self,
12910        bytes: &CudaSlice<u8>,
12911        aq: &CudaSlice<i8>,
12912        ad: &CudaSlice<f32>,
12913        m: usize,
12914        in_f: usize,
12915        out_f: usize,
12916        qtype: i32,
12917        row_bytes: usize,
12918        scale: f32,
12919        rp: bool,
12920    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12921        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
12922        self.qmatvec_mmvq_into(
12923            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
12924        )?;
12925        Ok(y)
12926    }
12927
12928    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
12929    #[allow(clippy::too_many_arguments)]
12930    pub fn qmatvec_mmvq_into(
12931        &self,
12932        bytes: &CudaSlice<u8>,
12933        aq: &CudaSlice<i8>,
12934        ad: &CudaSlice<f32>,
12935        m: usize,
12936        in_f: usize,
12937        out_f: usize,
12938        qtype: i32,
12939        row_bytes: usize,
12940        scale: f32,
12941        rp: bool,
12942        y: &mut CudaSlice<f32>,
12943    ) -> Result<(), Box<dyn std::error::Error>> {
12944        debug_assert!(y.len() >= m * out_f);
12945        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12946        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
12947        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
12948        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
12949        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
12950        if qtype == QT_Q8_0
12951            && rp
12952            && m == 1
12953            && out_f >= 64
12954            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
12955            && {
12956                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12957                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
12958            }
12959        {
12960            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
12961            let cfg = LaunchConfig {
12962                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
12963                block_dim: (32, 2, 1),
12964                shared_mem_bytes: 0,
12965            };
12966            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
12967            let __s_b = self.gpu.stream();
12968            let mut b = __s_b.launch_builder(&f);
12969            b.arg(bytes)
12970                .arg(aq)
12971                .arg(ad)
12972                .arg(&mut *y)
12973                .arg(&inf)
12974                .arg(&outf)
12975                .arg(&mi)
12976                .arg(&rb);
12977            unsafe {
12978                b.launch(cfg)?;
12979            }
12980            if scale != 1.0 {
12981                self.scale_inplace(y, scale, out_f)?;
12982            }
12983            return Ok(());
12984        }
12985        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
12986        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
12987        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
12988        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
12989        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
12990        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
12991        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
12992        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
12993        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
12994            2
12995        } else {
12996            1
12997        };
12998        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
12999        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
13000        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
13001        // valid-window interleaved, bit-identical per row — same dot program).
13002        if m == 1 && qtype == QT_Q4_0 {
13003            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13004            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
13005            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
13006            mr = *Q40MR.get_or_init(|| {
13007                std::env::var("MEMRA_Q40_MR")
13008                    .ok()
13009                    .and_then(|v| v.parse().ok())
13010                    .unwrap_or(1)
13011            });
13012        }
13013        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
13014        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
13015        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
13016        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
13017        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
13018        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
13019        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
13020        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
13021        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
13022        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
13023        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
13024        let q5_force = q5_mode.as_deref() == Some("2");
13025        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
13026        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
13027        let q5_il = qtype == QT_Q5_K
13028            && m == 1
13029            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
13030        if q5_il && !q5_force && out_f > 65536 {
13031            mr = 1;
13032        }
13033        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
13034        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
13035        if qtype == QT_Q4_0 && rp && mr != 1 {
13036            mr = 2;
13037        }
13038        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
13039        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
13040        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
13041        if qtype == QT_Q8_0 && rp {
13042            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13043            mr = *Q80MR.get_or_init(|| {
13044                std::env::var("MEMRA_Q80_MR")
13045                    .ok()
13046                    .and_then(|v| v.parse().ok())
13047                    .unwrap_or(1)
13048            });
13049        }
13050        let name = match (qtype, mr, rp) {
13051            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
13052            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
13053            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
13054            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
13055            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
13056            (QT_Q5_K, 2, _) => {
13057                if q5_il {
13058                    "qmatvec_q5_K_mmvq_mr2_il"
13059                } else {
13060                    "qmatvec_q5_K_mmvq_mr2"
13061                }
13062            }
13063            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
13064            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
13065            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
13066            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
13067            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
13068            (QT_Q8_0, _, true)
13069                if in_f % 1024 == 0 && {
13070                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13071                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
13072                } =>
13073            {
13074                "qmatvec_q8_0_mmvq_rpca"
13075            }
13076            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
13077            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
13078            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
13079            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
13080            // reach a GGUF-layout kernel or vice versa.
13081            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
13082            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
13083            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
13084            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
13085            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
13086            (QT_Q5_K, _, _) => {
13087                if q5_il {
13088                    "qmatvec_q5_K_mmvq_il"
13089                } else {
13090                    "qmatvec_q5_K_mmvq"
13091                }
13092            }
13093            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
13094            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
13095            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
13096            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
13097        };
13098        let f = self.func(name);
13099        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
13100        let rows_per_block = ROWS_PER_BLOCK * mr;
13101        let cfg = LaunchConfig {
13102            grid_dim: (
13103                (out_f as u32 + rows_per_block - 1) / rows_per_block,
13104                m as u32,
13105                1,
13106            ),
13107            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
13108            shared_mem_bytes: 0,                // warp-only reduce at m=1
13109        };
13110        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13111        let __s_b = self.gpu.stream();
13112        let mut b = __s_b.launch_builder(&f);
13113        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
13114        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
13115        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
13116        // weight_scale). Other mmvq kernels keep the 8-arg signature.
13117        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
13118            b.arg(bytes)
13119                .arg(aq)
13120                .arg(ad)
13121                .arg(&mut *y)
13122                .arg(&inf)
13123                .arg(&outf)
13124                .arg(&mi)
13125                .arg(&rb)
13126                .arg(&scale);
13127            unsafe {
13128                b.launch(cfg)?;
13129            }
13130        } else if Self::pdl_on()
13131            && Self::pdl_mmvq_on()
13132            && matches!(
13133                name,
13134                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
13135            )
13136        {
13137            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
13138            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
13139            // names may take this launch (unmarked kernels would read unordered).
13140            {
13141                use cudarc::driver::{DevicePtr, DevicePtrMut};
13142                let s = &self.gpu.stream();
13143                let (pw, _g0) = bytes.device_ptr(s);
13144                let (paq, _g1) = aq.device_ptr(s);
13145                let (pad, _g2) = ad.device_ptr(s);
13146                let (py, _g3) = y.device_ptr_mut(s);
13147                let mut ps = [
13148                    &pw as *const _ as *mut std::ffi::c_void,
13149                    &paq as *const _ as *mut _,
13150                    &pad as *const _ as *mut _,
13151                    &py as *const _ as *mut _,
13152                    &inf as *const _ as *mut _,
13153                    &outf as *const _ as *mut _,
13154                    &mi as *const _ as *mut _,
13155                    &rb as *const _ as *mut _,
13156                ];
13157                unsafe {
13158                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13159                }
13160            }
13161            if scale != 1.0 {
13162                self.scale_inplace(y, scale, m * out_f)?;
13163            }
13164        } else {
13165            b.arg(bytes)
13166                .arg(aq)
13167                .arg(ad)
13168                .arg(&mut *y)
13169                .arg(&inf)
13170                .arg(&outf)
13171                .arg(&mi)
13172                .arg(&rb);
13173            unsafe {
13174                b.launch(cfg)?;
13175            }
13176            if scale != 1.0 {
13177                self.scale_inplace(y, scale, m * out_f)?;
13178            }
13179        }
13180        Ok(())
13181    }
13182
13183    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
13184    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
13185    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
13186    pub fn qmatvec_mmvq_raw(
13187        &self,
13188        bytes: &CudaSlice<u8>,
13189        x: &CudaSlice<f32>,
13190        m: usize,
13191        in_f: usize,
13192        out_f: usize,
13193        qtype: i32,
13194        row_bytes: usize,
13195        rp: bool,
13196    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13197        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13198        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
13199    }
13200
13201    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
13202    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
13203    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
13204    pub fn batched_supports(&self, qtype: i32) -> bool {
13205        matches!(
13206            qtype,
13207            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
13208        )
13209    }
13210
13211    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
13212    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
13213    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
13214    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
13215    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
13216    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
13217    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
13218    pub fn iq_fast_enabled() -> bool {
13219        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13220        *ON.get_or_init(|| {
13221            std::env::var("MEMRA_IQ_FAST")
13222                .map(|v| v != "0")
13223                .unwrap_or(true)
13224        })
13225    }
13226
13227    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
13228    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
13229    pub fn b8_enabled() -> bool {
13230        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13231        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
13232    }
13233
13234    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
13235    pub fn batched_mcols(m: usize) -> usize {
13236        if m == 2 {
13237            2
13238        } else if m <= 4 {
13239            4
13240        } else if m <= 8 {
13241            8
13242        } else {
13243            16
13244        }
13245    }
13246
13247    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
13248    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
13249    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
13250    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
13251    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
13252        Some(match (qtype, mcols) {
13253            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
13254            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
13255            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
13256            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
13257            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
13258            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
13259            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
13260            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
13261            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
13262            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
13263            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
13264            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
13265            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
13266            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
13267            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
13268            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
13269            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
13270            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
13271            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
13272            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
13273            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
13274            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
13275            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
13276            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
13277            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
13278            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
13279            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
13280            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
13281            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
13282            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
13283            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
13284            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
13285            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
13286            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
13287            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
13288            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
13289            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
13290            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
13291            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
13292            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
13293            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
13294            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
13295            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
13296            _ => return None,
13297        })
13298    }
13299
13300    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
13301    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
13302    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
13303    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
13304    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
13305    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
13306    ///
13307    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
13308    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
13309    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
13310    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
13311    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
13312    /// msweep on all six 27B shapes (2026-07-03):
13313    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
13314    ///          it applies for b4 (-3..-14%), never loses;
13315    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
13316    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
13317    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
13318    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
13319    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
13320    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
13321    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
13322    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
13323    /// b2: in_f>=6144 -> r2, else base.
13324    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
13325    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
13326    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
13327    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
13328    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
13329    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
13330    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
13331    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
13332    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
13333    /// Device SM count (cached) — grid-fill policy input.
13334    pub fn sm_count(&self) -> i32 {
13335        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13336        *SMS.get_or_init(|| {
13337            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13338            self.gpu
13339                .ctx
13340                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13341                .unwrap_or(82)
13342        })
13343    }
13344
13345    pub fn batched_variant(
13346        &self,
13347        _m: usize,
13348        in_f: usize,
13349        out_f: usize,
13350        qtype: i32,
13351        row_bytes: usize,
13352        mcols: usize,
13353        rp: bool,
13354    ) -> &'static str {
13355        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
13356        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
13357        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
13358        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
13359        if qtype == QT_Q8_0 {
13360            return if rp { "rp" } else { "base" };
13361        }
13362        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13363        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
13364            Ok("base") => "base",
13365            Ok("pf") => "pf",
13366            Ok("r2") => "r2",
13367            Ok("r2w8") => "r2w8",
13368            Ok("pfr2") => "pfr2",
13369            Ok("ca") => "ca",
13370            Ok("car2") => "car2",
13371            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
13372            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
13373            Ok("rp") => "rp",
13374            Ok("rpr2") => "rpr2",
13375            Ok("rpr2w8") => "rpr2w8",
13376            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
13377            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
13378            Ok("rpca") => "rpca",
13379            Ok("rpcar2") => "rpcar2",
13380            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
13381            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
13382            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
13383            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
13384            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
13385            // bit-identical to the decode path — measurement corpus ONLY, never auto).
13386            Ok("rpsc") => "rpsc",
13387            Ok("rpms") => "rpms",
13388            Ok("rpmsc") => "rpmsc",
13389            Ok("rpks") => "rpks",
13390            Ok("rpksc") => "rpksc",
13391            _ => "auto",
13392        });
13393        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
13394        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
13395        // shapes qualify; anything else falls back to the register variants.
13396        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
13397        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
13398        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
13399        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
13400        // forced MEMRA_MMVQ_BV values still work).
13401        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13402        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
13403        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
13404        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
13405        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13406        let sms = *SMS.get_or_init(|| {
13407            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13408            self.gpu
13409                .ctx
13410                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13411                .unwrap_or(82)
13412        });
13413        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
13414        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
13415        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
13416        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
13417        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
13418        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
13419        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
13420        // AUTO RULE = the measured winners table (differs from NVFP4's!):
13421        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
13422        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
13423        //     r2 1258us) — kernels kept behind the force seam for the corpus;
13424        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
13425        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
13426        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
13427        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
13428        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
13429        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
13430        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
13431        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
13432        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
13433        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
13434        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
13435        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13436        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
13437            Ok("base") => "base",
13438            Ok("r2") => "r2",
13439            Ok("r2w8") => "r2w8",
13440            _ => "auto",
13441        });
13442        let variant: &'static str = if qtype == QT_Q4_0 {
13443            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
13444            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
13445            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
13446            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13447            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
13448                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
13449                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
13450                // + syncs cost more than the stalls, bank-pad made no difference);
13451                // register load-ahead flat (nvcc already reorders). The b-tier limiter
13452                // is still unidentified — see the jsonl row.
13453                Ok("base") => "base",
13454                Ok("r2") => "r2",
13455                Ok("ms") => "ms",
13456                Ok("sm") => "sm",
13457                Ok("la") => "la",
13458                _ => "auto",
13459            });
13460            let v = if q40 != "auto" {
13461                q40
13462            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
13463                "r2"
13464            } else {
13465                "base"
13466            };
13467            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
13468            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
13469            // and the limiter is the per-column activation load chain (long_scoreboard
13470            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
13471            if rp {
13472                match v {
13473                    "ms" => "r2ms_rp",
13474                    "sm" => "r2sm_rp",
13475                    "la" => "r2la_rp",
13476                    "r2" => "r2_rp",
13477                    _ => "rp",
13478                }
13479            } else if matches!(v, "ms" | "sm" | "la") {
13480                "r2"
13481            } else {
13482                v
13483            }
13484        } else if qtype != QT_NVFP4 && !kq_r2 {
13485            "base"
13486        } else if kq_r2 && rp {
13487            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
13488            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
13489            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
13490            "rp"
13491        } else if kq_r2 {
13492            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
13493            // mcols != 4 forced r2w8 falls to unbounded r2.
13494            if kq_bv != "auto" {
13495                if kq_bv == "r2w8" && mcols != 4 {
13496                    "r2"
13497                } else {
13498                    kq_bv
13499                }
13500            } else if bv != "auto" {
13501                match bv {
13502                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
13503                    "r2w8" | "rpr2w8" => {
13504                        if mcols != 4 {
13505                            "r2"
13506                        } else {
13507                            "r2w8"
13508                        }
13509                    }
13510                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
13511                }
13512            } else {
13513                let blocks = (out_f + 7) / 8;
13514                let waves = blocks as f64 / (7 * sms as usize) as f64;
13515                let filled = blocks >= 4 * sms as usize;
13516                let use_r2 = if qtype == QT_Q4_K {
13517                    filled
13518                } else {
13519                    waves >= 2.0
13520                };
13521                if use_r2 { "r2" } else { "base" }
13522            }
13523        } else if bv != "auto" {
13524            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
13525            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
13526            // unsupported (shape, mcols) combos fall back to pf/r2.
13527            // On rp buffers, forced legacy names map to their rp twins (layout law).
13528            let v = if bv == "r2w8" && mcols == 2 {
13529                "r2"
13530            } else if bv == "ca" && (!ca_ok || mcols == 8) {
13531                "pf"
13532            } else if bv == "car2" && (!ca_ok || mcols == 8) {
13533                "r2"
13534            } else if bv == "pfr2" && mcols == 8 {
13535                "r2"
13536            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
13537                "rpr2"
13538            }
13539            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
13540            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
13541                if mcols == 8 { "rpr2w8" } else { "rpr2" }
13542            } else if bv == "rpcar2" && mcols == 2 {
13543                "rpca"
13544            }
13545            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
13546            // (rpms has no smem and no alignment need — always valid on rp buffers).
13547            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
13548                "rpr2"
13549            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
13550                "rpr2"
13551            } else {
13552                bv
13553            };
13554            if rp {
13555                match v {
13556                    "base" | "pf" | "ca" | "rp" => "rp",
13557                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
13558                    "r2w8" | "rpr2w8" => {
13559                        if mcols == 2 {
13560                            "rpr2"
13561                        } else {
13562                            "rpr2w8"
13563                        }
13564                    }
13565                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
13566                }
13567            } else {
13568                v
13569            }
13570        } else if mcols == 8 {
13571            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
13572            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
13573            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
13574            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
13575            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
13576            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
13577            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
13578            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
13579            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
13580            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
13581            if rp {
13582                if sc_ok { "rpsc" } else { "rpr2w8" }
13583            } else {
13584                "r2w8"
13585            }
13586        } else if mcols >= 4 {
13587            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
13588            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
13589            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
13590            let blocks = (out_f + 7) / 8;
13591            let r7 = 7 * sms as usize;
13592            let r8 = 8 * sms as usize;
13593            let waves = blocks as f64 / r7 as f64;
13594            let filled = blocks >= 4 * sms as usize;
13595            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
13596            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
13597            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
13598            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
13599                // the extra residency drops the INTEGER wave count -> the straggler wave a
13600                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
13601                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
13602                if rp { "rpr2w8" } else { "r2w8" }
13603            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
13604                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
13605                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
13606                if rp { "rpr2" } else { "r2" }
13607            } else {
13608                // fractional straggler-wave window with no crossing, or grid too small to fill
13609                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
13610                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
13611                if rp { "rp" } else { "pf" }
13612            }
13613        } else if in_f >= 6144 {
13614            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
13615            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
13616            // stays.
13617            if rp { "rpr2" } else { "r2" }
13618        } else if rp {
13619            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
13620            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
13621            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
13622            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
13623            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
13624            if sc_ok && waves >= 0.9 && waves <= 1.1 {
13625                "rpsc"
13626            } else {
13627                "rp"
13628            }
13629        } else {
13630            "base"
13631        };
13632        variant
13633    }
13634
13635    pub fn qmatvec_mmvq_batched(
13636        &self,
13637        bytes: &CudaSlice<u8>,
13638        aq: &CudaSlice<i8>,
13639        ad: &CudaSlice<f32>,
13640        m: usize,
13641        in_f: usize,
13642        out_f: usize,
13643        qtype: i32,
13644        row_bytes: usize,
13645        mcols: usize,
13646        scale: f32,
13647        rp: bool,
13648    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13649        const ROWS_PER_BLOCK: u32 = 4;
13650        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
13651        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
13652        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
13653        // weight keeps its rp-layout kernel family regardless of the override.
13654        let forced: Option<&'static str> = {
13655            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
13656            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
13657                .as_deref()
13658                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
13659        };
13660        let variant = match forced {
13661            Some(v) if !rp || v.contains("rp") => v,
13662            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
13663        };
13664        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
13665            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
13666        })?;
13667        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
13668        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
13669        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
13670        let variant = if mcols == 16 {
13671            if rp { "rp" } else { "base" }
13672        } else {
13673            variant
13674        };
13675        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
13676        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
13677        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
13678        // per-(token,row) chain (columns c >= m never execute in either form) ->
13679        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
13680        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
13681        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13682        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
13683        if b567
13684            && qtype == QT_NVFP4
13685            && rp
13686            && mcols == 8
13687            && (5..=7).contains(&m)
13688            && matches!(variant, "rpsc" | "rpr2w8")
13689        {
13690            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
13691            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
13692            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13693            let cfg = LaunchConfig {
13694                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13695                block_dim: (32, ROWS_PER_BLOCK, 1),
13696                shared_mem_bytes: 0,
13697            };
13698            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13699            let __s_b = self.gpu.stream();
13700            let mut b = __s_b.launch_builder(&f);
13701            b.arg(bytes)
13702                .arg(aq)
13703                .arg(ad)
13704                .arg(&mut y)
13705                .arg(&inf)
13706                .arg(&outf)
13707                .arg(&mi)
13708                .arg(&rb);
13709            unsafe {
13710                b.launch(cfg)?;
13711            }
13712            if scale != 1.0 {
13713                self.scale_inplace(&mut y, scale, m * out_f)?;
13714            }
13715            return Ok(y);
13716        }
13717        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
13718            "base" => (base_name.into(), ROWS_PER_BLOCK),
13719            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
13720            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
13721            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
13722            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
13723            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
13724            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
13725            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
13726            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
13727            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
13728            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
13729            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
13730            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
13731            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
13732            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
13733        };
13734        debug_assert!(
13735            !rp || name.contains("_rp"),
13736            "rp weight dispatched to a GGUF-layout kernel"
13737        );
13738        let f = self.func(&name);
13739        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13740        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
13741        let smem = if name.contains("_r2sm_rp") {
13742            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
13743        } else {
13744            0
13745        };
13746        let cfg = LaunchConfig {
13747            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13748            block_dim: (32, ROWS_PER_BLOCK, 1),
13749            shared_mem_bytes: smem,
13750        };
13751        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13752        let __s_b = self.gpu.stream();
13753        let mut b = __s_b.launch_builder(&f);
13754        b.arg(bytes)
13755            .arg(aq)
13756            .arg(ad)
13757            .arg(&mut y)
13758            .arg(&inf)
13759            .arg(&outf)
13760            .arg(&mi)
13761            .arg(&rb);
13762        unsafe {
13763            b.launch(cfg)?;
13764        }
13765        if scale != 1.0 {
13766            self.scale_inplace(&mut y, scale, m * out_f)?;
13767        }
13768        Ok(y)
13769    }
13770
13771    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
13772    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
13773    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
13774    pub fn qmatvec_batched_raw(
13775        &self,
13776        bytes: &CudaSlice<u8>,
13777        x: &CudaSlice<f32>,
13778        m: usize,
13779        in_f: usize,
13780        out_f: usize,
13781        qtype: i32,
13782        row_bytes: usize,
13783        mcols: usize,
13784        rp: bool,
13785    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13786        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13787        self.qmatvec_mmvq_batched(
13788            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
13789        )
13790    }
13791
13792    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
13793    pub fn qmatvec_nvfp4_batched_raw(
13794        &self,
13795        bytes: &CudaSlice<u8>,
13796        x: &CudaSlice<f32>,
13797        m: usize,
13798        in_f: usize,
13799        out_f: usize,
13800        row_bytes: usize,
13801        mcols: usize,
13802        rp: bool,
13803    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13804        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
13805    }
13806
13807    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
13808    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
13809    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
13810    fn try_fp4_gemm(
13811        &self,
13812        w: &crate::model::GpuTensor,
13813        x: &CudaSlice<f32>,
13814        m: usize,
13815        in_f: usize,
13816        out_f: usize,
13817    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13818        use crate::model::GpuTensor;
13819        if cfg!(memra_portable_cuda) {
13820            return Ok(None);
13821        }
13822        if std::env::var("MEMRA_FP4").is_err() {
13823            return Ok(None);
13824        }
13825        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
13826        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
13827        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
13828        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
13829        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
13830        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
13831        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
13832        // for the common no-macro-scale case.
13833        #[cfg(memra_cutlass)]
13834        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
13835            if let GpuTensor::Quant {
13836                bytes,
13837                qtype,
13838                scale,
13839                row_bytes,
13840                cutlass,
13841                ..
13842            } = w
13843            {
13844                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
13845                    if let Some(cw) = cutlass {
13846                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
13847                        let y = self.cutlass_fp4_gemm(
13848                            &cw.b_packed,
13849                            &cw.sfb_swizzled,
13850                            x,
13851                            *scale,
13852                            m,
13853                            out_f,
13854                            in_f,
13855                        )?;
13856                        return Ok(Some(y));
13857                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
13858                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
13859                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
13860                        // (the load-time repack ~doubles it) — needed for models that don't fit the
13861                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
13862                        let (b_packed, sfb_sw) =
13863                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
13864                        let y =
13865                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
13866                        return Ok(Some(y));
13867                    }
13868                }
13869            }
13870        }
13871        if let GpuTensor::Quant {
13872            bytes,
13873            qtype,
13874            row_bytes,
13875            scale,
13876            rp,
13877            ..
13878        } = w
13879        {
13880            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
13881            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
13882            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
13883                let y =
13884                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
13885                return Ok(Some(y));
13886            }
13887        }
13888        Ok(None)
13889    }
13890
13891    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
13892    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
13893    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
13894    pub fn rms_norm_f16out(
13895        &self,
13896        x: &CudaSlice<f32>,
13897        w: &CudaSlice<f32>,
13898        dst: &mut CudaSlice<f32>,
13899        dst16: &mut CudaSlice<u8>,
13900        ncols: usize,
13901        nrows: usize,
13902        eps: f32,
13903    ) -> Result<(), Box<dyn std::error::Error>> {
13904        let f = self.func("rms_norm_f16out_f32");
13905        let cfg = LaunchConfig {
13906            grid_dim: (nrows as u32, 1, 1),
13907            block_dim: (rms_block(), 1, 1),
13908            shared_mem_bytes: 0,
13909        };
13910        let (nc, e) = (ncols as i32, eps);
13911        let __s_b = self.gpu.stream();
13912        let mut b = __s_b.launch_builder(&f);
13913        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
13914        unsafe {
13915            b.launch(cfg)?;
13916        }
13917        Ok(())
13918    }
13919
13920    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
13921    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
13922    #[allow(clippy::too_many_arguments)]
13923    pub fn add_rms_norm_f16out(
13924        &self,
13925        a: &CudaSlice<f32>,
13926        b: &CudaSlice<f32>,
13927        w: &CudaSlice<f32>,
13928        res: &mut CudaSlice<f32>,
13929        dst: &mut CudaSlice<f32>,
13930        dst16: &mut CudaSlice<u8>,
13931        ncols: usize,
13932        nrows: usize,
13933        eps: f32,
13934    ) -> Result<(), Box<dyn std::error::Error>> {
13935        let f = self.func("add_rms_norm_f16out_f32");
13936        let cfg = LaunchConfig {
13937            grid_dim: (nrows as u32, 1, 1),
13938            block_dim: (rms_block(), 1, 1),
13939            shared_mem_bytes: 0,
13940        };
13941        let (nc, e) = (ncols as i32, eps);
13942        let __s_lb = self.gpu.stream();
13943        let mut lb = __s_lb.launch_builder(&f);
13944        lb.arg(a)
13945            .arg(b)
13946            .arg(w)
13947            .arg(res)
13948            .arg(dst)
13949            .arg(dst16)
13950            .arg(&nc)
13951            .arg(&e);
13952        unsafe {
13953            lb.launch(cfg)?;
13954        }
13955        Ok(())
13956    }
13957
13958    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
13959    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
13960    pub fn matmul_group_xh(
13961        &self,
13962        ws: &[&crate::model::GpuTensor],
13963        x: &CudaSlice<f32>,
13964        xh: &CudaSlice<u8>,
13965        m: usize,
13966    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13967        let mut out = Vec::with_capacity(ws.len());
13968        let in_f = ws[0].in_features();
13969        for w in ws {
13970            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
13971                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
13972                    out.push(y);
13973                    continue;
13974                }
13975            }
13976            out.push(self.matmul(w, x, m)?);
13977        }
13978        Ok(out)
13979    }
13980
13981    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
13982    /// GDN steps). Layouts [T, H].
13983    pub fn gdn_pad_mask(
13984        &self,
13985        beta: &mut CudaSlice<f32>,
13986        g_log: &mut CudaSlice<f32>,
13987        len_d: &CudaSlice<i32>,
13988        h: usize,
13989        t: usize,
13990    ) -> Result<(), Box<dyn std::error::Error>> {
13991        let f = self.func("gdn_pad_mask_f32");
13992        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
13993        let (hi, ti) = (h as i32, t as i32);
13994        let __s_b = self.gpu.stream();
13995        let mut b = __s_b.launch_builder(&f);
13996        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
13997        unsafe {
13998            b.launch(cfg)?;
13999        }
14000        Ok(())
14001    }
14002
14003    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
14004    /// gather for the padded prime graph's h_seed/hlast.
14005    pub fn row_gather_dev(
14006        &self,
14007        src: &CudaSlice<f32>,
14008        dst: &mut CudaSlice<f32>,
14009        len_d: &CudaSlice<i32>,
14010        ncols: usize,
14011    ) -> Result<(), Box<dyn std::error::Error>> {
14012        let f = self.func("row_gather_dev_f32");
14013        let cfg = LaunchConfig::for_num_elems(ncols as u32);
14014        let nc = ncols as i32;
14015        let __s_b = self.gpu.stream();
14016        let mut b = __s_b.launch_builder(&f);
14017        b.arg(src).arg(dst).arg(len_d).arg(&nc);
14018        unsafe {
14019            b.launch(cfg)?;
14020        }
14021        Ok(())
14022    }
14023
14024    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
14025    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
14026    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
14027    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
14028    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
14029    /// different in_f) falls back to its own `matmul` — behavior unchanged.
14030    pub fn matmul_group(
14031        &self,
14032        ws: &[&crate::model::GpuTensor],
14033        x: &CudaSlice<f32>,
14034        m: usize,
14035    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14036        use crate::model::GpuTensor;
14037        let mut out = Vec::with_capacity(ws.len());
14038        let any_mirror = ws
14039            .iter()
14040            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
14041        if m >= 16 && any_mirror && !self.verify_exact_on() {
14042            let in_f = ws[0].in_features();
14043            let xh = self.f16_act(x, m * in_f, in_f)?;
14044            for w in ws {
14045                if w.in_features() == in_f {
14046                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
14047                        out.push(y);
14048                        continue;
14049                    }
14050                }
14051                out.push(self.matmul(w, x, m)?);
14052            }
14053            return Ok(out);
14054        }
14055        for w in ws {
14056            out.push(self.matmul(w, x, m)?);
14057        }
14058        Ok(out)
14059    }
14060
14061    /// Cross-request grouped matmul (task #13): run ONE projection group over the
14062    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
14063    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
14064    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
14065    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
14066    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
14067    pub fn matmul_group_multi(
14068        &self,
14069        ws: &[&crate::model::GpuTensor],
14070        xs: &[&CudaSlice<f32>],
14071        ms: &[usize],
14072    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
14073        assert_eq!(xs.len(), ms.len());
14074        let in_f = ws[0].in_features();
14075        let total: usize = ms.iter().sum();
14076        let mut xcat = self.uninit(total * in_f)?;
14077        let mut off = 0usize;
14078        for (x, &m) in xs.iter().zip(ms) {
14079            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
14080            off += m;
14081        }
14082        let ys = self.matmul_group(ws, &xcat, total)?;
14083        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
14084        for (w, y) in ws.iter().zip(ys) {
14085            let out_f = w.out_features();
14086            let mut off = 0usize;
14087            for (s, &m) in ms.iter().enumerate() {
14088                let mut ys_s = self.uninit(m * out_f)?;
14089                let src = y.slice(off * out_f..(off + m) * out_f);
14090                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
14091                out[s].push(ys_s);
14092                off += m;
14093            }
14094        }
14095        Ok(out)
14096    }
14097
14098    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
14099    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
14100    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
14101    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
14102    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
14103    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
14104    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
14105    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
14106    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
14107    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
14108        use crate::model::GpuTensor;
14109        if !legacy_quant_gemm_allowed(
14110            cfg!(memra_portable_cuda),
14111            cfg!(memra_hopper_mma),
14112            std::env::var_os("MEMRA_NO_GEMM").is_some(),
14113        ) {
14114            return false;
14115        }
14116        match w {
14117            GpuTensor::Quant { qtype, .. } => {
14118                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
14119                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
14120            }
14121            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
14122        }
14123    }
14124
14125    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
14126    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
14127    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
14128    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
14129    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
14130    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
14131    pub fn qmatvec_gemm(
14132        &self,
14133        w: &crate::model::GpuTensor,
14134        aq: &CudaSlice<i8>,
14135        ad: &CudaSlice<f32>,
14136        m: usize,
14137    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14138        use crate::model::GpuTensor;
14139        let in_f = w.in_features();
14140        let out_f = w.out_features();
14141        let (bytes, qtype, row_bytes, scale, rp) = match w {
14142            GpuTensor::Quant {
14143                bytes,
14144                qtype,
14145                row_bytes,
14146                scale,
14147                rp,
14148                ..
14149            } => (bytes, *qtype, *row_bytes, *scale, *rp),
14150            _ => unreachable!("gemm_supports guaranteed Quant"),
14151        };
14152        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
14153        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
14154        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
14155        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
14156        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
14157        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
14158            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
14159                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
14160                if scale != 1.0 {
14161                    self.scale_inplace(&mut y, scale, m * out_f)?;
14162                }
14163                return Ok(y);
14164            }
14165        }
14166        let name = match qtype {
14167            QT_Q8_0 => "qmatvec_gemm_q8_0",
14168            QT_Q4_K => "qmatvec_gemm_q4_K",
14169            QT_Q4_0 => {
14170                if rp {
14171                    "qmatvec_gemm_q4_0_rp"
14172                } else {
14173                    "qmatvec_gemm_q4_0"
14174                }
14175            }
14176            QT_Q5_K => "qmatvec_gemm_q5_K",
14177            QT_Q6_K => "qmatvec_gemm_q6_K",
14178            QT_NVFP4 => {
14179                if rp {
14180                    "qmatvec_gemm_nvfp4_rp"
14181                } else {
14182                    "qmatvec_gemm_nvfp4"
14183                }
14184            }
14185            _ => unreachable!(),
14186        };
14187        let f = self.func(name);
14188        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14189        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
14190        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
14191        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
14192        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14193        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14194        let k1_tile = if is_k1 {
14195            k1_launch_override().unwrap_or((128, 128, 8))
14196        } else {
14197            (128, 128, 8)
14198        };
14199        let (bm, bn): (u32, u32) = if is_k1 {
14200            (k1_tile.0, k1_tile.1)
14201        } else {
14202            (64, 256)
14203        };
14204        let warps: u32 = if is_k1 {
14205            k1_tile.2
14206        } else {
14207            match qtype {
14208                QT_NVFP4 => 8,
14209                _ => 4,
14210            }
14211        };
14212        let cfg = LaunchConfig {
14213            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14214            block_dim: (32, warps, 1),
14215            shared_mem_bytes: 0,
14216        };
14217        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14218        let __s_b = self.gpu.stream();
14219        let mut b = __s_b.launch_builder(&f);
14220        b.arg(bytes)
14221            .arg(aq)
14222            .arg(ad)
14223            .arg(&mut y)
14224            .arg(&inf)
14225            .arg(&outf)
14226            .arg(&mi)
14227            .arg(&rb);
14228        unsafe {
14229            b.launch(cfg)?;
14230        }
14231        if scale != 1.0 {
14232            self.scale_inplace(&mut y, scale, m * out_f)?;
14233        }
14234        Ok(y)
14235    }
14236
14237    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
14238    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
14239    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
14240    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
14241    pub fn qmatvec_gemm_raw(
14242        &self,
14243        bytes: &CudaSlice<u8>,
14244        x: &CudaSlice<f32>,
14245        m: usize,
14246        in_f: usize,
14247        out_f: usize,
14248        qtype: i32,
14249        row_bytes: usize,
14250    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14251        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14252        let name = match qtype {
14253            QT_Q8_0 => "qmatvec_gemm_q8_0",
14254            QT_Q4_K => "qmatvec_gemm_q4_K",
14255            QT_Q4_0 => "qmatvec_gemm_q4_0",
14256            QT_Q5_K => "qmatvec_gemm_q5_K",
14257            QT_Q6_K => "qmatvec_gemm_q6_K",
14258            QT_NVFP4 => "qmatvec_gemm_nvfp4",
14259            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
14260            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
14261        };
14262        let f = self.func(name);
14263        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14264        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
14265        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
14266        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14267        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14268        let k1_tile = if is_k1 {
14269            k1_launch_override().unwrap_or((128, 128, 8))
14270        } else {
14271            (128, 128, 8)
14272        };
14273        let (bm, bn): (u32, u32) = if is_k1 {
14274            (k1_tile.0, k1_tile.1)
14275        } else {
14276            (64, 256)
14277        };
14278        let warps: u32 = if is_k1 {
14279            k1_tile.2
14280        } else {
14281            match qtype {
14282                QT_NVFP4 | QT_NVFP4_RP => 8,
14283                _ => 4,
14284            }
14285        };
14286        let cfg = LaunchConfig {
14287            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14288            block_dim: (32, warps, 1),
14289            shared_mem_bytes: 0,
14290        };
14291        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14292        let __s_b = self.gpu.stream();
14293        let mut b = __s_b.launch_builder(&f);
14294        b.arg(bytes)
14295            .arg(&aq)
14296            .arg(&ad)
14297            .arg(&mut y)
14298            .arg(&inf)
14299            .arg(&outf)
14300            .arg(&mi)
14301            .arg(&rb);
14302        unsafe {
14303            b.launch(cfg)?;
14304        }
14305        Ok(y)
14306    }
14307
14308    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
14309    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
14310    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
14311    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
14312    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
14313    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
14314    pub fn qmatvec_gemm_q8_0_wgmma_raw(
14315        &self,
14316        rp4: &CudaSlice<u8>,
14317        aq: &CudaSlice<i8>,
14318        ad: &CudaSlice<f32>,
14319        m: usize,
14320        in_f: usize,
14321        out_f: usize,
14322    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14323        assert!(
14324            out_f % 64 == 0 && in_f % 32 == 0,
14325            "wgmma GEMM needs out_f%64==0, in_f%32==0"
14326        );
14327        let f = self.func("qmatvec_gemm_q8_0_wgmma");
14328        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
14329        let cfg = LaunchConfig {
14330            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
14331            block_dim: (128, 1, 1),
14332            shared_mem_bytes: 0,
14333        };
14334        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
14335        let __s_b = self.gpu.stream();
14336        let mut b = __s_b.launch_builder(&f);
14337        b.arg(rp4)
14338            .arg(aq)
14339            .arg(ad)
14340            .arg(&mut y)
14341            .arg(&inf)
14342            .arg(&outf)
14343            .arg(&mi);
14344        unsafe {
14345            b.launch(cfg)?;
14346        }
14347        Ok(y)
14348    }
14349
14350    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
14351    pub fn scale_inplace(
14352        &self,
14353        y: &mut CudaSlice<f32>,
14354        s: f32,
14355        n: usize,
14356    ) -> Result<(), Box<dyn std::error::Error>> {
14357        let f = self.func("scale_f32");
14358        let cfg = LaunchConfig::for_num_elems(n as u32);
14359        let (sf, ni) = (s, n as i32);
14360        let __s_b = self.gpu.stream();
14361        let mut b = __s_b.launch_builder(&f);
14362        b.arg(y).arg(&sf).arg(&ni);
14363        unsafe {
14364            b.launch(cfg)?;
14365        }
14366        Ok(())
14367    }
14368
14369    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
14370    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
14371    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
14372    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
14373    pub fn bf16_to_f32(
14374        &self,
14375        data: &cudarc::driver::CudaView<'_, u8>,
14376        n: usize,
14377    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14378        let mut out = self.alloc_uninit::<f32>(n)?;
14379        let f = self.func("bf16_to_f32");
14380        let cfg = LaunchConfig::for_num_elems(n as u32);
14381        let ni = n as i32;
14382        let __s_b = self.gpu.stream();
14383        let mut b = __s_b.launch_builder(&f);
14384        b.arg(data).arg(&mut out).arg(&ni);
14385        unsafe {
14386            b.launch(cfg)?;
14387        }
14388        Ok(out)
14389    }
14390
14391    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
14392    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
14393    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
14394    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
14395    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
14396    /// calls, the spec-verify contract) vs plain linear.
14397    fn linear_bf16_chunked(
14398        &self,
14399        x: &CudaSlice<f32>,
14400        data: &CudaSlice<u8>,
14401        m: usize,
14402        in_f: usize,
14403        out_f: usize,
14404        exact: bool,
14405    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14406        const CHUNK_BYTES: usize = 256 << 20;
14407        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
14408        if chunk_rows >= out_f {
14409            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
14410            return if exact {
14411                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
14412            } else {
14413                self.linear(x, &wf32, m, in_f, out_f)
14414            };
14415        }
14416        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14417        let mut r0 = 0usize;
14418        while r0 < out_f {
14419            let rows = chunk_rows.min(out_f - r0);
14420            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
14421            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
14422            let yc = if exact {
14423                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
14424            } else {
14425                self.linear(x, &wf32, m, in_f, rows)?
14426            };
14427            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
14428            for mi in 0..m {
14429                let src = yc.slice(mi * rows..(mi + 1) * rows);
14430                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
14431                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
14432            }
14433            r0 += rows;
14434        }
14435        Ok(y)
14436    }
14437
14438    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
14439    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
14440    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
14441    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
14442    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
14443    /// router/shexp sites and matmul_decode_exact's Float arm.
14444    pub fn linear_decode_exact(
14445        &self,
14446        x: &CudaSlice<f32>,
14447        w: &CudaSlice<f32>,
14448        m_tokens: usize,
14449        in_f: usize,
14450        out_f: usize,
14451    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14452        if m_tokens == 1 {
14453            return self.linear(x, w, 1, in_f, out_f);
14454        }
14455        let xv = self.view(x, m_tokens * in_f);
14456        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
14457        for t in 0..m_tokens {
14458            let row = xv.slice(t * in_f..(t + 1) * in_f);
14459            let mut xr = self.alloc_uninit::<f32>(in_f)?;
14460            self.copy_view_into(&mut xr, 0, &row, in_f)?;
14461            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
14462            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
14463        }
14464        Ok(y)
14465    }
14466
14467    pub fn linear(
14468        &self,
14469        x: &CudaSlice<f32>,
14470        w: &CudaSlice<f32>,
14471        m_tokens: usize,
14472        in_f: usize,
14473        out_f: usize,
14474    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14475        use cudarc::cublaslt::{Matmul, MatmulConfig};
14476        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
14477        let cfg = MatmulConfig {
14478            transa: true,
14479            transb: false,
14480            transc: false,
14481            m: out_f as u64,
14482            n: m_tokens as u64,
14483            k: in_f as u64,
14484            alpha: 1.0,
14485            lda: in_f as i64,
14486            ldb: in_f as i64,
14487            beta: 0.0,
14488            ldc: out_f as i64,
14489            stride_a: None,
14490            stride_b: None,
14491            stride_c: None,
14492            stride_bias: None,
14493            batch_size: None,
14494        };
14495        unsafe {
14496            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
14497        }
14498        Ok(c)
14499    }
14500
14501    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
14502    pub fn sdpa_naive(
14503        &self,
14504        q: &CudaSlice<f32>,
14505        k: &CudaSlice<f32>,
14506        v: &CudaSlice<f32>,
14507        o: &mut CudaSlice<f32>,
14508        head_dim: usize,
14509        n_head: usize,
14510        n_head_kv: usize,
14511        t: usize,
14512        t_kv: usize,
14513        scale: f32,
14514        causal: bool,
14515    ) -> Result<(), Box<dyn std::error::Error>> {
14516        let f = self.func("sdpa_naive_f32");
14517        let cfg = LaunchConfig {
14518            grid_dim: (n_head as u32, t as u32, 1),
14519            block_dim: (128, 1, 1),
14520            shared_mem_bytes: (t_kv * 4) as u32,
14521        };
14522        let (hd, nh, nhkv, ti, tkvi, cz) = (
14523            head_dim as i32,
14524            n_head as i32,
14525            n_head_kv as i32,
14526            t as i32,
14527            t_kv as i32,
14528            causal as i32,
14529        );
14530        let __s_b = self.gpu.stream();
14531        let mut b = __s_b.launch_builder(&f);
14532        b.arg(q)
14533            .arg(k)
14534            .arg(v)
14535            .arg(o)
14536            .arg(&hd)
14537            .arg(&nh)
14538            .arg(&nhkv)
14539            .arg(&ti)
14540            .arg(&tkvi)
14541            .arg(&scale)
14542            .arg(&cz);
14543        unsafe {
14544            b.launch(cfg)?;
14545        }
14546        Ok(())
14547    }
14548
14549    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
14550    #[allow(clippy::too_many_arguments)]
14551    pub fn sdpa_naive_w(
14552        &self,
14553        q: &CudaSlice<f32>,
14554        k: &CudaSlice<f32>,
14555        v: &CudaSlice<f32>,
14556        o: &mut CudaSlice<f32>,
14557        head_dim: usize,
14558        n_head: usize,
14559        n_head_kv: usize,
14560        t: usize,
14561        t_kv: usize,
14562        scale: f32,
14563        causal: bool,
14564        window: usize,
14565    ) -> Result<(), Box<dyn std::error::Error>> {
14566        let f = self.func("sdpa_naive_w_f32");
14567        let cfg = LaunchConfig {
14568            grid_dim: (n_head as u32, t as u32, 1),
14569            block_dim: (128, 1, 1),
14570            shared_mem_bytes: (t_kv * 4) as u32,
14571        };
14572        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14573            head_dim as i32,
14574            n_head as i32,
14575            n_head_kv as i32,
14576            t as i32,
14577            t_kv as i32,
14578            causal as i32,
14579            window as i32,
14580        );
14581        let __s_b = self.gpu.stream();
14582        let mut b = __s_b.launch_builder(&f);
14583        b.arg(q)
14584            .arg(k)
14585            .arg(v)
14586            .arg(o)
14587            .arg(&hd)
14588            .arg(&nh)
14589            .arg(&nhkv)
14590            .arg(&ti)
14591            .arg(&tkvi)
14592            .arg(&scale)
14593            .arg(&cz)
14594            .arg(&wi);
14595        unsafe {
14596            b.launch(cfg)?;
14597        }
14598        Ok(())
14599    }
14600
14601    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
14602    pub fn sdpa_naive_view(
14603        &self,
14604        q: &CudaSlice<f32>,
14605        k: &cudarc::driver::CudaView<f32>,
14606        v: &cudarc::driver::CudaView<f32>,
14607        o: &mut CudaSlice<f32>,
14608        head_dim: usize,
14609        n_head: usize,
14610        n_head_kv: usize,
14611        t: usize,
14612        t_kv: usize,
14613        scale: f32,
14614        causal: bool,
14615    ) -> Result<(), Box<dyn std::error::Error>> {
14616        let f = self.func("sdpa_naive_f32");
14617        let cfg = LaunchConfig {
14618            grid_dim: (n_head as u32, t as u32, 1),
14619            block_dim: (128, 1, 1),
14620            shared_mem_bytes: (t_kv * 4) as u32,
14621        };
14622        let (hd, nh, nhkv, ti, tkvi, cz) = (
14623            head_dim as i32,
14624            n_head as i32,
14625            n_head_kv as i32,
14626            t as i32,
14627            t_kv as i32,
14628            causal as i32,
14629        );
14630        let __s_b = self.gpu.stream();
14631        let mut b = __s_b.launch_builder(&f);
14632        b.arg(q)
14633            .arg(k)
14634            .arg(v)
14635            .arg(o)
14636            .arg(&hd)
14637            .arg(&nh)
14638            .arg(&nhkv)
14639            .arg(&ti)
14640            .arg(&tkvi)
14641            .arg(&scale)
14642            .arg(&cz);
14643        unsafe {
14644            b.launch(cfg)?;
14645        }
14646        Ok(())
14647    }
14648
14649    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
14650    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
14651    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
14652    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
14653    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
14654    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
14655    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
14656    #[allow(clippy::too_many_arguments)]
14657    pub fn fa_dequant_kv_view_f32(
14658        &self,
14659        k: &cudarc::driver::CudaView<u8>,
14660        v: &cudarc::driver::CudaView<u8>,
14661        kf: &mut CudaSlice<f32>,
14662        vf: &mut CudaSlice<f32>,
14663        kv_dim_k: usize,
14664        kv_dim_v: usize,
14665        t_kv: usize,
14666        k_tok_bytes: usize,
14667        v_tok_bytes: usize,
14668        g: bool,
14669    ) -> Result<(), Box<dyn std::error::Error>> {
14670        let f = if g {
14671            self.func_g("fa_dequant_kv_ws_f32")
14672        } else {
14673            self.func("fa_dequant_kv_ws_f32")
14674        };
14675        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
14676        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14677        let cfg = LaunchConfig {
14678            grid_dim: (nblk.max(1), 1, 1),
14679            block_dim: (256, 1, 1),
14680            shared_mem_bytes: 0,
14681        };
14682        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
14683        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
14684        let __s_b = self.gpu.stream();
14685        let mut b = __s_b.launch_builder(&f);
14686        b.arg(k)
14687            .arg(v)
14688            .arg(&mut *kf)
14689            .arg(&mut *vf)
14690            .arg(&kdk)
14691            .arg(&kdv)
14692            .arg(&tkvi)
14693            .arg(&ktb)
14694            .arg(&vtb);
14695        unsafe {
14696            b.launch(cfg)?;
14697        }
14698        Ok(())
14699    }
14700
14701    #[allow(clippy::too_many_arguments)]
14702    pub fn sdpa_naive_quantized_view(
14703        &self,
14704        q: &CudaSlice<f32>,
14705        k: &cudarc::driver::CudaView<u8>,
14706        v: &cudarc::driver::CudaView<u8>,
14707        o: &mut CudaSlice<f32>,
14708        head_dim: usize,
14709        n_head: usize,
14710        n_head_kv: usize,
14711        t: usize,
14712        t_kv: usize,
14713        scale: f32,
14714        causal: bool,
14715        k_tok_bytes: usize,
14716        v_tok_bytes: usize,
14717    ) -> Result<(), Box<dyn std::error::Error>> {
14718        let kv_dim = n_head_kv * head_dim;
14719        let mut kf = self.uninit(t_kv * kv_dim)?;
14720        let mut vf = self.uninit(t_kv * kv_dim)?;
14721        let f = self.func("fa_dequant_kv_ws_f32");
14722        let total = (2 * t_kv * kv_dim) as u64;
14723        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14724        let cfg = LaunchConfig {
14725            grid_dim: (nblk.max(1), 1, 1),
14726            block_dim: (256, 1, 1),
14727            shared_mem_bytes: 0,
14728        };
14729        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14730        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14731        let __s_b = self.gpu.stream();
14732        let mut b = __s_b.launch_builder(&f);
14733        b.arg(k)
14734            .arg(v)
14735            .arg(&mut kf)
14736            .arg(&mut vf)
14737            .arg(&kv_dim_i)
14738            .arg(&kv_dim_i)
14739            .arg(&t_kv_i)
14740            .arg(&k_tok_bytes_i)
14741            .arg(&v_tok_bytes_i);
14742        unsafe { b.launch(cfg)? };
14743        self.sdpa_naive(
14744            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14745        )
14746    }
14747
14748    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
14749    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
14750    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
14751    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
14752    /// unwindowed function above and produces bit-identical output at window == 0.
14753    ///
14754    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
14755    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
14756    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
14757    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
14758    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
14759    #[allow(clippy::too_many_arguments)]
14760    pub fn sdpa_naive_w_quantized_view(
14761        &self,
14762        q: &CudaSlice<f32>,
14763        k: &cudarc::driver::CudaView<u8>,
14764        v: &cudarc::driver::CudaView<u8>,
14765        o: &mut CudaSlice<f32>,
14766        head_dim: usize,
14767        n_head: usize,
14768        n_head_kv: usize,
14769        t: usize,
14770        t_kv: usize,
14771        scale: f32,
14772        causal: bool,
14773        window: usize,
14774        k_tok_bytes: usize,
14775        v_tok_bytes: usize,
14776    ) -> Result<(), Box<dyn std::error::Error>> {
14777        let kv_dim = n_head_kv * head_dim;
14778        let mut kf = self.uninit(t_kv * kv_dim)?;
14779        let mut vf = self.uninit(t_kv * kv_dim)?;
14780        let f = self.func("fa_dequant_kv_ws_f32");
14781        let total = (2 * t_kv * kv_dim) as u64;
14782        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14783        let cfg = LaunchConfig {
14784            grid_dim: (nblk.max(1), 1, 1),
14785            block_dim: (256, 1, 1),
14786            shared_mem_bytes: 0,
14787        };
14788        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14789        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14790        let __s_b = self.gpu.stream();
14791        let mut b = __s_b.launch_builder(&f);
14792        b.arg(k)
14793            .arg(v)
14794            .arg(&mut kf)
14795            .arg(&mut vf)
14796            .arg(&kv_dim_i)
14797            .arg(&kv_dim_i)
14798            .arg(&t_kv_i)
14799            .arg(&k_tok_bytes_i)
14800            .arg(&v_tok_bytes_i);
14801        unsafe { b.launch(cfg)? };
14802        self.sdpa_naive_w(
14803            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
14804        )
14805    }
14806
14807    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
14808    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
14809    /// Q/K/V/O [head_dim, n_head(_kv), T].
14810    pub fn fa_prefill(
14811        &self,
14812        q: &CudaSlice<f32>,
14813        k: &CudaSlice<f32>,
14814        v: &CudaSlice<f32>,
14815        o: &mut CudaSlice<f32>,
14816        head_dim: usize,
14817        n_head: usize,
14818        n_head_kv: usize,
14819        t: usize,
14820        t_kv: usize,
14821        scale: f32,
14822        causal: bool,
14823    ) -> Result<(), Box<dyn std::error::Error>> {
14824        if portable_mma_gated() {
14825            return self.sdpa_naive(
14826                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14827            );
14828        }
14829        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
14830        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
14831        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
14832        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
14833        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
14834        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
14835        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
14836        let fa3_on = head_dim == 256
14837            && causal
14838            && t == t_kv
14839            && match std::env::var("MEMRA_FA3").as_deref() {
14840                Ok("0") => false,
14841                Ok("1") => true,
14842                _ => cfg!(memra_hopper_mma),
14843            };
14844        if fa3_on {
14845            let n = t * n_head * head_dim;
14846            let nkv = t * n_head_kv * head_dim;
14847            let mut q16 = self.alloc_u8_uninit(n * 2)?;
14848            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
14849            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
14850            self.f32_to_bf16_into(q, &mut q16, n)?;
14851            self.f32_to_bf16_into(k, &mut k16, nkv)?;
14852            self.f32_to_bf16_into(v, &mut v16, nkv)?;
14853            let rc = {
14854                use cudarc::driver::{DevicePtr, DevicePtrMut};
14855                let stream = self.gpu.stream();
14856                let (qp, _g1) = q16.device_ptr(&stream);
14857                let (kp, _g2) = k16.device_ptr(&stream);
14858                let (vp, _g3) = v16.device_ptr(&stream);
14859                let (op, _g4) = o.device_ptr_mut(&stream);
14860                unsafe {
14861                    memra_fa3_prefill(
14862                        qp as *const core::ffi::c_void,
14863                        kp as *const core::ffi::c_void,
14864                        vp as *const core::ffi::c_void,
14865                        op as *mut f32,
14866                        t as i32,
14867                        n_head as i32,
14868                        n_head_kv as i32,
14869                        head_dim as i32,
14870                        scale,
14871                        stream.cu_stream() as *mut core::ffi::c_void,
14872                    )
14873                }
14874            };
14875            if rc != 0 {
14876                return Err(format!("memra_fa3_prefill rc={rc}").into());
14877            }
14878            return Ok(());
14879        }
14880        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
14881        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
14882        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
14883        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
14884        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14885        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
14886        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
14887            const BLOCK_Q: usize = 64;
14888            const BKX: usize = 32;
14889            let f = self.func("fa_prefill_bf16_p1");
14890            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
14891                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
14892            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14893            f.set_attribute(
14894                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14895                shmem as i32,
14896            )?;
14897            let cfg = LaunchConfig {
14898                grid_dim: (
14899                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14900                    n_head as u32,
14901                    1,
14902                ),
14903                block_dim: (32, 4, 1),
14904                shared_mem_bytes: shmem,
14905            };
14906            let (hd, nh, nhkv, ti, tkvi, cz) = (
14907                head_dim as i32,
14908                n_head as i32,
14909                n_head_kv as i32,
14910                t as i32,
14911                t_kv as i32,
14912                causal as i32,
14913            );
14914            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
14915            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
14916            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
14917            let __s_b = self.gpu.stream();
14918            let mut b = __s_b.launch_builder(&f);
14919            b.arg(&qb)
14920                .arg(&kb)
14921                .arg(&vb)
14922                .arg(o)
14923                .arg(&hd)
14924                .arg(&nh)
14925                .arg(&nhkv)
14926                .arg(&ti)
14927                .arg(&tkvi)
14928                .arg(&scale)
14929                .arg(&cz);
14930            unsafe {
14931                b.launch(cfg)?;
14932            }
14933            return Ok(());
14934        }
14935        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
14936        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
14937        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
14938        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
14939        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
14940        const BK: usize = 32;
14941        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
14942        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
14943        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
14944        let (block_q, warps, w2_sfx): (usize, u32, &str) =
14945            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
14946        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
14947        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
14948        // other head_dims to sdpa_naive before reaching here.
14949        let hd_sfx = fa_hd_suffix(head_dim)?;
14950        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
14951        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
14952        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
14953        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
14954        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
14955        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
14956        let (kb16, vb16) = if bf16kv {
14957            let n = t_kv * n_head_kv * head_dim;
14958            let mut kb = self.alloc_u8_uninit(n * 2)?;
14959            let mut vb = self.alloc_u8_uninit(n * 2)?;
14960            let fcv = self.func("f32_to_bf16_bulk");
14961            let ni = n as i64;
14962            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
14963            let __s_b = self.gpu.stream();
14964            let mut b = __s_b.launch_builder(&fcv);
14965            b.arg(k).arg(&mut kb).arg(&ni);
14966            unsafe {
14967                b.launch(cfgc)?;
14968            }
14969            let __s_b = self.gpu.stream();
14970            let mut b = __s_b.launch_builder(&fcv);
14971            b.arg(v).arg(&mut vb).arg(&ni);
14972            unsafe {
14973                b.launch(cfgc)?;
14974            }
14975            (Some(kb), Some(vb))
14976        } else {
14977            (None, None)
14978        };
14979        let f = self.func(&if bf16kv {
14980            format!("fa_prefill_bf16kv_pp{hd_sfx}")
14981        } else {
14982            format!(
14983                "fa_prefill_f32{}{}{hd_sfx}",
14984                if floor { "" } else { "_pp" },
14985                if floor { "" } else { w2_sfx }
14986            )
14987        });
14988        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
14989        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
14990        let kv_stages = if bf16kv { 2 } else { 1 };
14991        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
14992            + 4 * (block_q * BK + 2 * block_q)) as u32;
14993        use cudarc::driver::sys::CUfunction_attribute_enum as A;
14994        f.set_attribute(
14995            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14996            shmem as i32,
14997        )?;
14998        let cfg = LaunchConfig {
14999            grid_dim: (
15000                (t as u32 + block_q as u32 - 1) / block_q as u32,
15001                n_head as u32,
15002                1,
15003            ),
15004            block_dim: (32, warps, 1),
15005            shared_mem_bytes: shmem,
15006        };
15007        let (hd, nh, nhkv, ti, tkvi, cz) = (
15008            head_dim as i32,
15009            n_head as i32,
15010            n_head_kv as i32,
15011            t as i32,
15012            t_kv as i32,
15013            causal as i32,
15014        );
15015        let __s_b = self.gpu.stream();
15016        let mut b = __s_b.launch_builder(&f);
15017        b.arg(q);
15018        match (&kb16, &vb16) {
15019            (Some(kb), Some(vb)) => {
15020                b.arg(kb).arg(vb);
15021            }
15022            _ => {
15023                b.arg(k).arg(v);
15024            }
15025        }
15026        b.arg(o)
15027            .arg(&hd)
15028            .arg(&nh)
15029            .arg(&nhkv)
15030            .arg(&ti)
15031            .arg(&tkvi)
15032            .arg(&scale)
15033            .arg(&cz);
15034        unsafe {
15035            b.launch(cfg)?;
15036        }
15037        Ok(())
15038    }
15039
15040    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
15041    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
15042    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
15043    #[allow(clippy::too_many_arguments)]
15044    pub fn fa_prefill_w(
15045        &self,
15046        q: &CudaSlice<f32>,
15047        k: &CudaSlice<f32>,
15048        v: &CudaSlice<f32>,
15049        o: &mut CudaSlice<f32>,
15050        head_dim: usize,
15051        n_head: usize,
15052        n_head_kv: usize,
15053        t: usize,
15054        t_kv: usize,
15055        scale: f32,
15056        causal: bool,
15057        window: usize,
15058    ) -> Result<(), Box<dyn std::error::Error>> {
15059        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
15060        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
15061        if portable_mma_gated() {
15062            return self.sdpa_naive_w(
15063                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15064            );
15065        }
15066        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
15067        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
15068        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
15069        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15070        let faw_f32 =
15071            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
15072        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15073        self.fa_prefill_w_arm(
15074            q,
15075            k,
15076            v,
15077            o,
15078            head_dim,
15079            n_head,
15080            n_head_kv,
15081            t,
15082            t_kv,
15083            scale,
15084            causal,
15085            window,
15086            floor || faw_f32,
15087            floor,
15088        )
15089    }
15090
15091    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
15092    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
15093    #[allow(clippy::too_many_arguments)]
15094    pub fn fa_prefill_w_pre(
15095        &self,
15096        qb: &CudaSlice<u8>,
15097        kb: &CudaSlice<u8>,
15098        vb: &CudaSlice<u8>,
15099        o: &mut CudaSlice<f32>,
15100        head_dim: usize,
15101        n_head: usize,
15102        n_head_kv: usize,
15103        t: usize,
15104        t_kv: usize,
15105        scale: f32,
15106        causal: bool,
15107        window: usize,
15108        v_f16: bool,
15109    ) -> Result<(), Box<dyn std::error::Error>> {
15110        const BLOCK_Q: usize = 64;
15111        const BK: usize = 32;
15112        debug_assert_eq!(head_dim, 256);
15113        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15114        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
15115        if hp {
15116            const BLOCK_QH: usize = 32;
15117            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
15118            // else re-encode through the pooled scratch (stream-ordered reuse).
15119            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15120            let vh: &CudaSlice<u8> = if v_f16 {
15121                vb
15122            } else {
15123                let n = t_kv * n_head_kv * head_dim;
15124                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
15125                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
15126                }
15127                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
15128                vguard.as_ref().unwrap()
15129            };
15130            let f = self.func("fa_prefill_w_bf16_p1h2");
15131            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15132            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15133            f.set_attribute(
15134                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15135                shmem as i32,
15136            )?;
15137            let cfg = LaunchConfig {
15138                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15139                block_dim: (32, 4, 1),
15140                shared_mem_bytes: shmem,
15141            };
15142            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15143                head_dim as i32,
15144                n_head as i32,
15145                n_head_kv as i32,
15146                t as i32,
15147                t_kv as i32,
15148                causal as i32,
15149                window as i32,
15150            );
15151            let __s_b = self.gpu.stream();
15152            let mut b = __s_b.launch_builder(&f);
15153            b.arg(qb)
15154                .arg(kb)
15155                .arg(vh)
15156                .arg(o)
15157                .arg(&hd)
15158                .arg(&nh)
15159                .arg(&nhkv)
15160                .arg(&ti)
15161                .arg(&tkvi)
15162                .arg(&scale)
15163                .arg(&cz)
15164                .arg(&wi);
15165            unsafe {
15166                b.launch(cfg)?;
15167            }
15168            return Ok(());
15169        }
15170        let f = self.func("fa_prefill_w_bf16_p1");
15171        let shmem =
15172            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15173        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15174        f.set_attribute(
15175            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15176            shmem as i32,
15177        )?;
15178        let cfg = LaunchConfig {
15179            grid_dim: (
15180                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15181                n_head as u32,
15182                1,
15183            ),
15184            block_dim: (32, 4, 1),
15185            shared_mem_bytes: shmem,
15186        };
15187        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15188            head_dim as i32,
15189            n_head as i32,
15190            n_head_kv as i32,
15191            t as i32,
15192            t_kv as i32,
15193            causal as i32,
15194            window as i32,
15195        );
15196        let __s_b = self.gpu.stream();
15197        let mut b = __s_b.launch_builder(&f);
15198        b.arg(qb)
15199            .arg(kb)
15200            .arg(vb)
15201            .arg(o)
15202            .arg(&hd)
15203            .arg(&nh)
15204            .arg(&nhkv)
15205            .arg(&ti)
15206            .arg(&tkvi)
15207            .arg(&scale)
15208            .arg(&cz)
15209            .arg(&wi);
15210        unsafe {
15211            b.launch(cfg)?;
15212        }
15213        Ok(())
15214    }
15215
15216    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
15217    #[allow(clippy::too_many_arguments)]
15218    pub fn fa_prefill_w_arm(
15219        &self,
15220        q: &CudaSlice<f32>,
15221        k: &CudaSlice<f32>,
15222        v: &CudaSlice<f32>,
15223        o: &mut CudaSlice<f32>,
15224        head_dim: usize,
15225        n_head: usize,
15226        n_head_kv: usize,
15227        t: usize,
15228        t_kv: usize,
15229        scale: f32,
15230        causal: bool,
15231        window: usize,
15232        f32_stage: bool,
15233        floor: bool,
15234    ) -> Result<(), Box<dyn std::error::Error>> {
15235        const BLOCK_Q: usize = 64;
15236        const BK: usize = 32;
15237        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
15238        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
15239        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
15240        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
15241        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15242        let p1 = !floor
15243            && !f32_stage
15244            && *P1_ON.get_or_init(|| {
15245                std::env::var("MEMRA_FAW_P1")
15246                    .map(|v| v != "0")
15247                    .unwrap_or(true)
15248            });
15249        let hp =
15250            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15251        if hp {
15252            const BLOCK_QH: usize = 32;
15253            let f = self.func("fa_prefill_w_bf16_p1h2");
15254            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15255            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15256            f.set_attribute(
15257                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15258                shmem as i32,
15259            )?;
15260            let cfg = LaunchConfig {
15261                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15262                block_dim: (32, 4, 1),
15263                shared_mem_bytes: shmem,
15264            };
15265            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15266                head_dim as i32,
15267                n_head as i32,
15268                n_head_kv as i32,
15269                t as i32,
15270                t_kv as i32,
15271                causal as i32,
15272                window as i32,
15273            );
15274            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15275            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15276            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
15277            let __s_b = self.gpu.stream();
15278            let mut b = __s_b.launch_builder(&f);
15279            b.arg(&qb)
15280                .arg(&kb)
15281                .arg(&vh)
15282                .arg(o)
15283                .arg(&hd)
15284                .arg(&nh)
15285                .arg(&nhkv)
15286                .arg(&ti)
15287                .arg(&tkvi)
15288                .arg(&scale)
15289                .arg(&cz)
15290                .arg(&wi);
15291            unsafe {
15292                b.launch(cfg)?;
15293            }
15294            return Ok(());
15295        }
15296        if p1 {
15297            let f = self.func("fa_prefill_w_bf16_p1");
15298            let shmem =
15299                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15300            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15301            f.set_attribute(
15302                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15303                shmem as i32,
15304            )?;
15305            let cfg = LaunchConfig {
15306                grid_dim: (
15307                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15308                    n_head as u32,
15309                    1,
15310                ),
15311                block_dim: (32, 4, 1),
15312                shared_mem_bytes: shmem,
15313            };
15314            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15315                head_dim as i32,
15316                n_head as i32,
15317                n_head_kv as i32,
15318                t as i32,
15319                t_kv as i32,
15320                causal as i32,
15321                window as i32,
15322            );
15323            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15324            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15325            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15326            let __s_b = self.gpu.stream();
15327            let mut b = __s_b.launch_builder(&f);
15328            b.arg(&qb)
15329                .arg(&kb)
15330                .arg(&vb)
15331                .arg(o)
15332                .arg(&hd)
15333                .arg(&nh)
15334                .arg(&nhkv)
15335                .arg(&ti)
15336                .arg(&tkvi)
15337                .arg(&scale)
15338                .arg(&cz)
15339                .arg(&wi);
15340            unsafe {
15341                b.launch(cfg)?;
15342            }
15343            return Ok(());
15344        }
15345        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
15346        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
15347        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15348        let g4 = !floor
15349            && !f32_stage
15350            && n_head_kv == 1
15351            && n_head % 4 == 0
15352            && *G4_ON.get_or_init(|| {
15353                std::env::var("MEMRA_FAW_G4")
15354                    .map(|v| v != "0")
15355                    .unwrap_or(true)
15356            });
15357        if g4 {
15358            const SP_M: usize = 16;
15359            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
15360            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
15361            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15362            let o2 = *O2_ON.get_or_init(|| {
15363                std::env::var("MEMRA_FAW_O2")
15364                    .map(|v| v != "0")
15365                    .unwrap_or(true)
15366            });
15367            let f = self.func(if o2 {
15368                "fa_prefill_w_bf16_g4o2"
15369            } else {
15370                "fa_prefill_w_bf16_g4"
15371            });
15372            let shmem = if o2 {
15373                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
15374            } else {
15375                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
15376                    as u32
15377            };
15378            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15379            f.set_attribute(
15380                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15381                shmem as i32,
15382            )?;
15383            let cfg = LaunchConfig {
15384                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
15385                block_dim: (32, 4, 1),
15386                shared_mem_bytes: shmem,
15387            };
15388            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15389                head_dim as i32,
15390                n_head as i32,
15391                n_head_kv as i32,
15392                t as i32,
15393                t_kv as i32,
15394                causal as i32,
15395                window as i32,
15396            );
15397            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15398            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15399            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15400            let __s_b = self.gpu.stream();
15401            let mut b = __s_b.launch_builder(&f);
15402            b.arg(&qb)
15403                .arg(&kb)
15404                .arg(&vb)
15405                .arg(o)
15406                .arg(&hd)
15407                .arg(&nh)
15408                .arg(&nhkv)
15409                .arg(&ti)
15410                .arg(&tkvi)
15411                .arg(&scale)
15412                .arg(&cz)
15413                .arg(&wi);
15414            unsafe {
15415                b.launch(cfg)?;
15416            }
15417            return Ok(());
15418        }
15419        let f = self.func(if floor {
15420            "fa_prefill_w_f32"
15421        } else if f32_stage {
15422            "fa_prefill_w_f32_pp"
15423        } else {
15424            "fa_prefill_w_bf16_pp"
15425        });
15426        let shmem =
15427            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15428        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15429        f.set_attribute(
15430            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15431            shmem as i32,
15432        )?;
15433        let cfg = LaunchConfig {
15434            grid_dim: (
15435                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15436                n_head as u32,
15437                1,
15438            ),
15439            block_dim: (32, 4, 1),
15440            shared_mem_bytes: shmem,
15441        };
15442        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15443            head_dim as i32,
15444            n_head as i32,
15445            n_head_kv as i32,
15446            t as i32,
15447            t_kv as i32,
15448            causal as i32,
15449            window as i32,
15450        );
15451        if f32_stage {
15452            let __s_b = self.gpu.stream();
15453            let mut b = __s_b.launch_builder(&f);
15454            b.arg(q)
15455                .arg(k)
15456                .arg(v)
15457                .arg(o)
15458                .arg(&hd)
15459                .arg(&nh)
15460                .arg(&nhkv)
15461                .arg(&ti)
15462                .arg(&tkvi)
15463                .arg(&scale)
15464                .arg(&cz)
15465                .arg(&wi);
15466            unsafe {
15467                b.launch(cfg)?;
15468            }
15469        } else {
15470            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15471            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15472            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15473            let __s_b = self.gpu.stream();
15474            let mut b = __s_b.launch_builder(&f);
15475            b.arg(&qb)
15476                .arg(&kb)
15477                .arg(&vb)
15478                .arg(o)
15479                .arg(&hd)
15480                .arg(&nh)
15481                .arg(&nhkv)
15482                .arg(&ti)
15483                .arg(&tkvi)
15484                .arg(&scale)
15485                .arg(&cz)
15486                .arg(&wi);
15487            unsafe {
15488                b.launch(cfg)?;
15489            }
15490        }
15491        Ok(())
15492    }
15493
15494    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
15495    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
15496    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
15497    #[allow(clippy::too_many_arguments)]
15498    pub fn fa_prefill_hd512(
15499        &self,
15500        q: &CudaSlice<f32>,
15501        k: &CudaSlice<f32>,
15502        v: &CudaSlice<f32>,
15503        o: &mut CudaSlice<f32>,
15504        head_dim: usize,
15505        n_head: usize,
15506        n_head_kv: usize,
15507        t: usize,
15508        t_kv: usize,
15509        scale: f32,
15510        causal: bool,
15511    ) -> Result<(), Box<dyn std::error::Error>> {
15512        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
15513        if portable_mma_gated() {
15514            return self.sdpa_naive(
15515                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15516            );
15517        }
15518        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
15519        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
15520        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
15521        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
15522        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
15523        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15524        let f32_stage =
15525            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
15526        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
15527        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
15528        // Own numeric config (partial-sum order) — battery-gated.
15529        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15530        let sp = !f32_stage
15531            && *SP_ON.get_or_init(|| {
15532                std::env::var("MEMRA_FA512_SP")
15533                    .map(|v| v != "0")
15534                    .unwrap_or(true)
15535            });
15536        self.fa_prefill_hd512_arm(
15537            q,
15538            k,
15539            v,
15540            o,
15541            head_dim,
15542            n_head,
15543            n_head_kv,
15544            t,
15545            t_kv,
15546            scale,
15547            causal,
15548            f32_stage,
15549            sp,
15550            sp && fa_f16pv_on(),
15551        )
15552    }
15553
15554    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
15555    #[allow(clippy::too_many_arguments)]
15556    pub fn fa_prefill_hd512_pre(
15557        &self,
15558        qb: &CudaSlice<u8>,
15559        kb: &CudaSlice<u8>,
15560        vb: &CudaSlice<u8>,
15561        o: &mut CudaSlice<f32>,
15562        head_dim: usize,
15563        n_head: usize,
15564        n_head_kv: usize,
15565        t: usize,
15566        t_kv: usize,
15567        scale: f32,
15568        causal: bool,
15569        v_f16: bool,
15570    ) -> Result<(), Box<dyn std::error::Error>> {
15571        debug_assert_eq!(head_dim, 512);
15572        const SP_M: usize = 16;
15573        const BKS: usize = 32;
15574        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
15575        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
15576        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
15577        let f16pv = fa_f16pv_on();
15578        let nw = if f16pv { fa512_wide_warps() } else { 2 };
15579        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15580        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
15581        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15582        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
15583            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
15584            let n = t_kv * n_head_kv * head_dim;
15585            let need = n * 2;
15586            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
15587                *vguard = Some(self.alloc_uninit::<u8>(need)?);
15588            }
15589            let dst = vguard.as_mut().unwrap();
15590            self.bf16_to_f16_into(vb, n, dst)?;
15591            vguard.as_ref().unwrap()
15592        } else {
15593            vb
15594        };
15595        let f = self.func(if hp {
15596            "fa_prefill_bf16_hd512_sp16h2"
15597        } else {
15598            match (f16pv, nw) {
15599                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15600                (true, _) => "fa_prefill_bf16_hd512_sp16",
15601                _ => "fa_prefill_bf16_hd512_sp",
15602            }
15603        });
15604        let (nwarp, npart) = if hp {
15605            (4usize, 4usize)
15606        } else if nw > 2 {
15607            (nw, nw)
15608        } else {
15609            (2, 1)
15610        };
15611        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
15612        let shmem = if hp {
15613            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
15614                as u32
15615        } else {
15616            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15617                + 4 * (npart * SP_M * BKS + SP_M)) as u32
15618        };
15619        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15620        f.set_attribute(
15621            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15622            shmem as i32,
15623        )?;
15624        let grid_y = if hp {
15625            (n_head / 2) as u32
15626        } else {
15627            n_head as u32
15628        };
15629        let cfg = LaunchConfig {
15630            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15631            block_dim: (32, nwarp as u32, 1),
15632            shared_mem_bytes: shmem,
15633        };
15634        let (hd, nh, nhkv, ti, tkvi, cz) = (
15635            head_dim as i32,
15636            n_head as i32,
15637            n_head_kv as i32,
15638            t as i32,
15639            t_kv as i32,
15640            causal as i32,
15641        );
15642        let __s_b = self.gpu.stream();
15643        let mut b = __s_b.launch_builder(&f);
15644        b.arg(qb)
15645            .arg(kb)
15646            .arg(vref)
15647            .arg(o)
15648            .arg(&hd)
15649            .arg(&nh)
15650            .arg(&nhkv)
15651            .arg(&ti)
15652            .arg(&tkvi)
15653            .arg(&scale)
15654            .arg(&cz);
15655        unsafe {
15656            b.launch(cfg)?;
15657        }
15658        Ok(())
15659    }
15660
15661    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
15662    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
15663    #[allow(clippy::too_many_arguments)]
15664    pub fn fa_prefill_hd512_arm(
15665        &self,
15666        q: &CudaSlice<f32>,
15667        k: &CudaSlice<f32>,
15668        v: &CudaSlice<f32>,
15669        o: &mut CudaSlice<f32>,
15670        head_dim: usize,
15671        n_head: usize,
15672        n_head_kv: usize,
15673        t: usize,
15674        t_kv: usize,
15675        scale: f32,
15676        causal: bool,
15677        f32_stage: bool,
15678        sp: bool,
15679        f16pv: bool,
15680    ) -> Result<(), Box<dyn std::error::Error>> {
15681        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
15682        if sp && !f32_stage {
15683            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
15684            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
15685            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
15686            const SP_M: usize = 16;
15687            const BKS: usize = 32;
15688            let nw = if f16pv { fa512_wide_warps() } else { 2 };
15689            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15690            let f = self.func(if hp {
15691                "fa_prefill_bf16_hd512_sp16h2"
15692            } else {
15693                match (f16pv, nw) {
15694                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15695                    (true, _) => "fa_prefill_bf16_hd512_sp16",
15696                    _ => "fa_prefill_bf16_hd512_sp",
15697                }
15698            });
15699            let (nwarp, npart) = if hp {
15700                (4usize, 4usize)
15701            } else if nw > 2 {
15702                (nw, nw)
15703            } else {
15704                (2, 1)
15705            };
15706            let shmem = if hp {
15707                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
15708                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
15709            } else {
15710                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15711                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
15712            };
15713            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15714            f.set_attribute(
15715                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15716                shmem as i32,
15717            )?;
15718            let grid_y = if hp {
15719                (n_head / 2) as u32
15720            } else {
15721                n_head as u32
15722            };
15723            let cfg = LaunchConfig {
15724                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15725                block_dim: (32, nwarp as u32, 1),
15726                shared_mem_bytes: shmem,
15727            };
15728            let (hd, nh, nhkv, ti, tkvi, cz) = (
15729                head_dim as i32,
15730                n_head as i32,
15731                n_head_kv as i32,
15732                t as i32,
15733                t_kv as i32,
15734                causal as i32,
15735            );
15736            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15737            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15738            let vb = if f16pv {
15739                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
15740            } else {
15741                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
15742            };
15743            let __s_b = self.gpu.stream();
15744            let mut b = __s_b.launch_builder(&f);
15745            b.arg(&qb)
15746                .arg(&kb)
15747                .arg(&vb)
15748                .arg(o)
15749                .arg(&hd)
15750                .arg(&nh)
15751                .arg(&nhkv)
15752                .arg(&ti)
15753                .arg(&tkvi)
15754                .arg(&scale)
15755                .arg(&cz);
15756            unsafe {
15757                b.launch(cfg)?;
15758            }
15759            return Ok(());
15760        }
15761        const BLOCK_Q: usize = 32;
15762        const BK: usize = 32;
15763        const HALF: usize = 256;
15764        let f = self.func(if f32_stage {
15765            "fa_prefill_f32_hd512"
15766        } else {
15767            "fa_prefill_bf16_hd512"
15768        });
15769        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
15770        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
15771            + 4 * BLOCK_Q) as u32;
15772        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15773        f.set_attribute(
15774            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15775            shmem as i32,
15776        )?;
15777        let cfg = LaunchConfig {
15778            grid_dim: (
15779                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15780                n_head as u32,
15781                2,
15782            ),
15783            block_dim: (32, 2, 1),
15784            shared_mem_bytes: shmem,
15785        };
15786        let (hd, nh, nhkv, ti, tkvi, cz) = (
15787            head_dim as i32,
15788            n_head as i32,
15789            n_head_kv as i32,
15790            t as i32,
15791            t_kv as i32,
15792            causal as i32,
15793        );
15794        if f32_stage {
15795            let __s_b = self.gpu.stream();
15796            let mut b = __s_b.launch_builder(&f);
15797            b.arg(q)
15798                .arg(k)
15799                .arg(v)
15800                .arg(o)
15801                .arg(&hd)
15802                .arg(&nh)
15803                .arg(&nhkv)
15804                .arg(&ti)
15805                .arg(&tkvi)
15806                .arg(&scale)
15807                .arg(&cz);
15808            unsafe {
15809                b.launch(cfg)?;
15810            }
15811        } else {
15812            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15813            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15814            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15815            let __s_b = self.gpu.stream();
15816            let mut b = __s_b.launch_builder(&f);
15817            b.arg(&qb)
15818                .arg(&kb)
15819                .arg(&vb)
15820                .arg(o)
15821                .arg(&hd)
15822                .arg(&nh)
15823                .arg(&nhkv)
15824                .arg(&ti)
15825                .arg(&tkvi)
15826                .arg(&scale)
15827                .arg(&cz);
15828            unsafe {
15829                b.launch(cfg)?;
15830            }
15831        }
15832        Ok(())
15833    }
15834
15835    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
15836    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
15837    /// separate f32_to_bf16 the FA entries would run).
15838    #[allow(clippy::too_many_arguments)]
15839    pub fn rope_neox2_bf16e(
15840        &self,
15841        q: &mut CudaSlice<f32>,
15842        k: &mut CudaSlice<f32>,
15843        qb: &mut CudaSlice<u8>,
15844        kb: &mut CudaSlice<u8>,
15845        pos: &CudaSlice<i32>,
15846        head_dim: usize,
15847        n_dims: usize,
15848        nh_q: usize,
15849        nh_k: usize,
15850        n_tokens: usize,
15851        base: f32,
15852        freq_scale: f32,
15853        ff: Option<&CudaSlice<f32>>,
15854    ) -> Result<(), Box<dyn std::error::Error>> {
15855        let f = self.func("rope_neox2_bf16e_f32");
15856        let rows = ((nh_q + nh_k) * n_tokens) as u32;
15857        let cfg = LaunchConfig {
15858            grid_dim: (rows, 1, 1),
15859            block_dim: ((head_dim / 2) as u32, 1, 1),
15860            shared_mem_bytes: 0,
15861        };
15862        let theta_scale = base.powf(-2.0 / n_dims as f32);
15863        let (hd, nd, nhq, nhk, nt) = (
15864            head_dim as i32,
15865            n_dims as i32,
15866            nh_q as i32,
15867            nh_k as i32,
15868            n_tokens as i32,
15869        );
15870        let __s_b = self.gpu.stream();
15871        let mut b = __s_b.launch_builder(&f);
15872        match ff {
15873            Some(t) => {
15874                b.arg(&mut *q)
15875                    .arg(&mut *k)
15876                    .arg(&mut *qb)
15877                    .arg(&mut *kb)
15878                    .arg(pos)
15879                    .arg(&hd)
15880                    .arg(&nd)
15881                    .arg(&nhq)
15882                    .arg(&nhk)
15883                    .arg(&nt)
15884                    .arg(&theta_scale)
15885                    .arg(&freq_scale)
15886                    .arg(t);
15887                unsafe {
15888                    b.launch(cfg)?;
15889                }
15890            }
15891            None => {
15892                let null: u64 = 0;
15893                b.arg(&mut *q)
15894                    .arg(&mut *k)
15895                    .arg(&mut *qb)
15896                    .arg(&mut *kb)
15897                    .arg(pos)
15898                    .arg(&hd)
15899                    .arg(&nd)
15900                    .arg(&nhq)
15901                    .arg(&nhk)
15902                    .arg(&nt)
15903                    .arg(&theta_scale)
15904                    .arg(&freq_scale)
15905                    .arg(&null);
15906                unsafe {
15907                    b.launch(cfg)?;
15908                }
15909            }
15910        }
15911        Ok(())
15912    }
15913
15914    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
15915    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
15916    pub fn f32_to_bf16(
15917        &self,
15918        x: &CudaSlice<f32>,
15919        n: usize,
15920    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15921        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
15922        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15923        let f = self.func("f32_to_bf16_flat");
15924        let n_i = n as i64;
15925        let cfg = LaunchConfig {
15926            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15927            block_dim: (256, 1, 1),
15928            shared_mem_bytes: 0,
15929        };
15930        let __s_b = self.gpu.stream();
15931        let mut b = __s_b.launch_builder(&f);
15932        b.arg(x).arg(&mut y).arg(&n_i);
15933        unsafe {
15934            b.launch(cfg)?;
15935        }
15936        Ok(y)
15937    }
15938
15939    pub fn f32_to_f16(
15940        &self,
15941        x: &CudaSlice<f32>,
15942        n: usize,
15943    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15944        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
15945        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15946        let f = self.func("f32_to_f16_flat");
15947        let n_i = n as i64;
15948        let cfg = LaunchConfig {
15949            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15950            block_dim: (256, 1, 1),
15951            shared_mem_bytes: 0,
15952        };
15953        let __s_b = self.gpu.stream();
15954        let mut b = __s_b.launch_builder(&f);
15955        b.arg(x).arg(&mut y).arg(&n_i);
15956        unsafe {
15957            b.launch(cfg)?;
15958        }
15959        Ok(y)
15960    }
15961
15962    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
15963    pub fn bf16_to_f16(
15964        &self,
15965        xb: &CudaSlice<u8>,
15966        n: usize,
15967    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15968        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15969        self.bf16_to_f16_into(xb, n, &mut y)?;
15970        Ok(y)
15971    }
15972
15973    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
15974    pub fn bf16_to_f16_into(
15975        &self,
15976        xb: &CudaSlice<u8>,
15977        n: usize,
15978        y: &mut CudaSlice<u8>,
15979    ) -> Result<(), Box<dyn std::error::Error>> {
15980        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
15981        assert!(y.len() >= n * 2);
15982        let f = self.func("bf16_to_f16_flat");
15983        let n2 = (n / 2) as i64;
15984        let cfg = LaunchConfig {
15985            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
15986            block_dim: (256, 1, 1),
15987            shared_mem_bytes: 0,
15988        };
15989        let __s_b = self.gpu.stream();
15990        let mut b = __s_b.launch_builder(&f);
15991        b.arg(xb).arg(y).arg(&n2);
15992        unsafe {
15993            b.launch(cfg)?;
15994        }
15995        Ok(())
15996    }
15997
15998    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
15999    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
16000    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
16001    /// head_dim in {256, 128}, bf16kv lane on.
16002    #[allow(clippy::too_many_arguments)]
16003    pub fn fa_prefill_vl8(
16004        &self,
16005        seqs: &[FaSeqVl],
16006        head_dim: usize,
16007        n_head: usize,
16008        n_head_kv: usize,
16009        scale: f32,
16010    ) -> Result<(), Box<dyn std::error::Error>> {
16011        const BK: usize = 32;
16012        let b = seqs.len();
16013        assert!(b >= 1 && b <= 8);
16014        let mut packed = [FaSeqVl::default(); 8];
16015        packed[..b].copy_from_slice(seqs);
16016        let v = FaVl8(packed);
16017        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16018        let ept = (n_head_kv * head_dim) as i32;
16019        {
16020            let f = self.func("fa_mirror_vl");
16021            let max_n = (max_t as i64) * ept as i64;
16022            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
16023            for which in 0..2i32 {
16024                let cfg = LaunchConfig {
16025                    grid_dim: (blocks, 1, b as u32),
16026                    block_dim: (256, 1, 1),
16027                    shared_mem_bytes: 0,
16028                };
16029                let __s_lb = self.gpu.stream();
16030                let mut lb = __s_lb.launch_builder(&f);
16031                lb.arg(&v).arg(&ept).arg(&which);
16032                unsafe {
16033                    lb.launch(cfg)?;
16034                }
16035            }
16036        }
16037        let hd_sfx = fa_hd_suffix(head_dim)?;
16038        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
16039        let block_q = 64usize;
16040        let kv_stages = 2usize;
16041        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
16042            + 4 * (block_q * BK + 2 * block_q)) as u32;
16043        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16044        f.set_attribute(
16045            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16046            shmem as i32,
16047        )?;
16048        let cfg = LaunchConfig {
16049            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
16050            block_dim: (32, 4, 1),
16051            shared_mem_bytes: shmem,
16052        };
16053        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16054        let __s_lb = self.gpu.stream();
16055        let mut lb = __s_lb.launch_builder(&f);
16056        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
16057        unsafe {
16058            lb.launch(cfg)?;
16059        }
16060        Ok(())
16061    }
16062
16063    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
16064    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
16065    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
16066    #[allow(clippy::too_many_arguments)]
16067    pub fn attn_pre_vl8(
16068        &self,
16069        seqs: &[AttnPreVl],
16070        wq: &CudaSlice<f32>,
16071        wk: &CudaSlice<f32>,
16072        head_dim: usize,
16073        rope_dims: usize,
16074        n_head: usize,
16075        n_head_kv: usize,
16076        eps: f32,
16077        freq_base: f32,
16078        freq_scale: f32,
16079        kv_dim_k: usize,
16080        kv_dim_v: usize,
16081        k_tok_bytes: usize,
16082        v_tok_bytes: usize,
16083    ) -> Result<(), Box<dyn std::error::Error>> {
16084        let b = seqs.len();
16085        assert!(b >= 1 && b <= 8);
16086        let mut packed = [AttnPreVl::default(); 8];
16087        packed[..b].copy_from_slice(seqs);
16088        let v = AttnPreVl8(packed);
16089        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16090        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16091        {
16092            let f = self.func("q_gate_split_vl");
16093            let n = max_t * (n_head * head_dim) as u32;
16094            let cfg = LaunchConfig {
16095                grid_dim: (n.div_ceil(256), 1, b as u32),
16096                block_dim: (256, 1, 1),
16097                shared_mem_bytes: 0,
16098            };
16099            let __s_lb = self.gpu.stream();
16100            let mut lb = __s_lb.launch_builder(&f);
16101            lb.arg(&v).arg(&hd).arg(&nh);
16102            unsafe {
16103                lb.launch(cfg)?;
16104            }
16105        }
16106        {
16107            let f = self.func("attn_rms_vl");
16108            let cfg = LaunchConfig {
16109                grid_dim: (max_t * n_head as u32, 2, b as u32),
16110                block_dim: (rms_block(), 1, 1),
16111                shared_mem_bytes: 0,
16112            };
16113            let __s_lb = self.gpu.stream();
16114            let mut lb = __s_lb.launch_builder(&f);
16115            lb.arg(&v)
16116                .arg(wq)
16117                .arg(wk)
16118                .arg(&hd)
16119                .arg(&nh)
16120                .arg(&nhkv)
16121                .arg(&eps);
16122            unsafe {
16123                lb.launch(cfg)?;
16124            }
16125        }
16126        {
16127            let f = self.func("attn_rope_vl");
16128            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
16129            let nd = rope_dims as i32;
16130            let cfg = LaunchConfig {
16131                grid_dim: (max_t * n_head as u32, 2, b as u32),
16132                block_dim: ((head_dim / 2) as u32, 1, 1),
16133                shared_mem_bytes: 0,
16134            };
16135            let __s_lb = self.gpu.stream();
16136            let mut lb = __s_lb.launch_builder(&f);
16137            lb.arg(&v)
16138                .arg(&hd)
16139                .arg(&nd)
16140                .arg(&nh)
16141                .arg(&nhkv)
16142                .arg(&theta_scale)
16143                .arg(&freq_scale);
16144            unsafe {
16145                lb.launch(cfg)?;
16146            }
16147        }
16148        {
16149            let f = self.func("append_kv_vl");
16150            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
16151            let cfg = LaunchConfig {
16152                grid_dim: (nblk, max_t, b as u32),
16153                block_dim: (32, 1, 1),
16154                shared_mem_bytes: 0,
16155            };
16156            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16157            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16158            let __s_lb = self.gpu.stream();
16159            let mut lb = __s_lb.launch_builder(&f);
16160            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
16161            unsafe {
16162                lb.launch(cfg)?;
16163            }
16164        }
16165        Ok(())
16166    }
16167
16168    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
16169    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
16170    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
16171    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
16172    pub fn fa_prefill_view(
16173        &self,
16174        q: &CudaSlice<f32>,
16175        k: &cudarc::driver::CudaView<u8>,
16176        v: &cudarc::driver::CudaView<u8>,
16177        o: &mut CudaSlice<f32>,
16178        head_dim: usize,
16179        n_head: usize,
16180        n_head_kv: usize,
16181        t: usize,
16182        t_kv: usize,
16183        scale: f32,
16184        causal: bool,
16185        k_tok_bytes: usize,
16186        v_tok_bytes: usize,
16187        g: bool,
16188    ) -> Result<(), Box<dyn std::error::Error>> {
16189        if portable_mma_gated() {
16190            return self.sdpa_naive_quantized_view(
16191                q,
16192                k,
16193                v,
16194                o,
16195                head_dim,
16196                n_head,
16197                n_head_kv,
16198                t,
16199                t_kv,
16200                scale,
16201                causal,
16202                k_tok_bytes,
16203                v_tok_bytes,
16204            );
16205        }
16206        const BLOCK_Q: usize = 64;
16207        const BK: usize = 32;
16208        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
16209        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
16210        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
16211        let f = if g {
16212            self.func_g(&name)
16213        } else {
16214            self.func(&name)
16215        };
16216        let shmem =
16217            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16218        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16219        f.set_attribute(
16220            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16221            shmem as i32,
16222        )?;
16223        let cfg = LaunchConfig {
16224            grid_dim: (
16225                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16226                n_head as u32,
16227                1,
16228            ),
16229            block_dim: (32, 4, 1),
16230            shared_mem_bytes: shmem,
16231        };
16232        let (hd, nh, nhkv, ti, tkvi, cz) = (
16233            head_dim as i32,
16234            n_head as i32,
16235            n_head_kv as i32,
16236            t as i32,
16237            t_kv as i32,
16238            causal as i32,
16239        );
16240        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16241        let __s_b = self.gpu.stream();
16242        let mut b = __s_b.launch_builder(&f);
16243        b.arg(q)
16244            .arg(k)
16245            .arg(v)
16246            .arg(o)
16247            .arg(&hd)
16248            .arg(&nh)
16249            .arg(&nhkv)
16250            .arg(&ti)
16251            .arg(&tkvi)
16252            .arg(&scale)
16253            .arg(&cz)
16254            .arg(&ktb)
16255            .arg(&vtb);
16256        unsafe {
16257            b.launch(cfg)?;
16258        }
16259        Ok(())
16260    }
16261
16262    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
16263    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
16264    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
16265    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
16266    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
16267    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
16268    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
16269    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
16270    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
16271    #[allow(clippy::too_many_arguments)]
16272    pub fn fa_prefill_view_ws(
16273        &self,
16274        q: &CudaSlice<f32>,
16275        k: &cudarc::driver::CudaView<u8>,
16276        v: &cudarc::driver::CudaView<u8>,
16277        o: &mut CudaSlice<f32>,
16278        head_dim: usize,
16279        n_head: usize,
16280        n_head_kv: usize,
16281        t: usize,
16282        t_kv: usize,
16283        scale: f32,
16284        causal: bool,
16285        k_tok_bytes: usize,
16286        v_tok_bytes: usize,
16287        g: bool,
16288    ) -> Result<(), Box<dyn std::error::Error>> {
16289        if portable_mma_gated() {
16290            return self.sdpa_naive_quantized_view(
16291                q,
16292                k,
16293                v,
16294                o,
16295                head_dim,
16296                n_head,
16297                n_head_kv,
16298                t,
16299                t_kv,
16300                scale,
16301                causal,
16302                k_tok_bytes,
16303                v_tok_bytes,
16304            );
16305        }
16306        const BLOCK_Q: usize = 64;
16307        const BK: usize = 32;
16308        let kv_dim_k = n_head_kv * head_dim;
16309        let kv_dim_v = n_head_kv * head_dim;
16310        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16311        let v_ws_bytes = t_kv * kv_dim_v * 2;
16312        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
16313        let mut guard = self.prime_deqw_ws.lock().unwrap();
16314        let need_grow = match guard.as_ref() {
16315            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16316            None => true,
16317        };
16318        if need_grow {
16319            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16320            let (ck, cv) = guard
16321                .as_ref()
16322                .map(|(a, b)| (a.len(), b.len()))
16323                .unwrap_or((0, 0));
16324            *guard = Some((
16325                self.alloc_u8(grow(ck, k_ws_bytes))?,
16326                self.alloc_u8(grow(cv, v_ws_bytes))?,
16327            ));
16328        }
16329        let (kw, vw) = guard.as_mut().unwrap();
16330        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
16331        {
16332            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
16333            let f = if g {
16334                self.func_g("fa_dequant_kv_ws_bf16")
16335            } else {
16336                self.func("fa_dequant_kv_ws_bf16")
16337            };
16338            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16339            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16340            let cfg = LaunchConfig {
16341                grid_dim: (nblk.max(1), 1, 1),
16342                block_dim: (256, 1, 1),
16343                shared_mem_bytes: 0,
16344            };
16345            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16346            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16347            let __s_b = self.gpu.stream();
16348            let mut b = __s_b.launch_builder(&f);
16349            b.arg(k)
16350                .arg(v)
16351                .arg(&mut *kw)
16352                .arg(&mut *vw)
16353                .arg(&kdk)
16354                .arg(&kdv)
16355                .arg(&tkvi)
16356                .arg(&ktb)
16357                .arg(&vtb);
16358            unsafe {
16359                b.launch(cfg)?;
16360            }
16361        }
16362        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
16363        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
16364        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
16365        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
16366        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
16367        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
16368        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
16369        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16370            .map(|v| v != "0")
16371            .unwrap_or(true);
16372        {
16373            let hd_sfx = fa_hd_suffix(head_dim)?;
16374            let f = self.func(&format!(
16375                "fa_prefill_qw{}{hd_sfx}",
16376                if db { "_db" } else { "" }
16377            ));
16378            let shmem = if db {
16379                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
16380                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16381            } else {
16382                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16383            };
16384            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16385            f.set_attribute(
16386                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16387                shmem as i32,
16388            )?;
16389            let cfg = LaunchConfig {
16390                grid_dim: (
16391                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16392                    n_head as u32,
16393                    1,
16394                ),
16395                block_dim: (32, 4, 1),
16396                shared_mem_bytes: shmem,
16397            };
16398            let (hd, nh, nhkv, ti, tkvi, cz) = (
16399                head_dim as i32,
16400                n_head as i32,
16401                n_head_kv as i32,
16402                t as i32,
16403                t_kv as i32,
16404                causal as i32,
16405            );
16406            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16407            let __s_b = self.gpu.stream();
16408            let mut b = __s_b.launch_builder(&f);
16409            b.arg(q)
16410                .arg(&*kw)
16411                .arg(&*vw)
16412                .arg(o)
16413                .arg(&hd)
16414                .arg(&nh)
16415                .arg(&nhkv)
16416                .arg(&ti)
16417                .arg(&tkvi)
16418                .arg(&scale)
16419                .arg(&cz)
16420                .arg(&kdk)
16421                .arg(&kdv);
16422            unsafe {
16423                b.launch(cfg)?;
16424            }
16425        }
16426        Ok(())
16427    }
16428
16429    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
16430    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
16431    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
16432    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
16433    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
16434    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
16435    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
16436    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
16437    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
16438    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
16439    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
16440    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
16441    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
16442    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
16443    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
16444    #[allow(clippy::too_many_arguments)]
16445    pub fn fa_prefill_view_ws_w_hd128(
16446        &self,
16447        q: &CudaSlice<f32>,
16448        k: &cudarc::driver::CudaView<u8>,
16449        v: &cudarc::driver::CudaView<u8>,
16450        o: &mut CudaSlice<f32>,
16451        head_dim: usize,
16452        n_head: usize,
16453        n_head_kv: usize,
16454        t: usize,
16455        t_kv: usize,
16456        scale: f32,
16457        causal: bool,
16458        window: usize,
16459        k_tok_bytes: usize,
16460        v_tok_bytes: usize,
16461    ) -> Result<(), Box<dyn std::error::Error>> {
16462        assert_eq!(
16463            head_dim, 128,
16464            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
16465        );
16466        if portable_mma_gated() {
16467            return self.sdpa_naive_w_quantized_view(
16468                q,
16469                k,
16470                v,
16471                o,
16472                head_dim,
16473                n_head,
16474                n_head_kv,
16475                t,
16476                t_kv,
16477                scale,
16478                causal,
16479                window,
16480                k_tok_bytes,
16481                v_tok_bytes,
16482            );
16483        }
16484        const BLOCK_Q: usize = 64;
16485        const BK: usize = 32;
16486        let kv_dim_k = n_head_kv * head_dim;
16487        let kv_dim_v = n_head_kv * head_dim;
16488        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16489        let v_ws_bytes = t_kv * kv_dim_v * 2;
16490        let mut guard = self.prime_deqw_ws.lock().unwrap();
16491        let need_grow = match guard.as_ref() {
16492            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16493            None => true,
16494        };
16495        if need_grow {
16496            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16497            let (ck, cv) = guard
16498                .as_ref()
16499                .map(|(a, b)| (a.len(), b.len()))
16500                .unwrap_or((0, 0));
16501            *guard = Some((
16502                self.alloc_u8(grow(ck, k_ws_bytes))?,
16503                self.alloc_u8(grow(cv, v_ws_bytes))?,
16504            ));
16505        }
16506        let (kw, vw) = guard.as_mut().unwrap();
16507        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
16508        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
16509        {
16510            let f = self.func("fa_dequant_kv_ws_bf16");
16511            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16512            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16513            let cfg = LaunchConfig {
16514                grid_dim: (nblk.max(1), 1, 1),
16515                block_dim: (256, 1, 1),
16516                shared_mem_bytes: 0,
16517            };
16518            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16519            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16520            let __s_b = self.gpu.stream();
16521            let mut b = __s_b.launch_builder(&f);
16522            b.arg(k)
16523                .arg(v)
16524                .arg(&mut *kw)
16525                .arg(&mut *vw)
16526                .arg(&kdk)
16527                .arg(&kdv)
16528                .arg(&tkvi)
16529                .arg(&ktb)
16530                .arg(&vtb);
16531            unsafe {
16532                b.launch(cfg)?;
16533            }
16534        }
16535        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
16536        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16537            .map(|v| v != "0")
16538            .unwrap_or(true);
16539        {
16540            let f = self.func(if db {
16541                "fa_prefill_qw_db_w_hd128"
16542            } else {
16543                "fa_prefill_qw_w_hd128"
16544            });
16545            let shmem = if db {
16546                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16547            } else {
16548                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16549            };
16550            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16551            f.set_attribute(
16552                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16553                shmem as i32,
16554            )?;
16555            let cfg = LaunchConfig {
16556                grid_dim: (
16557                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16558                    n_head as u32,
16559                    1,
16560                ),
16561                block_dim: (32, 4, 1),
16562                shared_mem_bytes: shmem,
16563            };
16564            let (hd, nh, nhkv, ti, tkvi, cz) = (
16565                head_dim as i32,
16566                n_head as i32,
16567                n_head_kv as i32,
16568                t as i32,
16569                t_kv as i32,
16570                causal as i32,
16571            );
16572            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
16573            let __s_b = self.gpu.stream();
16574            let mut b = __s_b.launch_builder(&f);
16575            b.arg(q)
16576                .arg(&*kw)
16577                .arg(&*vw)
16578                .arg(o)
16579                .arg(&hd)
16580                .arg(&nh)
16581                .arg(&nhkv)
16582                .arg(&ti)
16583                .arg(&tkvi)
16584                .arg(&scale)
16585                .arg(&cz)
16586                .arg(&kdk)
16587                .arg(&kdv)
16588                .arg(&wnd);
16589            unsafe {
16590                b.launch(cfg)?;
16591            }
16592        }
16593        Ok(())
16594    }
16595
16596    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
16597    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
16598    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
16599    pub fn fa_decode(
16600        &self,
16601        q: &CudaSlice<f32>,
16602        k: &cudarc::driver::CudaView<u8>,
16603        v: &cudarc::driver::CudaView<u8>,
16604        o: &mut CudaSlice<f32>,
16605        head_dim: usize,
16606        n_head: usize,
16607        n_head_kv: usize,
16608        t_kv: usize,
16609        scale: f32,
16610        k_tok_bytes: usize,
16611        v_tok_bytes: usize,
16612    ) -> Result<(), Box<dyn std::error::Error>> {
16613        self.fa_decode_kvmod(
16614            q,
16615            k,
16616            v,
16617            o,
16618            head_dim,
16619            n_head,
16620            n_head_kv,
16621            t_kv,
16622            scale,
16623            k_tok_bytes,
16624            v_tok_bytes,
16625            false,
16626        )
16627    }
16628
16629    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
16630    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
16631    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
16632    #[allow(clippy::too_many_arguments)]
16633    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
16634    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
16635    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
16636    #[allow(clippy::too_many_arguments)]
16637    #[allow(clippy::too_many_arguments)]
16638    fn fa_decode_scalar_unified(
16639        &self,
16640        q: &cudarc::driver::CudaView<f32>,
16641        k: &cudarc::driver::CudaView<u8>,
16642        v: &cudarc::driver::CudaView<u8>,
16643        o: &mut cudarc::driver::CudaViewMut<f32>,
16644        head_dim: usize,
16645        n_head: usize,
16646        n_head_kv: usize,
16647        t_kv_host: usize,
16648        t_kv_dev: Option<&CudaSlice<i32>>,
16649        scale: f32,
16650        n_splits: usize,
16651        split_keys: usize,
16652        k_tok_bytes: usize,
16653        v_tok_bytes: usize,
16654        g: bool,
16655        part_o: &mut CudaSlice<f32>,
16656        part_m: &mut CudaSlice<f32>,
16657        part_l: &mut CudaSlice<f32>,
16658        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
16659    ) -> Result<(), Box<dyn std::error::Error>> {
16660        let f = if g {
16661            self.func_g("fa_decode_f32")
16662        } else {
16663            self.fa_func("fa_decode_f32", head_dim)
16664        };
16665        let cfg = LaunchConfig {
16666            grid_dim: (n_head as u32, n_splits as u32, 1),
16667            block_dim: (head_dim as u32, 1, 1),
16668            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
16669        };
16670        let (hd, nh, nhkv, nsp) = (
16671            head_dim as i32,
16672            n_head as i32,
16673            n_head_kv as i32,
16674            n_splits as i32,
16675        );
16676        let (ktb, vtb, tkvi, ski) = (
16677            k_tok_bytes as i64,
16678            v_tok_bytes as i64,
16679            t_kv_host as i32,
16680            split_keys as i32,
16681        );
16682        let __s_b = self.gpu.stream();
16683        let mut b = __s_b.launch_builder(&f);
16684        match t_kv_dev {
16685            Some(d) => {
16686                b.arg(q)
16687                    .arg(k)
16688                    .arg(v)
16689                    .arg(&mut *part_o)
16690                    .arg(&mut *part_m)
16691                    .arg(&mut *part_l)
16692                    .arg(&hd)
16693                    .arg(&nh)
16694                    .arg(&nhkv)
16695                    .arg(&tkvi)
16696                    .arg(d)
16697                    .arg(&scale)
16698                    .arg(&nsp)
16699                    .arg(&ski)
16700                    .arg(&ktb)
16701                    .arg(&vtb);
16702                unsafe {
16703                    b.launch(cfg)?;
16704                }
16705            }
16706            None => {
16707                let null: u64 = 0;
16708                b.arg(q)
16709                    .arg(k)
16710                    .arg(v)
16711                    .arg(&mut *part_o)
16712                    .arg(&mut *part_m)
16713                    .arg(&mut *part_l)
16714                    .arg(&hd)
16715                    .arg(&nh)
16716                    .arg(&nhkv)
16717                    .arg(&tkvi)
16718                    .arg(&null)
16719                    .arg(&scale)
16720                    .arg(&nsp)
16721                    .arg(&ski)
16722                    .arg(&ktb)
16723                    .arg(&vtb);
16724                unsafe {
16725                    b.launch(cfg)?;
16726                }
16727            }
16728        }
16729        let cfg2 = LaunchConfig {
16730            grid_dim: (n_head as u32, 1, 1),
16731            block_dim: (head_dim as u32, 1, 1),
16732            shared_mem_bytes: 0,
16733        };
16734        if let Some((oq, od)) = q8_out {
16735            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
16736            let fc = if g {
16737                self.func_g("fa_decode_combine_q8_1")
16738            } else {
16739                self.fa_func("fa_decode_combine_q8_1", head_dim)
16740            };
16741            let __s_b2 = self.gpu.stream();
16742            let mut b2 = __s_b2.launch_builder(&fc);
16743            b2.arg(&*part_o)
16744                .arg(&*part_m)
16745                .arg(&*part_l)
16746                .arg(oq)
16747                .arg(od)
16748                .arg(&hd)
16749                .arg(&nh)
16750                .arg(&nsp);
16751            unsafe {
16752                b2.launch(cfg2)?;
16753            }
16754            return Ok(());
16755        }
16756        let fc = if g {
16757            self.func_g("fa_decode_combine_f32")
16758        } else {
16759            self.fa_func("fa_decode_combine_f32", head_dim)
16760        };
16761        let __s_b2 = self.gpu.stream();
16762        let mut b2 = __s_b2.launch_builder(&fc);
16763        b2.arg(&*part_o)
16764            .arg(&*part_m)
16765            .arg(&*part_l)
16766            .arg(o)
16767            .arg(&hd)
16768            .arg(&nh)
16769            .arg(&nsp);
16770        unsafe {
16771            b2.launch(cfg2)?;
16772        }
16773        Ok(())
16774    }
16775
16776    pub fn fa_decode_kvmod(
16777        &self,
16778        q: &CudaSlice<f32>,
16779        k: &cudarc::driver::CudaView<u8>,
16780        v: &cudarc::driver::CudaView<u8>,
16781        o: &mut CudaSlice<f32>,
16782        head_dim: usize,
16783        n_head: usize,
16784        n_head_kv: usize,
16785        t_kv: usize,
16786        scale: f32,
16787        k_tok_bytes: usize,
16788        v_tok_bytes: usize,
16789        g: bool,
16790    ) -> Result<(), Box<dyn std::error::Error>> {
16791        let q_view = q.as_view();
16792        let mut o_view = o.as_view_mut();
16793        self.fa_decode_kvmod_view(
16794            &q_view,
16795            k,
16796            v,
16797            &mut o_view,
16798            head_dim,
16799            n_head,
16800            n_head_kv,
16801            t_kv,
16802            scale,
16803            k_tok_bytes,
16804            v_tok_bytes,
16805            g,
16806        )
16807    }
16808
16809    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
16810    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
16811    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
16812    /// per-session KV view and FA launch.
16813    #[allow(clippy::too_many_arguments)]
16814    pub fn fa_decode_kvmod_view(
16815        &self,
16816        q: &cudarc::driver::CudaView<f32>,
16817        k: &cudarc::driver::CudaView<u8>,
16818        v: &cudarc::driver::CudaView<u8>,
16819        o: &mut cudarc::driver::CudaViewMut<f32>,
16820        head_dim: usize,
16821        n_head: usize,
16822        n_head_kv: usize,
16823        t_kv: usize,
16824        scale: f32,
16825        k_tok_bytes: usize,
16826        v_tok_bytes: usize,
16827        g: bool,
16828    ) -> Result<(), Box<dyn std::error::Error>> {
16829        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
16830        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
16831        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
16832        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
16833        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
16834        //
16835        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
16836        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
16837        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
16838        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
16839        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
16840        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
16841        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
16842        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
16843        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
16844        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
16845        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
16846        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
16847        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
16848        // fall to the exact scalar there instead of the broken register arm.
16849        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
16850        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
16851        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
16852        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
16853        if g && head_dim == 256 && !fa_v4_at(t_kv) {
16854            fa_vec = false;
16855        }
16856        let sp = fa_split_keys(t_kv, n_head_kv);
16857        let n_splits = if fa_vec {
16858            ((t_kv + sp - 1) / sp).max(1)
16859        } else {
16860            ((t_kv + 255) / 256).max(1)
16861        };
16862        let o_len = n_head * n_splits * head_dim;
16863        let ml_len = n_head * n_splits;
16864        let mut part_guard = self.fa_part_pool.lock().unwrap();
16865        if part_guard
16866            .as_ref()
16867            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
16868            .unwrap_or(true)
16869        {
16870            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
16871            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
16872            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
16873            // later live allocations land at those addresses, and the next graph REPLAY writes
16874            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
16875            // output corruption began the burst after the trunk's t_kv growth first realloc'd
16876            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
16877            // the baked addresses alive (single-stream: eager writes the new buffers, replays
16878            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
16879            // (total retired < final size).
16880            let old = part_guard.take();
16881            let (co, cm) = old
16882                .as_ref()
16883                .map(|pp| (pp.0.len(), pp.1.len()))
16884                .unwrap_or((0, 0));
16885            if let Some(old) = old {
16886                self.fa_part_retired.lock().unwrap().push(old);
16887            }
16888            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
16889                eprintln!(
16890                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
16891                    co, o_len, cm, ml_len
16892                );
16893            }
16894            *part_guard = Some((
16895                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
16896                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16897                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16898            ));
16899        }
16900        let pg = part_guard.as_mut().unwrap();
16901        self.gpu
16902            .stream()
16903            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
16904        self.gpu
16905            .stream()
16906            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
16907        self.gpu
16908            .stream()
16909            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
16910        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
16911        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
16912        let (hd, nh, nhkv, tkvi, nsp) = (
16913            head_dim as i32,
16914            n_head as i32,
16915            n_head_kv as i32,
16916            t_kv as i32,
16917            n_splits as i32,
16918        );
16919        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16920        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
16921        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
16922        // silently truncating the accumulator.
16923        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
16924        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
16925        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
16926        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
16927        // 178.4 -> 173.7 when 512 rode vec unconditionally).
16928        let fa512_min = fa512_min_tkv();
16929        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
16930        // g-module keeps the v4 pick (its class is not the depth-decay class).
16931        let deep = fa_vec
16932            && head_dim == 256
16933            && fa_v4_at(t_kv)
16934            && !g
16935            && fa_deep_at(t_kv)
16936            && !matches!(fa_v4_mode(), "noB3" | "stage");
16937        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
16938            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
16939            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
16940            let gqa = (n_head / n_head_kv).max(1) as u32;
16941            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
16942            (
16943                fv,
16944                LaunchConfig {
16945                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16946                    block_dim: (32, gqa, 1),
16947                    shared_mem_bytes: 0,
16948                },
16949            )
16950        } else if fa_vec && head_dim <= 256 {
16951            let gqa = (n_head / n_head_kv).max(1) as u32;
16952            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
16953            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
16954            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
16955            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
16956            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
16957            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
16958            // dequant each tile ONCE per block.
16959            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
16960            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
16961            // there by 12x — latency, not bandwidth, rules small KV).
16962            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
16963            let smem_tkv = *SMEM_TKV.get_or_init(|| {
16964                std::env::var("MEMRA_FA_SMEM_TKV")
16965                    .ok()
16966                    .and_then(|v| v.parse().ok())
16967                    .unwrap_or_else(|| {
16968                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
16969                    })
16970            });
16971            if fa_v4_at(t_kv) && head_dim == 256 {
16972                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
16973                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
16974                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
16975                let v4name = match fa_v4_mode() {
16976                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
16977                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
16978                    _ if deep => "fa_decode_vec_q_v4_deep",
16979                    _ => "fa_decode_vec_q_v4",
16980                };
16981                let fv = if g {
16982                    self.func_g(v4name)
16983                } else {
16984                    self.func(v4name)
16985                };
16986                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
16987                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
16988                let shmem = (if deep { 12160 } else { 11520 }
16989                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
16990                use cudarc::driver::sys::CUfunction_attribute_enum as A;
16991                fv.set_attribute(
16992                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16993                    shmem as i32,
16994                )?;
16995                (
16996                    fv,
16997                    LaunchConfig {
16998                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16999                        block_dim: (32, gqa, 1),
17000                        shared_mem_bytes: shmem,
17001                    },
17002                )
17003            } else if fa_v3_active(head_dim) {
17004                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
17005                // smem = sV only (half of v2's).
17006                let fv = if g {
17007                    self.func_g("fa_decode_vec_q_v3")
17008                } else {
17009                    self.func("fa_decode_vec_q_v3")
17010                };
17011                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
17012                (
17013                    fv,
17014                    LaunchConfig {
17015                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17016                        block_dim: (32, gqa, 1),
17017                        shared_mem_bytes: shmem,
17018                    },
17019                )
17020            } else if fa_v2_on() {
17021                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
17022                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
17023                // partials; same 32KB sK+sV tile as the smem twin.
17024                let fv = if g {
17025                    self.func_g("fa_decode_vec_q_v2")
17026                } else {
17027                    self.func("fa_decode_vec_q_v2")
17028                };
17029                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17030                (
17031                    fv,
17032                    LaunchConfig {
17033                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17034                        block_dim: (32, gqa, 1),
17035                        shared_mem_bytes: shmem,
17036                    },
17037                )
17038            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
17039            {
17040                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
17041                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
17042                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
17043                let fv = if g {
17044                    self.func_g("fa_decode_vec_q_smem")
17045                } else {
17046                    self.func("fa_decode_vec_q_smem")
17047                };
17048                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17049                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17050                fv.set_attribute(
17051                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17052                    shmem as i32,
17053                )?;
17054                (
17055                    fv,
17056                    LaunchConfig {
17057                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17058                        block_dim: (32, gqa, 1),
17059                        shared_mem_bytes: shmem,
17060                    },
17061                )
17062            } else {
17063                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
17064                // dequant, zero dynamic shared memory.
17065                let fv = if g {
17066                    self.func_g("fa_decode_vec_q")
17067                } else {
17068                    self.func("fa_decode_vec_q")
17069                };
17070                (
17071                    fv,
17072                    LaunchConfig {
17073                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17074                        block_dim: (32, gqa, 1),
17075                        shared_mem_bytes: 0,
17076                    },
17077                )
17078            }
17079        } else {
17080            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
17081            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
17082            return self.fa_decode_scalar_unified(
17083                q,
17084                k,
17085                v,
17086                o,
17087                head_dim,
17088                n_head,
17089                n_head_kv,
17090                t_kv,
17091                None,
17092                scale,
17093                n_splits,
17094                if fa_vec { sp } else { 256 },
17095                k_tok_bytes,
17096                v_tok_bytes,
17097                g,
17098                part_o,
17099                part_m,
17100                part_l,
17101                None,
17102            );
17103        };
17104        let __s_b = self.gpu.stream();
17105        let mut b = __s_b.launch_builder(&f);
17106        b.arg(q)
17107            .arg(k)
17108            .arg(v)
17109            .arg(&mut *part_o)
17110            .arg(&mut *part_m)
17111            .arg(&mut *part_l)
17112            .arg(&hd)
17113            .arg(&nh)
17114            .arg(&nhkv)
17115            .arg(&tkvi)
17116            .arg(&scale)
17117            .arg(&nsp)
17118            .arg(&ktb)
17119            .arg(&vtb);
17120        unsafe {
17121            b.launch(cfg)?;
17122        }
17123        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
17124        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
17125        let (fc, cfg2) = (
17126            if g {
17127                self.func_g("fa_decode_combine_f32")
17128            } else {
17129                self.fa_func("fa_decode_combine_f32", head_dim)
17130            },
17131            LaunchConfig {
17132                grid_dim: (n_head as u32, 1, 1),
17133                block_dim: (head_dim as u32, 1, 1),
17134                shared_mem_bytes: 0,
17135            },
17136        );
17137        let __s_b2 = self.gpu.stream();
17138        let mut b2 = __s_b2.launch_builder(&fc);
17139        b2.arg(&*part_o)
17140            .arg(&*part_m)
17141            .arg(&*part_l)
17142            .arg(o)
17143            .arg(&hd)
17144            .arg(&nh)
17145            .arg(&nsp);
17146        unsafe {
17147            b2.launch(cfg2)?;
17148        }
17149        Ok(())
17150    }
17151
17152    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
17153    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
17154    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
17155    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
17156    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
17157    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
17158    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
17159    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
17160    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
17161    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
17162    #[allow(clippy::too_many_arguments)]
17163    pub fn fa_decode_batch_seqs_v4(
17164        &self,
17165        q: &CudaSlice<f32>,
17166        kv_ptrs: &cudarc::driver::CudaView<u64>,
17167        pos_seq: &CudaSlice<i32>,
17168        o: &mut CudaSlice<f32>,
17169        head_dim: usize,
17170        n_head: usize,
17171        n_head_kv: usize,
17172        b_n: usize,
17173        t_kv_max: usize,
17174        scale: f32,
17175        split_keys: usize,
17176        k_tok_bytes: usize,
17177        v_tok_bytes: usize,
17178    ) -> Result<(), Box<dyn std::error::Error>> {
17179        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
17180        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
17181        let o_len = b_n * n_head * n_splits_max * head_dim;
17182        let ml_len = b_n * n_head * n_splits_max;
17183        let mut part_guard = self.fa_part_pool.lock().unwrap();
17184        if part_guard
17185            .as_ref()
17186            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17187            .unwrap_or(true)
17188        {
17189            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17190            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17191            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17192            // later live allocations land at those addresses, and the next graph REPLAY writes
17193            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17194            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17195            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17196            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17197            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17198            // (total retired < final size).
17199            let old = part_guard.take();
17200            let (co, cm) = old
17201                .as_ref()
17202                .map(|pp| (pp.0.len(), pp.1.len()))
17203                .unwrap_or((0, 0));
17204            if let Some(old) = old {
17205                self.fa_part_retired.lock().unwrap().push(old);
17206            }
17207            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17208                eprintln!(
17209                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17210                    co, o_len, cm, ml_len
17211                );
17212            }
17213            *part_guard = Some((
17214                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17215                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17216                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17217            ));
17218        }
17219        let pg = part_guard.as_mut().unwrap();
17220        self.gpu
17221            .stream()
17222            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17223        self.gpu
17224            .stream()
17225            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17226        self.gpu
17227            .stream()
17228            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17229        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17230        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17231        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
17232        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17233        let gqa = (n_head / n_head_kv).max(1) as u32;
17234        let f = self.func("fa_decode_vec_q_seqs_v4");
17235        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
17236        let shmem = (11520 + 32 * head_dim * 2) as u32;
17237        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17238        f.set_attribute(
17239            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17240            shmem as i32,
17241        )?;
17242        let cfg = LaunchConfig {
17243            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
17244            block_dim: (32, gqa, 1),
17245            shared_mem_bytes: shmem,
17246        };
17247        {
17248            let __s_b = self.gpu.stream();
17249            let mut b = __s_b.launch_builder(&f);
17250            b.arg(q)
17251                .arg(kv_ptrs)
17252                .arg(pos_seq)
17253                .arg(&mut *part_o)
17254                .arg(&mut *part_m)
17255                .arg(&mut *part_l)
17256                .arg(&hd)
17257                .arg(&nh)
17258                .arg(&nhkv)
17259                .arg(&scale)
17260                .arg(&nspm)
17261                .arg(&spk)
17262                .arg(&ktb)
17263                .arg(&vtb);
17264            unsafe {
17265                b.launch(cfg)?;
17266            }
17267        }
17268        let fc = self.func("fa_decode_combine_seqs");
17269        let cfg2 = LaunchConfig {
17270            grid_dim: (n_head as u32, b_n as u32, 1),
17271            block_dim: (head_dim as u32, 1, 1),
17272            shared_mem_bytes: 0,
17273        };
17274        let __s_b2 = self.gpu.stream();
17275        let mut b2 = __s_b2.launch_builder(&fc);
17276        b2.arg(&*part_o)
17277            .arg(&*part_m)
17278            .arg(&*part_l)
17279            .arg(o)
17280            .arg(&hd)
17281            .arg(&nh)
17282            .arg(pos_seq)
17283            .arg(&nspm)
17284            .arg(&spk);
17285        unsafe {
17286            b2.launch(cfg2)?;
17287        }
17288        Ok(())
17289    }
17290
17291    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
17292    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
17293    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
17294    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
17295    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
17296    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
17297    #[allow(clippy::too_many_arguments)]
17298    pub fn append_kv_quantized_seqs(
17299        &self,
17300        k_rows: &CudaSlice<f32>,
17301        v_rows: &CudaSlice<f32>,
17302        kv_ptrs: &cudarc::driver::CudaView<u64>,
17303        pos_seq: &CudaSlice<i32>,
17304        b_n: usize,
17305        kv_dim_k: usize,
17306        kv_dim_v: usize,
17307        k_tok_bytes: usize,
17308        v_tok_bytes: usize,
17309    ) -> Result<(), Box<dyn std::error::Error>> {
17310        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
17311        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
17312        let cfg = LaunchConfig {
17313            grid_dim: (nblk, b_n as u32, 1),
17314            block_dim: (32, 1, 1),
17315            shared_mem_bytes: 0,
17316        };
17317        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17318        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17319        let __s_b = self.gpu.stream();
17320        let mut b = __s_b.launch_builder(&f);
17321        b.arg(k_rows)
17322            .arg(v_rows)
17323            .arg(kv_ptrs)
17324            .arg(pos_seq)
17325            .arg(&kdk)
17326            .arg(&kdv)
17327            .arg(&ktb)
17328            .arg(&vtb);
17329        unsafe {
17330            b.launch(cfg)?;
17331        }
17332        Ok(())
17333    }
17334
17335    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
17336    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
17337    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
17338    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
17339    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
17340    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
17341        std::env::var("MEMRA_NO_FA_VEC").is_err()
17342            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
17343            && base_len + 1 >= fa_vec_min_tkv()
17344            && head_dim <= 256
17345            && head_dim % 32 == 0
17346    }
17347
17348    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
17349    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
17350    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
17351    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
17352    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
17353    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
17354    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
17355    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
17356    #[allow(clippy::too_many_arguments)]
17357    pub fn fa_decode_rows(
17358        &self,
17359        q: &CudaSlice<f32>,
17360        k: &cudarc::driver::CudaView<u8>,
17361        v: &cudarc::driver::CudaView<u8>,
17362        o: &mut CudaSlice<f32>,
17363        head_dim: usize,
17364        n_head: usize,
17365        n_head_kv: usize,
17366        base_len: usize,
17367        t: usize,
17368        scale: f32,
17369        k_tok_bytes: usize,
17370        v_tok_bytes: usize,
17371        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
17372        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
17373        // keep the host arg. None is a bug for hd512 (asserted below).
17374        base_dev: Option<(&CudaSlice<i32>, i32)>,
17375        // K and V planes hold the same values (gemma globals, wv:=wk): pick
17376        // the _kv twin — V plane never read, value rides the q8_0 key dq.
17377        kv_shared: bool,
17378        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
17379        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
17380        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
17381        g: bool,
17382        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
17383        // (hd512 path) — the standalone quantize launch folds away.
17384        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17385    ) -> Result<(), Box<dyn std::error::Error>> {
17386        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
17387        let t_kv_max = base_len + t; // LAST row's key bound
17388        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
17389        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
17390        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
17391        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
17392        // (parity law), so the partition is freely tunable — verify and decode move together.
17393        if head_dim == 512 {
17394            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17395            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
17396            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
17397            let v = *SP512.get_or_init(|| {
17398                std::env::var("MEMRA_FA_SP512")
17399                    .ok()
17400                    .and_then(|x| x.parse().ok())
17401                    .unwrap_or(0)
17402            });
17403            sp = if v >= 8 {
17404                v
17405            } else {
17406                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17407            };
17408        }
17409        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17410        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17411        let gqa = (n_head / n_head_kv).max(1) as u32;
17412        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
17413        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
17414        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
17415        // the different partition changes the combine's FP order (greedy tie flips at depth;
17416        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
17417        // consecutive rows by their OWN ladder value and launch once per group — each row then
17418        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
17419        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
17420        // sp override is t_kv-independent by construction).
17421        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
17422        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
17423            groups.push((0, t, sp));
17424        } else {
17425            let mut r0 = 0usize;
17426            while r0 < t {
17427                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
17428                let mut r1 = r0 + 1;
17429                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
17430                    r1 += 1;
17431                }
17432                groups.push((r0, r1 - r0, sp_g));
17433                r0 = r1;
17434            }
17435        }
17436        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
17437        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
17438        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
17439        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17440        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
17441            std::env::var("MEMRA_FA_SMEM_TKV")
17442                .ok()
17443                .and_then(|v| v.parse().ok())
17444                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17445        });
17446        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
17447        let v3 = fa_v3_active(head_dim);
17448        let smem_rows =
17449            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
17450        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
17451        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
17452        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
17453        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
17454        let _ = kv_shared;
17455        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
17456        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
17457        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
17458        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
17459        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
17460        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
17461        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
17462        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
17463        // (kv_head, split) stages its tile once and loops the rows over it — kills the
17464        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
17465        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
17466        // shared by every hd512 caller through this wrapper (decode+verify flip together;
17467        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
17468        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
17469        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
17470        // not unpack-bound; jsonl 2026-07-14.
17471        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17472        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
17473        let tb512 = head_dim == 512
17474            && sp <= 32
17475            && n_head / n_head_kv.max(1) <= 16
17476            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
17477        let fname = if tb512 {
17478            "fa_decode_vec_q_rows_v4_512_tb"
17479        } else if i2 {
17480            "fa_decode_vec_q_rows_dpl16_i2"
17481        } else if head_dim == 512 {
17482            "fa_decode_vec_q_rows_dpl16"
17483        }
17484        // gemma globals (parity law)
17485        else if v4 {
17486            "fa_decode_vec_q_rows_v4"
17487        } else if v3 {
17488            "fa_decode_vec_q_rows_v3"
17489        } else if fa_v2_on() {
17490            "fa_decode_vec_q_rows_v2"
17491        } else if smem_rows {
17492            "fa_decode_vec_q_rows_smem"
17493        } else {
17494            "fa_decode_vec_q_rows"
17495        };
17496        let f = if head_dim == 512 {
17497            self.fa_func(fname, head_dim)
17498        } else if g {
17499            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
17500            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
17501            // g-module rows against decode's g-module v4 — different programs, short-VG
17502            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
17503            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
17504            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
17505            // dq macros are format-aware.
17506            self.func_g(if smem_rows {
17507                "fa_decode_vec_q_rows"
17508            } else {
17509                fname
17510            })
17511        } else {
17512            self.func(fname)
17513        };
17514        let shmem = if tb512 {
17515            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
17516            let gk = Self::gkv_on();
17517            let sh =
17518                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
17519            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17520            f.set_attribute(
17521                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17522                sh as i32,
17523            )?;
17524            sh
17525        } else if v4 || v3 || smem_rows || fa_v2_on() {
17526            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
17527            let sh = (if v4 {
17528                11520 + 32 * head_dim * if g { 1 } else { 2 }
17529            } else if v3 {
17530                32 * head_dim * 2
17531            } else {
17532                2 * 32 * head_dim * 2
17533            }) as u32;
17534            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17535            f.set_attribute(
17536                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17537                sh as i32,
17538            )?;
17539            sh
17540        } else {
17541            0
17542        };
17543        // Per-GROUP launches (single group in the common case — identical to the pre-fix
17544        // single launch there): each group gets its own partials (the rows kernel indexes
17545        // partials by its LOCAL grid.z row) and q/o row-offset views.
17546        for &(r0, t_g, sp_g) in &groups {
17547            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
17548            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
17549            let base_i = (base_len + r0) as i32;
17550            let o_len = t_g * n_head * n_splits_g * head_dim;
17551            let ml_len = t_g * n_head * n_splits_g;
17552            let mut part_guard = self.fa_part_pool.lock().unwrap();
17553            if part_guard
17554                .as_ref()
17555                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17556                .unwrap_or(true)
17557            {
17558                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17559                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17560                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17561                // later live allocations land at those addresses, and the next graph REPLAY writes
17562                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17563                // output corruption began the burst after the trunk's t_kv growth first realloc'd
17564                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17565                // the baked addresses alive (single-stream: eager writes the new buffers, replays
17566                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17567                // (total retired < final size).
17568                let old = part_guard.take();
17569                let (co, cm) = old
17570                    .as_ref()
17571                    .map(|pp| (pp.0.len(), pp.1.len()))
17572                    .unwrap_or((0, 0));
17573                if let Some(old) = old {
17574                    self.fa_part_retired.lock().unwrap().push(old);
17575                }
17576                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17577                    eprintln!(
17578                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17579                        co, o_len, cm, ml_len
17580                    );
17581                }
17582                *part_guard = Some((
17583                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17584                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17585                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17586                ));
17587            }
17588            let pg = part_guard.as_mut().unwrap();
17589            self.gpu
17590                .stream()
17591                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17592            self.gpu
17593                .stream()
17594                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17595            self.gpu
17596                .stream()
17597                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17598            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17599            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17600            let qv = self.view(q, t * n_head * head_dim);
17601            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17602            let cfg = LaunchConfig {
17603                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
17604                block_dim: (32, gqa, 1),
17605                shared_mem_bytes: shmem,
17606            };
17607            {
17608                let __s_b = self.gpu.stream();
17609                let mut b = __s_b.launch_builder(&f);
17610                if tb512 {
17611                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
17612                    let (bd, plus) =
17613                        base_dev.expect("hd512 rows twin requires a device base counter");
17614                    let plus_g = plus + r0 as i32;
17615                    let nr = t_g as i32;
17616                    if Self::pdl_on() && Self::pdl_wb_on() {
17617                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
17618                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17619                        let s = &self.gpu.stream();
17620                        let (pq, _b0) = q_g.device_ptr(s);
17621                        let (pk, _b1) = k.device_ptr(s);
17622                        let (pv, _b2) = v.device_ptr(s);
17623                        let (po, _b3) = part_o.device_ptr_mut(s);
17624                        let (pm, _b4) = part_m.device_ptr_mut(s);
17625                        let (pl, _b5) = part_l.device_ptr_mut(s);
17626                        let (pb, _b6) = bd.device_ptr(s);
17627                        let mut ps = [
17628                            &pq as *const _ as *mut std::ffi::c_void,
17629                            &pk as *const _ as *mut _,
17630                            &pv as *const _ as *mut _,
17631                            &po as *const _ as *mut _,
17632                            &pm as *const _ as *mut _,
17633                            &pl as *const _ as *mut _,
17634                            &hd as *const _ as *mut _,
17635                            &nh as *const _ as *mut _,
17636                            &nhkv as *const _ as *mut _,
17637                            &pb as *const _ as *mut _,
17638                            &plus_g as *const _ as *mut _,
17639                            &scale as *const _ as *mut _,
17640                            &nspm as *const _ as *mut _,
17641                            &spk as *const _ as *mut _,
17642                            &ktb as *const _ as *mut _,
17643                            &vtb as *const _ as *mut _,
17644                            &nr as *const _ as *mut _,
17645                        ];
17646                        unsafe {
17647                            self.launch_pdl_flash(
17648                                Self::gkv_on(),
17649                                "fa_decode_vec_q_rows_v4_512_tb",
17650                                (n_head_kv as u32, n_splits_g as u32, 1),
17651                                (32, gqa, 1),
17652                                shmem,
17653                                &mut ps,
17654                            )?;
17655                        }
17656                    } else {
17657                        let cfg_tb = LaunchConfig {
17658                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
17659                            block_dim: (32, gqa, 1),
17660                            shared_mem_bytes: shmem,
17661                        };
17662                        b.arg(&q_g)
17663                            .arg(k)
17664                            .arg(v)
17665                            .arg(&mut *part_o)
17666                            .arg(&mut *part_m)
17667                            .arg(&mut *part_l)
17668                            .arg(&hd)
17669                            .arg(&nh)
17670                            .arg(&nhkv)
17671                            .arg(bd)
17672                            .arg(&plus_g)
17673                            .arg(&scale)
17674                            .arg(&nspm)
17675                            .arg(&spk)
17676                            .arg(&ktb)
17677                            .arg(&vtb)
17678                            .arg(&nr);
17679                        unsafe {
17680                            b.launch(cfg_tb)?;
17681                        }
17682                    }
17683                } else if head_dim == 512 {
17684                    let (bd, plus) =
17685                        base_dev.expect("hd512 rows twin requires a device base counter");
17686                    let plus_g = plus + r0 as i32;
17687                    b.arg(&q_g)
17688                        .arg(k)
17689                        .arg(v)
17690                        .arg(&mut *part_o)
17691                        .arg(&mut *part_m)
17692                        .arg(&mut *part_l)
17693                        .arg(&hd)
17694                        .arg(&nh)
17695                        .arg(&nhkv)
17696                        .arg(bd)
17697                        .arg(&plus_g)
17698                        .arg(&scale)
17699                        .arg(&nspm)
17700                        .arg(&spk)
17701                        .arg(&ktb)
17702                        .arg(&vtb);
17703                    unsafe {
17704                        b.launch(cfg)?;
17705                    }
17706                } else {
17707                    b.arg(&q_g)
17708                        .arg(k)
17709                        .arg(v)
17710                        .arg(&mut *part_o)
17711                        .arg(&mut *part_m)
17712                        .arg(&mut *part_l)
17713                        .arg(&hd)
17714                        .arg(&nh)
17715                        .arg(&nhkv)
17716                        .arg(&base_i)
17717                        .arg(&scale)
17718                        .arg(&nspm)
17719                        .arg(&spk)
17720                        .arg(&ktb)
17721                        .arg(&vtb);
17722                    unsafe {
17723                        b.launch(cfg)?;
17724                    }
17725                }
17726            }
17727            let cfg2 = LaunchConfig {
17728                grid_dim: (n_head as u32, t_g as u32, 1),
17729                block_dim: (head_dim as u32, 1, 1),
17730                shared_mem_bytes: 0,
17731            };
17732            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17733            if head_dim == 512 {
17734                // device-len combine (shared by verify/eager/graph — parity by symbol): the
17735                // per-row n_splits derives from the SAME counter the rows kernel read.
17736                let (bd, plus) = base_dev.unwrap();
17737                let plus_g = plus + r0 as i32;
17738                if let Some((oq, od)) = q8_out.as_mut() {
17739                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
17740                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
17741                    if Self::pdl_on() && Self::pdl_wb_on() {
17742                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
17743                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17744                        let s = &self.gpu.stream();
17745                        let (po, _g0) = part_o.device_ptr(s);
17746                        let (pm, _g1) = part_m.device_ptr(s);
17747                        let (pl, _g2) = part_l.device_ptr(s);
17748                        let (pq, _g3) = oq.device_ptr_mut(s);
17749                        let (pd, _g4) = od.device_ptr_mut(s);
17750                        let (pb, _g5) = bd.device_ptr(s);
17751                        let mut ps = [
17752                            &po as *const _ as *mut std::ffi::c_void,
17753                            &pm as *const _ as *mut _,
17754                            &pl as *const _ as *mut _,
17755                            &pq as *const _ as *mut _,
17756                            &pd as *const _ as *mut _,
17757                            &hd as *const _ as *mut _,
17758                            &nh as *const _ as *mut _,
17759                            &pb as *const _ as *mut _,
17760                            &plus_g as *const _ as *mut _,
17761                            &nspm as *const _ as *mut _,
17762                            &spk as *const _ as *mut _,
17763                        ];
17764                        unsafe {
17765                            self.launch_pdl_flash(
17766                                Self::gkv_on(),
17767                                "fa_decode_combine_rows_dc_q8_1",
17768                                cfg2.grid_dim,
17769                                cfg2.block_dim,
17770                                0,
17771                                &mut ps,
17772                            )?;
17773                        }
17774                        continue;
17775                    }
17776                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
17777                    let __s_b2 = self.gpu.stream();
17778                    let mut b2 = __s_b2.launch_builder(&fc);
17779                    b2.arg(&*part_o)
17780                        .arg(&*part_m)
17781                        .arg(&*part_l)
17782                        .arg(&mut **oq)
17783                        .arg(&mut **od)
17784                        .arg(&hd)
17785                        .arg(&nh)
17786                        .arg(bd)
17787                        .arg(&plus_g)
17788                        .arg(&nspm)
17789                        .arg(&spk);
17790                    unsafe {
17791                        b2.launch(cfg2)?;
17792                    }
17793                    continue;
17794                }
17795                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
17796                let __s_b2 = self.gpu.stream();
17797                let mut b2 = __s_b2.launch_builder(&fc);
17798                b2.arg(&*part_o)
17799                    .arg(&*part_m)
17800                    .arg(&*part_l)
17801                    .arg(&mut o_g)
17802                    .arg(&hd)
17803                    .arg(&nh)
17804                    .arg(bd)
17805                    .arg(&plus_g)
17806                    .arg(&nspm)
17807                    .arg(&spk);
17808                unsafe {
17809                    b2.launch(cfg2)?;
17810                }
17811            } else {
17812                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
17813                // leave the caller's pair unwritten (consumer would read garbage).
17814                assert!(
17815                    q8_out.is_none(),
17816                    "rows q8 emit requires the hd512 dc combine"
17817                );
17818                let fc = self.func("fa_decode_combine_rows");
17819                let __s_b2 = self.gpu.stream();
17820                let mut b2 = __s_b2.launch_builder(&fc);
17821                b2.arg(&*part_o)
17822                    .arg(&*part_m)
17823                    .arg(&*part_l)
17824                    .arg(&mut o_g)
17825                    .arg(&hd)
17826                    .arg(&nh)
17827                    .arg(&base_i)
17828                    .arg(&nspm)
17829                    .arg(&spk);
17830                unsafe {
17831                    b2.launch(cfg2)?;
17832                }
17833            }
17834        }
17835        Ok(())
17836    }
17837
17838    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
17839    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
17840    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
17841    #[allow(clippy::too_many_arguments)]
17842    pub fn fa_decode_rows_w(
17843        &self,
17844        q: &CudaSlice<f32>,
17845        k: &cudarc::driver::CudaView<u8>,
17846        v: &cudarc::driver::CudaView<u8>,
17847        o: &mut CudaSlice<f32>,
17848        head_dim: usize,
17849        n_head: usize,
17850        n_head_kv: usize,
17851        base_dev: &CudaSlice<i32>,
17852        base_plus: i32,
17853        t: usize,
17854        scale: f32,
17855        window: usize,
17856        k_tok_bytes: usize,
17857        v_tok_bytes: usize,
17858        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17859    ) -> Result<(), Box<dyn std::error::Error>> {
17860        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
17861        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
17862        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
17863        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
17864        debug_assert!(head_dim == 256);
17865        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
17866        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
17867        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
17868        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
17869        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
17870        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
17871        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
17872        let sp = {
17873            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17874            let v = *SPW.get_or_init(|| {
17875                std::env::var("MEMRA_FA_SPW")
17876                    .ok()
17877                    .and_then(|x| x.parse().ok())
17878                    .unwrap_or(0)
17879            });
17880            if v >= 8 {
17881                v
17882            } else {
17883                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17884            }
17885        };
17886        let n_splits_max = (window + sp - 1) / sp;
17887        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17888        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
17889        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17890        let gqa = (n_head / n_head_kv).max(1) as u32;
17891        let o_len = t * n_head * n_splits_max * head_dim;
17892        let ml_len = t * n_head * n_splits_max;
17893        let mut part_guard = self.fa_part_pool.lock().unwrap();
17894        if part_guard
17895            .as_ref()
17896            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17897            .unwrap_or(true)
17898        {
17899            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17900            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17901            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17902            // later live allocations land at those addresses, and the next graph REPLAY writes
17903            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17904            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17905            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17906            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17907            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17908            // (total retired < final size).
17909            let old = part_guard.take();
17910            let (co, cm) = old
17911                .as_ref()
17912                .map(|pp| (pp.0.len(), pp.1.len()))
17913                .unwrap_or((0, 0));
17914            if let Some(old) = old {
17915                self.fa_part_retired.lock().unwrap().push(old);
17916            }
17917            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17918                eprintln!(
17919                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17920                    co, o_len, cm, ml_len
17921                );
17922            }
17923            *part_guard = Some((
17924                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17925                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17926                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17927            ));
17928        }
17929        let pg = part_guard.as_mut().unwrap();
17930        self.gpu
17931            .stream()
17932            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17933        self.gpu
17934            .stream()
17935            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17936        self.gpu
17937            .stream()
17938            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17939        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17940        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
17941        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
17942        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
17943        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
17944        // floor (deep-ctx broadcast win); register twin between.
17945        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17946        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
17947            std::env::var("MEMRA_FA_SMEM_TKV")
17948                .ok()
17949                .and_then(|v| v.parse().ok())
17950                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17951        });
17952        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
17953        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
17954        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
17955        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
17956        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
17957        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17958        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
17959        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
17960        // per (lane, format-module) keeps parity structural; the old register-i2 detour
17961        // (-33%) is retired.
17962        let wg = Self::wkv_on();
17963        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
17964        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
17965        let sp2 =
17966            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
17967        if sp2 {
17968            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
17969            if Self::pdl_on() && Self::pdl_wb_on() {
17970                // wave-B2b: flavor mirrors wg.
17971                use cudarc::driver::{DevicePtr, DevicePtrMut};
17972                let s = &self.gpu.stream();
17973                let (pq, _b0) = q.device_ptr(s);
17974                let (pk, _b1) = k.device_ptr(s);
17975                let (pv, _b2) = v.device_ptr(s);
17976                let (po, _b3) = part_o.device_ptr_mut(s);
17977                let (pm, _b4) = part_m.device_ptr_mut(s);
17978                let (pl, _b5) = part_l.device_ptr_mut(s);
17979                let (pb, _b6) = base_dev.device_ptr(s);
17980                let mut ps = [
17981                    &pq as *const _ as *mut std::ffi::c_void,
17982                    &pk as *const _ as *mut _,
17983                    &pv as *const _ as *mut _,
17984                    &po as *const _ as *mut _,
17985                    &pm as *const _ as *mut _,
17986                    &pl as *const _ as *mut _,
17987                    &hd as *const _ as *mut _,
17988                    &nh as *const _ as *mut _,
17989                    &nhkv as *const _ as *mut _,
17990                    &pb as *const _ as *mut _,
17991                    &base_plus as *const _ as *mut _,
17992                    &scale as *const _ as *mut _,
17993                    &nspm as *const _ as *mut _,
17994                    &spk as *const _ as *mut _,
17995                    &ktb as *const _ as *mut _,
17996                    &vtb as *const _ as *mut _,
17997                    &wini as *const _ as *mut _,
17998                ];
17999                unsafe {
18000                    self.launch_pdl_flash(
18001                        wg,
18002                        "fa_decode_vec_q_rows_v4_w_sp",
18003                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18004                        (32, gqa + 1, 1),
18005                        sh,
18006                        &mut ps,
18007                    )?;
18008                }
18009            } else {
18010                let f = if wg {
18011                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
18012                } else {
18013                    self.func("fa_decode_vec_q_rows_v4_w_sp")
18014                };
18015                f.set_attribute(
18016                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18017                    sh as i32,
18018                )?;
18019                let cfg = LaunchConfig {
18020                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18021                    block_dim: (32, gqa + 1, 1),
18022                    shared_mem_bytes: sh,
18023                };
18024                let __s_b = self.gpu.stream();
18025                let mut b = __s_b.launch_builder(&f);
18026                b.arg(q)
18027                    .arg(k)
18028                    .arg(v)
18029                    .arg(&mut *part_o)
18030                    .arg(&mut *part_m)
18031                    .arg(&mut *part_l)
18032                    .arg(&hd)
18033                    .arg(&nh)
18034                    .arg(&nhkv)
18035                    .arg(base_dev)
18036                    .arg(&base_plus)
18037                    .arg(&scale)
18038                    .arg(&nspm)
18039                    .arg(&spk)
18040                    .arg(&ktb)
18041                    .arg(&vtb)
18042                    .arg(&wini);
18043                unsafe {
18044                    b.launch(cfg)?;
18045                }
18046            }
18047        } else {
18048            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
18049                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
18050                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18051                use cudarc::driver::{DevicePtr, DevicePtrMut};
18052                let s = &self.gpu.stream();
18053                let (pq, _b0) = q.device_ptr(s);
18054                let (pk, _b1) = k.device_ptr(s);
18055                let (pv, _b2) = v.device_ptr(s);
18056                let (po, _b3) = part_o.device_ptr_mut(s);
18057                let (pm, _b4) = part_m.device_ptr_mut(s);
18058                let (pl, _b5) = part_l.device_ptr_mut(s);
18059                let (pb, _b6) = base_dev.device_ptr(s);
18060                let mut ps = [
18061                    &pq as *const _ as *mut std::ffi::c_void,
18062                    &pk as *const _ as *mut _,
18063                    &pv as *const _ as *mut _,
18064                    &po as *const _ as *mut _,
18065                    &pm as *const _ as *mut _,
18066                    &pl as *const _ as *mut _,
18067                    &hd as *const _ as *mut _,
18068                    &nh as *const _ as *mut _,
18069                    &nhkv as *const _ as *mut _,
18070                    &pb as *const _ as *mut _,
18071                    &base_plus as *const _ as *mut _,
18072                    &scale as *const _ as *mut _,
18073                    &nspm as *const _ as *mut _,
18074                    &spk as *const _ as *mut _,
18075                    &ktb as *const _ as *mut _,
18076                    &vtb as *const _ as *mut _,
18077                    &wini as *const _ as *mut _,
18078                ];
18079                unsafe {
18080                    self.launch_pdl_flash(
18081                        wg,
18082                        "fa_decode_vec_q_rows_v4_w",
18083                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18084                        (32, gqa, 1),
18085                        sh,
18086                        &mut ps,
18087                    )?;
18088                }
18089            } else {
18090                let pick = |name: &str| {
18091                    if wg {
18092                        self.func_g(name)
18093                    } else {
18094                        self.func(name)
18095                    }
18096                };
18097                let (f, sh) = if fa_v4_at(window) {
18098                    let f = pick("fa_decode_vec_q_rows_v4_w");
18099                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
18100                } else if smem_tkv > 0 && window >= smem_tkv {
18101                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
18102                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
18103                    (
18104                        pick("fa_decode_vec_q_rows_smem_w"),
18105                        (2 * 32 * head_dim * 2) as u32,
18106                    )
18107                } else {
18108                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
18109                };
18110                f.set_attribute(
18111                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18112                    sh as i32,
18113                )?;
18114                let cfg = LaunchConfig {
18115                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18116                    block_dim: (32, gqa, 1),
18117                    shared_mem_bytes: sh,
18118                };
18119                let __s_b = self.gpu.stream();
18120                let mut b = __s_b.launch_builder(&f);
18121                b.arg(q)
18122                    .arg(k)
18123                    .arg(v)
18124                    .arg(&mut *part_o)
18125                    .arg(&mut *part_m)
18126                    .arg(&mut *part_l)
18127                    .arg(&hd)
18128                    .arg(&nh)
18129                    .arg(&nhkv)
18130                    .arg(base_dev)
18131                    .arg(&base_plus)
18132                    .arg(&scale)
18133                    .arg(&nspm)
18134                    .arg(&spk)
18135                    .arg(&ktb)
18136                    .arg(&vtb)
18137                    .arg(&wini);
18138                unsafe {
18139                    b.launch(cfg)?;
18140                }
18141            }
18142        }
18143        let cfg2 = LaunchConfig {
18144            grid_dim: (n_head as u32, t as u32, 1),
18145            block_dim: (head_dim as u32, 1, 1),
18146            shared_mem_bytes: 0,
18147        };
18148        if let Some((oq, od)) = q8_out {
18149            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
18150            // consumes the pair directly; the standalone quantize launch folds away.
18151            if Self::pdl_on() && Self::pdl_wb_on() {
18152                // wave-B2: flavor mirrors the builder's wg choice.
18153                use cudarc::driver::{DevicePtr, DevicePtrMut};
18154                let s = &self.gpu.stream();
18155                let (po, _g0) = part_o.device_ptr(s);
18156                let (pm, _g1) = part_m.device_ptr(s);
18157                let (pl, _g2) = part_l.device_ptr(s);
18158                let (pq, _g3) = oq.device_ptr_mut(s);
18159                let (pd, _g4) = od.device_ptr_mut(s);
18160                let mut ps = [
18161                    &po as *const _ as *mut std::ffi::c_void,
18162                    &pm as *const _ as *mut _,
18163                    &pl as *const _ as *mut _,
18164                    &pq as *const _ as *mut _,
18165                    &pd as *const _ as *mut _,
18166                    &hd as *const _ as *mut _,
18167                    &nh as *const _ as *mut _,
18168                    &nspm as *const _ as *mut _,
18169                    &spk as *const _ as *mut _,
18170                    &wini as *const _ as *mut _,
18171                ];
18172                unsafe {
18173                    self.launch_pdl_flash(
18174                        wg,
18175                        "fa_decode_combine_rows_w_q8_1",
18176                        cfg2.grid_dim,
18177                        cfg2.block_dim,
18178                        0,
18179                        &mut ps,
18180                    )?;
18181                }
18182                return Ok(());
18183            }
18184            let fc = if wg {
18185                self.func_g("fa_decode_combine_rows_w_q8_1")
18186            } else {
18187                self.func("fa_decode_combine_rows_w_q8_1")
18188            };
18189            let __s_b2 = self.gpu.stream();
18190            let mut b2 = __s_b2.launch_builder(&fc);
18191            b2.arg(&*part_o)
18192                .arg(&*part_m)
18193                .arg(&*part_l)
18194                .arg(oq)
18195                .arg(od)
18196                .arg(&hd)
18197                .arg(&nh)
18198                .arg(&nspm)
18199                .arg(&spk)
18200                .arg(&wini);
18201            unsafe {
18202                b2.launch(cfg2)?;
18203            }
18204            return Ok(());
18205        }
18206        let fc = if wg {
18207            self.func_g("fa_decode_combine_rows_w")
18208        } else {
18209            self.func("fa_decode_combine_rows_w")
18210        };
18211        let __s_b2 = self.gpu.stream();
18212        let mut b2 = __s_b2.launch_builder(&fc);
18213        b2.arg(&*part_o)
18214            .arg(&*part_m)
18215            .arg(&*part_l)
18216            .arg(o)
18217            .arg(&hd)
18218            .arg(&nh)
18219            .arg(&nspm)
18220            .arg(&spk)
18221            .arg(&wini);
18222        unsafe {
18223            b2.launch(cfg2)?;
18224        }
18225        Ok(())
18226    }
18227
18228    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
18229    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
18230    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
18231    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
18232    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
18233    #[allow(clippy::too_many_arguments)]
18234    pub fn fa_decode_rows_dc(
18235        &self,
18236        q: &CudaSlice<f32>,
18237        k: &cudarc::driver::CudaView<u8>,
18238        v: &cudarc::driver::CudaView<u8>,
18239        o: &mut CudaSlice<f32>,
18240        head_dim: usize,
18241        n_head: usize,
18242        n_head_kv: usize,
18243        base_dev: &CudaSlice<i32>,
18244        t_kv_upper: usize,
18245        t: usize,
18246        scale: f32,
18247        k_tok_bytes: usize,
18248        v_tok_bytes: usize,
18249        base_plus: i32,
18250        g: bool,
18251    ) -> Result<(), Box<dyn std::error::Error>> {
18252        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
18253        assert!(
18254            v4 || fa_v3_active(head_dim),
18255            "stream fa rows requires the v3 or v4 lane"
18256        );
18257        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
18258        if v4 {
18259            let sp = fa_split_keys(t_kv_upper, n_head_kv);
18260            let n_splits_max = (t_kv_upper + sp - 1) / sp;
18261            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18262            let (nspm, spk) = (n_splits_max as i32, sp as i32);
18263            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18264            let gqa = (n_head / n_head_kv).max(1) as u32;
18265            let o_len = t * n_head * n_splits_max * head_dim;
18266            let ml_len = t * n_head * n_splits_max;
18267            let mut part_guard = self.fa_part_pool.lock().unwrap();
18268            if part_guard
18269                .as_ref()
18270                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18271                .unwrap_or(true)
18272            {
18273                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18274                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18275                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18276                // later live allocations land at those addresses, and the next graph REPLAY writes
18277                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18278                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18279                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18280                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18281                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18282                // (total retired < final size).
18283                let old = part_guard.take();
18284                let (co, cm) = old
18285                    .as_ref()
18286                    .map(|pp| (pp.0.len(), pp.1.len()))
18287                    .unwrap_or((0, 0));
18288                if let Some(old) = old {
18289                    self.fa_part_retired.lock().unwrap().push(old);
18290                }
18291                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18292                    eprintln!(
18293                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18294                        co, o_len, cm, ml_len
18295                    );
18296                }
18297                *part_guard = Some((
18298                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18299                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18300                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18301                ));
18302            }
18303            let pg = part_guard.as_mut().unwrap();
18304            self.gpu
18305                .stream()
18306                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18307            self.gpu
18308                .stream()
18309                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18310            self.gpu
18311                .stream()
18312                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18313            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18314            let f = if g {
18315                self.func_g("fa_decode_vec_q_rows_v4_dc")
18316            } else {
18317                self.func("fa_decode_vec_q_rows_v4_dc")
18318            };
18319            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18320            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18321            f.set_attribute(
18322                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18323                sh as i32,
18324            )?;
18325            let cfg = LaunchConfig {
18326                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18327                block_dim: (32, gqa, 1),
18328                shared_mem_bytes: sh,
18329            };
18330            let __s_b = self.gpu.stream();
18331            let mut b = __s_b.launch_builder(&f);
18332            b.arg(q)
18333                .arg(k)
18334                .arg(v)
18335                .arg(&mut *part_o)
18336                .arg(&mut *part_m)
18337                .arg(&mut *part_l)
18338                .arg(&hd)
18339                .arg(&nh)
18340                .arg(&nhkv)
18341                .arg(base_dev)
18342                .arg(&base_plus)
18343                .arg(&scale)
18344                .arg(&nspm)
18345                .arg(&spk)
18346                .arg(&ktb)
18347                .arg(&vtb);
18348            unsafe {
18349                b.launch(cfg)?;
18350            }
18351            let fc = self.func("fa_decode_combine_rows_dc");
18352            let cfg2 = LaunchConfig {
18353                grid_dim: (n_head as u32, t as u32, 1),
18354                block_dim: (head_dim as u32, 1, 1),
18355                shared_mem_bytes: 0,
18356            };
18357            let __s_b2 = self.gpu.stream();
18358            let mut b2 = __s_b2.launch_builder(&fc);
18359            b2.arg(&*part_o)
18360                .arg(&*part_m)
18361                .arg(&*part_l)
18362                .arg(o)
18363                .arg(&hd)
18364                .arg(&nh)
18365                .arg(base_dev)
18366                .arg(&base_plus)
18367                .arg(&nspm)
18368                .arg(&spk);
18369            unsafe {
18370                b2.launch(cfg2)?;
18371            }
18372            return Ok(());
18373        }
18374        let sp = fa_split_keys(t_kv_upper, n_head_kv);
18375        let n_splits_max = (t_kv_upper + sp - 1) / sp;
18376        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18377        let (nspm, spk) = (n_splits_max as i32, sp as i32);
18378        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18379        let gqa = (n_head / n_head_kv).max(1) as u32;
18380        let o_len = t * n_head * n_splits_max * head_dim;
18381        let ml_len = t * n_head * n_splits_max;
18382        let mut part_guard = self.fa_part_pool.lock().unwrap();
18383        if part_guard
18384            .as_ref()
18385            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18386            .unwrap_or(true)
18387        {
18388            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18389            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18390            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18391            // later live allocations land at those addresses, and the next graph REPLAY writes
18392            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18393            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18394            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18395            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18396            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18397            // (total retired < final size).
18398            let old = part_guard.take();
18399            let (co, cm) = old
18400                .as_ref()
18401                .map(|pp| (pp.0.len(), pp.1.len()))
18402                .unwrap_or((0, 0));
18403            if let Some(old) = old {
18404                self.fa_part_retired.lock().unwrap().push(old);
18405            }
18406            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18407                eprintln!(
18408                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18409                    co, o_len, cm, ml_len
18410                );
18411            }
18412            *part_guard = Some((
18413                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18414                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18415                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18416            ));
18417        }
18418        let pg = part_guard.as_mut().unwrap();
18419        self.gpu
18420            .stream()
18421            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18422        self.gpu
18423            .stream()
18424            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18425        self.gpu
18426            .stream()
18427            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18428        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18429        let f = self.func("fa_decode_vec_q_rows_v3_dc");
18430        let sh = (32 * head_dim * 2) as u32;
18431        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18432        f.set_attribute(
18433            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18434            sh as i32,
18435        )?;
18436        let cfg = LaunchConfig {
18437            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18438            block_dim: (32, gqa, 1),
18439            shared_mem_bytes: sh,
18440        };
18441        let __s_b = self.gpu.stream();
18442        let mut b = __s_b.launch_builder(&f);
18443        b.arg(q)
18444            .arg(k)
18445            .arg(v)
18446            .arg(&mut *part_o)
18447            .arg(&mut *part_m)
18448            .arg(&mut *part_l)
18449            .arg(&hd)
18450            .arg(&nh)
18451            .arg(&nhkv)
18452            .arg(base_dev)
18453            .arg(&scale)
18454            .arg(&nspm)
18455            .arg(&spk)
18456            .arg(&ktb)
18457            .arg(&vtb);
18458        unsafe {
18459            b.launch(cfg)?;
18460        }
18461        let fc = self.func("fa_decode_combine_rows_dc");
18462        let cfg2 = LaunchConfig {
18463            grid_dim: (n_head as u32, t as u32, 1),
18464            block_dim: (head_dim as u32, 1, 1),
18465            shared_mem_bytes: 0,
18466        };
18467        let plus0 = 0i32;
18468        let __s_b2 = self.gpu.stream();
18469        let mut b2 = __s_b2.launch_builder(&fc);
18470        b2.arg(&*part_o)
18471            .arg(&*part_m)
18472            .arg(&*part_l)
18473            .arg(o)
18474            .arg(&hd)
18475            .arg(&nh)
18476            .arg(base_dev)
18477            .arg(&plus0)
18478            .arg(&nspm)
18479            .arg(&spk);
18480        unsafe {
18481            b2.launch(cfg2)?;
18482        }
18483        Ok(())
18484    }
18485
18486    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
18487    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
18488    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
18489    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
18490    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
18491    ///
18492    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
18493    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
18494    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
18495    /// grouping (different but mathematically-equal log-sum-exp merge).
18496    pub fn fa_decode_dc(
18497        &self,
18498        q: &CudaSlice<f32>,
18499        k: &cudarc::driver::CudaView<u8>,
18500        v: &cudarc::driver::CudaView<u8>,
18501        o: &mut CudaSlice<f32>,
18502        head_dim: usize,
18503        n_head: usize,
18504        n_head_kv: usize,
18505        t_kv_dev: &CudaSlice<i32>,
18506        bucket_max: usize,
18507        scale: f32,
18508        k_tok_bytes: usize,
18509        v_tok_bytes: usize,
18510        g: bool,
18511    ) -> Result<(), Box<dyn std::error::Error>> {
18512        self.fa_decode_dc_q8(
18513            q,
18514            k,
18515            v,
18516            o,
18517            head_dim,
18518            n_head,
18519            n_head_kv,
18520            t_kv_dev,
18521            bucket_max,
18522            scale,
18523            k_tok_bytes,
18524            v_tok_bytes,
18525            g,
18526            None,
18527        )
18528    }
18529
18530    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
18531    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
18532    #[allow(clippy::too_many_arguments)]
18533    pub fn fa_decode_dc_q8(
18534        &self,
18535        q: &CudaSlice<f32>,
18536        k: &cudarc::driver::CudaView<u8>,
18537        v: &cudarc::driver::CudaView<u8>,
18538        o: &mut CudaSlice<f32>,
18539        head_dim: usize,
18540        n_head: usize,
18541        n_head_kv: usize,
18542        t_kv_dev: &CudaSlice<i32>,
18543        bucket_max: usize,
18544        scale: f32,
18545        k_tok_bytes: usize,
18546        v_tok_bytes: usize,
18547        g: bool,
18548        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18549    ) -> Result<(), Box<dyn std::error::Error>> {
18550        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
18551        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
18552        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
18553        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
18554        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
18555        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
18556        // 2026-07-12).
18557        let mut fa_vec =
18558            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
18559        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
18560            fa_vec = false;
18561        } // mirror kvmod/geom
18562        let sp = fa_split_keys(bucket_max, n_head_kv);
18563        let n_splits = if fa_vec {
18564            ((bucket_max + sp - 1) / sp).max(1)
18565        } else {
18566            ((bucket_max + 255) / 256).max(1)
18567        };
18568        let o_len = n_head * n_splits * head_dim;
18569        let ml_len = n_head * n_splits;
18570        let mut part_guard = self.fa_part_pool.lock().unwrap();
18571        if part_guard
18572            .as_ref()
18573            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18574            .unwrap_or(true)
18575        {
18576            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18577            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18578            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18579            // later live allocations land at those addresses, and the next graph REPLAY writes
18580            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18581            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18582            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18583            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18584            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18585            // (total retired < final size).
18586            let old = part_guard.take();
18587            let (co, cm) = old
18588                .as_ref()
18589                .map(|pp| (pp.0.len(), pp.1.len()))
18590                .unwrap_or((0, 0));
18591            if let Some(old) = old {
18592                self.fa_part_retired.lock().unwrap().push(old);
18593            }
18594            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18595                eprintln!(
18596                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18597                    co, o_len, cm, ml_len
18598                );
18599            }
18600            *part_guard = Some((
18601                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18602                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18603                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18604            ));
18605        }
18606        let pg = part_guard.as_mut().unwrap();
18607        self.gpu
18608            .stream()
18609            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18610        self.gpu
18611            .stream()
18612            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18613        self.gpu
18614            .stream()
18615            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18616        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18617        let (hd, nh, nhkv, nsp) = (
18618            head_dim as i32,
18619            n_head as i32,
18620            n_head_kv as i32,
18621            n_splits as i32,
18622        );
18623        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18624        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
18625        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
18626        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
18627        let deep = fa_vec
18628            && head_dim == 256
18629            && fa_v4_at(bucket_max)
18630            && !g
18631            && fa_deep_at(bucket_max)
18632            && !matches!(fa_v4_mode(), "noB3" | "stage");
18633        let (f, cfg) = if fa_vec
18634            && head_dim == 512
18635            && bucket_max >= {
18636                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18637                *FA512_MIN_DC.get_or_init(|| {
18638                    std::env::var("MEMRA_FA512_MIN")
18639                        .ok()
18640                        .and_then(|v| v.parse().ok())
18641                        .unwrap_or(512)
18642                })
18643            } {
18644            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
18645            let gqa = (n_head / n_head_kv).max(1) as u32;
18646            (
18647                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
18648                LaunchConfig {
18649                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18650                    block_dim: (32, gqa, 1),
18651                    shared_mem_bytes: 0,
18652                },
18653            )
18654        } else if fa_vec && head_dim == 512 {
18655            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
18656            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
18657            let q_view = q.as_view();
18658            let mut o_view = o.as_view_mut();
18659            return self.fa_decode_scalar_unified(
18660                &q_view,
18661                k,
18662                v,
18663                &mut o_view,
18664                head_dim,
18665                n_head,
18666                n_head_kv,
18667                0,
18668                Some(t_kv_dev),
18669                scale,
18670                n_splits,
18671                sp,
18672                k_tok_bytes,
18673                v_tok_bytes,
18674                g,
18675                &mut *part_o,
18676                &mut *part_m,
18677                &mut *part_l,
18678                q8_out,
18679            );
18680        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
18681            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
18682            // incl the g-module route + raw-e4m3 sV sizing.
18683            let gqa = (n_head / n_head_kv).max(1) as u32;
18684            let fv = if g {
18685                self.func_g("fa_decode_vec_q_v4_dc")
18686            } else if deep {
18687                self.func("fa_decode_vec_q_v4_deep_dc")
18688            } else {
18689                self.func("fa_decode_vec_q_v4_dc")
18690            };
18691            let shmem =
18692                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18693            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18694            fv.set_attribute(
18695                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18696                shmem as i32,
18697            )?;
18698            (
18699                fv,
18700                LaunchConfig {
18701                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18702                    block_dim: (32, gqa, 1),
18703                    shared_mem_bytes: shmem,
18704                },
18705            )
18706        } else if fa_vec && fa_v3_active(head_dim) {
18707            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
18708            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
18709            let gqa = (n_head / n_head_kv).max(1) as u32;
18710            let fv = if g {
18711                self.func_g("fa_decode_vec_q_v3_dc")
18712            } else {
18713                self.func("fa_decode_vec_q_v3_dc")
18714            };
18715            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
18716            (
18717                fv,
18718                LaunchConfig {
18719                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18720                    block_dim: (32, gqa, 1),
18721                    shared_mem_bytes: shmem,
18722                },
18723            )
18724        } else if fa_vec && fa_v2_on() {
18725            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
18726            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
18727            // a numeric config; eager, rows-verify and graph all switch together).
18728            let gqa = (n_head / n_head_kv).max(1) as u32;
18729            let fv = if g {
18730                self.func_g("fa_decode_vec_q_v2_dc")
18731            } else {
18732                self.func("fa_decode_vec_q_v2_dc")
18733            };
18734            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
18735            (
18736                fv,
18737                LaunchConfig {
18738                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18739                    block_dim: (32, gqa, 1),
18740                    shared_mem_bytes: shmem,
18741                },
18742            )
18743        } else if fa_vec {
18744            let gqa = (n_head / n_head_kv).max(1) as u32;
18745            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
18746            let fv = if g {
18747                self.func_g("fa_decode_vec_q_dc")
18748            } else {
18749                self.func("fa_decode_vec_q_dc")
18750            };
18751            (
18752                fv,
18753                LaunchConfig {
18754                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18755                    block_dim: (32, gqa, 1),
18756                    shared_mem_bytes: 0,
18757                },
18758            )
18759        } else {
18760            let q_view = q.as_view();
18761            let mut o_view = o.as_view_mut();
18762            return self.fa_decode_scalar_unified(
18763                &q_view,
18764                k,
18765                v,
18766                &mut o_view,
18767                head_dim,
18768                n_head,
18769                n_head_kv,
18770                0,
18771                Some(t_kv_dev),
18772                scale,
18773                n_splits,
18774                if fa_vec { sp } else { 256 },
18775                k_tok_bytes,
18776                v_tok_bytes,
18777                g,
18778                &mut *part_o,
18779                &mut *part_m,
18780                &mut *part_l,
18781                q8_out,
18782            );
18783        };
18784        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
18785        let __s_b = self.gpu.stream();
18786        let mut b = __s_b.launch_builder(&f);
18787        b.arg(q)
18788            .arg(k)
18789            .arg(v)
18790            .arg(&mut *part_o)
18791            .arg(&mut *part_m)
18792            .arg(&mut *part_l)
18793            .arg(&hd)
18794            .arg(&nh)
18795            .arg(&nhkv)
18796            .arg(t_kv_dev)
18797            .arg(&scale)
18798            .arg(&nsp)
18799            .arg(&ski)
18800            .arg(&ktb)
18801            .arg(&vtb);
18802        unsafe {
18803            b.launch(cfg)?;
18804        }
18805        let cfg2 = LaunchConfig {
18806            grid_dim: (n_head as u32, 1, 1),
18807            block_dim: (head_dim as u32, 1, 1),
18808            shared_mem_bytes: 0,
18809        };
18810        if let Some((oq, od)) = q8_out {
18811            let fc = if g {
18812                self.func_g("fa_decode_combine_q8_1")
18813            } else {
18814                self.fa_func("fa_decode_combine_q8_1", head_dim)
18815            };
18816            let __s_b2 = self.gpu.stream();
18817            let mut b2 = __s_b2.launch_builder(&fc);
18818            b2.arg(&*part_o)
18819                .arg(&*part_m)
18820                .arg(&*part_l)
18821                .arg(oq)
18822                .arg(od)
18823                .arg(&hd)
18824                .arg(&nh)
18825                .arg(&nsp);
18826            unsafe {
18827                b2.launch(cfg2)?;
18828            }
18829            return Ok(());
18830        }
18831        let fc = if g {
18832            self.func_g("fa_decode_combine_f32")
18833        } else {
18834            self.fa_func("fa_decode_combine_f32", head_dim)
18835        };
18836        let __s_b2 = self.gpu.stream();
18837        let mut b2 = __s_b2.launch_builder(&fc);
18838        b2.arg(&*part_o)
18839            .arg(&*part_m)
18840            .arg(&*part_l)
18841            .arg(o)
18842            .arg(&hd)
18843            .arg(&nh)
18844            .arg(&nsp);
18845        unsafe {
18846            b2.launch(cfg2)?;
18847        }
18848        Ok(())
18849    }
18850
18851    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
18852    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
18853    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
18854    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
18855    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
18856    pub fn fa_geom_eager(
18857        &self,
18858        t_kv: usize,
18859        head_dim: usize,
18860        n_head_kv: usize,
18861        g: bool,
18862    ) -> (bool, usize) {
18863        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
18864        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
18865        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
18866        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
18867        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
18868        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
18869        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
18870        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
18871        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
18872        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
18873        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
18874        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
18875        // family; everything else falls to the g-module scalar.
18876        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
18877        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
18878        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
18879        if g && head_dim == 256 && !fa_v4_at(t_kv) {
18880            fa_vec = false;
18881        }
18882        let sp = fa_split_keys(t_kv, n_head_kv);
18883        let n_splits = if fa_vec {
18884            ((t_kv + sp - 1) / sp).max(1)
18885        } else {
18886            ((t_kv + 255) / 256).max(1)
18887        };
18888        (fa_vec, n_splits)
18889    }
18890
18891    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
18892    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
18893    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
18894    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
18895    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
18896    pub fn fa_bucket_key(
18897        &self,
18898        t_kv: usize,
18899        head_dim: usize,
18900        n_head_kv: usize,
18901        g: bool,
18902    ) -> (bool, usize) {
18903        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
18904    }
18905
18906    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
18907    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
18908    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
18909    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
18910    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
18911    /// device data) — every per-step varying scalar must come from a device counter. Returns the
18912    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
18913    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
18914    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
18915    /// replays (transients returning to the pool get reused by unrelated work and corrupt
18916    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
18917    pub fn capture_graph_retained<F>(
18918        &self,
18919        step: F,
18920    ) -> Result<
18921        (
18922            cudarc::driver::CudaGraph,
18923            Vec<Box<dyn std::any::Any + Send>>,
18924        ),
18925        Box<dyn std::error::Error>,
18926    >
18927    where
18928        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18929    {
18930        use cudarc::driver::sys::CUgraphInstantiate_flags;
18931        self.capture_graph_retained_flags(
18932            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
18933            step,
18934        )
18935    }
18936
18937    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
18938    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
18939    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
18940    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
18941    pub fn capture_graph_retained_flags<F>(
18942        &self,
18943        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
18944        mut step: F,
18945    ) -> Result<
18946        (
18947            cudarc::driver::CudaGraph,
18948            Vec<Box<dyn std::any::Any + Send>>,
18949        ),
18950        Box<dyn std::error::Error>,
18951    >
18952    where
18953        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18954    {
18955        use cudarc::driver::sys::CUstreamCaptureMode;
18956        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
18957        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
18958        // while the capture region is open become dead copy NODES replayed every launch
18959        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
18960        // warmup runs allocate the same transient sequence at the same pool addresses, so
18961        // retaining the warmup clones preserves the draft-graph fix without polluting the
18962        // captured graph.
18963        self.capture_keep.lock().unwrap().clear();
18964        let was_tracking = self.gpu.ctx.is_event_tracking();
18965        if was_tracking {
18966            unsafe {
18967                self.gpu.ctx.disable_event_tracking();
18968            }
18969        }
18970        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
18971            self.capture_keep_on
18972                .store(true, std::sync::atomic::Ordering::Relaxed);
18973            let w = (|| {
18974                step(self)?;
18975                step(self)
18976            })();
18977            self.capture_keep_on
18978                .store(false, std::sync::atomic::Ordering::Relaxed);
18979            w?;
18980            self.gpu.stream().synchronize()?;
18981            self.gpu
18982                .stream()
18983                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
18984            let r = step(self);
18985            let g = self.gpu.stream().end_capture(flags);
18986            r?;
18987            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
18988            graph.upload()?;
18989            Ok(graph)
18990        };
18991        let result = run();
18992        self.capture_keep_on
18993            .store(false, std::sync::atomic::Ordering::Relaxed);
18994        if was_tracking {
18995            unsafe {
18996                self.gpu.ctx.enable_event_tracking();
18997            }
18998        }
18999        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
19000        Ok((result?, keeper))
19001    }
19002
19003    pub fn capture_graph<F>(
19004        &self,
19005        mut step: F,
19006    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
19007    where
19008        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19009    {
19010        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
19011        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
19012        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
19013        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
19014        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
19015        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
19016        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
19017        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
19018        let was_tracking = self.gpu.ctx.is_event_tracking();
19019        if was_tracking {
19020            unsafe {
19021                self.gpu.ctx.disable_event_tracking();
19022            }
19023        }
19024        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
19025        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
19026        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
19027        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
19028        // measure that scan's real cost on the generic path. Diagnostic door only; the
19029        // default stays AUTO_FREE until a measured A/B justifies moving it.
19030        let iflag = {
19031            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
19032            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
19033                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
19034                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
19035                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
19036                Ok("priority") => {
19037                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
19038                }
19039                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19040            })
19041        };
19042        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
19043        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
19044        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
19045        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
19046        // eager step executions and are node-count-invariant. Printing the split bounds the
19047        // refactor's ceiling instead of assuming it.
19048        let ct = {
19049            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19050            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
19051        };
19052        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
19053        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
19054        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
19055        // chased, and node-count-invariant, so no capture-body refactor could touch it.
19056        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
19057        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
19058        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
19059        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
19060        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
19061        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
19062        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
19063        // grow and never frees, resident counters/scratch, cache set in place), and the
19064        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
19065        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
19066        // settling and pool mapping. Arbitrated adversarially, not by taste:
19067        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
19068        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
19069        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
19070        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
19071        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
19072        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
19073        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
19074        let warmups = {
19075            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19076            *W.get_or_init(|| {
19077                std::env::var("MEMRA_GRAPH_WARMUPS")
19078                    .ok()
19079                    .and_then(|v| v.parse().ok())
19080                    .filter(|n| *n >= 1)
19081                    .unwrap_or(1)
19082            })
19083        };
19084        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19085            let t_w = std::time::Instant::now();
19086            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
19087            for _ in 0..warmups {
19088                step(self)?;
19089            }
19090            self.gpu.stream().synchronize()?;
19091            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
19092            // capture the third run.
19093            let t_c = std::time::Instant::now();
19094            self.gpu
19095                .stream()
19096                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19097            // If the body errors mid-capture, end the capture before propagating so the stream isn't
19098            // left in a capturing state.
19099            let r = step(self);
19100            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
19101            let t_i = std::time::Instant::now();
19102            let g = self.gpu.stream().end_capture(iflag);
19103            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
19104            r?;
19105            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19106            let t_u = std::time::Instant::now();
19107            graph.upload()?;
19108            if ct {
19109                println!(
19110                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
19111                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
19112                    t_u.elapsed().as_secs_f64() * 1e3
19113                );
19114            }
19115            Ok(graph)
19116        };
19117        let result = run();
19118        if was_tracking {
19119            unsafe {
19120                self.gpu.ctx.enable_event_tracking();
19121            }
19122        }
19123        result
19124    }
19125
19126    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
19127    pub fn gdn_scan_s128_view(
19128        &self,
19129        q: &CudaSlice<f32>,
19130        k: &CudaSlice<f32>,
19131        v: &CudaSlice<f32>,
19132        g: &CudaSlice<f32>,
19133        beta: &CudaSlice<f32>,
19134        state_in: &cudarc::driver::CudaView<f32>,
19135        state_out: &mut cudarc::driver::CudaViewMut<f32>,
19136        o: &mut CudaSlice<f32>,
19137        n_head: usize,
19138        t: usize,
19139        scale: f32,
19140    ) -> Result<(), Box<dyn std::error::Error>> {
19141        let f = self.func("gdn_scan_s128");
19142        const S_V: u32 = 128;
19143        const WARP: u32 = 32;
19144        const COLS: u32 = 4;
19145        let cfg = LaunchConfig {
19146            grid_dim: (n_head as u32, 1, S_V / COLS),
19147            block_dim: (WARP, COLS, 1),
19148            shared_mem_bytes: 0,
19149        };
19150        let (h, ti) = (n_head as i32, t as i32);
19151        let __s_b = self.gpu.stream();
19152        let mut b = __s_b.launch_builder(&f);
19153        b.arg(q)
19154            .arg(k)
19155            .arg(v)
19156            .arg(g)
19157            .arg(beta)
19158            .arg(state_in)
19159            .arg(state_out)
19160            .arg(o)
19161            .arg(&h)
19162            .arg(&ti)
19163            .arg(&scale);
19164        unsafe {
19165            b.launch(cfg)?;
19166        }
19167        Ok(())
19168    }
19169
19170    /// conv1d where the input is a CudaView (resident conv state assembled in place).
19171    pub fn ssm_conv1d_view(
19172        &self,
19173        x: &cudarc::driver::CudaView<f32>,
19174        w: &CudaSlice<f32>,
19175        y: &mut CudaSlice<f32>,
19176        conv_dim: usize,
19177        t: usize,
19178        d_conv: usize,
19179        silu: bool,
19180    ) -> Result<(), Box<dyn std::error::Error>> {
19181        let f = self.func("ssm_conv1d_silu_f32");
19182        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
19183        let cfg = LaunchConfig {
19184            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19185            block_dim: (256, 1, 1),
19186            shared_mem_bytes: 0,
19187        };
19188        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19189        let __s_b = self.gpu.stream();
19190        let mut b = __s_b.launch_builder(&f);
19191        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19192        unsafe {
19193            b.launch(cfg)?;
19194        }
19195        Ok(())
19196    }
19197
19198    /// Depthwise causal conv1d + optional SiLU.
19199    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
19200    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
19201    /// FUSED prefill conv (token-major input, zero left-state): replaces
19202    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
19203    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
19204    pub fn ssm_conv1d_tm(
19205        &self,
19206        qkv_tm: &CudaSlice<f32>,
19207        w: &CudaSlice<f32>,
19208        y: &mut CudaSlice<f32>,
19209        conv_dim: usize,
19210        t: usize,
19211        d_conv: usize,
19212    ) -> Result<(), Box<dyn std::error::Error>> {
19213        let f = self.func("ssm_conv1d_tm_f32");
19214        let cfg = LaunchConfig {
19215            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19216            block_dim: (256, 1, 1),
19217            shared_mem_bytes: 0,
19218        };
19219        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19220        let __s_b = self.gpu.stream();
19221        let mut b = __s_b.launch_builder(&f);
19222        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
19223        unsafe {
19224            b.launch(cfg)?;
19225        }
19226        Ok(())
19227    }
19228
19229    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
19230    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
19231    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
19232    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
19233    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
19234    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
19235    /// columns; the final ring == what T sequential decode ring rolls leave).
19236    pub fn ssm_conv1d_tm_state(
19237        &self,
19238        qkv_tm: &CudaSlice<f32>,
19239        conv_state: &mut CudaSlice<f32>,
19240        w: &CudaSlice<f32>,
19241        y: &mut CudaSlice<f32>,
19242        conv_dim: usize,
19243        t: usize,
19244        d_conv: usize,
19245    ) -> Result<(), Box<dyn std::error::Error>> {
19246        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
19247    }
19248
19249    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
19250    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
19251    #[allow(clippy::too_many_arguments)]
19252    pub fn ssm_conv1d_tm_state_pad(
19253        &self,
19254        qkv_tm: &CudaSlice<f32>,
19255        conv_state: &mut CudaSlice<f32>,
19256        w: &CudaSlice<f32>,
19257        y: &mut CudaSlice<f32>,
19258        conv_dim: usize,
19259        t: usize,
19260        d_conv: usize,
19261        pad_len: Option<&CudaSlice<i32>>,
19262    ) -> Result<(), Box<dyn std::error::Error>> {
19263        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19264        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19265        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19266        // cloning first keeps the ordering trivially correct under any future stream split.
19267        let ring_old = if t < d_conv - 1 {
19268            Some(self.clone_dtod(conv_state)?)
19269        } else {
19270            None
19271        };
19272        {
19273            let f = self.func("ssm_conv1d_tm_state_f32");
19274            let cfg = LaunchConfig {
19275                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19276                block_dim: (256, 1, 1),
19277                shared_mem_bytes: 0,
19278            };
19279            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19280            let __s_b = self.gpu.stream();
19281            let mut b = __s_b.launch_builder(&f);
19282            b.arg(qkv_tm)
19283                .arg(&*conv_state)
19284                .arg(w)
19285                .arg(y)
19286                .arg(&cd)
19287                .arg(&ti)
19288                .arg(&dc);
19289            unsafe {
19290                b.launch(cfg)?;
19291            }
19292        }
19293        match (ring_old, pad_len) {
19294            (None, Some(len_d)) => {
19295                let f = self.func("ssm_conv_ring_update_dev_f32");
19296                let n = conv_dim * (d_conv - 1);
19297                let cfg = LaunchConfig::for_num_elems(n as u32);
19298                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19299                let __s_b = self.gpu.stream();
19300                let mut b = __s_b.launch_builder(&f);
19301                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19302                unsafe {
19303                    b.launch(cfg)?;
19304                }
19305            }
19306            (None, None) => {
19307                let f = self.func("ssm_conv_ring_update_f32");
19308                let n = conv_dim * (d_conv - 1);
19309                let cfg = LaunchConfig::for_num_elems(n as u32);
19310                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19311                let __s_b = self.gpu.stream();
19312                let mut b = __s_b.launch_builder(&f);
19313                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19314                unsafe {
19315                    b.launch(cfg)?;
19316                }
19317            }
19318            (Some(old), _) => {
19319                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
19320            }
19321        }
19322        Ok(())
19323    }
19324
19325    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
19326    pub fn ssm_conv1d_tm_state_pad_v(
19327        &self,
19328        qkv_tm: &cudarc::driver::CudaView<f32>,
19329        conv_state: &mut CudaSlice<f32>,
19330        w: &CudaSlice<f32>,
19331        y: &mut CudaSlice<f32>,
19332        conv_dim: usize,
19333        t: usize,
19334        d_conv: usize,
19335        pad_len: Option<&CudaSlice<i32>>,
19336    ) -> Result<(), Box<dyn std::error::Error>> {
19337        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19338        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19339        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19340        // cloning first keeps the ordering trivially correct under any future stream split.
19341        let ring_old = if t < d_conv - 1 {
19342            Some(self.clone_dtod(conv_state)?)
19343        } else {
19344            None
19345        };
19346        {
19347            let f = self.func("ssm_conv1d_tm_state_f32");
19348            let cfg = LaunchConfig {
19349                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19350                block_dim: (256, 1, 1),
19351                shared_mem_bytes: 0,
19352            };
19353            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19354            let __s_b = self.gpu.stream();
19355            let mut b = __s_b.launch_builder(&f);
19356            b.arg(qkv_tm)
19357                .arg(&*conv_state)
19358                .arg(w)
19359                .arg(y)
19360                .arg(&cd)
19361                .arg(&ti)
19362                .arg(&dc);
19363            unsafe {
19364                b.launch(cfg)?;
19365            }
19366        }
19367        match (ring_old, pad_len) {
19368            (None, Some(len_d)) => {
19369                let f = self.func("ssm_conv_ring_update_dev_f32");
19370                let n = conv_dim * (d_conv - 1);
19371                let cfg = LaunchConfig::for_num_elems(n as u32);
19372                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19373                let __s_b = self.gpu.stream();
19374                let mut b = __s_b.launch_builder(&f);
19375                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19376                unsafe {
19377                    b.launch(cfg)?;
19378                }
19379            }
19380            (None, None) => {
19381                let f = self.func("ssm_conv_ring_update_f32");
19382                let n = conv_dim * (d_conv - 1);
19383                let cfg = LaunchConfig::for_num_elems(n as u32);
19384                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19385                let __s_b = self.gpu.stream();
19386                let mut b = __s_b.launch_builder(&f);
19387                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19388                unsafe {
19389                    b.launch(cfg)?;
19390                }
19391            }
19392            (Some(_), _) => unreachable!(
19393                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
19394            ),
19395        }
19396        Ok(())
19397    }
19398
19399    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
19400    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
19401    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
19402    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
19403    pub fn ssm_conv_ring_rebuild(
19404        &self,
19405        qkv_tm: &CudaSlice<f32>,
19406        ring_old: &CudaSlice<f32>,
19407        conv_state: &mut CudaSlice<f32>,
19408        conv_dim: usize,
19409        tc: usize,
19410        d_conv: usize,
19411    ) -> Result<(), Box<dyn std::error::Error>> {
19412        let f = self.func("ssm_conv_ring_rebuild_f32");
19413        let n = conv_dim * (d_conv - 1);
19414        let cfg = LaunchConfig::for_num_elems(n as u32);
19415        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
19416        let __s_b = self.gpu.stream();
19417        let mut b = __s_b.launch_builder(&f);
19418        b.arg(qkv_tm)
19419            .arg(ring_old)
19420            .arg(conv_state)
19421            .arg(&cd)
19422            .arg(&ti)
19423            .arg(&dc);
19424        unsafe {
19425            b.launch(cfg)?;
19426        }
19427        Ok(())
19428    }
19429
19430    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
19431    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
19432    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
19433    /// the argmax + run-spec gates are the authority.
19434    #[allow(clippy::too_many_arguments)]
19435    pub fn gdn_prep_decode(
19436        &self,
19437        conv_out: &CudaSlice<f32>,
19438        beta_raw: &CudaSlice<f32>,
19439        alpha: &CudaSlice<f32>,
19440        dt_bias: &CudaSlice<f32>,
19441        a: &CudaSlice<f32>,
19442        q_l2: &mut CudaSlice<f32>,
19443        k_l2: &mut CudaSlice<f32>,
19444        v_g: &mut CudaSlice<f32>,
19445        beta: &mut CudaSlice<f32>,
19446        g_log: &mut CudaSlice<f32>,
19447        d_state: usize,
19448        num_v: usize,
19449        num_k: usize,
19450        key_dim: usize,
19451        eps: f32,
19452    ) -> Result<(), Box<dyn std::error::Error>> {
19453        let f = self.func("gdn_prep_decode_f32");
19454        let cfg = LaunchConfig {
19455            grid_dim: (num_v as u32, 1, 1),
19456            block_dim: (32, 4, 1),
19457            shared_mem_bytes: 0,
19458        };
19459        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19460        let __s_b = self.gpu.stream();
19461        let mut b = __s_b.launch_builder(&f);
19462        b.arg(conv_out)
19463            .arg(beta_raw)
19464            .arg(alpha)
19465            .arg(dt_bias)
19466            .arg(a)
19467            .arg(q_l2)
19468            .arg(k_l2)
19469            .arg(v_g)
19470            .arg(beta)
19471            .arg(g_log)
19472            .arg(&ds)
19473            .arg(&nv)
19474            .arg(&nk)
19475            .arg(&kd)
19476            .arg(&eps);
19477        unsafe {
19478            b.launch(cfg)?;
19479        }
19480        Ok(())
19481    }
19482
19483    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
19484    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
19485    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
19486    #[allow(clippy::too_many_arguments)]
19487    pub fn ssm_conv1d_gdn(
19488        &self,
19489        qkv_tm: &CudaSlice<f32>,
19490        w: &CudaSlice<f32>,
19491        q_g: &mut CudaSlice<f32>,
19492        k_g: &mut CudaSlice<f32>,
19493        v_g: &mut CudaSlice<f32>,
19494        conv_dim: usize,
19495        t: usize,
19496        d_conv: usize,
19497        d_state: usize,
19498        num_v: usize,
19499        num_k: usize,
19500        key_dim: usize,
19501    ) -> Result<(), Box<dyn std::error::Error>> {
19502        let f = self.func("ssm_conv1d_gdn_f32");
19503        let cfg = LaunchConfig {
19504            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19505            block_dim: (256, 1, 1),
19506            shared_mem_bytes: 0,
19507        };
19508        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19509        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19510        let __s_b = self.gpu.stream();
19511        let mut b = __s_b.launch_builder(&f);
19512        b.arg(qkv_tm)
19513            .arg(w)
19514            .arg(q_g)
19515            .arg(k_g)
19516            .arg(v_g)
19517            .arg(&cd)
19518            .arg(&ti)
19519            .arg(&dc)
19520            .arg(&ds)
19521            .arg(&nv)
19522            .arg(&nk)
19523            .arg(&kd);
19524        unsafe {
19525            b.launch(cfg)?;
19526        }
19527        Ok(())
19528    }
19529
19530    pub fn ssm_conv1d(
19531        &self,
19532        x: &CudaSlice<f32>,
19533        w: &CudaSlice<f32>,
19534        y: &mut CudaSlice<f32>,
19535        conv_dim: usize,
19536        t: usize,
19537        d_conv: usize,
19538        silu: bool,
19539    ) -> Result<(), Box<dyn std::error::Error>> {
19540        let f = self.func("ssm_conv1d_silu_f32");
19541        let cfg = LaunchConfig {
19542            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19543            block_dim: (256, 1, 1),
19544            shared_mem_bytes: 0,
19545        };
19546        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19547        let __s_b = self.gpu.stream();
19548        let mut b = __s_b.launch_builder(&f);
19549        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19550        unsafe {
19551            b.launch(cfg)?;
19552        }
19553        Ok(())
19554    }
19555
19556    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
19557    /// o:[128,H,T]. Single sequence.
19558    pub fn gdn_scan_s128(
19559        &self,
19560        q: &CudaSlice<f32>,
19561        k: &CudaSlice<f32>,
19562        v: &CudaSlice<f32>,
19563        g: &CudaSlice<f32>,
19564        beta: &CudaSlice<f32>,
19565        state_in: &CudaSlice<f32>,
19566        state_out: &mut CudaSlice<f32>,
19567        o: &mut CudaSlice<f32>,
19568        n_head: usize,
19569        t: usize,
19570        scale: f32,
19571    ) -> Result<(), Box<dyn std::error::Error>> {
19572        let f = self.func("gdn_scan_s128");
19573        const S_V: u32 = 128;
19574        const WARP: u32 = 32;
19575        const COLS_PER_BLOCK: u32 = 4;
19576        let cfg = LaunchConfig {
19577            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
19578            block_dim: (WARP, COLS_PER_BLOCK, 1),
19579            shared_mem_bytes: 0,
19580        };
19581        let (h, ti) = (n_head as i32, t as i32);
19582        let __s_b = self.gpu.stream();
19583        let mut b = __s_b.launch_builder(&f);
19584        b.arg(q)
19585            .arg(k)
19586            .arg(v)
19587            .arg(g)
19588            .arg(beta)
19589            .arg(state_in)
19590            .arg(state_out)
19591            .arg(o)
19592            .arg(&h)
19593            .arg(&ti)
19594            .arg(&scale);
19595        unsafe {
19596            b.launch(cfg)?;
19597        }
19598        Ok(())
19599    }
19600
19601    // ==== B2' batched decode state ops (decode_batch.rs) ====
19602    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
19603    // Bodies are the single-seq kernels per sequence — bit-identical per row.
19604
19605    #[allow(clippy::too_many_arguments)]
19606    pub fn ssm_conv1d_fused_decode_b(
19607        &self,
19608        qkv_cols: &CudaSlice<f32>,
19609        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
19610        w: &CudaSlice<f32>,
19611        conv_outs: &mut CudaSlice<f32>,
19612        conv_dim: usize,
19613        d_conv: usize,
19614        b_n: usize,
19615    ) -> Result<(), Box<dyn std::error::Error>> {
19616        let f = self.func("ssm_conv1d_fused_decode_b_f32");
19617        let cfg = LaunchConfig {
19618            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
19619            block_dim: (256, 1, 1),
19620            shared_mem_bytes: 0,
19621        };
19622        let (cd, dc) = (conv_dim as i32, d_conv as i32);
19623        let __s_b = self.gpu.stream();
19624        let mut b = __s_b.launch_builder(&f);
19625        b.arg(qkv_cols)
19626            .arg(conv_state_ptrs)
19627            .arg(w)
19628            .arg(conv_outs)
19629            .arg(&cd)
19630            .arg(&dc);
19631        unsafe {
19632            b.launch(cfg)?;
19633        }
19634        Ok(())
19635    }
19636
19637    #[allow(clippy::too_many_arguments)]
19638    pub fn gdn_prep_decode_b(
19639        &self,
19640        conv_outs: &CudaSlice<f32>,
19641        beta_raws: &CudaSlice<f32>,
19642        alphas: &CudaSlice<f32>,
19643        dt_bias: &CudaSlice<f32>,
19644        a: &CudaSlice<f32>,
19645        q_l2: &mut CudaSlice<f32>,
19646        k_l2: &mut CudaSlice<f32>,
19647        v_g: &mut CudaSlice<f32>,
19648        beta: &mut CudaSlice<f32>,
19649        g_log: &mut CudaSlice<f32>,
19650        d_state: usize,
19651        num_v: usize,
19652        num_k: usize,
19653        key_dim: usize,
19654        eps: f32,
19655        conv_dim: usize,
19656        b_n: usize,
19657    ) -> Result<(), Box<dyn std::error::Error>> {
19658        let f = self.func("gdn_prep_decode_b_f32");
19659        let cfg = LaunchConfig {
19660            grid_dim: (num_v as u32, 1, b_n as u32),
19661            block_dim: (32, 4, 1),
19662            shared_mem_bytes: 0,
19663        };
19664        let (ds, nv, nk, kd, cd) = (
19665            d_state as i32,
19666            num_v as i32,
19667            num_k as i32,
19668            key_dim as i32,
19669            conv_dim as i32,
19670        );
19671        let __s_b = self.gpu.stream();
19672        let mut b = __s_b.launch_builder(&f);
19673        b.arg(conv_outs)
19674            .arg(beta_raws)
19675            .arg(alphas)
19676            .arg(dt_bias)
19677            .arg(a)
19678            .arg(q_l2)
19679            .arg(k_l2)
19680            .arg(v_g)
19681            .arg(beta)
19682            .arg(g_log)
19683            .arg(&ds)
19684            .arg(&nv)
19685            .arg(&nk)
19686            .arg(&kd)
19687            .arg(&eps)
19688            .arg(&cd);
19689        unsafe {
19690            b.launch(cfg)?;
19691        }
19692        Ok(())
19693    }
19694
19695    #[allow(clippy::too_many_arguments)]
19696    pub fn gdn_scan_s128_batched(
19697        &self,
19698        q: &CudaSlice<f32>,
19699        k: &CudaSlice<f32>,
19700        v: &CudaSlice<f32>,
19701        g: &CudaSlice<f32>,
19702        beta: &CudaSlice<f32>,
19703        state_in_ptrs: &cudarc::driver::CudaView<u64>,
19704        state_out_ptrs: &cudarc::driver::CudaView<u64>,
19705        o: &mut CudaSlice<f32>,
19706        n_head: usize,
19707        b_n: usize,
19708        scale: f32,
19709    ) -> Result<(), Box<dyn std::error::Error>> {
19710        let f = self.func("gdn_scan_s128_b");
19711        const S_V: u32 = 128;
19712        const WARP: u32 = 32;
19713        const COLS_PER_BLOCK: u32 = 4;
19714        let cfg = LaunchConfig {
19715            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
19716            block_dim: (WARP, COLS_PER_BLOCK, 1),
19717            shared_mem_bytes: 0,
19718        };
19719        let h = n_head as i32;
19720        let __s_b = self.gpu.stream();
19721        let mut b = __s_b.launch_builder(&f);
19722        b.arg(q)
19723            .arg(k)
19724            .arg(v)
19725            .arg(g)
19726            .arg(beta)
19727            .arg(state_in_ptrs)
19728            .arg(state_out_ptrs)
19729            .arg(o)
19730            .arg(&h)
19731            .arg(&scale);
19732        unsafe {
19733            b.launch(cfg)?;
19734        }
19735        Ok(())
19736    }
19737
19738    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
19739    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
19740    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
19741    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
19742    /// numeric class; only the pointer arithmetic moved host-side.
19743    #[allow(clippy::too_many_arguments)]
19744    pub fn ssm_conv1d_fused_decode_b_view(
19745        &self,
19746        qkv_cols: &cudarc::driver::CudaView<f32>,
19747        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
19748        w: &CudaSlice<f32>,
19749        conv_outs: &mut CudaSlice<f32>,
19750        conv_dim: usize,
19751        d_conv: usize,
19752        b_n: usize,
19753    ) -> Result<(), Box<dyn std::error::Error>> {
19754        let f = self.func("ssm_conv1d_fused_decode_b_f32");
19755        let cfg = LaunchConfig {
19756            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
19757            block_dim: (256, 1, 1),
19758            shared_mem_bytes: 0,
19759        };
19760        let (cd, dc) = (conv_dim as i32, d_conv as i32);
19761        let __s_b = self.gpu.stream();
19762        let mut b = __s_b.launch_builder(&f);
19763        b.arg(qkv_cols)
19764            .arg(conv_state_ptrs)
19765            .arg(w)
19766            .arg(conv_outs)
19767            .arg(&cd)
19768            .arg(&dc);
19769        unsafe {
19770            b.launch(cfg)?;
19771        }
19772        Ok(())
19773    }
19774
19775    #[allow(clippy::too_many_arguments)]
19776    pub fn gdn_prep_decode_b_view(
19777        &self,
19778        conv_outs: &CudaSlice<f32>,
19779        beta_raws: &cudarc::driver::CudaView<f32>,
19780        alphas: &cudarc::driver::CudaView<f32>,
19781        dt_bias: &CudaSlice<f32>,
19782        a: &CudaSlice<f32>,
19783        q_l2: &mut CudaSlice<f32>,
19784        k_l2: &mut CudaSlice<f32>,
19785        v_g: &mut CudaSlice<f32>,
19786        beta: &mut CudaSlice<f32>,
19787        g_log: &mut CudaSlice<f32>,
19788        d_state: usize,
19789        num_v: usize,
19790        num_k: usize,
19791        key_dim: usize,
19792        eps: f32,
19793        conv_dim: usize,
19794        b_n: usize,
19795    ) -> Result<(), Box<dyn std::error::Error>> {
19796        let f = self.func("gdn_prep_decode_b_f32");
19797        let cfg = LaunchConfig {
19798            grid_dim: (num_v as u32, 1, b_n as u32),
19799            block_dim: (32, 4, 1),
19800            shared_mem_bytes: 0,
19801        };
19802        let (ds, nv, nk, kd, cd) = (
19803            d_state as i32,
19804            num_v as i32,
19805            num_k as i32,
19806            key_dim as i32,
19807            conv_dim as i32,
19808        );
19809        let __s_b = self.gpu.stream();
19810        let mut b = __s_b.launch_builder(&f);
19811        b.arg(conv_outs)
19812            .arg(beta_raws)
19813            .arg(alphas)
19814            .arg(dt_bias)
19815            .arg(a)
19816            .arg(q_l2)
19817            .arg(k_l2)
19818            .arg(v_g)
19819            .arg(beta)
19820            .arg(g_log)
19821            .arg(&ds)
19822            .arg(&nv)
19823            .arg(&nk)
19824            .arg(&kd)
19825            .arg(&eps)
19826            .arg(&cd);
19827        unsafe {
19828            b.launch(cfg)?;
19829        }
19830        Ok(())
19831    }
19832
19833    #[allow(clippy::too_many_arguments)]
19834    pub fn gdn_scan_s128_batched_view(
19835        &self,
19836        q: &CudaSlice<f32>,
19837        k: &CudaSlice<f32>,
19838        v: &CudaSlice<f32>,
19839        g: &CudaSlice<f32>,
19840        beta: &CudaSlice<f32>,
19841        state_in_ptrs: &cudarc::driver::CudaView<u64>,
19842        state_out_ptrs: &cudarc::driver::CudaView<u64>,
19843        o: &mut cudarc::driver::CudaViewMut<f32>,
19844        n_head: usize,
19845        b_n: usize,
19846        scale: f32,
19847    ) -> Result<(), Box<dyn std::error::Error>> {
19848        let f = self.func("gdn_scan_s128_b");
19849        const S_V: u32 = 128;
19850        const WARP: u32 = 32;
19851        const COLS_PER_BLOCK: u32 = 4;
19852        let cfg = LaunchConfig {
19853            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
19854            block_dim: (WARP, COLS_PER_BLOCK, 1),
19855            shared_mem_bytes: 0,
19856        };
19857        let h = n_head as i32;
19858        let __s_b = self.gpu.stream();
19859        let mut b = __s_b.launch_builder(&f);
19860        b.arg(q)
19861            .arg(k)
19862            .arg(v)
19863            .arg(g)
19864            .arg(beta)
19865            .arg(state_in_ptrs)
19866            .arg(state_out_ptrs)
19867            .arg(o)
19868            .arg(&h)
19869            .arg(&scale);
19870        unsafe {
19871            b.launch(cfg)?;
19872        }
19873        Ok(())
19874    }
19875
19876    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
19877    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
19878    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
19879    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
19880    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
19881    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
19882    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
19883    /// identity law); prime_cache/forward/forward_last are the only callers.
19884    pub fn gdn_chunked_enabled() -> bool {
19885        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19886        *E.get_or_init(|| {
19887            std::env::var("MEMRA_GDN_CHUNKED")
19888                .map(|v| v != "0")
19889                .unwrap_or(true)
19890        })
19891    }
19892
19893    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
19894    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
19895    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
19896    /// of 32 in [32, 128] (kernel row mappings require it).
19897    pub fn gdn_chunk_size() -> usize {
19898        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19899        *C.get_or_init(|| {
19900            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
19901                .ok()
19902                .and_then(|v| v.parse().ok())
19903                .unwrap_or(32);
19904            c.clamp(32, 128) / 32 * 32
19905        })
19906    }
19907
19908    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
19909    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
19910    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
19911    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
19912    #[allow(clippy::too_many_arguments)]
19913    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
19914    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
19915    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
19916    #[allow(clippy::too_many_arguments)]
19917    pub fn gdn_chunk_k123(
19918        &self,
19919        q: &CudaSlice<f32>,
19920        k: &CudaSlice<f32>,
19921        v: &CudaSlice<f32>,
19922        g: &CudaSlice<f32>,
19923        beta: &CudaSlice<f32>,
19924        wb16: Option<&mut CudaSlice<u8>>,
19925        n_head: usize,
19926        t: usize,
19927        c: usize,
19928        hk: usize,
19929        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
19930    ) -> Result<
19931        (
19932            CudaSlice<f32>,
19933            CudaSlice<f32>,
19934            CudaSlice<f32>,
19935            CudaSlice<f32>,
19936        ),
19937        Box<dyn std::error::Error>,
19938    > {
19939        const D: usize = 128;
19940        let h = n_head;
19941        let nc = (t + c - 1) / c;
19942        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
19943        let mut gcum = self.uninit(t * h)?;
19944        let mut a = self.uninit(nc * h * c * c)?;
19945        let mut p = self.uninit(nc * h * c * c)?;
19946        let mut u = self.uninit(nc * h * c * D)?;
19947        let mut w = self.uninit(nc * h * c * D)?;
19948        {
19949            // K1
19950            let f = self.func("gdn_chunk_cumgate_f32");
19951            let cfg = LaunchConfig {
19952                grid_dim: (nc as u32, h as u32, 1),
19953                block_dim: (32, 1, 1),
19954                shared_mem_bytes: 0,
19955            };
19956            let __s_b = self.gpu.stream();
19957            let mut b = __s_b.launch_builder(&f);
19958            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
19959            unsafe {
19960                b.launch(cfg)?;
19961            }
19962        }
19963        if let Some((qb, kb, pb)) = k2w {
19964            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
19965            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
19966            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
19967            let f = self.func("gdn_k2_wgmma");
19968            let cfg = LaunchConfig {
19969                grid_dim: (nc as u32, h as u32, 1),
19970                block_dim: (128, 1, 1),
19971                shared_mem_bytes: 0,
19972            };
19973            let hki = hk as i32;
19974            let __s_b = self.gpu.stream();
19975            let mut b = __s_b.launch_builder(&f);
19976            b.arg(qb)
19977                .arg(kb)
19978                .arg(&gcum)
19979                .arg(beta)
19980                .arg(&mut a)
19981                .arg(&mut *pb)
19982                .arg(&hi)
19983                .arg(&ti)
19984                .arg(&ci)
19985                .arg(&hki);
19986            unsafe {
19987                b.launch(cfg)?;
19988            }
19989        } else if c <= 64 && !portable_mma_gated() {
19990            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
19991            let f = self.func("gdn_chunk_attn_f32");
19992            let jt = ((c + 31) / 32) as u32;
19993            let cfg = LaunchConfig {
19994                grid_dim: (nc as u32, h as u32, jt),
19995                block_dim: (256, 1, 1),
19996                shared_mem_bytes: 0,
19997            };
19998            let hki = hk as i32;
19999            let __s_b = self.gpu.stream();
20000            let mut b = __s_b.launch_builder(&f);
20001            b.arg(q)
20002                .arg(k)
20003                .arg(&gcum)
20004                .arg(beta)
20005                .arg(&mut a)
20006                .arg(&mut p)
20007                .arg(&hi)
20008                .arg(&ti)
20009                .arg(&ci)
20010                .arg(&hki);
20011            unsafe {
20012                b.launch(cfg)?;
20013            }
20014        } else {
20015            // K2 generic (C = 128, or the portable target's low-smem fallback)
20016            assert!(
20017                hk == h,
20018                "generic K2 is broadcast-only (de-broadcast rides C==32)"
20019            );
20020            let f = self.func("gdn_chunk_attn_g_f32");
20021            let cfg = LaunchConfig {
20022                grid_dim: (nc as u32, h as u32, 1),
20023                block_dim: (32, 8, 1),
20024                shared_mem_bytes: 0,
20025            };
20026            let __s_b = self.gpu.stream();
20027            let mut b = __s_b.launch_builder(&f);
20028            b.arg(q)
20029                .arg(k)
20030                .arg(&gcum)
20031                .arg(beta)
20032                .arg(&mut a)
20033                .arg(&mut p)
20034                .arg(&hi)
20035                .arg(&ti)
20036                .arg(&ci);
20037            unsafe {
20038                b.launch(cfg)?;
20039            }
20040        }
20041        {
20042            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
20043            let cfg = LaunchConfig {
20044                grid_dim: (nc as u32, h as u32, 1),
20045                block_dim: (256, 1, 1),
20046                shared_mem_bytes: 0,
20047            };
20048            match c {
20049                32 | 64 => {
20050                    let f = self.func(if c == 32 {
20051                        "gdn_chunk_solve32_f32"
20052                    } else {
20053                        "gdn_chunk_solve64_f32"
20054                    });
20055                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
20056                    let wb: u64 = match wb16 {
20057                        Some(d) => self.addr_u8(d),
20058                        None => 0,
20059                    };
20060                    let hki = hk as i32;
20061                    let __s_b = self.gpu.stream();
20062                    let mut b = __s_b.launch_builder(&f);
20063                    b.arg(v)
20064                        .arg(k)
20065                        .arg(&a)
20066                        .arg(&gcum)
20067                        .arg(&mut u)
20068                        .arg(&mut w)
20069                        .arg(&wb)
20070                        .arg(&hi)
20071                        .arg(&ti)
20072                        .arg(&hki);
20073                    unsafe {
20074                        b.launch(cfg)?;
20075                    }
20076                }
20077                _ => {
20078                    assert!(hk == h, "generic K3 is broadcast-only");
20079                    let f = self.func("gdn_chunk_solve_f32");
20080                    let __s_b = self.gpu.stream();
20081                    let mut b = __s_b.launch_builder(&f);
20082                    b.arg(v)
20083                        .arg(k)
20084                        .arg(&a)
20085                        .arg(&gcum)
20086                        .arg(&mut u)
20087                        .arg(&mut w)
20088                        .arg(&hi)
20089                        .arg(&ti)
20090                        .arg(&ci);
20091                    unsafe {
20092                        b.launch(cfg)?;
20093                    }
20094                }
20095            }
20096        }
20097        Ok((gcum, p, u, w))
20098    }
20099
20100    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
20101    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
20102    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
20103    pub fn gdn_db_on() -> bool {
20104        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
20105    }
20106
20107    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
20108    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
20109    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
20110        !portable_mma_gated()
20111            && c == 32
20112            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20113                Ok("1") => true,
20114                Ok("0") => false,
20115                _ => cfg!(memra_hopper_mma),
20116            }
20117    }
20118
20119    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
20120    /// mma config; same per-call env read discipline).
20121    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
20122        self.gdn_mma_enabled(c)
20123            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20124                Ok("0") => false,
20125                Ok("1") => true,
20126                _ => cfg!(memra_hopper_mma),
20127            }
20128    }
20129
20130    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
20131    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
20132    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
20133    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
20134    #[allow(clippy::too_many_arguments)]
20135    pub fn ssm_conv1d_gdn_state_pad(
20136        &self,
20137        qkv_tm: &cudarc::driver::CudaView<f32>,
20138        conv_state: &mut CudaSlice<f32>,
20139        w: &CudaSlice<f32>,
20140        q_g: &mut CudaSlice<f32>,
20141        k_g: &mut CudaSlice<f32>,
20142        v_g: &mut CudaSlice<f32>,
20143        conv_dim: usize,
20144        t: usize,
20145        d_conv: usize,
20146        d_state: usize,
20147        num_v: usize,
20148        num_k: usize,
20149        key_dim: usize,
20150        hk: usize,
20151        pad_len: Option<&CudaSlice<i32>>,
20152    ) -> Result<(), Box<dyn std::error::Error>> {
20153        assert!(
20154            t >= d_conv - 1,
20155            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
20156        );
20157        {
20158            let f = self.func("ssm_conv1d_gdn_state_f32");
20159            let cfg = LaunchConfig {
20160                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20161                block_dim: (256, 1, 1),
20162                shared_mem_bytes: 0,
20163            };
20164            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20165            let (ds, nv, nk, kd, hki) = (
20166                d_state as i32,
20167                num_v as i32,
20168                num_k as i32,
20169                key_dim as i32,
20170                hk as i32,
20171            );
20172            let __s_b = self.gpu.stream();
20173            let mut b = __s_b.launch_builder(&f);
20174            b.arg(qkv_tm)
20175                .arg(&*conv_state)
20176                .arg(w)
20177                .arg(q_g)
20178                .arg(k_g)
20179                .arg(v_g)
20180                .arg(&cd)
20181                .arg(&ti)
20182                .arg(&dc)
20183                .arg(&ds)
20184                .arg(&nv)
20185                .arg(&nk)
20186                .arg(&kd)
20187                .arg(&hki);
20188            unsafe {
20189                b.launch(cfg)?;
20190            }
20191        }
20192        match pad_len {
20193            Some(len_d) => {
20194                let f = self.func("ssm_conv_ring_update_dev_f32");
20195                let n = conv_dim * (d_conv - 1);
20196                let cfg = LaunchConfig::for_num_elems(n as u32);
20197                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20198                let __s_b = self.gpu.stream();
20199                let mut b = __s_b.launch_builder(&f);
20200                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20201                unsafe {
20202                    b.launch(cfg)?;
20203                }
20204            }
20205            None => {
20206                let f = self.func("ssm_conv_ring_update_f32");
20207                let n = conv_dim * (d_conv - 1);
20208                let cfg = LaunchConfig::for_num_elems(n as u32);
20209                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20210                let __s_b = self.gpu.stream();
20211                let mut b = __s_b.launch_builder(&f);
20212                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20213                unsafe {
20214                    b.launch(cfg)?;
20215                }
20216            }
20217        }
20218        Ok(())
20219    }
20220
20221    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
20222    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
20223    /// K2/K3 can write them.
20224    pub fn gdn_chunk_alloc(
20225        &self,
20226        n_head: usize,
20227        t: usize,
20228        c: usize,
20229        hk: usize,
20230    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
20231        const D: usize = 128;
20232        assert!(
20233            c == 32,
20234            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
20235        );
20236        let h = n_head;
20237        let nc = (t + c - 1) / c;
20238        Ok(GdnChunkBufs {
20239            gcum: self.uninit(t * h)?,
20240            a: self.uninit(nc * h * c * c)?,
20241            p: self.uninit(nc * h * c * c)?,
20242            u: self.uninit(nc * h * c * D)?,
20243            w: self.uninit(nc * h * c * D)?,
20244            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20245            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20246            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20247            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
20248            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20249            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
20250            o: self.uninit(D * h * t)?,
20251            t,
20252            nc,
20253        })
20254    }
20255
20256    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
20257    pub fn f32_to_bf16_v(
20258        &self,
20259        x: &cudarc::driver::CudaView<f32>,
20260        dst: &mut CudaSlice<u8>,
20261        n: usize,
20262    ) -> Result<(), Box<dyn std::error::Error>> {
20263        let f = self.func("f32_to_bf16_bulk");
20264        let ni = n as i64;
20265        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20266        let __s_b = self.gpu.stream();
20267        let mut b = __s_b.launch_builder(&f);
20268        b.arg(x).arg(dst).arg(&ni);
20269        unsafe {
20270            b.launch(cfg)?;
20271        }
20272        Ok(())
20273    }
20274
20275    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
20276    pub fn f32_to_bf16_into(
20277        &self,
20278        x: &CudaSlice<f32>,
20279        dst: &mut CudaSlice<u8>,
20280        n: usize,
20281    ) -> Result<(), Box<dyn std::error::Error>> {
20282        let f = self.func("f32_to_bf16_bulk");
20283        let ni = n as i64;
20284        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20285        let __s_b = self.gpu.stream();
20286        let mut b = __s_b.launch_builder(&f);
20287        b.arg(x).arg(dst).arg(&ni);
20288        unsafe {
20289            b.launch(cfg)?;
20290        }
20291        Ok(())
20292    }
20293
20294    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
20295    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
20296    pub fn gdn_chunk_k123_vl8(
20297        &self,
20298        seqs: &[GdnSeqVl],
20299        n_head: usize,
20300        hk: usize,
20301        wq: Option<&GdnWVl8>,
20302    ) -> Result<(), Box<dyn std::error::Error>> {
20303        let b = seqs.len();
20304        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
20305        let mut packed = [GdnSeqVl::default(); 8];
20306        packed[..b].copy_from_slice(seqs);
20307        let v = GdnVl8(packed);
20308        let (hi, ci) = (n_head as i32, 32i32);
20309        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20310        {
20311            let f = self.func("gdn_chunk_cumgate_vl");
20312            let cfg = LaunchConfig {
20313                grid_dim: (max_nc, n_head as u32, b as u32),
20314                block_dim: (32, 1, 1),
20315                shared_mem_bytes: 0,
20316            };
20317            let __s_lb = self.gpu.stream();
20318            let mut lb = __s_lb.launch_builder(&f);
20319            lb.arg(&v).arg(&hi).arg(&ci);
20320            unsafe {
20321                lb.launch(cfg)?;
20322            }
20323        }
20324        let hki = hk as i32;
20325        if let Some(w) = wq {
20326            // K2-wgmma vl twin (writes A + pre-masked Pb16)
20327            let f = self.func("gdn_k2_wgmma_vl");
20328            let cfg = LaunchConfig {
20329                grid_dim: (max_nc, n_head as u32, b as u32),
20330                block_dim: (128, 1, 1),
20331                shared_mem_bytes: 0,
20332            };
20333            let __s_lb = self.gpu.stream();
20334            let mut lb = __s_lb.launch_builder(&f);
20335            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
20336            unsafe {
20337                lb.launch(cfg)?;
20338            }
20339        } else {
20340            let f = self.func("gdn_chunk_attn_vl");
20341            let cfg = LaunchConfig {
20342                grid_dim: (max_nc, n_head as u32, b as u32),
20343                block_dim: (256, 1, 1),
20344                shared_mem_bytes: 0,
20345            };
20346            let __s_lb = self.gpu.stream();
20347            let mut lb = __s_lb.launch_builder(&f);
20348            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20349            unsafe {
20350                lb.launch(cfg)?;
20351            }
20352        }
20353        {
20354            let f = self.func("gdn_chunk_solve32_vl");
20355            let cfg = LaunchConfig {
20356                grid_dim: (max_nc, n_head as u32, b as u32),
20357                block_dim: (256, 1, 1),
20358                shared_mem_bytes: 0,
20359            };
20360            let __s_lb = self.gpu.stream();
20361            let mut lb = __s_lb.launch_builder(&f);
20362            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20363            unsafe {
20364                lb.launch(cfg)?;
20365            }
20366        }
20367        Ok(())
20368    }
20369
20370    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
20371    /// fused gate-prep, 5 launches for every sequence (per-element math identical
20372    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
20373    #[allow(clippy::too_many_arguments)]
20374    pub fn gdn_prep_vl8(
20375        &self,
20376        seqs: &[GdnPrepVl],
20377        conv_w: &CudaSlice<f32>,
20378        dt_bias: &CudaSlice<f32>,
20379        a: &CudaSlice<f32>,
20380        conv_dim: usize,
20381        d_conv: usize,
20382        d_state: usize,
20383        num_v: usize,
20384        num_k: usize,
20385        key_dim: usize,
20386        hk: usize,
20387        eps: f32,
20388    ) -> Result<(), Box<dyn std::error::Error>> {
20389        let b = seqs.len();
20390        assert!(b >= 1 && b <= 8);
20391        let mut packed = [GdnPrepVl::default(); 8];
20392        packed[..b].copy_from_slice(seqs);
20393        let v = GdnPrepVl8(packed);
20394        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20395        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
20396        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
20397        assert!(
20398            conv_fuse || hk == num_v,
20399            "de-broadcast requires the fused conv"
20400        );
20401        if conv_fuse {
20402            let f = self.func("ssm_conv1d_gdn_state_vl");
20403            let cfg = LaunchConfig {
20404                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
20405                block_dim: (256, 1, 1),
20406                shared_mem_bytes: 0,
20407            };
20408            let (dsi, nvi, nki, kdi, hki) = (
20409                d_state as i32,
20410                num_v as i32,
20411                num_k as i32,
20412                key_dim as i32,
20413                hk as i32,
20414            );
20415            let __s_lb = self.gpu.stream();
20416            let mut lb = __s_lb.launch_builder(&f);
20417            lb.arg(&v)
20418                .arg(conv_w)
20419                .arg(&cdi)
20420                .arg(&dci)
20421                .arg(&dsi)
20422                .arg(&nvi)
20423                .arg(&nki)
20424                .arg(&kdi)
20425                .arg(&hki);
20426            unsafe {
20427                lb.launch(cfg)?;
20428            }
20429        } else {
20430            let f = self.func("ssm_conv1d_tm_state_vl");
20431            let cfg = LaunchConfig {
20432                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
20433                block_dim: (256, 1, 1),
20434                shared_mem_bytes: 0,
20435            };
20436            let __s_lb = self.gpu.stream();
20437            let mut lb = __s_lb.launch_builder(&f);
20438            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
20439            unsafe {
20440                lb.launch(cfg)?;
20441            }
20442        }
20443        {
20444            let f = self.func("ssm_conv_ring_update_vl");
20445            let n = (conv_dim * (d_conv - 1)) as u32;
20446            let cfg = LaunchConfig {
20447                grid_dim: (n.div_ceil(256), 1, b as u32),
20448                block_dim: (256, 1, 1),
20449                shared_mem_bytes: 0,
20450            };
20451            let __s_lb = self.gpu.stream();
20452            let mut lb = __s_lb.launch_builder(&f);
20453            lb.arg(&v).arg(&cdi).arg(&dci);
20454            unsafe {
20455                lb.launch(cfg)?;
20456            }
20457        }
20458        if !conv_fuse {
20459            let f = self.func("qkv_to_gdn_repack_vl");
20460            let n = max_t * (num_v * d_state) as u32;
20461            let cfg = LaunchConfig {
20462                grid_dim: (n.div_ceil(256), 1, b as u32),
20463                block_dim: (256, 1, 1),
20464                shared_mem_bytes: 0,
20465            };
20466            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20467            let __s_lb = self.gpu.stream();
20468            let mut lb = __s_lb.launch_builder(&f);
20469            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
20470            unsafe {
20471                lb.launch(cfg)?;
20472            }
20473        }
20474        if Self::l2_v2_on(d_state) {
20475            let f = self.func("gdn_l2_v2_vl");
20476            let cfg = LaunchConfig {
20477                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
20478                block_dim: (256, 1, 1),
20479                shared_mem_bytes: 0,
20480            };
20481            let (dsi, nvi) = (d_state as i32, hk as i32);
20482            let __s_lb = self.gpu.stream();
20483            let mut lb = __s_lb.launch_builder(&f);
20484            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20485            unsafe {
20486                lb.launch(cfg)?;
20487            }
20488        } else {
20489            let f = self.func("gdn_l2_vl");
20490            let cfg = LaunchConfig {
20491                grid_dim: (max_t * hk as u32, 2, b as u32),
20492                block_dim: (256, 1, 1),
20493                shared_mem_bytes: 0,
20494            };
20495            let (dsi, nvi) = (d_state as i32, hk as i32);
20496            let __s_lb = self.gpu.stream();
20497            let mut lb = __s_lb.launch_builder(&f);
20498            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20499            unsafe {
20500                lb.launch(cfg)?;
20501            }
20502        }
20503        {
20504            let f = self.func("gdn_gate_prep_vl");
20505            let n = max_t * num_v as u32;
20506            let cfg = LaunchConfig {
20507                grid_dim: (n.div_ceil(256), 1, b as u32),
20508                block_dim: (256, 1, 1),
20509                shared_mem_bytes: 0,
20510            };
20511            let nvi = num_v as i32;
20512            let __s_lb = self.gpu.stream();
20513            let mut lb = __s_lb.launch_builder(&f);
20514            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
20515            unsafe {
20516                lb.launch(cfg)?;
20517            }
20518        }
20519        Ok(())
20520    }
20521
20522    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
20523    pub fn gdn_mirror_vl8(
20524        &self,
20525        seqs: &[GdnSeqVl],
20526        n_head: usize,
20527        which: i32,
20528        hk: usize,
20529    ) -> Result<(), Box<dyn std::error::Error>> {
20530        let b = seqs.len();
20531        assert!(b >= 1 && b <= 8);
20532        let mut packed = [GdnSeqVl::default(); 8];
20533        packed[..b].copy_from_slice(seqs);
20534        let v = GdnVl8(packed);
20535        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
20536        let max_n = seqs
20537            .iter()
20538            .map(|s| {
20539                if which == 0 {
20540                    s.t as i64 * ept as i64
20541                } else {
20542                    s.nc as i64 * ept as i64 * 32
20543                }
20544            })
20545            .max()
20546            .unwrap();
20547        let f = self.func("gdn_mirror_vl");
20548        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
20549        let cfg = LaunchConfig {
20550            grid_dim: (blocks, 1, b as u32),
20551            block_dim: (256, 1, 1),
20552            shared_mem_bytes: 0,
20553        };
20554        let __s_lb = self.gpu.stream();
20555        let mut lb = __s_lb.launch_builder(&f);
20556        lb.arg(&v).arg(&ept).arg(&which);
20557        unsafe {
20558            lb.launch(cfg)?;
20559        }
20560        Ok(())
20561    }
20562
20563    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
20564    pub fn gdn_tail_vl8(
20565        &self,
20566        seqs: &[GdnPrepVl],
20567        norm_w: &CudaSlice<f32>,
20568        d_state: usize,
20569        num_v: usize,
20570        eps: f32,
20571    ) -> Result<(), Box<dyn std::error::Error>> {
20572        let b = seqs.len();
20573        assert!(b >= 1 && b <= 8);
20574        let mut packed = [GdnPrepVl::default(); 8];
20575        packed[..b].copy_from_slice(seqs);
20576        let v = GdnPrepVl8(packed);
20577        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20578        let f = self.func("gated_rmsnorm_f16out_vl");
20579        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
20580        let cfg = LaunchConfig {
20581            grid_dim: (max_t * num_v as u32, 1, b as u32),
20582            block_dim: (128, 1, 1),
20583            shared_mem_bytes: 0,
20584        };
20585        let (dsi, nvi) = (d_state as i32, num_v as i32);
20586        let __s_lb = self.gpu.stream();
20587        let mut lb = __s_lb.launch_builder(&f);
20588        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
20589        unsafe {
20590            lb.launch(cfg)?;
20591        }
20592        Ok(())
20593    }
20594
20595    /// Raw device address helpers for the varlen by-value arg struct (single-stream
20596    /// launches; every buffer outlives the call — the f16 FFI discipline).
20597    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
20598        use cudarc::driver::DevicePtr;
20599        let s = self.gpu.stream();
20600        let (p, _g) = x.device_ptr(&s);
20601        p as u64
20602    }
20603    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
20604        use cudarc::driver::DevicePtrMut;
20605        let s = self.gpu.stream();
20606        let (p, _g) = x.device_ptr_mut(&s);
20607        p as u64
20608    }
20609    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
20610        use cudarc::driver::DevicePtr;
20611        let s = self.gpu.stream();
20612        let (p, _g) = x.device_ptr(&s);
20613        p as u64
20614    }
20615    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
20616        use cudarc::driver::DevicePtr;
20617        let s = self.gpu.stream();
20618        let (p, _g) = x.device_ptr(&s);
20619        p as u64
20620    }
20621
20622    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
20623    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
20624    /// launches, so this is strictly bit-gateable against them).
20625    pub fn gdn_chunk_vl8(
20626        &self,
20627        seqs: &[GdnSeqVl],
20628        n_head: usize,
20629        scale: f32,
20630        hk: usize,
20631        wq: Option<&GdnWVl8>,
20632    ) -> Result<(), Box<dyn std::error::Error>> {
20633        const NSPLIT: u32 = 4;
20634        let b = seqs.len();
20635        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
20636        let mut packed = [GdnSeqVl::default(); 8];
20637        packed[..b].copy_from_slice(seqs);
20638        let v = GdnVl8(packed);
20639        let (hi, ci) = (n_head as i32, 32i32);
20640        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20641        let hki = hk as i32;
20642        if let Some(w) = wq {
20643            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
20644            let f = self.func("gdn_k45_wgmma_vl");
20645            let cfg = LaunchConfig {
20646                grid_dim: (n_head as u32, NSPLIT, b as u32),
20647                block_dim: (256, 1, 1),
20648                shared_mem_bytes: 0,
20649            };
20650            let __s_lb = self.gpu.stream();
20651            let mut lb = __s_lb.launch_builder(&f);
20652            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
20653            unsafe {
20654                lb.launch(cfg)?;
20655            }
20656            let _ = max_nc;
20657            return Ok(());
20658        }
20659        {
20660            let f = self.func("gdn_chunk_state_mma_vl");
20661            let cfg = LaunchConfig {
20662                grid_dim: (n_head as u32, NSPLIT, b as u32),
20663                block_dim: (256, 1, 1),
20664                shared_mem_bytes: 0,
20665            };
20666            let __s_lb = self.gpu.stream();
20667            let mut lb = __s_lb.launch_builder(&f);
20668            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20669            unsafe {
20670                lb.launch(cfg)?;
20671            }
20672        }
20673        {
20674            let f = self.func("gdn_chunk_output_mma_vl");
20675            let cfg = LaunchConfig {
20676                grid_dim: (max_nc, n_head as u32, b as u32),
20677                block_dim: (256, 1, 1),
20678                shared_mem_bytes: 0,
20679            };
20680            let __s_lb = self.gpu.stream();
20681            let mut lb = __s_lb.launch_builder(&f);
20682            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
20683            unsafe {
20684                lb.launch(cfg)?;
20685            }
20686        }
20687        Ok(())
20688    }
20689    pub fn gdn_scan_chunked(
20690        &self,
20691        q: &CudaSlice<f32>,
20692        k: &CudaSlice<f32>,
20693        v: &CudaSlice<f32>,
20694        g: &CudaSlice<f32>,
20695        beta: &CudaSlice<f32>,
20696        kb16_pre: Option<&CudaSlice<u8>>,
20697        qb16_pre: Option<&CudaSlice<u8>>,
20698        state_in: &CudaSlice<f32>,
20699        state_out: &mut CudaSlice<f32>,
20700        o: &mut CudaSlice<f32>,
20701        n_head: usize,
20702        t: usize,
20703        scale: f32,
20704        c: usize,
20705        hk: usize,
20706    ) -> Result<(), Box<dyn std::error::Error>> {
20707        const D: usize = 128;
20708        const NSPLIT: u32 = 4;
20709        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
20710        let h = n_head;
20711        let nc = (t + c - 1) / c;
20712        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20713        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
20714        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
20715        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
20716        let gdn_mma_pre = !portable_mma_gated()
20717            && c == 32
20718            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20719                Ok("1") => true,
20720                Ok("0") => false,
20721                _ => cfg!(memra_hopper_mma),
20722            };
20723        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
20724            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
20725        } else {
20726            None
20727        };
20728        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
20729        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
20730        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
20731        let gdn_wgmma_pre = gdn_mma_pre
20732            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20733                Ok("0") => false,
20734                Ok("1") => true,
20735                _ => cfg!(memra_hopper_mma),
20736            };
20737        let nk = t * hk * D;
20738        let mut kb16_local: Option<CudaSlice<u8>> = None;
20739        if gdn_mma_pre && kb16_pre.is_none() {
20740            let mut kb = self.alloc_u8_uninit(nk * 2)?;
20741            let f = self.func("f32_to_bf16_bulk");
20742            let n2 = nk as i64;
20743            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20744            let __s_b = self.gpu.stream();
20745            let mut b = __s_b.launch_builder(&f);
20746            b.arg(k).arg(&mut kb).arg(&n2);
20747            unsafe {
20748                b.launch(cfg2)?;
20749            }
20750            kb16_local = Some(kb);
20751        }
20752        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
20753        if let Some(kb) = kb16_pre {
20754            assert!(kb.len() >= nk * 2, "kb16_pre too small");
20755        }
20756        let mut qb16: Option<CudaSlice<u8>> = None;
20757        let mut pb16: Option<CudaSlice<u8>> = None;
20758        if gdn_wgmma_pre {
20759            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
20760            // the standalone bulk cvt only serves callers without the prep mirror.
20761            if qb16_pre.is_none() {
20762                let mut qb = self.alloc_u8_uninit(nk * 2)?;
20763                let f = self.func("f32_to_bf16_bulk");
20764                let n2 = nk as i64;
20765                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20766                let __s_b = self.gpu.stream();
20767                let mut b = __s_b.launch_builder(&f);
20768                b.arg(q).arg(&mut qb).arg(&n2);
20769                unsafe {
20770                    b.launch(cfg2)?;
20771                }
20772                qb16 = Some(qb);
20773            } else if let Some(qb) = qb16_pre {
20774                assert!(qb.len() >= nk * 2, "qb16_pre too small");
20775            }
20776            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
20777        }
20778        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
20779        let k2w = if gdn_wgmma_pre {
20780            Some((
20781                *qb16_ref0.as_ref().unwrap(),
20782                *kb16_ref0.as_ref().unwrap(),
20783                pb16.as_mut().unwrap(),
20784            ))
20785        } else {
20786            None
20787        };
20788        let (gcum, p, u, w) =
20789            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
20790        let _ = &w;
20791        let mut y = self.uninit(nc * h * c * D)?;
20792        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
20793        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
20794        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
20795        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
20796        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
20797        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
20798        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
20799        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
20800        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
20801        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
20802        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
20803        let gdn_mma = !portable_mma_gated()
20804            && c == 32
20805            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20806                Ok("1") => true,
20807                Ok("0") => false,
20808                _ => cfg!(memra_hopper_mma),
20809            };
20810        if gdn_mma {
20811            let wb16 = wb16_pre
20812                .take()
20813                .expect("mma path pre-allocates wb16 (K3 store fold)");
20814            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
20815            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
20816            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
20817            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
20818            // pass runs inside the persistent-M kernel; Y and Ssnap are never
20819            // materialized. New numeric class (gk folds into k^T instead of ys) —
20820            // explicit opt-in until the state-carry battery promotes it. Env read per
20821            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
20822            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
20823            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
20824            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
20825            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
20826            if gdn_wgmma_pre {
20827                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
20828                let qb16 = qb16_ref0.unwrap();
20829                let pb16 = pb16.as_ref().unwrap();
20830                {
20831                    let f = self.func("gdn_k45_wgmma");
20832                    let cfg = LaunchConfig {
20833                        grid_dim: (h as u32, 4, 1),
20834                        block_dim: (256, 1, 1),
20835                        shared_mem_bytes: 0,
20836                    };
20837                    let hki = hk as i32;
20838                    let __s_b = self.gpu.stream();
20839                    let mut b = __s_b.launch_builder(&f);
20840                    b.arg(kb16_ref)
20841                        .arg(&gcum)
20842                        .arg(beta)
20843                        .arg(&u)
20844                        .arg(&wb16)
20845                        .arg(qb16)
20846                        .arg(pb16)
20847                        .arg(o)
20848                        .arg(&scale)
20849                        .arg(state_in)
20850                        .arg(&mut *state_out)
20851                        .arg(&hi)
20852                        .arg(&ti)
20853                        .arg(&ci)
20854                        .arg(&hki);
20855                    unsafe {
20856                        b.launch(cfg)?;
20857                    }
20858                }
20859                return Ok(());
20860            }
20861            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
20862            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
20863            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
20864            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
20865            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
20866            {
20867                let f = self.func("gdn_chunk_state_mma");
20868                let cfg = LaunchConfig {
20869                    grid_dim: (h as u32, NSPLIT, 1),
20870                    block_dim: (256, 1, 1),
20871                    shared_mem_bytes: 0,
20872                };
20873                let hki = hk as i32;
20874                let __s_b = self.gpu.stream();
20875                let mut b = __s_b.launch_builder(&f);
20876                b.arg(kb16_ref)
20877                    .arg(&gcum)
20878                    .arg(beta)
20879                    .arg(&u)
20880                    .arg(&wb16)
20881                    .arg(&mut y16)
20882                    .arg(&mut ssnap16)
20883                    .arg(state_in)
20884                    .arg(&mut *state_out)
20885                    .arg(&hi)
20886                    .arg(&ti)
20887                    .arg(&ci)
20888                    .arg(&hki);
20889                unsafe {
20890                    b.launch(cfg)?;
20891                }
20892            }
20893            {
20894                // K5-mma (bf16 St/Y consumers)
20895                let f = self.func("gdn_chunk_output_mma");
20896                let jt = ((c + 31) / 32) as u32;
20897                let cfg = LaunchConfig {
20898                    grid_dim: (nc as u32, h as u32, jt),
20899                    block_dim: (256, 1, 1),
20900                    shared_mem_bytes: 0,
20901                };
20902                let hki = hk as i32;
20903                let __s_b = self.gpu.stream();
20904                let mut b = __s_b.launch_builder(&f);
20905                b.arg(q)
20906                    .arg(&gcum)
20907                    .arg(&p)
20908                    .arg(&y16)
20909                    .arg(&ssnap16)
20910                    .arg(o)
20911                    .arg(&hi)
20912                    .arg(&ti)
20913                    .arg(&ci)
20914                    .arg(&scale)
20915                    .arg(&hki);
20916                unsafe {
20917                    b.launch(cfg)?;
20918                }
20919            }
20920            return Ok(());
20921        }
20922        {
20923            // K4 (sequential over chunks inside; blocks col-partition the state)
20924            let f = self.func("gdn_chunk_state_f32");
20925            let cfg = LaunchConfig {
20926                grid_dim: (h as u32, NSPLIT, 1),
20927                block_dim: (256, 1, 1),
20928                shared_mem_bytes: 0,
20929            };
20930            let __s_b = self.gpu.stream();
20931            let mut b = __s_b.launch_builder(&f);
20932            b.arg(k)
20933                .arg(&gcum)
20934                .arg(beta)
20935                .arg(&u)
20936                .arg(&w)
20937                .arg(&mut y)
20938                .arg(&mut ssnap)
20939                .arg(state_in)
20940                .arg(&mut *state_out)
20941                .arg(&hi)
20942                .arg(&ti)
20943                .arg(&ci);
20944            unsafe {
20945                b.launch(cfg)?;
20946            }
20947        }
20948        {
20949            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
20950            let f = self.func("gdn_chunk_output_f32");
20951            let jt = ((c + 31) / 32) as u32;
20952            let cfg = LaunchConfig {
20953                grid_dim: (nc as u32, h as u32, jt),
20954                block_dim: (256, 1, 1),
20955                shared_mem_bytes: 0,
20956            };
20957            let __s_b = self.gpu.stream();
20958            let mut b = __s_b.launch_builder(&f);
20959            b.arg(q)
20960                .arg(&gcum)
20961                .arg(&p)
20962                .arg(&y)
20963                .arg(&ssnap)
20964                .arg(o)
20965                .arg(&hi)
20966                .arg(&ti)
20967                .arg(&ci)
20968                .arg(&scale);
20969            unsafe {
20970                b.launch(cfg)?;
20971            }
20972        }
20973        Ok(())
20974    }
20975
20976    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
20977    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
20978    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
20979    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
20980    ///
20981    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
20982    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
20983    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
20984    #[allow(clippy::too_many_arguments)]
20985    #[allow(clippy::too_many_arguments)]
20986    pub fn gdn_scan_prefill(
20987        &self,
20988        q: &CudaSlice<f32>,
20989        k: &CudaSlice<f32>,
20990        v: &CudaSlice<f32>,
20991        g: &CudaSlice<f32>,
20992        beta: &CudaSlice<f32>,
20993        kb16_pre: Option<&CudaSlice<u8>>,
20994        qb16_pre: Option<&CudaSlice<u8>>,
20995        state_in: &CudaSlice<f32>,
20996        state_out: &mut CudaSlice<f32>,
20997        o: &mut CudaSlice<f32>,
20998        n_head: usize,
20999        t: usize,
21000        scale: f32,
21001        hk: usize,
21002    ) -> Result<(), Box<dyn std::error::Error>> {
21003        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
21004            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
21005            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
21006        }
21007        if Self::gdn_chunked_enabled() && t >= 16 {
21008            self.gdn_scan_chunked(
21009                q,
21010                k,
21011                v,
21012                g,
21013                beta,
21014                kb16_pre,
21015                qb16_pre,
21016                state_in,
21017                state_out,
21018                o,
21019                n_head,
21020                t,
21021                scale,
21022                Self::gdn_chunk_size(),
21023                hk,
21024            )
21025        } else {
21026            assert!(
21027                hk == n_head,
21028                "s128 scan is broadcast-only (prep guarantees by predicate)"
21029            );
21030            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
21031        }
21032    }
21033
21034    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
21035    #[allow(clippy::too_many_arguments)]
21036    fn gdn_scan_diff(
21037        &self,
21038        q: &CudaSlice<f32>,
21039        k: &CudaSlice<f32>,
21040        v: &CudaSlice<f32>,
21041        g: &CudaSlice<f32>,
21042        beta: &CudaSlice<f32>,
21043        state_in: &CudaSlice<f32>,
21044        state_out: &mut CudaSlice<f32>,
21045        o: &mut CudaSlice<f32>,
21046        n_head: usize,
21047        t: usize,
21048        scale: f32,
21049    ) -> Result<(), Box<dyn std::error::Error>> {
21050        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
21051        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21052        let mut o_c = self.uninit(o.len())?;
21053        let mut st_c = self.uninit(state_out.len())?;
21054        self.gdn_scan_chunked(
21055            q,
21056            k,
21057            v,
21058            g,
21059            beta,
21060            None,
21061            None,
21062            state_in,
21063            &mut st_c,
21064            &mut o_c,
21065            n_head,
21066            t,
21067            scale,
21068            Self::gdn_chunk_size(),
21069            n_head,
21070        )?;
21071        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
21072        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
21073        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
21074        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
21075            let mut max_abs = 0f32;
21076            let mut max_rel = 0f32;
21077            let mut sum_rel = 0f64;
21078            for (x, y) in a.iter().zip(b) {
21079                let ad = (x - y).abs();
21080                let rel = ad / x.abs().max(y.abs()).max(1e-3);
21081                if ad > max_abs {
21082                    max_abs = ad;
21083                }
21084                if rel > max_rel {
21085                    max_rel = rel;
21086                }
21087                sum_rel += rel as f64;
21088            }
21089            (max_abs, max_rel, sum_rel / a.len() as f64)
21090        };
21091        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
21092        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
21093        println!(
21094            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
21095                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
21096            Self::gdn_chunk_size()
21097        );
21098        Ok(())
21099    }
21100
21101    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
21102    pub fn gdn_glog(
21103        &self,
21104        alpha: &CudaSlice<f32>,
21105        dt_bias: &CudaSlice<f32>,
21106        a: &CudaSlice<f32>,
21107        g_log: &mut CudaSlice<f32>,
21108        n_head: usize,
21109        t: usize,
21110    ) -> Result<(), Box<dyn std::error::Error>> {
21111        let f = self.func("gdn_glog_f32");
21112        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21113        let (h, ti) = (n_head as i32, t as i32);
21114        let __s_b = self.gpu.stream();
21115        let mut b = __s_b.launch_builder(&f);
21116        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21117        unsafe {
21118            b.launch(cfg)?;
21119        }
21120        Ok(())
21121    }
21122
21123    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
21124    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
21125    pub fn sigmoid_v(
21126        &self,
21127        x: &cudarc::driver::CudaView<f32>,
21128        y: &mut CudaSlice<f32>,
21129        n: usize,
21130    ) -> Result<(), Box<dyn std::error::Error>> {
21131        let f = self.func("sigmoid_f32");
21132        let cfg = LaunchConfig::for_num_elems(n as u32);
21133        let ni = n as i32;
21134        let __s_b = self.gpu.stream();
21135        let mut b = __s_b.launch_builder(&f);
21136        b.arg(x).arg(y).arg(&ni);
21137        unsafe {
21138            b.launch(cfg)?;
21139        }
21140        Ok(())
21141    }
21142
21143    pub fn gdn_glog_v(
21144        &self,
21145        alpha: &cudarc::driver::CudaView<f32>,
21146        dt_bias: &CudaSlice<f32>,
21147        a: &CudaSlice<f32>,
21148        g_log: &mut CudaSlice<f32>,
21149        n_head: usize,
21150        t: usize,
21151    ) -> Result<(), Box<dyn std::error::Error>> {
21152        let f = self.func("gdn_glog_f32");
21153        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21154        let (h, ti) = (n_head as i32, t as i32);
21155        let __s_b = self.gpu.stream();
21156        let mut b = __s_b.launch_builder(&f);
21157        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21158        unsafe {
21159            b.launch(cfg)?;
21160        }
21161        Ok(())
21162    }
21163
21164    pub fn sigmoid(
21165        &self,
21166        x: &CudaSlice<f32>,
21167        y: &mut CudaSlice<f32>,
21168        n: usize,
21169    ) -> Result<(), Box<dyn std::error::Error>> {
21170        let f = self.func("sigmoid_f32");
21171        let cfg = LaunchConfig::for_num_elems(n as u32);
21172        let ni = n as i32;
21173        let __s_b = self.gpu.stream();
21174        let mut b = __s_b.launch_builder(&f);
21175        b.arg(x).arg(y).arg(&ni);
21176        unsafe {
21177            b.launch(cfg)?;
21178        }
21179        Ok(())
21180    }
21181
21182    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
21183    /// (replaces sigmoid + mul + convert). Bit-identical class.
21184    pub fn sig_mul_f16out(
21185        &self,
21186        a: &CudaSlice<f32>,
21187        g: &CudaSlice<f32>,
21188        dst: &mut CudaSlice<f32>,
21189        dst16: &mut CudaSlice<u8>,
21190        n: usize,
21191    ) -> Result<(), Box<dyn std::error::Error>> {
21192        let f = self.func("sig_mul_f16out_f32");
21193        let cfg = LaunchConfig::for_num_elems(n as u32);
21194        let ni = n as i32;
21195        let __s_b = self.gpu.stream();
21196        let mut b = __s_b.launch_builder(&f);
21197        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
21198        unsafe {
21199            b.launch(cfg)?;
21200        }
21201        Ok(())
21202    }
21203
21204    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
21205    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
21206    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
21207    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
21208    ///
21209    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
21210    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
21211    /// applies the wrong number of distinct gate values.
21212    #[allow(clippy::too_many_arguments)]
21213    pub fn attn_head_gate(
21214        &self,
21215        a: &CudaSlice<f32>,
21216        g: &CudaSlice<f32>,
21217        dst: &mut CudaSlice<f32>,
21218        dst16: Option<&mut CudaSlice<u8>>,
21219        head_dim: usize,
21220        n_head: usize,
21221        t: usize,
21222    ) -> Result<(), Box<dyn std::error::Error>> {
21223        let f = self.func("attn_head_gate_f32");
21224        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21225        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21226        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
21227        let d16: u64 = match dst16 {
21228            Some(d) => self.addr_u8(d),
21229            None => 0,
21230        };
21231        let __s_b = self.gpu.stream();
21232        let mut b = __s_b.launch_builder(&f);
21233        b.arg(a)
21234            .arg(g)
21235            .arg(dst)
21236            .arg(&d16)
21237            .arg(&hd)
21238            .arg(&nh)
21239            .arg(&ti);
21240        unsafe {
21241            b.launch(cfg)?;
21242        }
21243        Ok(())
21244    }
21245
21246    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
21247    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
21248    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
21249    ///
21250    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
21251    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
21252    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
21253    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
21254    #[allow(clippy::too_many_arguments)]
21255    pub fn swiglu_clamped_mul_scaled(
21256        &self,
21257        gate: &CudaSlice<f32>,
21258        up: &CudaSlice<f32>,
21259        gs: f32,
21260        us: f32,
21261        limit: f32,
21262        dst: &mut CudaSlice<f32>,
21263        n: usize,
21264    ) -> Result<(), Box<dyn std::error::Error>> {
21265        debug_assert!(
21266            limit > 1e-6,
21267            "swiglu_clamped needs a live limit; use silu_mul_scaled"
21268        );
21269        let f = self.func("swiglu_clamped_mul_scaled_f32");
21270        let cfg = LaunchConfig::for_num_elems(n as u32);
21271        let ni = n as i32;
21272        let __s_b = self.gpu.stream();
21273        let mut b = __s_b.launch_builder(&f);
21274        b.arg(gate)
21275            .arg(up)
21276            .arg(&gs)
21277            .arg(&us)
21278            .arg(&limit)
21279            .arg(dst)
21280            .arg(&ni);
21281        unsafe {
21282            b.launch(cfg)?;
21283        }
21284        Ok(())
21285    }
21286
21287    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
21288    pub fn gated_rmsnorm(
21289        &self,
21290        o: &CudaSlice<f32>,
21291        w: &CudaSlice<f32>,
21292        z: &CudaSlice<f32>,
21293        dst: &mut CudaSlice<f32>,
21294        ncols: usize,
21295        nrows: usize,
21296        eps: f32,
21297    ) -> Result<(), Box<dyn std::error::Error>> {
21298        let f = self.func("gated_rmsnorm_f32");
21299        let cfg = LaunchConfig {
21300            grid_dim: (nrows as u32, 1, 1),
21301            block_dim: (128, 1, 1),
21302            shared_mem_bytes: 0,
21303        };
21304        let (nc, e) = (ncols as i32, eps);
21305        let __s_b = self.gpu.stream();
21306        let mut b = __s_b.launch_builder(&f);
21307        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
21308        unsafe {
21309            b.launch(cfg)?;
21310        }
21311        Ok(())
21312    }
21313
21314    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
21315    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
21316    pub fn gated_rmsnorm_f16out(
21317        &self,
21318        o: &CudaSlice<f32>,
21319        w: &CudaSlice<f32>,
21320        z: &CudaSlice<f32>,
21321        dst: &mut CudaSlice<f32>,
21322        dst16: &mut CudaSlice<u8>,
21323        ncols: usize,
21324        nrows: usize,
21325        eps: f32,
21326    ) -> Result<(), Box<dyn std::error::Error>> {
21327        let f = self.func("gated_rmsnorm_f16out_f32");
21328        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21329        let cfg = LaunchConfig {
21330            grid_dim: (nrows as u32, 1, 1),
21331            block_dim: (128, 1, 1),
21332            shared_mem_bytes: 0,
21333        };
21334        let (nc, e) = (ncols as i32, eps);
21335        let __s_b = self.gpu.stream();
21336        let mut b = __s_b.launch_builder(&f);
21337        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
21338        unsafe {
21339            b.launch(cfg)?;
21340        }
21341        Ok(())
21342    }
21343
21344    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
21345    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
21346    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
21347    #[allow(clippy::too_many_arguments)]
21348    pub fn add_rms_norm_zq8(
21349        &self,
21350        a: &CudaSlice<f32>,
21351        b_in: &CudaSlice<f32>,
21352        w: &CudaSlice<f32>,
21353        res: &mut CudaSlice<f32>,
21354        z: &mut CudaSlice<f32>,
21355        ncols: usize,
21356        nrows: usize,
21357        eps: f32,
21358    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
21359        assert!(ncols % 32 == 0);
21360        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
21361        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
21362        let f = self.func("add_rms_norm_zq8");
21363        let cfg = LaunchConfig {
21364            grid_dim: (nrows as u32, 1, 1),
21365            block_dim: (1024, 1, 1),
21366            shared_mem_bytes: 0,
21367        };
21368        let (nc, ep) = (ncols as i32, eps);
21369        let __s_b = self.gpu.stream();
21370        let mut b = __s_b.launch_builder(&f);
21371        b.arg(a)
21372            .arg(b_in)
21373            .arg(w)
21374            .arg(res)
21375            .arg(z)
21376            .arg(&mut q)
21377            .arg(&mut d)
21378            .arg(&nc)
21379            .arg(&ep);
21380        unsafe {
21381            b.launch(cfg)?;
21382        }
21383        Ok((q, d))
21384    }
21385
21386    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
21387    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
21388    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
21389    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
21390    pub fn gated_rmsnorm_zv(
21391        &self,
21392        o: &CudaSlice<f32>,
21393        w: &CudaSlice<f32>,
21394        z: &cudarc::driver::CudaView<f32>,
21395        dst: &mut CudaSlice<f32>,
21396        ncols: usize,
21397        nrows: usize,
21398        eps: f32,
21399    ) -> Result<(), Box<dyn std::error::Error>> {
21400        let f = self.func("gated_rmsnorm_f32");
21401        let cfg = LaunchConfig {
21402            grid_dim: (nrows as u32, 1, 1),
21403            block_dim: (128, 1, 1),
21404            shared_mem_bytes: 0,
21405        };
21406        let (nc, e) = (ncols as i32, eps);
21407        let __s_b = self.gpu.stream();
21408        let mut b = __s_b.launch_builder(&f);
21409        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
21410        unsafe {
21411            b.launch(cfg)?;
21412        }
21413        Ok(())
21414    }
21415
21416    pub fn gated_rmsnorm_f16out_zv(
21417        &self,
21418        o: &CudaSlice<f32>,
21419        w: &CudaSlice<f32>,
21420        z: &cudarc::driver::CudaView<f32>,
21421        dst: &mut CudaSlice<f32>,
21422        dst16: &mut CudaSlice<u8>,
21423        ncols: usize,
21424        nrows: usize,
21425        eps: f32,
21426    ) -> Result<(), Box<dyn std::error::Error>> {
21427        let f = self.func("gated_rmsnorm_f16out_f32");
21428        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21429        let cfg = LaunchConfig {
21430            grid_dim: (nrows as u32, 1, 1),
21431            block_dim: (128, 1, 1),
21432            shared_mem_bytes: 0,
21433        };
21434        let (nc, e) = (ncols as i32, eps);
21435        let __s_b = self.gpu.stream();
21436        let mut b = __s_b.launch_builder(&f);
21437        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
21438        unsafe {
21439            b.launch(cfg)?;
21440        }
21441        Ok(())
21442    }
21443
21444    pub fn gated_rmsnorm_q8_1(
21445        &self,
21446        o: &CudaSlice<f32>,
21447        w: &CudaSlice<f32>,
21448        z: &CudaSlice<f32>,
21449        ncols: usize,
21450        nrows: usize,
21451        eps: f32,
21452    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
21453        assert!(ncols % 32 == 0);
21454        let f = self.func("gated_rmsnorm_q8_1");
21455        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
21456        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
21457        let cfg = LaunchConfig {
21458            grid_dim: (nrows as u32, 1, 1),
21459            block_dim: (128, 1, 1),
21460            shared_mem_bytes: 0,
21461        };
21462        let (nc, ep) = (ncols as i32, eps);
21463        let __s_b = self.gpu.stream();
21464        let mut b = __s_b.launch_builder(&f);
21465        b.arg(o)
21466            .arg(w)
21467            .arg(z)
21468            .arg(&mut out_q)
21469            .arg(&mut out_d)
21470            .arg(&nc)
21471            .arg(&ep);
21472        unsafe {
21473            b.launch(cfg)?;
21474        }
21475        Ok((out_q, out_d))
21476    }
21477
21478    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
21479    pub fn transpose(
21480        &self,
21481        inp: &CudaSlice<f32>,
21482        rows: usize,
21483        cols: usize,
21484    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21485        let f = self.func("transpose_f32");
21486        let mut out = self.zeros(rows * cols)?;
21487        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
21488        let (r, c) = (rows as i32, cols as i32);
21489        let __s_b = self.gpu.stream();
21490        let mut b = __s_b.launch_builder(&f);
21491        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
21492        unsafe {
21493            b.launch(cfg)?;
21494        }
21495        Ok(out)
21496    }
21497
21498    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
21499    pub fn repeat_heads(
21500        &self,
21501        inp: &CudaSlice<f32>,
21502        out: &mut CudaSlice<f32>,
21503        head_dim: usize,
21504        n_in: usize,
21505        n_out: usize,
21506        t: usize,
21507    ) -> Result<(), Box<dyn std::error::Error>> {
21508        let f = self.func("repeat_heads_f32");
21509        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
21510        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
21511        let __s_b = self.gpu.stream();
21512        let mut b = __s_b.launch_builder(&f);
21513        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
21514        unsafe {
21515            b.launch(cfg)?;
21516        }
21517        Ok(())
21518    }
21519
21520    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
21521    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
21522    pub fn q_gate_split(
21523        &self,
21524        qf: &CudaSlice<f32>,
21525        q_out: &mut CudaSlice<f32>,
21526        gate_out: &mut CudaSlice<f32>,
21527        head_dim: usize,
21528        n_head: usize,
21529        t: usize,
21530    ) -> Result<(), Box<dyn std::error::Error>> {
21531        let f = self.func("q_gate_split_f32");
21532        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21533        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21534        let __s_b = self.gpu.stream();
21535        let mut b = __s_b.launch_builder(&f);
21536        b.arg(qf)
21537            .arg(q_out)
21538            .arg(gate_out)
21539            .arg(&hd)
21540            .arg(&nh)
21541            .arg(&ti);
21542        unsafe {
21543            b.launch(cfg)?;
21544        }
21545        Ok(())
21546    }
21547
21548    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
21549    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
21550    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
21551    pub fn qkv_to_gdn_repack(
21552        &self,
21553        conv_out: &CudaSlice<f32>,
21554        q_g: &mut CudaSlice<f32>,
21555        k_g: &mut CudaSlice<f32>,
21556        v_g: &mut CudaSlice<f32>,
21557        d_state: usize,
21558        num_v: usize,
21559        num_k: usize,
21560        key_dim: usize,
21561        t: usize,
21562    ) -> Result<(), Box<dyn std::error::Error>> {
21563        let f = self.func("qkv_to_gdn_repack_f32");
21564        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
21565        let (ds, nv, nk, kd, ti) = (
21566            d_state as i32,
21567            num_v as i32,
21568            num_k as i32,
21569            key_dim as i32,
21570            t as i32,
21571        );
21572        let __s_b = self.gpu.stream();
21573        let mut b = __s_b.launch_builder(&f);
21574        b.arg(conv_out)
21575            .arg(q_g)
21576            .arg(k_g)
21577            .arg(v_g)
21578            .arg(&ds)
21579            .arg(&nv)
21580            .arg(&nk)
21581            .arg(&kd)
21582            .arg(&ti);
21583        unsafe {
21584            b.launch(cfg)?;
21585        }
21586        Ok(())
21587    }
21588
21589    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
21590    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
21591    pub fn conv_left_pad(
21592        &self,
21593        src: &CudaSlice<f32>,
21594        dst: &mut CudaSlice<f32>,
21595        conv_dim: usize,
21596        t: usize,
21597        pad: usize,
21598    ) -> Result<(), Box<dyn std::error::Error>> {
21599        let f = self.func("conv_left_pad_f32");
21600        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
21601        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
21602        let __s_b = self.gpu.stream();
21603        let mut b = __s_b.launch_builder(&f);
21604        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
21605        unsafe {
21606            b.launch(cfg)?;
21607        }
21608        Ok(())
21609    }
21610
21611    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
21612    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
21613    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
21614    pub fn conv_assemble_and_roll(
21615        &self,
21616        qkv_col: &CudaSlice<f32>,
21617        conv_state: &mut CudaSlice<f32>,
21618        conv_in: &mut CudaSlice<f32>,
21619        conv_dim: usize,
21620        pad: usize,
21621    ) -> Result<(), Box<dyn std::error::Error>> {
21622        let f = self.func("conv_assemble_and_roll_f32");
21623        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21624        let (cd, p) = (conv_dim as i32, pad as i32);
21625        let __s_b = self.gpu.stream();
21626        let mut b = __s_b.launch_builder(&f);
21627        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
21628        unsafe {
21629            b.launch(cfg)?;
21630        }
21631        Ok(())
21632    }
21633
21634    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
21635    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
21636    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
21637    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
21638    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
21639    pub fn ssm_conv1d_fused_decode(
21640        &self,
21641        qkv_col: &CudaSlice<f32>,
21642        conv_state: &mut CudaSlice<f32>,
21643        w: &CudaSlice<f32>,
21644        conv_out: &mut CudaSlice<f32>,
21645        conv_dim: usize,
21646        d_conv: usize,
21647    ) -> Result<(), Box<dyn std::error::Error>> {
21648        let f = self.func("ssm_conv1d_fused_decode_f32");
21649        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21650        let (cd, dc) = (conv_dim as i32, d_conv as i32);
21651        let __s_b = self.gpu.stream();
21652        let mut b = __s_b.launch_builder(&f);
21653        b.arg(qkv_col)
21654            .arg(conv_state)
21655            .arg(w)
21656            .arg(conv_out)
21657            .arg(&cd)
21658            .arg(&dc);
21659        unsafe {
21660            b.launch(cfg)?;
21661        }
21662        Ok(())
21663    }
21664
21665    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
21666    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
21667    pub fn slice_range(
21668        &self,
21669        src: &CudaSlice<f32>,
21670        start: usize,
21671        len: usize,
21672    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21673        let host = self.gpu.stream().clone_dtoh(src)?;
21674        self.gpu.stream().synchronize()?;
21675        Ok(self.htod(&host[start..start + len])?)
21676    }
21677}
21678
21679#[cfg(test)]
21680mod target_dispatch_tests {
21681    use super::legacy_quant_gemm_allowed;
21682
21683    #[test]
21684    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
21685        // sm_120a native lane
21686        assert!(legacy_quant_gemm_allowed(false, false, false));
21687        assert!(!legacy_quant_gemm_allowed(false, false, true));
21688        // pure portable lane (sm_89): gated
21689        assert!(!legacy_quant_gemm_allowed(true, false, false));
21690        assert!(!legacy_quant_gemm_allowed(true, false, true));
21691        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
21692        assert!(legacy_quant_gemm_allowed(true, true, false));
21693        assert!(!legacy_quant_gemm_allowed(true, true, true));
21694    }
21695
21696    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
21697    #[test]
21698    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
21699        assert!(!legacy_quant_gemm_allowed(
21700            cfg!(memra_portable_cuda),
21701            cfg!(memra_hopper_mma),
21702            false
21703        ));
21704    }
21705
21706    #[cfg(memra_hopper_mma)]
21707    #[test]
21708    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
21709        assert!(legacy_quant_gemm_allowed(
21710            cfg!(memra_portable_cuda),
21711            cfg!(memra_hopper_mma),
21712            false
21713        ));
21714        assert!(super::portable_mma_gated() == false);
21715    }
21716}
21717
21718/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
21719/// inherent methods (inherent methods win name resolution, so no recursion).
21720impl memra_kv::KvDev for Engine {
21721    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21722        Engine::zeros(self, n)
21723    }
21724    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21725        Engine::uninit(self, n)
21726    }
21727    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
21728        Engine::alloc_u8(self, n)
21729    }
21730    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
21731        Engine::htod_i32(self, v)
21732    }
21733    fn clone_dtod(
21734        &self,
21735        src: &CudaSlice<f32>,
21736    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21737        Engine::clone_dtod(self, src)
21738    }
21739    fn copy_into(
21740        &self,
21741        dst: &mut CudaSlice<f32>,
21742        off: usize,
21743        src: &CudaSlice<f32>,
21744        len: usize,
21745    ) -> Result<(), Box<dyn std::error::Error>> {
21746        Engine::copy_into(self, dst, off, src, len)
21747    }
21748    fn set_i32_one(
21749        &self,
21750        d: &mut CudaSlice<i32>,
21751        v: i32,
21752    ) -> Result<(), Box<dyn std::error::Error>> {
21753        Engine::set_i32_one(self, d, v)
21754    }
21755}