Skip to main content

memra_engine/
lib.rs

1//! memra engine: Stage-1 correctness-first forward-pass kernels + ops, on sm_120 via cudarc.
2
3use cudarc::driver::{
4    CudaContext, CudaFunction, CudaModule, CudaSlice, CudaStream, LaunchConfig, PushKernelArg,
5};
6use cudarc::nvrtc::Ptx;
7use std::sync::{Arc, Mutex};
8
9#[cfg(debug_assertions)]
10pub(crate) fn debug_assert_tensor_stream_device<T>(
11    tensor: &CudaSlice<T>,
12    stream: &CudaStream,
13    site: &str,
14) {
15    let tensor_dev = tensor.ordinal();
16    let stream_dev = stream.context().ordinal();
17    assert_eq!(
18        tensor_dev, stream_dev,
19        "PP cross-device tensor read at {site}: tensor on dev{tensor_dev}, stream on dev{stream_dev}"
20    );
21}
22
23pub use memra_gguf;
24pub use memra_runtime;
25
26pub mod forward;
27pub mod hybrid;
28pub mod hybrid_forward;
29pub mod model;
30pub mod sigrouter_contract;
31pub mod vision;
32pub mod vision_gemma;
33pub mod vision_pre;
34/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
35/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
36pub mod cache {
37    pub use memra_kv::*;
38}
39pub mod decode;
40pub mod decode_batch;
41pub mod dflash;
42pub mod eagle;
43pub mod gemma_spec;
44pub mod graph_update;
45/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
46/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
47/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
48pub mod mla;
49pub mod moesd;
50pub mod parallel;
51pub mod pp;
52pub mod round_stream;
53pub mod spec;
54pub use memra_sampling as sampler;
55
56/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
57/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
58/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
59/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
60/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
61///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
62///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
63///                     stream sync per projection (round-47 ledgered defect).
64///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
65///                     construction, zero syncs, f32 C with the act row-scale folded in.
66/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
67/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
68/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
69/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
70/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
71/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
72///
73/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
74/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
75/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
76/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
77/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
78/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
79/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
80/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
81///
82/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
83/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
84/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
85/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
86/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
87/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
88/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
89///
90/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
91/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
92/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
93/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
94/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
95/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
96/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
97/// the k-quant-only admission survives as the rollback seam, not the default.
98/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
99pub fn moe_f16g_mode() -> u8 {
100    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
101    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
102        Ok("0") => 0,
103        Ok("2") => 2,
104        Ok("3") => 3,
105        Ok(_) => 1,
106        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
107        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
108        Err(_) => 2,
109    })
110}
111/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
112/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
113/// (shape_sel, cross) for the FFI:
114///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
115///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
116///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
117///                         back to 32x64 in-launcher when the device/in_f can't take it).
118///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
119///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
120///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
121///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
122///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
123///                         verdict was stale).
124pub fn moe_f16g_sk_params() -> (i32, i32) {
125    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
126    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
127        Ok("0") => (-1, 0),
128        Ok("32") => (0, i32::MAX),
129        Ok("128") => (0, 1),
130        _ => {
131            let cross = std::env::var("MEMRA_F16G_SK_CROSS")
132                .ok()
133                .and_then(|v| v.parse().ok())
134                .unwrap_or(64);
135            (0, cross)
136        }
137    })
138}
139/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
140/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
141/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
142/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
143/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
144/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
145/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
146/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
147/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
148/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
149pub fn moe_f16g_direct_on(qtype: i32) -> bool {
150    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
151    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
152        Ok("0") => 0,
153        Ok("kq") => 1,
154        _ => 2,
155    });
156    match m {
157        0 => false,
158        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
159        _ => true,
160    }
161}
162/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
163/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
164/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
165/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
166/// stage under q35's routing skew. Bit-identical to every other sk form by construction
167/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
168/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
169/// tail. in_f % 64 != 0 falls back in-launcher.
170pub fn moe_f16g_tail_on() -> bool {
171    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
172    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
173}
174
175/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
176/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
177/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
178/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
179/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
180/// still opens this door for A/B.
181pub fn moe_f16g_gemma_on() -> bool {
182    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
183    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
184}
185
186/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
187/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
188/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
189pub fn moe_fuse_actq_on() -> bool {
190    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
191    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
192}
193
194/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
195/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
196/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
197/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
198/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
199/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
200/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
201/// verify already use (dispatch parity, one router kernel for every t).
202/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
203pub fn router_prefill_exact_on() -> bool {
204    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
205    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
206}
207
208pub fn router_kernel_on() -> bool {
209    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
210    *ON.get_or_init(|| {
211        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
212        if !on {
213            eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)");
214        }
215        on
216    })
217}
218
219/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
220/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
221/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
222/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
223/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
224/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
225/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
226/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
227/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
228/// seam, perf-only: bits are equal by the kernel-check gate).
229/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
230/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
231/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
232/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
233pub const ROUTER_BATCH_MIN_T: usize = 8;
234pub fn router_batch_on() -> bool {
235    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
236    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
237}
238mod cpu_experts;
239#[cfg(memra_cutlass)]
240pub mod cutlass_ffi;
241pub mod f16_ffi;
242pub mod fp8_ffi;
243pub mod mmq_ffi;
244pub mod moe_cache;
245pub mod prime_graph;
246pub mod spill;
247mod spill_pread;
248
249// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
250// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
251// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
252// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
253// broke every machine that wasn't the build machine. Same bytes, same module image;
254// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
255const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
256const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
257const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
258const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
259const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
260const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
261/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
262const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
263
264/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
265/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
266/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
267/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
268/// compile-time default (zero behavior change).
269fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
270    assert!(
271        !(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
272        "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane"
273    );
274    match std::env::var("MEMRA_GEMM_FATBIN") {
275        Ok(path) => std::borrow::Cow::Owned(
276            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}")),
277        ),
278        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
279    }
280}
281
282/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
283/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
284/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
285/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
286/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
287/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
288pub(crate) const fn portable_mma_gated() -> bool {
289    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
290}
291
292/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
293/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
294/// in a pure helper so the dispatch guard can be regression-tested without constructing an
295/// Engine or allocating a GPU tensor.
296const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
297    (!portable_cuda || hopper_mma) && !no_gemm
298}
299
300// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
301// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
302// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
303// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
304// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
305// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
306// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
307const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
308const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
309const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
310const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
311const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
312
313/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
314/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
315pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
316
317/// The flash_attn fatbin matching the selected KV formats.
318fn flash_fatbin_bytes() -> &'static [u8] {
319    match kv_cache_formats() {
320        ("q8_0", "q5_1") => FLASH_FATBIN,
321        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
322        ("q8_0", "fp8") => FLASH_FATBIN_VF8,
323        ("fp8", "q5_1") => FLASH_FATBIN_KF8,
324        ("fp8", "q4_0") => FLASH_FATBIN_KF8VQ4,
325        ("fp8", "fp8") => FLASH_FATBIN_KF8VF8,
326        other => unreachable!("kv_cache_formats returned {other:?}"),
327    }
328}
329
330/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
331/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
332/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
333/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
334/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
335/// defaults (zero behavior change).
336fn k1_launch_override() -> Option<(u32, u32, u32)> {
337    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
338    *K1.get_or_init(|| {
339        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
340        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
341        match p.as_slice() {
342            [bm, bn, w] => Some((*bm, *bn, *w)),
343            _ => None,
344        }
345    })
346}
347
348/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
349/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
350/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
351/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
352/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
353/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
354pub(crate) fn wgmma_gemm_enabled() -> bool {
355    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
356    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
357}
358
359/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
360/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
361/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
362/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
363/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
364/// the split count changes the combine's FP summation order, and the spec verify's batched forward
365/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
366/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
367/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
368/// adaptive retries (any retry MUST pass run-spec self-consistency first).
369/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
370/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
371/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
372/// between eager decode and the verify (the spec-exactness law).
373pub const FA_VEC_MIN_TKV: usize = 96;
374/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
375/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
376/// which moves the crossover — sweep per model, adopt per the battery.
377pub fn fa_vec_min_tkv() -> usize {
378    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
379    *V.get_or_init(|| {
380        std::env::var("MEMRA_FA_VEC_MIN")
381            .ok()
382            .and_then(|v| v.parse().ok())
383            .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
384    })
385}
386
387/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
388/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
389/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
390///
391/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
392/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
393/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
394/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
395/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
396/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
397pub fn fa_f16pv_on() -> bool {
398    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
399    *ON.get_or_init(|| {
400        std::env::var("MEMRA_FA_F16PV")
401            .map(|v| v != "0")
402            .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
403    })
404}
405
406/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
407/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
408/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
409pub fn fa512_hp_on() -> bool {
410    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
411    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
412}
413
414/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
415/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
416/// accumulation. Even n_head and even GQA group required (guarded per call).
417pub fn faw_hp_on() -> bool {
418    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
419    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
420}
421
422/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
423/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
424/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
425pub fn fa512_wide_warps() -> usize {
426    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
427    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
428        Ok("1") => 4,
429        _ => 2,
430    })
431}
432
433/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
434/// and the gemma global-layer rows/parity call sites.
435pub fn fa512_min_tkv() -> usize {
436    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
437    *FA512_MIN.get_or_init(|| {
438        std::env::var("MEMRA_FA512_MIN")
439            .ok()
440            .and_then(|v| v.parse().ok())
441            .unwrap_or(512)
442    })
443}
444/// Per-model crossover default, set at model load BEFORE the first decode (per-model
445/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
446/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
447pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
448    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
449/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
450/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
451/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
452pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(32);
453/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
454/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
455/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
456/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
457/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
458pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
459    std::sync::atomic::AtomicBool::new(false);
460/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
461/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
462/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
463/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
464/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
465/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
466pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
467    std::sync::atomic::AtomicBool::new(true);
468pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
469    std::sync::atomic::AtomicUsize::new(16);
470/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
471/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
472/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
473/// latency-bound at 256 threads — 7us/launch measured).
474pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
475/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
476pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
477/// Per-model stream-k entry override for SPEC serving (-1 = unset → env/default;
478/// 0 = force tiling; 1 = admit the deterministic form selector). The former timing
479/// selector made identical boots choose different fold orders; `MEMRA_MMQ_SK_FORM` is the
480/// explicit numerical-form seam. mmq_ffi reads this before the env.
481pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
482/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
483/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
484pub use memra_kv::KV_FP8_FORCE;
485pub(crate) fn rms_block() -> u32 {
486    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
487    *V.get_or_init(|| {
488        std::env::var("MEMRA_RMS_BLOCK")
489            .ok()
490            .and_then(|v| v.parse().ok())
491            .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
492    })
493}
494
495pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
496    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
497    if let Some(forced) = *S.get_or_init(|| {
498        std::env::var("MEMRA_FA_SPLIT")
499            .ok()
500            .and_then(|v| v.parse().ok())
501            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
502    }) {
503        return forced;
504    }
505    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
506    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
507    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
508    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
509    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
510    //
511    // SM-AWARE SHORT-CTX RUNG (2026-07-06 g7e): the 32-key rung was tuned on the 82-SM 5090.
512    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
513    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on g7e (N=1 sweep + N=3
514    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
515    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
516    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
517    // rig-divergence law: this branch is measured on 188 SMs only).
518    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
519    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
520    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
521    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
522    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
523        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1")
524    {
525        return if t_kv <= 8192 {
526            16
527        } else if t_kv <= 16384 {
528            64
529        } else {
530            128
531        };
532    }
533    let big_rig = fa_sm_count() >= 128;
534    if big_rig {
535        let _ = n_head_kv;
536        if t_kv <= 2048 {
537            16
538        } else if t_kv <= 16384 {
539            64
540        } else {
541            128
542        }
543    } else if n_head_kv <= 4 {
544        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
545        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
546        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
547        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
548        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
549        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
550        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
551        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
552        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
553        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
554        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
555        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
556        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
557        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
558        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
559        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
560        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
561        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
562        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
563        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
564        if t_kv <= 512 {
565            8
566        } else if t_kv <= 16384 {
567            64
568        } else {
569            128
570        }
571    } else {
572        if t_kv <= 8192 {
573            32
574        } else if t_kv <= 16384 {
575            64
576        } else {
577            128
578        }
579    }
580}
581
582/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
583/// same attribute Engine::batched_variant reads).
584fn fa_sm_count() -> i32 {
585    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
586    *N.get_or_init(|| {
587        cudarc::driver::result::init().ok();
588        cudarc::driver::result::device::get(0)
589            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
590                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
591            .unwrap_or(82)
592    })
593}
594
595/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
596/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
597/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
598fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
599    match head_dim {
600        256 => Ok(""),
601        128 => Ok("_hd128"),
602        d => Err(format!(
603            "fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
604                          callers must gate to sdpa_naive"
605        )
606        .into()),
607    }
608}
609
610/// Quant type codes matching qmatvec.cu QType enum.
611pub const QT_Q8_0: i32 = 0;
612pub const QT_Q4_K: i32 = 1;
613pub const QT_Q6_K: i32 = 2;
614pub const QT_Q5_K: i32 = 3;
615pub const QT_Q3_K: i32 = 4;
616pub const QT_IQ4_XS: i32 = 5;
617pub const QT_IQ3_S: i32 = 6;
618pub const QT_NVFP4: i32 = 7;
619/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
620/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
621/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
622/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
623/// — ONE weight copy total, no Q8_0 re-encode duplicate.
624pub const QT_F8_E4M3: i32 = 10;
625/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
626/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
627pub const QT_NVFP4_RP: i32 = 9;
628/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
629pub const QT_F32: i32 = 8;
630pub const QT_BF16: i32 = 11;
631pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
632/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
633/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
634/// dp4a/MMQ implementation exists.
635pub const QT_Q2_K: i32 = 13;
636/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
637/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
638/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
639/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
640/// scalar `scale` field is 1.0 by the layout contract.
641///
642/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
643/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
644/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
645/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
646/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
647/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
648/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
649/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
650/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
651pub const QT_F8_E4M3_BLK: i32 = 14;
652
653/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
654pub struct Engine {
655    pub gpu: memra_runtime::Gpu,
656    module: Arc<CudaModule>,
657    hybrid: Arc<CudaModule>,
658    qmatvec: Arc<CudaModule>,
659    flash: Arc<CudaModule>,
660    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
661    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
662    /// Lazy: loaded on first global-format use; None until then.
663    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
664    gemm: Arc<CudaModule>,
665    router: Arc<CudaModule>,
666    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
667    sample: Arc<CudaModule>,
668    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
669    /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
670    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
671    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
672    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
673    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
674    /// the single largest block. The cache still owns every address for its full lifetime.
675    moe_cache_layout: Mutex<Option<Vec<usize>>>,
676    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
677    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
678    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
679    /// verify between replays) reuse their addresses and the replay reads/writes live memory
680    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
681    capture_keep_on: std::sync::atomic::AtomicBool,
682    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
683    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
684    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
685    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
686    verify_exact: std::sync::atomic::AtomicBool,
687    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
688    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
689    pub copy_stream: Arc<CudaStream>,
690    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
691    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
692    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
693    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
694    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
695    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
696    #[cfg(memra_cutlass)]
697    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
698    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
699    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
700    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
701    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
702    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
703    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
704    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
705    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
706    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
707    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
708    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
709    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
710    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
711    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
712    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
713    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
714    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
715    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
716    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
717    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
718    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
719    /// before capture under the generate_graph tracking-off window so it carries no events).
720    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
721    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
722    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
723    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
724    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
725    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
726    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
727    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
728    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
729    router_stage: Mutex<Option<PinnedStage>>,
730}
731
732/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
733/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
734/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
735/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
736/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
737/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
738/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
739/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
740/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
741fn fa_v2_on() -> bool {
742    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
743    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
744    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
745    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
746    // + graph bit-identity green on all three models.
747    std::env::var("MEMRA_FA_V2")
748        .map(|v| v != "0")
749        .unwrap_or(true)
750}
751
752/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
753/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
754/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
755/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
756/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
757/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
758/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
759fn fa_v3_on() -> bool {
760    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
761    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
762    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
763    std::env::var("MEMRA_FA_V3")
764        .map(|v| v != "0")
765        .unwrap_or(true)
766}
767
768/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
769/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
770/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
771/// predicate so the twins can never diverge.
772fn fa_v4_mode() -> &'static str {
773    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
774    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
775}
776fn fa_v4_on() -> bool {
777    fa_v4_mode() != "0"
778} // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
779/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
780/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
781/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
782/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
783/// stays kernel-family-identical to decode at the same t_kv.
784/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
785/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
786pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
787    std::sync::atomic::AtomicUsize::new(1024);
788pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
789    std::sync::atomic::AtomicUsize::new(usize::MAX);
790pub fn fa_v4_at_pub(t_kv: usize) -> bool {
791    fa_v4_at(t_kv)
792}
793fn fa_v4_at(t_kv: usize) -> bool {
794    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
795    let mx = *M.get_or_init(|| {
796        std::env::var("MEMRA_FA_V4_MAX")
797            .ok()
798            .and_then(|v| v.parse().ok())
799            .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
800    });
801    fa_v4_on() && t_kv < mx
802}
803/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
804/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
805/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
806/// (same split partition, same softmax/accumulation order, same partials/combine) and only
807/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
808/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
809/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
810/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
811/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
812/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
813/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
814/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
815/// within one process (the v2/v3 pattern).
816pub const FA_DEEP_MIN_DEFAULT: usize = 0;
817fn fa_deep_at(t_kv: usize) -> bool {
818    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") {
819        return false;
820    }
821    let min = std::env::var("MEMRA_FA_DEEP_MIN")
822        .ok()
823        .and_then(|v| v.parse().ok())
824        .unwrap_or(FA_DEEP_MIN_DEFAULT);
825    t_kv >= min
826}
827/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
828pub fn fa_deep_at_pub(t_kv: usize) -> bool {
829    fa_deep_at(t_kv)
830}
831
832fn fa_v3_active(head_dim: usize) -> bool {
833    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
834    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
835    fa_v3_on()
836        && head_dim % 128 == 0
837        && kv_cache_formats() == ("q8_0", "q5_1")
838        && !Engine::kv_fp8_on()
839}
840
841/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
842/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
843/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
844/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
845/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
846/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
847/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
848pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
849    std::env::var("MEMRA_NO_FA_VEC").is_err()
850        && t_kv >= fa_vec_min_tkv()
851        && head_dim == 256
852        && fa_v4_at(t_kv)
853        && !matches!(fa_v4_mode(), "noB3" | "stage")
854        && !Engine::kv_fp8_on()
855}
856/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
857pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize {
858    fa_split_keys(t_kv, n_head_kv)
859}
860
861/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
862/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
863/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
864/// so we allocate through `result::malloc_host` with flags=0 directly.
865struct PinnedStage {
866    ptr: *mut u8,
867    cap: usize,
868}
869unsafe impl Send for PinnedStage {}
870impl PinnedStage {
871    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
872        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
873        Ok(PinnedStage { ptr, cap })
874    }
875}
876impl Drop for PinnedStage {
877    fn drop(&mut self) {
878        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
879    }
880}
881
882/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
883/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
884pub const ARGMAX_NB: usize = 256;
885
886/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
887pub(crate) use memra_fa3_vl as fa3_vl_raw;
888
889unsafe extern "C" {
890    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
891    fn memra_fa3_prefill(
892        q16: *const core::ffi::c_void,
893        k16: *const core::ffi::c_void,
894        v16: *const core::ffi::c_void,
895        o: *mut f32,
896        t: i32,
897        h: i32,
898        hkv: i32,
899        d: i32,
900        scale: f32,
901        stream: *mut core::ffi::c_void,
902    ) -> i32;
903    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
904    pub(crate) fn memra_fa3_vl(
905        q16s: *const *const core::ffi::c_void,
906        k16s: *const *const core::ffi::c_void,
907        v16s: *const *const core::ffi::c_void,
908        os: *const *mut f32,
909        ts: *const i32,
910        b: i32,
911        h: i32,
912        hkv: i32,
913        d: i32,
914        scale: f32,
915        stream: *mut core::ffi::c_void,
916    ) -> i32;
917}
918
919/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
920/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
921/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
922/// (slots are never re-allocated), so passing raw values is stable across the launch.
923#[repr(C)]
924#[derive(Clone, Copy)]
925pub struct WPtr8(pub [u64; 8]);
926unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
927
928/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
929/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
930/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
931/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
932#[repr(C)]
933#[derive(Clone, Copy, Default)]
934pub struct GdnSeqVl {
935    pub kb16: u64,
936    pub gcum: u64,
937    pub beta: u64,
938    pub u: u64,
939    pub wb16: u64,
940    pub y: u64,
941    pub ssnap: u64,
942    pub state_in: u64,
943    pub state_out: u64,
944    pub q: u64,
945    pub p: u64,
946    pub o: u64,
947    pub k: u64,
948    pub v: u64,
949    pub g: u64,
950    pub a: u64,
951    pub w: u64,
952    pub t: i32,
953    pub nc: i32,
954}
955unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
956#[repr(C)]
957#[derive(Clone, Copy)]
958pub struct GdnVl8(pub [GdnSeqVl; 8]);
959unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
960
961/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
962/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
963#[repr(C)]
964#[derive(Clone, Copy, Default)]
965pub struct GdnWVl {
966    pub qb16: u64,
967    pub pb16: u64,
968}
969unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
970#[repr(C)]
971#[derive(Clone, Copy)]
972pub struct GdnWVl8(pub [GdnWVl; 8]);
973unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
974
975/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
976#[repr(C)]
977#[derive(Clone, Copy, Default)]
978pub struct GdnPrepVl {
979    pub qkv: u64,
980    pub conv_state: u64,
981    pub conv_out: u64,
982    pub q_g: u64,
983    pub k_g: u64,
984    pub v_g: u64,
985    pub q_l2: u64,
986    pub k_l2: u64,
987    pub beta_raw: u64,
988    pub alpha: u64,
989    pub beta: u64,
990    pub g_log: u64,
991    pub o: u64,
992    pub z: u64,
993    pub gn: u64,
994    pub gn16: u64,
995    pub kb16: u64,
996    pub qb16: u64,
997    pub t: i32,
998    pub pad: i32,
999}
1000unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
1001#[repr(C)]
1002#[derive(Clone, Copy)]
1003pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
1004unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
1005
1006/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
1007#[repr(C)]
1008#[derive(Clone, Copy, Default)]
1009pub struct FaSeqVl {
1010    pub q: u64,
1011    pub k16: u64,
1012    pub v16: u64,
1013    pub o: u64,
1014    pub kf: u64,
1015    pub vf: u64,
1016    pub t: i32,
1017    pub pad: i32,
1018}
1019unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
1020#[repr(C)]
1021#[derive(Clone, Copy)]
1022pub struct FaVl8(pub [FaSeqVl; 8]);
1023unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
1024
1025/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
1026#[repr(C)]
1027#[derive(Clone, Copy, Default)]
1028pub struct AttnPreVl {
1029    pub qf: u64,
1030    pub kf: u64,
1031    pub vf: u64,
1032    pub q: u64,
1033    pub gate: u64,
1034    pub qn: u64,
1035    pub kn: u64,
1036    pub kc: u64,
1037    pub vc: u64,
1038    pub t: i32,
1039    pub pad: i32,
1040}
1041unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
1042#[repr(C)]
1043#[derive(Clone, Copy)]
1044pub struct AttnPreVl8(pub [AttnPreVl; 8]);
1045unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
1046
1047/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
1048/// varlen K1-K5 chain fills them).
1049pub struct GdnChunkBufs {
1050    pub gcum: CudaSlice<f32>,
1051    pub a: CudaSlice<f32>,
1052    pub p: CudaSlice<f32>,
1053    pub u: CudaSlice<f32>,
1054    pub w: CudaSlice<f32>,
1055    pub kb16: CudaSlice<u8>,
1056    pub wb16: CudaSlice<u8>,
1057    pub y16: CudaSlice<u8>,
1058    pub ssnap16: CudaSlice<u8>,
1059    pub qb16: CudaSlice<u8>,
1060    pub pb16: CudaSlice<u8>,
1061    pub o: CudaSlice<f32>,
1062    pub t: usize,
1063    pub nc: usize,
1064}
1065
1066/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
1067#[repr(C)]
1068#[derive(Clone, Copy)]
1069pub struct F32x8(pub [f32; 8]);
1070unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
1071
1072/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
1073/// process. Bench binaries read it right after the call to print gen-only throughput without the
1074/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
1075pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1076
1077impl Engine {
1078    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
1079        let gpu = memra_runtime::Gpu::new(ordinal)?;
1080        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
1081        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
1082        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
1083        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
1084            use cudarc::driver::sys::CUdevice_attribute_enum as A;
1085            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
1086                .and_then(|d| unsafe {
1087                    Ok((
1088                        cudarc::driver::result::device::get_attribute(
1089                            d,
1090                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
1091                        )?,
1092                        cudarc::driver::result::device::get_attribute(
1093                            d,
1094                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
1095                        )?,
1096                    ))
1097                })
1098                .unwrap_or((0, 0));
1099            let built = env!("MEMRA_BUILT_CUDA_ARCH");
1100            let ok = matches!(
1101                (built, maj, min),
1102                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9)
1103            );
1104            if !ok {
1105                return Err(format!(
1106                    "memra was built for sm_{built} but device {ordinal} reports compute \
1107                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
1108                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass."
1109                )
1110                .into());
1111            }
1112        }
1113        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
1114        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
1115        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
1116        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
1117        unsafe {
1118            use cudarc::driver::sys;
1119            let dev: sys::CUdevice = ordinal as sys::CUdevice;
1120            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1121            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
1122                let mut thresh: u64 = u64::MAX;
1123                let _ = sys::cuMemPoolSetAttribute(
1124                    pool,
1125                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
1126                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
1127                );
1128            }
1129        }
1130        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
1131        let hybrid = gpu
1132            .ctx
1133            .load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
1134        let qmatvec = gpu
1135            .ctx
1136            .load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
1137        let flash = gpu
1138            .ctx
1139            .load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
1140        let gemm = gpu
1141            .ctx
1142            .load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
1143        let router = gpu
1144            .ctx
1145            .load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
1146        let sample = gpu
1147            .ctx
1148            .load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
1149        let copy_stream = gpu.ctx.new_stream()?;
1150        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
1151        // cudarc is in multi-stream mode (main stream +
1152        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
1153        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
1154        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
1155        // (~7 ms/tok host time, measured nsys 2026-07-04 g7e), and +4.6% measured on 27B decode —
1156        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
1157        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
1158        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
1159        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
1160        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
1161        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
1162        // implicit event tracking.
1163        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
1164        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
1165        if std::env::var("MEMRA_EVT")
1166            .map(|v| v == "1")
1167            .unwrap_or(false)
1168        {
1169            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1170        } else {
1171            unsafe {
1172                gpu.ctx.disable_event_tracking();
1173            }
1174        }
1175        Ok(Self {
1176            gpu,
1177            module,
1178            hybrid,
1179            qmatvec,
1180            flash,
1181            flash_g: std::sync::OnceLock::new(),
1182            gemm,
1183            router,
1184            sample,
1185            moe_cache: Mutex::new(None),
1186            moe_cache_layout: Mutex::new(None),
1187            copy_stream,
1188            capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1189            verify_exact: std::sync::atomic::AtomicBool::new(false),
1190            capture_keep: Mutex::new(Vec::new()),
1191            argmax_partials: Mutex::new(None),
1192            prime_deqw_ws: Mutex::new(None),
1193            router_stage: Mutex::new(None),
1194            fp8_scratch: Mutex::new(None),
1195            fa_vf16_scratch: Mutex::new(None),
1196            fa_part_pool: Mutex::new(None),
1197            fa_part_retired: Mutex::new(Vec::new()),
1198            fn_cache: Mutex::new(Default::default()),
1199            f16_scratch: Mutex::new(None),
1200            #[cfg(memra_cutlass)]
1201            cutlass_scratch: Mutex::new(None),
1202        })
1203    }
1204
1205    pub fn ctx(&self) -> &Arc<CudaContext> {
1206        &self.gpu.ctx
1207    }
1208
1209    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1210    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1211    ///
1212    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1213    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1214    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1215    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1216    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1217    ///
1218    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1219    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1220    /// under-count headroom does not belong in a gate that queues real work, but the honest
1221    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1222    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1223    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1224    ///
1225    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1226    pub fn pool_cached_bytes(&self) -> usize {
1227        let (reserved, used) = self.pool_reserved_used();
1228        reserved.saturating_sub(used)
1229    }
1230
1231    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1232    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1233    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1234    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1235    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1236    /// (0, 0) if the pool cannot be queried.
1237    pub fn pool_reserved_used(&self) -> (usize, usize) {
1238        use cudarc::driver::sys;
1239        unsafe {
1240            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1241            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1242                != sys::CUresult::CUDA_SUCCESS
1243            {
1244                return (0, 0);
1245            }
1246            let (mut reserved, mut used) = (0u64, 0u64);
1247            if sys::cuMemPoolGetAttribute(
1248                pool,
1249                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1250                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1251            ) != sys::CUresult::CUDA_SUCCESS
1252            {
1253                return (0, 0);
1254            }
1255            if sys::cuMemPoolGetAttribute(
1256                pool,
1257                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1258                &mut used as *mut u64 as *mut core::ffi::c_void,
1259            ) != sys::CUresult::CUDA_SUCCESS
1260            {
1261                return (0, 0);
1262            }
1263            (reserved as usize, used as usize)
1264        }
1265    }
1266
1267    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1268    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1269    pub fn stream(&self) -> Arc<CudaStream> {
1270        self.gpu.stream()
1271    }
1272    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1273    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1274    pub fn gkv_on() -> bool {
1275        memra_kv::gkv_on()
1276    }
1277
1278    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1279    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1280    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1281    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1282    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1283    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1284    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1285    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1286    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1287    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1288    /// ON for both — no acceptance cost measured.
1289    pub fn wkv_on() -> bool {
1290        memra_kv::wkv_on()
1291    }
1292
1293    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1294    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1295    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1296    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1297    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1298    pub fn kv_fp8_on() -> bool {
1299        memra_kv::kv_fp8_on()
1300    }
1301
1302    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1303    /// when the fp8-globals arm is on; everything else from the default flash module.
1304    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1305        if head_dim == 512 && Self::gkv_on() {
1306            self.func_g(name)
1307        } else {
1308            self.func(name)
1309        }
1310    }
1311
1312    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1313    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1314    /// per-format fatbins; fall back to the base modules for those.
1315    fn func_g(&self, name: &str) -> CudaFunction {
1316        let m = self.flash_g.get_or_init(|| {
1317            self.gpu
1318                .ctx
1319                .load_module(cudarc::nvrtc::Ptx::from_binary(
1320                    FLASH_FATBIN_KF8VF8.to_vec(),
1321                ))
1322                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1323        });
1324        let key = format!("g:{name}");
1325        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) {
1326            return f.clone();
1327        }
1328        let f = match m.load_function(name) {
1329            Ok(f) => f,
1330            Err(_) => self.func(name),
1331        };
1332        self.fn_cache.lock().unwrap().insert(key, f.clone());
1333        f
1334    }
1335
1336    fn func(&self, name: &str) -> CudaFunction {
1337        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1338        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1339        if let Some(f) = self.fn_cache.lock().unwrap().get(name) {
1340            return f.clone();
1341        }
1342        let f = self
1343            .module
1344            .load_function(name)
1345            .or_else(|_| self.hybrid.load_function(name))
1346            .or_else(|_| self.qmatvec.load_function(name))
1347            .or_else(|_| self.flash.load_function(name))
1348            .or_else(|_| self.gemm.load_function(name))
1349            .or_else(|_| self.router.load_function(name))
1350            .or_else(|_| self.sample.load_function(name))
1351            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1352        self.fn_cache
1353            .lock()
1354            .unwrap()
1355            .insert(name.to_string(), f.clone());
1356        f
1357    }
1358
1359    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1360    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1361    pub fn scatter_trim_logits(
1362        &self,
1363        src: &CudaSlice<f32>,
1364        d2t: &CudaSlice<u32>,
1365        dst: &mut CudaSlice<f32>,
1366        d_vocab: usize,
1367        n_vocab: usize,
1368    ) -> Result<(), Box<dyn std::error::Error>> {
1369        let f1 = self.func("scatter_trim_logits_f32");
1370        let f2 = self.func("scatter_trim_logits_pass2_f32");
1371        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1372        let cfg1 = LaunchConfig {
1373            grid_dim: (256, 1, 1),
1374            block_dim: (256, 1, 1),
1375            shared_mem_bytes: 0,
1376        };
1377        let __s_b1 = self.gpu.stream();
1378        let mut b1 = __s_b1.launch_builder(&f1);
1379        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1380        unsafe {
1381            b1.launch(cfg1)?;
1382        }
1383        let cfg2 = LaunchConfig {
1384            grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1),
1385            block_dim: (256, 1, 1),
1386            shared_mem_bytes: 0,
1387        };
1388        let __s_b2 = self.gpu.stream();
1389        let mut b2 = __s_b2.launch_builder(&f2);
1390        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1391        unsafe {
1392            b2.launch(cfg2)?;
1393        }
1394        Ok(())
1395    }
1396
1397    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1398    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1399
1400    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1401    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1402    #[allow(clippy::too_many_arguments)]
1403    pub fn filter_stats(
1404        &self,
1405        x: &CudaSlice<f32>,
1406        row_stride: usize,
1407        rows: &CudaSlice<i32>,
1408        out_th: &mut CudaSlice<f32>,
1409        out_z: &mut CudaSlice<f32>,
1410        out_max: &mut CudaSlice<f32>,
1411        n: usize,
1412        nrow: usize,
1413        temp: f32,
1414        top_k: i32,
1415        top_p: f32,
1416        min_p: f32,
1417    ) -> Result<(), Box<dyn std::error::Error>> {
1418        let f = self.func("filter_stats_f32");
1419        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1420        let cfg = LaunchConfig {
1421            grid_dim: (nrow as u32, 1, 1),
1422            block_dim: (1024, 1, 1),
1423            shared_mem_bytes: 0,
1424        };
1425        let __s_b = self.gpu.stream();
1426        let mut b = __s_b.launch_builder(&f);
1427        b.arg(x)
1428            .arg(&rs)
1429            .arg(rows)
1430            .arg(&mut *out_th)
1431            .arg(&mut *out_z)
1432            .arg(&mut *out_max)
1433            .arg(&ni)
1434            .arg(&nr)
1435            .arg(&temp)
1436            .arg(&top_k)
1437            .arg(&top_p)
1438            .arg(&min_p);
1439        unsafe {
1440            b.launch(cfg)?;
1441        }
1442        Ok(())
1443    }
1444
1445    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1446    #[allow(clippy::too_many_arguments)]
1447    pub fn softmax_gather_filtered(
1448        &self,
1449        x: &CudaSlice<f32>,
1450        row_stride: usize,
1451        ids: &CudaSlice<u32>,
1452        rows: &CudaSlice<i32>,
1453        th: &CudaSlice<f32>,
1454        z: &CudaSlice<f32>,
1455        out: &mut CudaSlice<f32>,
1456        n: usize,
1457        npair: usize,
1458        temp: f32,
1459    ) -> Result<(), Box<dyn std::error::Error>> {
1460        let f = self.func("softmax_gather_filtered_f32");
1461        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1462        let cfg = LaunchConfig {
1463            grid_dim: (npair as u32, 1, 1),
1464            block_dim: (256, 1, 1),
1465            shared_mem_bytes: 0,
1466        };
1467        let __s_b = self.gpu.stream();
1468        let mut b = __s_b.launch_builder(&f);
1469        b.arg(x)
1470            .arg(&rs)
1471            .arg(ids)
1472            .arg(rows)
1473            .arg(th)
1474            .arg(z)
1475            .arg(&mut *out)
1476            .arg(&ni)
1477            .arg(&np)
1478            .arg(&temp);
1479        unsafe {
1480            b.launch(cfg)?;
1481        }
1482        Ok(())
1483    }
1484
1485    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1486    #[allow(clippy::too_many_arguments)]
1487    pub fn residual_sample_filtered(
1488        &self,
1489        p: &CudaSlice<f32>,
1490        q: Option<&CudaSlice<f32>>,
1491        n: usize,
1492        temp: f32,
1493        seed: u64,
1494        stream_pos: u32,
1495        p_stats: (f32, f32, f32),
1496        q_stats: (f32, f32, f32),
1497        out_tok: &mut CudaSlice<u32>,
1498    ) -> Result<(), Box<dyn std::error::Error>> {
1499        let f = self.func("residual_sample_filtered_f32");
1500        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1501        let has_q: i32 = q.is_some() as i32;
1502        let qbuf = q.unwrap_or(p);
1503        let (pm, pth, pz) = p_stats;
1504        let (qm, qth, qz) = q_stats;
1505        let cfg = LaunchConfig {
1506            grid_dim: (1, 1, 1),
1507            block_dim: (1024, 1, 1),
1508            shared_mem_bytes: 0,
1509        };
1510        let __s_b = self.gpu.stream();
1511        let mut b = __s_b.launch_builder(&f);
1512        b.arg(p)
1513            .arg(qbuf)
1514            .arg(&has_q)
1515            .arg(&ni)
1516            .arg(&temp)
1517            .arg(&slo)
1518            .arg(&shi)
1519            .arg(&stream_pos)
1520            .arg(&pm)
1521            .arg(&pth)
1522            .arg(&pz)
1523            .arg(&qm)
1524            .arg(&qth)
1525            .arg(&qz)
1526            .arg(&mut *out_tok);
1527        unsafe {
1528            b.launch(cfg)?;
1529        }
1530        Ok(())
1531    }
1532
1533    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1534    #[allow(clippy::too_many_arguments)]
1535    pub fn gumbel_perturb_filtered(
1536        &self,
1537        x: &CudaSlice<f32>,
1538        y: &mut CudaSlice<f32>,
1539        n: usize,
1540        seed: u64,
1541        stream_pos: u32,
1542        temp: f32,
1543        row_max: f32,
1544        th: f32,
1545    ) -> Result<(), Box<dyn std::error::Error>> {
1546        let f = self.func("gumbel_perturb_filtered_f32");
1547        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1548        let cfg = LaunchConfig {
1549            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1550            block_dim: (256, 1, 1),
1551            shared_mem_bytes: 0,
1552        };
1553        let __s_b = self.gpu.stream();
1554        let mut b = __s_b.launch_builder(&f);
1555        b.arg(x)
1556            .arg(&mut *y)
1557            .arg(&ni)
1558            .arg(&slo)
1559            .arg(&shi)
1560            .arg(&stream_pos)
1561            .arg(&temp)
1562            .arg(&row_max)
1563            .arg(&th);
1564        unsafe {
1565            b.launch(cfg)?;
1566        }
1567        Ok(())
1568    }
1569
1570    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1571    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1572    /// filtered rejection sampling exact for the penalized target.
1573    #[allow(clippy::too_many_arguments)]
1574    pub fn penalize_logits(
1575        &self,
1576        x: &mut CudaSlice<f32>,
1577        hist: &CudaSlice<u32>,
1578        n_hist: usize,
1579        rep: f32,
1580        freq: f32,
1581        present: f32,
1582        n: usize,
1583    ) -> Result<(), Box<dyn std::error::Error>> {
1584        if n_hist == 0 {
1585            return Ok(());
1586        }
1587        let f = self.func("penalize_logits_f32");
1588        let (nh, ni) = (n_hist as i32, n as i32);
1589        let cfg = LaunchConfig {
1590            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1591            block_dim: (128, 1, 1),
1592            shared_mem_bytes: 0,
1593        };
1594        let __s_b = self.gpu.stream();
1595        let mut b = __s_b.launch_builder(&f);
1596        b.arg(&mut *x)
1597            .arg(hist)
1598            .arg(&nh)
1599            .arg(&rep)
1600            .arg(&freq)
1601            .arg(&present)
1602            .arg(&ni);
1603        unsafe {
1604            b.launch(cfg)?;
1605        }
1606        Ok(())
1607    }
1608
1609    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1610    #[allow(clippy::too_many_arguments)]
1611    pub fn penalize_logits_rows(
1612        &self,
1613        x: &mut CudaSlice<f32>,
1614        hist: &CudaSlice<u32>,
1615        n_hist: usize,
1616        rep: f32,
1617        freq: f32,
1618        present: f32,
1619        n: usize,
1620        nrow: usize,
1621    ) -> Result<(), Box<dyn std::error::Error>> {
1622        if n_hist == 0 || nrow == 0 {
1623            return Ok(());
1624        }
1625        let f = self.func("penalize_logits_rows_f32");
1626        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1627        let cfg = LaunchConfig {
1628            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1629            block_dim: (128, 1, 1),
1630            shared_mem_bytes: 0,
1631        };
1632        let __s_b = self.gpu.stream();
1633        let mut b = __s_b.launch_builder(&f);
1634        b.arg(&mut *x)
1635            .arg(hist)
1636            .arg(&nh)
1637            .arg(&rep)
1638            .arg(&freq)
1639            .arg(&present)
1640            .arg(&ni)
1641            .arg(&nr);
1642        unsafe {
1643            b.launch(cfg)?;
1644        }
1645        Ok(())
1646    }
1647
1648    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1649    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1650    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1651    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1652    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1653    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1654    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1655    pub fn wpf_level() -> u32 {
1656        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1657        *ON.get_or_init(|| {
1658            std::env::var("MEMRA_WPF")
1659                .ok()
1660                .and_then(|v| v.parse().ok())
1661                .unwrap_or(1)
1662        })
1663    }
1664
1665    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1666    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1667    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1668    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1669    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1670    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1671    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1672    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1673    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1674    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1675    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1676    pub fn set_verify_exact(&self, on: bool) {
1677        self.verify_exact
1678            .store(on, std::sync::atomic::Ordering::Relaxed);
1679    }
1680    pub(crate) fn verify_exact_on(&self) -> bool {
1681        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1682    }
1683
1684    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1685    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1686    pub fn qkv_append_on() -> bool {
1687        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1688        *ON.get_or_init(|| {
1689            std::env::var("MEMRA_QKV_APPEND")
1690                .map(|v| v != "0")
1691                .unwrap_or(true)
1692        })
1693    }
1694
1695    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1696    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1697    pub fn pdl_wb_on() -> bool {
1698        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1699        *ON.get_or_init(|| {
1700            std::env::var("MEMRA_PDL_WB")
1701                .map(|v| v != "0")
1702                .unwrap_or(true)
1703        })
1704    }
1705
1706    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1707    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1708    /// per-model no-harm bisect knob.
1709    pub fn pdl_mmvq_on() -> bool {
1710        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1711        *ON.get_or_init(|| {
1712            std::env::var("MEMRA_PDL_MMVQ")
1713                .map(|v| v != "0")
1714                .unwrap_or(true)
1715        })
1716    }
1717
1718    pub fn pdl_on() -> bool {
1719        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1720        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1721    }
1722
1723    /// PDL wave-B seam (gap-diagnosis arc, GAP-DIAGNOSIS.md verdict 8): the gemma
1724    /// NVFP4mix decode chain's hot kernels — nvfp4 fused2/mr2 and the q8_0 `_rp`
1725    /// singles — join the wave-A launch class. Scheduling-only (the entry macro waits
1726    /// on the producer before any read), bit-identical by construction.
1727    /// MEMRA_PDL_NVFP4=0 reverts wave-B alone.
1728    pub fn pdl_nvfp4q8_on() -> bool {
1729        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1730        *ON.get_or_init(|| {
1731            std::env::var("MEMRA_PDL_NVFP4")
1732                .map(|v| v != "0")
1733                .unwrap_or(true)
1734        })
1735    }
1736
1737    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1738    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1739    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1740    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1741    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1742    fn q40_mr1_on() -> bool {
1743        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1744        match *Q40MR.get_or_init(|| {
1745            std::env::var("MEMRA_Q40_MR")
1746                .ok()
1747                .and_then(|v| v.parse().ok())
1748        }) {
1749            Some(v) => v == 1,
1750            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1751        }
1752    }
1753
1754    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1755    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1756    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1757    /// writes wrong bytes silently.
1758    fn pdl_func_flash(
1759        &self,
1760        g: bool,
1761        name: &'static str,
1762    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1763        use cudarc::driver::sys as cu;
1764        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1765        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1766        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1767        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1768        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1769        // this engine's CUcontext; single-context runs behave exactly as before.
1770        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1771            std::sync::Mutex::new(None);
1772        static FNS: std::sync::Mutex<
1773            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
1774        > = std::sync::Mutex::new(None);
1775        let ctx_key = self.ctx().cu_ctx() as usize;
1776        if let Some(&f) = FNS
1777            .lock()
1778            .unwrap()
1779            .get_or_insert_with(Default::default)
1780            .get(&(ctx_key, g, name))
1781        {
1782            return Ok(f as cu::CUfunction);
1783        }
1784        let module = {
1785            let mut mods = MODS.lock().unwrap();
1786            let map = mods.get_or_insert_with(Default::default);
1787            match map.get(&(ctx_key, g)) {
1788                Some(&m) => m,
1789                None => {
1790                    let m = self.pdl_load_module_in_ctx(if g {
1791                        FLASH_FATBIN_KF8VF8
1792                    } else {
1793                        FLASH_FATBIN
1794                    })?;
1795                    map.insert((ctx_key, g), m);
1796                    m
1797                }
1798            }
1799        };
1800        let cname = std::ffi::CString::new(name)?;
1801        let mut f: cu::CUfunction = std::ptr::null_mut();
1802        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1803        if r != cu::CUresult::CUDA_SUCCESS {
1804            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
1805        }
1806        FNS.lock()
1807            .unwrap()
1808            .get_or_insert_with(Default::default)
1809            .insert((ctx_key, g, name), f as usize);
1810        Ok(f)
1811    }
1812
1813    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1814    /// the module to the thread's CURRENT context — a remote-stage engine must not
1815    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1816    /// current context before returning.
1817    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1818        use cudarc::driver::sys as cu;
1819        let mut prev: cu::CUcontext = std::ptr::null_mut();
1820        unsafe {
1821            cu::cuCtxGetCurrent(&mut prev).result()?;
1822        }
1823        self.ctx().bind_to_thread()?;
1824        let mut m: cu::CUmodule = std::ptr::null_mut();
1825        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1826        let restore = if prev.is_null() {
1827            cu::CUresult::CUDA_SUCCESS
1828        } else {
1829            unsafe { cu::cuCtxSetCurrent(prev) }
1830        };
1831        if r != cu::CUresult::CUDA_SUCCESS {
1832            return Err(format!("pdl module load: {r:?}").into());
1833        }
1834        if restore != cu::CUresult::CUDA_SUCCESS {
1835            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1836        }
1837        Ok(m as usize)
1838    }
1839
1840    fn pdl_func(
1841        &self,
1842        name: &'static str,
1843    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1844        use cudarc::driver::sys as cu;
1845        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1846        // are context-scoped; key everything by this engine's CUcontext).
1847        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1848            std::sync::Mutex::new(None);
1849        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1850        // duplicate module, loaded lazily on the first kernels-module miss.
1851        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1852            std::sync::Mutex::new(None);
1853        static FNS: std::sync::Mutex<
1854            Option<std::collections::HashMap<(usize, &'static str), usize>>,
1855        > = std::sync::Mutex::new(None);
1856        let ctx_key = self.ctx().cu_ctx() as usize;
1857        if let Some(&f) = FNS
1858            .lock()
1859            .unwrap()
1860            .get_or_insert_with(Default::default)
1861            .get(&(ctx_key, name))
1862        {
1863            return Ok(f as cu::CUfunction);
1864        }
1865        let module = {
1866            let mut mods = MODULES.lock().unwrap();
1867            let map = mods.get_or_insert_with(Default::default);
1868            match map.get(&ctx_key) {
1869                Some(&m) => m,
1870                None => {
1871                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1872                    map.insert(ctx_key, m);
1873                    m
1874                }
1875            }
1876        };
1877        let cname = std::ffi::CString::new(name)?;
1878        let mut f: cu::CUfunction = std::ptr::null_mut();
1879        let mut r =
1880            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1881        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1882            let qmodule = {
1883                let mut mods = QMODULES.lock().unwrap();
1884                let map = mods.get_or_insert_with(Default::default);
1885                match map.get(&ctx_key) {
1886                    Some(&m) => m,
1887                    None => {
1888                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1889                        map.insert(ctx_key, m);
1890                        m
1891                    }
1892                }
1893            };
1894            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1895        }
1896        if r != cu::CUresult::CUDA_SUCCESS {
1897            return Err(format!("pdl_func {name}: {r:?}").into());
1898        }
1899        FNS.lock()
1900            .unwrap()
1901            .get_or_insert_with(Default::default)
1902            .insert((ctx_key, name), f as usize);
1903        Ok(f)
1904    }
1905
1906    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1907    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1908    ///
1909    /// # Safety
1910    /// `params` must match the kernel's exact parameter list (order, types, count) —
1911    /// a mismatch corrupts the launch silently.
1912    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1913    /// builder path's fa_func/func_g choice exactly).
1914    ///
1915    /// # Safety
1916    /// Same contract as `launch_pdl`.
1917    unsafe fn launch_pdl_flash(
1918        &self,
1919        g: bool,
1920        name: &'static str,
1921        grid: (u32, u32, u32),
1922        block: (u32, u32, u32),
1923        smem: u32,
1924        params: &mut [*mut std::ffi::c_void],
1925    ) -> Result<(), Box<dyn std::error::Error>> {
1926        use cudarc::driver::sys as cu;
1927        let f = self.pdl_func_flash(g, name)?;
1928        if smem > 0 {
1929            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1930            let r =
1931                unsafe {
1932                    cu::cuFuncSetAttribute(f,
1933                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1934                smem as i32)
1935                };
1936            if r != cu::CUresult::CUDA_SUCCESS {
1937                return Err(format!("pdl smem attr {name}: {r:?}").into());
1938            }
1939        }
1940        let mut attr = cu::CUlaunchAttribute {
1941            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1942            pad: [0; 4],
1943            value: cu::CUlaunchAttributeValue {
1944                programmaticStreamSerializationAllowed: 1,
1945            },
1946        };
1947        let cfg = cu::CUlaunchConfig {
1948            gridDimX: grid.0,
1949            gridDimY: grid.1,
1950            gridDimZ: grid.2,
1951            blockDimX: block.0,
1952            blockDimY: block.1,
1953            blockDimZ: block.2,
1954            sharedMemBytes: smem,
1955            hStream: self.gpu.stream().cu_stream(),
1956            attrs: &mut attr,
1957            numAttrs: 1,
1958        };
1959        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1960        if r != cu::CUresult::CUDA_SUCCESS {
1961            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
1962        }
1963        Ok(())
1964    }
1965
1966    unsafe fn launch_pdl(
1967        &self,
1968        name: &'static str,
1969        grid: (u32, u32, u32),
1970        block: (u32, u32, u32),
1971        params: &mut [*mut std::ffi::c_void],
1972    ) -> Result<(), Box<dyn std::error::Error>> {
1973        use cudarc::driver::sys as cu;
1974        let f = self.pdl_func(name)?;
1975        let mut attr = cu::CUlaunchAttribute {
1976            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1977            pad: [0; 4],
1978            value: cu::CUlaunchAttributeValue {
1979                programmaticStreamSerializationAllowed: 1,
1980            },
1981        };
1982        let cfg = cu::CUlaunchConfig {
1983            gridDimX: grid.0,
1984            gridDimY: grid.1,
1985            gridDimZ: grid.2,
1986            blockDimX: block.0,
1987            blockDimY: block.1,
1988            blockDimZ: block.2,
1989            sharedMemBytes: 0,
1990            hStream: self.gpu.stream().cu_stream(),
1991            attrs: &mut attr,
1992            numAttrs: 1,
1993        };
1994        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1995        if r != cu::CUresult::CUDA_SUCCESS {
1996            return Err(format!("launch_pdl {name}: {r:?}").into());
1997        }
1998        Ok(())
1999    }
2000
2001    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
2002    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
2003    pub fn prefetch_weight_l2(
2004        &self,
2005        w: &crate::model::GpuTensor,
2006    ) -> Result<(), Box<dyn std::error::Error>> {
2007        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
2008            let p = rp4.as_ref().unwrap_or(bytes);
2009            self.prefetch_l2(p, p.len())?;
2010        }
2011        Ok(())
2012    }
2013
2014    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
2015    /// by the DEVICE token id at tok[idx] into f32.
2016    pub fn gather_row_bf16(
2017        &self,
2018        table: &CudaSlice<u8>,
2019        tok: &CudaSlice<u32>,
2020        idx: usize,
2021        dst: &mut CudaSlice<f32>,
2022        ncols: usize,
2023    ) -> Result<(), Box<dyn std::error::Error>> {
2024        let f = self.func("gather_row_bf16_f32");
2025        let cfg = LaunchConfig {
2026            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2027            block_dim: (256, 1, 1),
2028            shared_mem_bytes: 0,
2029        };
2030        let (nc, ix) = (ncols as i32, idx as i32);
2031        let __s_b = self.gpu.stream();
2032        let mut b = __s_b.launch_builder(&f);
2033        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2034        unsafe {
2035            b.launch(cfg)?;
2036        }
2037        Ok(())
2038    }
2039
2040    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2041    pub fn add_row_inplace(
2042        &self,
2043        logits: &mut CudaSlice<f32>,
2044        bias: &CudaSlice<f32>,
2045        n: usize,
2046        row_off: usize,
2047    ) -> Result<(), Box<dyn std::error::Error>> {
2048        let f = self.func("add_row_inplace_f32");
2049        let cfg = LaunchConfig {
2050            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2051            block_dim: (256, 1, 1),
2052            shared_mem_bytes: 0,
2053        };
2054        let (ni, off) = (n as i32, row_off as i64);
2055        let __s_b = self.gpu.stream();
2056        let mut b = __s_b.launch_builder(&f);
2057        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2058        unsafe {
2059            b.launch(cfg)?;
2060        }
2061        Ok(())
2062    }
2063
2064    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2065    pub fn prefetch_l2(
2066        &self,
2067        p: &CudaSlice<u8>,
2068        n: usize,
2069    ) -> Result<(), Box<dyn std::error::Error>> {
2070        let f = self.func("prefetch_l2_bytes");
2071        let lines = n.div_ceil(128);
2072        let ni = n as i64;
2073        let cfg = LaunchConfig {
2074            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2075            block_dim: (256, 1, 1),
2076            shared_mem_bytes: 0,
2077        };
2078        let __s_b = self.gpu.stream();
2079        let mut b = __s_b.launch_builder(&f);
2080        b.arg(p).arg(&ni);
2081        unsafe {
2082            b.launch(cfg)?;
2083        }
2084        Ok(())
2085    }
2086
2087    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2088    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2089    pub fn router_gemv(
2090        &self,
2091        w: &CudaSlice<f32>,
2092        x: &CudaSlice<f32>,
2093        n_embd: usize,
2094        n_experts: usize,
2095        t: usize,
2096    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2097        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2098        // stream differs) — too small to justify a numeric config change; deleted.
2099        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2100        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2101        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2102        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2103            Ok("0") => false,
2104            Ok(_) => true,
2105            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2106        };
2107        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2108        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2109        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2110        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2111        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2112        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2113        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2114        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2115        // (perf-only, bits equal).
2116        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2117        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2118    }
2119
2120    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2121    /// force both forms; `batch` requires `w8`).
2122    pub fn router_gemv_form(
2123        &self,
2124        w: &CudaSlice<f32>,
2125        x: &CudaSlice<f32>,
2126        n_embd: usize,
2127        n_experts: usize,
2128        t: usize,
2129        w8: bool,
2130        batch: bool,
2131    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2132        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2133        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2134        let f = if batch {
2135            self.func("router_gemv_f32_w8_batch")
2136        } else if w8 {
2137            self.func("router_gemv_f32_w8")
2138        } else {
2139            self.func("router_gemv_f32")
2140        };
2141        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2142        let cfg = if batch {
2143            LaunchConfig {
2144                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2145                block_dim: (32, 8, 1),
2146                shared_mem_bytes: 0,
2147            }
2148        } else {
2149            LaunchConfig {
2150                grid_dim: (n_experts as u32, t as u32, 1),
2151                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2152                shared_mem_bytes: 0,
2153            }
2154        };
2155        let __s_b = self.gpu.stream();
2156        let mut b = __s_b.launch_builder(&f);
2157        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2158        unsafe {
2159            b.launch(cfg)?;
2160        }
2161        Ok(y)
2162    }
2163
2164    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2165    pub fn rows_permute(
2166        &self,
2167        src: &CudaSlice<f32>,
2168        idx: &CudaSlice<i32>,
2169        nrows: usize,
2170        ncols: usize,
2171    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2172        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2173        let f = self.func("rows_permute_f32");
2174        let (nc, nr) = (ncols as i32, nrows as i32);
2175        let cfg = LaunchConfig {
2176            grid_dim: (nrows as u32, 1, 1),
2177            block_dim: (256, 1, 1),
2178            shared_mem_bytes: 0,
2179        };
2180        let __s_b = self.gpu.stream();
2181        let mut b = __s_b.launch_builder(&f);
2182        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2183        unsafe {
2184            b.launch(cfg)?;
2185        }
2186        Ok(dst)
2187    }
2188
2189    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2190    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2191    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2192    /// decode chain and the small-t spec-verify chain match per row by construction.
2193    pub fn sigmoid_dot_rows(
2194        &self,
2195        x: &CudaSlice<f32>,
2196        w: &CudaSlice<f32>,
2197        n_embd: usize,
2198        t: usize,
2199    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2200        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2201        // config; same class as MEMRA_ROUTER_V2).
2202        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2203        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2204            let gs = self.linear(x, w, t, n_embd, 1)?;
2205            let mut g = self.uninit(t)?;
2206            self.sigmoid(&gs, &mut g, t)?;
2207            return Ok(g);
2208        }
2209        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2210        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2211        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2212        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2213        // flags doctrine; this per-token form serves every t.
2214        let mut g = self.alloc_uninit::<f32>(t)?;
2215        let f = self.func("sigmoid_dot_rows_f32");
2216        let (ne, ti) = (n_embd as i32, t as i32);
2217        let cfg = LaunchConfig {
2218            grid_dim: (t as u32, 1, 1),
2219            block_dim: (32, 8, 1),
2220            shared_mem_bytes: 0,
2221        };
2222        let __s_b = self.gpu.stream();
2223        let mut b = __s_b.launch_builder(&f);
2224        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2225        unsafe {
2226            b.launch(cfg)?;
2227        }
2228        Ok(g)
2229    }
2230
2231    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2232    pub fn spec_rollback_stream(
2233        &self,
2234        len_ptrs: &CudaSlice<u64>,
2235        pos_start: &CudaSlice<i32>,
2236        acc: &CudaSlice<u32>,
2237        base: usize,
2238        n_rows: usize,
2239    ) -> Result<(), Box<dyn std::error::Error>> {
2240        let f = self.func("spec_rollback_stream");
2241        let (b, nr) = (base as i32, n_rows as i32);
2242        let cfg = LaunchConfig {
2243            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2244            block_dim: (64, 1, 1),
2245            shared_mem_bytes: 0,
2246        };
2247        let __s_bl = self.gpu.stream();
2248        let mut bl = __s_bl.launch_builder(&f);
2249        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2250        unsafe {
2251            bl.launch(cfg)?;
2252        }
2253        Ok(())
2254    }
2255
2256    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2257    pub fn plain_tok_ring(
2258        &self,
2259        vam: &CudaSlice<u32>,
2260        pos_start: &CudaSlice<i32>,
2261        base: usize,
2262        ring: &mut CudaSlice<u32>,
2263    ) -> Result<(), Box<dyn std::error::Error>> {
2264        let f = self.func("plain_tok_ring");
2265        let (b, cap) = (base as i32, ring.len() as i32);
2266        let cfg = LaunchConfig {
2267            grid_dim: (1, 1, 1),
2268            block_dim: (32, 1, 1),
2269            shared_mem_bytes: 0,
2270        };
2271        let __s_bl = self.gpu.stream();
2272        let mut bl = __s_bl.launch_builder(&f);
2273        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2274        unsafe {
2275            bl.launch(cfg)?;
2276        }
2277        Ok(())
2278    }
2279
2280    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2281    pub fn spec_ring_commit(
2282        &self,
2283        vtok: &CudaSlice<u32>,
2284        acc: &CudaSlice<u32>,
2285        brk: &CudaSlice<u32>,
2286        ring: &mut CudaSlice<u32>,
2287        pend: &mut CudaSlice<u32>,
2288    ) -> Result<(), Box<dyn std::error::Error>> {
2289        let f = self.func("spec_ring_commit");
2290        let cfg = LaunchConfig {
2291            grid_dim: (1, 1, 1),
2292            block_dim: (32, 1, 1),
2293            shared_mem_bytes: 0,
2294        };
2295        let __s_b = self.gpu.stream();
2296        let mut b = __s_b.launch_builder(&f);
2297        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2298        unsafe {
2299            b.launch(cfg)?;
2300        }
2301        Ok(())
2302    }
2303    pub fn i32_copy_add(
2304        &self,
2305        src: &CudaSlice<i32>,
2306        dst: &mut CudaSlice<i32>,
2307        delta: i32,
2308    ) -> Result<(), Box<dyn std::error::Error>> {
2309        let f = self.func("i32_copy_add");
2310        let cfg = LaunchConfig {
2311            grid_dim: (1, 1, 1),
2312            block_dim: (32, 1, 1),
2313            shared_mem_bytes: 0,
2314        };
2315        let __s_b = self.gpu.stream();
2316        let mut b = __s_b.launch_builder(&f);
2317        b.arg(src).arg(dst).arg(&delta);
2318        unsafe {
2319            b.launch(cfg)?;
2320        }
2321        Ok(())
2322    }
2323    pub fn u32_copy(
2324        &self,
2325        src: &CudaSlice<u32>,
2326        dst: &mut CudaSlice<u32>,
2327    ) -> Result<(), Box<dyn std::error::Error>> {
2328        let f = self.func("u32_copy");
2329        let cfg = LaunchConfig {
2330            grid_dim: (1, 1, 1),
2331            block_dim: (32, 1, 1),
2332            shared_mem_bytes: 0,
2333        };
2334        let __s_b = self.gpu.stream();
2335        let mut b = __s_b.launch_builder(&f);
2336        b.arg(src).arg(dst);
2337        unsafe {
2338            b.launch(cfg)?;
2339        }
2340        Ok(())
2341    }
2342
2343    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2344    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2345    /// caps acceptance exactly like drafting fewer tokens).
2346    pub fn spec_adapt_k(
2347        &self,
2348        acc: &CudaSlice<u32>,
2349        brk: &mut CudaSlice<u32>,
2350        floor: usize,
2351        cap: usize,
2352    ) -> Result<(), Box<dyn std::error::Error>> {
2353        let f = self.func("spec_adapt_k");
2354        let (fl, cp) = (floor as i32, cap as i32);
2355        let cfg = LaunchConfig {
2356            grid_dim: (1, 1, 1),
2357            block_dim: (32, 1, 1),
2358            shared_mem_bytes: 0,
2359        };
2360        let __s_b = self.gpu.stream();
2361        let mut b = __s_b.launch_builder(&f);
2362        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2363        unsafe {
2364            b.launch(cfg)?;
2365        }
2366        Ok(())
2367    }
2368
2369    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2370    pub fn spec_accept_greedy_dc(
2371        &self,
2372        preds: &CudaSlice<u32>,
2373        vtok: &CudaSlice<u32>,
2374        last_pred: &CudaSlice<u32>,
2375        brk: &CudaSlice<u32>,
2376        out: &mut CudaSlice<u32>,
2377    ) -> Result<(), Box<dyn std::error::Error>> {
2378        let f = self.func("spec_accept_greedy_dc");
2379        let cfg = LaunchConfig {
2380            grid_dim: (1, 1, 1),
2381            block_dim: (32, 1, 1),
2382            shared_mem_bytes: 0,
2383        };
2384        let __s_b = self.gpu.stream();
2385        let mut b = __s_b.launch_builder(&f);
2386        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2387        unsafe {
2388            b.launch(cfg)?;
2389        }
2390        Ok(())
2391    }
2392
2393    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2394    pub fn pos_iota(
2395        &self,
2396        pos0: &CudaSlice<i32>,
2397        out: &mut CudaSlice<i32>,
2398        t: usize,
2399    ) -> Result<(), Box<dyn std::error::Error>> {
2400        let f = self.func("pos_iota_i32");
2401        let ti = t as i32;
2402        let cfg = LaunchConfig {
2403            grid_dim: (1, 1, 1),
2404            block_dim: (t.max(1) as u32, 1, 1),
2405            shared_mem_bytes: 0,
2406        };
2407        let __s_b = self.gpu.stream();
2408        let mut b = __s_b.launch_builder(&f);
2409        b.arg(pos0).arg(out).arg(&ti);
2410        unsafe {
2411            b.launch(cfg)?;
2412        }
2413        Ok(())
2414    }
2415    #[allow(clippy::too_many_arguments)]
2416    pub fn append_kv_quantized_rows_dc(
2417        &self,
2418        k_rows: &CudaSlice<f32>,
2419        v_rows: &CudaSlice<f32>,
2420        kc: &mut CudaSlice<u8>,
2421        vc: &mut CudaSlice<u8>,
2422        t0_dev: &CudaSlice<i32>,
2423        t: usize,
2424        kv_dim_k: usize,
2425        kv_dim_v: usize,
2426        k_tok_bytes: usize,
2427        v_tok_bytes: usize,
2428        g: bool,
2429    ) -> Result<(), Box<dyn std::error::Error>> {
2430        let f = if g {
2431            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2432        } else {
2433            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2434        };
2435        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2436        let cfg = LaunchConfig {
2437            grid_dim: (nblk, t as u32, 1),
2438            block_dim: (32, 1, 1),
2439            shared_mem_bytes: 0,
2440        };
2441        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2442        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2443        let __s_b = self.gpu.stream();
2444        let mut b = __s_b.launch_builder(&f);
2445        b.arg(k_rows)
2446            .arg(v_rows)
2447            .arg(kc)
2448            .arg(vc)
2449            .arg(t0_dev)
2450            .arg(&kdk)
2451            .arg(&kdv)
2452            .arg(&ktb)
2453            .arg(&vtb);
2454        unsafe {
2455            b.launch(cfg)?;
2456        }
2457        Ok(())
2458    }
2459
2460    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2461    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2462    #[allow(clippy::too_many_arguments)]
2463    pub fn append_kv_quantized_row_dc_inc(
2464        &self,
2465        k_row: &CudaSlice<f32>,
2466        v_row: &CudaSlice<f32>,
2467        kc: &mut CudaSlice<u8>,
2468        vc: &mut CudaSlice<u8>,
2469        t0_dev: &mut CudaSlice<i32>,
2470        kv_dim_k: usize,
2471        kv_dim_v: usize,
2472        k_tok_bytes: usize,
2473        v_tok_bytes: usize,
2474        g: bool,
2475    ) -> Result<(), Box<dyn std::error::Error>> {
2476        let f = if g {
2477            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2478        } else {
2479            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2480        };
2481        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2482        let cfg = LaunchConfig {
2483            grid_dim: (1, 1, 1),
2484            block_dim: (nthreads, 1, 1),
2485            shared_mem_bytes: 0,
2486        };
2487        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2488        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2489        let __s_b = self.gpu.stream();
2490        let mut b = __s_b.launch_builder(&f);
2491        b.arg(k_row)
2492            .arg(v_row)
2493            .arg(kc)
2494            .arg(vc)
2495            .arg(t0_dev)
2496            .arg(&kdk)
2497            .arg(&kdv)
2498            .arg(&ktb)
2499            .arg(&vtb);
2500        unsafe {
2501            b.launch(cfg)?;
2502        }
2503        Ok(())
2504    }
2505
2506    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
2507    pub fn pack_tok_p(
2508        &self,
2509        tok: &CudaSlice<u32>,
2510        p: &CudaSlice<f32>,
2511        out: &mut CudaSlice<u32>,
2512        slot: usize,
2513    ) -> Result<(), Box<dyn std::error::Error>> {
2514        let f = self.func("pack_tok_p");
2515        let sl = slot as i32;
2516        let cfg = LaunchConfig {
2517            grid_dim: (1, 1, 1),
2518            block_dim: (32, 1, 1),
2519            shared_mem_bytes: 0,
2520        };
2521        let __s_b = self.gpu.stream();
2522        let mut b = __s_b.launch_builder(&f);
2523        b.arg(tok).arg(p).arg(out).arg(&sl);
2524        unsafe {
2525            b.launch(cfg)?;
2526        }
2527        Ok(())
2528    }
2529    pub fn tok_map_u32(
2530        &self,
2531        tok: &mut CudaSlice<u32>,
2532        map: &CudaSlice<u32>,
2533    ) -> Result<(), Box<dyn std::error::Error>> {
2534        let f = self.func("tok_map_u32");
2535        let cfg = LaunchConfig {
2536            grid_dim: (1, 1, 1),
2537            block_dim: (32, 1, 1),
2538            shared_mem_bytes: 0,
2539        };
2540        let __s_b = self.gpu.stream();
2541        let mut b = __s_b.launch_builder(&f);
2542        b.arg(tok).arg(map);
2543        unsafe {
2544            b.launch(cfg)?;
2545        }
2546        Ok(())
2547    }
2548
2549    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
2550    #[allow(clippy::too_many_arguments)]
2551    pub fn spec_assemble_verify(
2552        &self,
2553        tokp: &CudaSlice<u32>,
2554        pend: &CudaSlice<u32>,
2555        d2t: Option<&CudaSlice<u32>>,
2556        vtok: &mut CudaSlice<u32>,
2557        brk: &mut CudaSlice<u32>,
2558        p_min: f32,
2559        k: usize,
2560        pmin0: bool,
2561    ) -> Result<(), Box<dyn std::error::Error>> {
2562        let f = self.func("spec_assemble_verify");
2563        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
2564        let cfg = LaunchConfig {
2565            grid_dim: (1, 1, 1),
2566            block_dim: (32, 1, 1),
2567            shared_mem_bytes: 0,
2568        };
2569        let __s_b = self.gpu.stream();
2570        let mut b = __s_b.launch_builder(&f);
2571        match d2t {
2572            Some(m) => {
2573                b.arg(tokp)
2574                    .arg(pend)
2575                    .arg(m)
2576                    .arg(vtok)
2577                    .arg(brk)
2578                    .arg(&p_min)
2579                    .arg(&ki)
2580                    .arg(&pm);
2581                unsafe {
2582                    b.launch(cfg)?;
2583                }
2584            }
2585            None => {
2586                let null: u64 = 0;
2587                b.arg(tokp)
2588                    .arg(pend)
2589                    .arg(&null)
2590                    .arg(vtok)
2591                    .arg(brk)
2592                    .arg(&p_min)
2593                    .arg(&ki)
2594                    .arg(&pm);
2595                unsafe {
2596                    b.launch(cfg)?;
2597                }
2598            }
2599        }
2600        Ok(())
2601    }
2602
2603    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
2604    #[allow(clippy::too_many_arguments)]
2605    pub fn ssm_conv_ring_rebuild_dc(
2606        &self,
2607        qkv_tm: &CudaSlice<f32>,
2608        ring_old: &CudaSlice<f32>,
2609        conv_state: &mut CudaSlice<f32>,
2610        conv_dim: usize,
2611        acc: &CudaSlice<u32>,
2612        base: usize,
2613        t_v: usize,
2614        d_conv: usize,
2615    ) -> Result<(), Box<dyn std::error::Error>> {
2616        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
2617        let n = conv_dim * (d_conv - 1);
2618        let cfg = LaunchConfig::for_num_elems(n as u32);
2619        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
2620        let __s_b = self.gpu.stream();
2621        let mut b = __s_b.launch_builder(&f);
2622        b.arg(qkv_tm)
2623            .arg(ring_old)
2624            .arg(conv_state)
2625            .arg(&cd)
2626            .arg(acc)
2627            .arg(&b0)
2628            .arg(&tv)
2629            .arg(&dc);
2630        unsafe {
2631            b.launch(cfg)?;
2632        }
2633        Ok(())
2634    }
2635    #[allow(clippy::too_many_arguments)]
2636    pub fn gdn_scan_s128_dc(
2637        &self,
2638        q: &CudaSlice<f32>,
2639        k: &CudaSlice<f32>,
2640        v: &CudaSlice<f32>,
2641        g: &CudaSlice<f32>,
2642        beta: &CudaSlice<f32>,
2643        state_in: &CudaSlice<f32>,
2644        state_out: &mut CudaSlice<f32>,
2645        o: &mut CudaSlice<f32>,
2646        n_head: usize,
2647        acc: &CudaSlice<u32>,
2648        base: usize,
2649        t_v: usize,
2650        scale: f32,
2651    ) -> Result<(), Box<dyn std::error::Error>> {
2652        let f = self.func("gdn_scan_s128_dc");
2653        const S_V: u32 = 128;
2654        const WARP: u32 = 32;
2655        const COLS_PER_BLOCK: u32 = 4;
2656        let cfg = LaunchConfig {
2657            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
2658            block_dim: (WARP, COLS_PER_BLOCK, 1),
2659            shared_mem_bytes: 0,
2660        };
2661        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
2662        let __s_b = self.gpu.stream();
2663        let mut b = __s_b.launch_builder(&f);
2664        b.arg(q)
2665            .arg(k)
2666            .arg(v)
2667            .arg(g)
2668            .arg(beta)
2669            .arg(state_in)
2670            .arg(state_out)
2671            .arg(o)
2672            .arg(&h)
2673            .arg(acc)
2674            .arg(&b0)
2675            .arg(&tv)
2676            .arg(&scale);
2677        unsafe {
2678            b.launch(cfg)?;
2679        }
2680        Ok(())
2681    }
2682
2683    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
2684    pub fn spec_rollback_kv(
2685        &self,
2686        len_ptrs: &CudaSlice<u64>,
2687        saved: &CudaSlice<i32>,
2688        acc: &CudaSlice<u32>,
2689        base: usize,
2690        n_layer: usize,
2691    ) -> Result<(), Box<dyn std::error::Error>> {
2692        let f = self.func("spec_rollback_kv");
2693        let (b, nl) = (base as i32, n_layer as i32);
2694        let cfg = LaunchConfig {
2695            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2696            block_dim: (64, 1, 1),
2697            shared_mem_bytes: 0,
2698        };
2699        let __s_bl = self.gpu.stream();
2700        let mut bl = __s_bl.launch_builder(&f);
2701        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
2702        unsafe {
2703            bl.launch(cfg)?;
2704        }
2705        Ok(())
2706    }
2707
2708    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
2709    pub fn spec_fork_valid(
2710        &self,
2711        acc: &CudaSlice<u32>,
2712        optimistic_pending: u32,
2713        valid: &mut CudaSlice<u32>,
2714    ) -> Result<(), Box<dyn std::error::Error>> {
2715        let f = self.func("spec_fork_valid");
2716        let cfg = LaunchConfig {
2717            grid_dim: (1, 1, 1),
2718            block_dim: (1, 1, 1),
2719            shared_mem_bytes: 0,
2720        };
2721        let __s_bl = self.gpu.stream();
2722        let mut bl = __s_bl.launch_builder(&f);
2723        bl.arg(acc).arg(&optimistic_pending).arg(valid);
2724        unsafe {
2725            bl.launch(cfg)?;
2726        }
2727        Ok(())
2728    }
2729
2730    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
2731    pub fn spec_fork_reconcile_kv(
2732        &self,
2733        len_ptrs: &CudaSlice<u64>,
2734        saved: &CudaSlice<i32>,
2735        acc: &CudaSlice<u32>,
2736        valid: &CudaSlice<u32>,
2737        base: usize,
2738        n_layer: usize,
2739    ) -> Result<(), Box<dyn std::error::Error>> {
2740        let f = self.func("spec_fork_reconcile_kv");
2741        let (b, nl) = (base as i32, n_layer as i32);
2742        let cfg = LaunchConfig {
2743            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2744            block_dim: (64, 1, 1),
2745            shared_mem_bytes: 0,
2746        };
2747        let __s_bl = self.gpu.stream();
2748        let mut bl = __s_bl.launch_builder(&f);
2749        bl.arg(len_ptrs)
2750            .arg(saved)
2751            .arg(acc)
2752            .arg(valid)
2753            .arg(&b)
2754            .arg(&nl);
2755        unsafe {
2756            bl.launch(cfg)?;
2757        }
2758        Ok(())
2759    }
2760
2761    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
2762    pub fn spec_fork_restore_f32(
2763        &self,
2764        snapshot: &CudaSlice<f32>,
2765        state: &mut CudaSlice<f32>,
2766        valid: &CudaSlice<u32>,
2767    ) -> Result<(), Box<dyn std::error::Error>> {
2768        assert_eq!(
2769            snapshot.len(),
2770            state.len(),
2771            "fork recurrent snapshot shape mismatch"
2772        );
2773        let f = self.func("spec_fork_restore_f32");
2774        let n = state.len() as i32;
2775        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
2776        let cfg = LaunchConfig {
2777            grid_dim: (blocks, 1, 1),
2778            block_dim: (256, 1, 1),
2779            shared_mem_bytes: 0,
2780        };
2781        let __s_bl = self.gpu.stream();
2782        let mut bl = __s_bl.launch_builder(&f);
2783        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
2784        unsafe {
2785            bl.launch(cfg)?;
2786        }
2787        Ok(())
2788    }
2789
2790    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
2791    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
2792    pub fn spec_seed_gather(
2793        &self,
2794        vx: &CudaSlice<f32>,
2795        fill_prev: &CudaSlice<f32>,
2796        acc: &CudaSlice<u32>,
2797        h_seed: &mut CudaSlice<f32>,
2798        base: usize,
2799        n_embd: usize,
2800    ) -> Result<(), Box<dyn std::error::Error>> {
2801        let f = self.func("spec_seed_gather");
2802        let (b, ne) = (base as i32, n_embd as i32);
2803        let cfg = LaunchConfig {
2804            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
2805            block_dim: (256, 1, 1),
2806            shared_mem_bytes: 0,
2807        };
2808        let __s_bl = self.gpu.stream();
2809        let mut bl = __s_bl.launch_builder(&f);
2810        bl.arg(vx)
2811            .arg(fill_prev)
2812            .arg(acc)
2813            .arg(h_seed)
2814            .arg(&b)
2815            .arg(&ne);
2816        unsafe {
2817            bl.launch(cfg)?;
2818        }
2819        Ok(())
2820    }
2821
2822    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2823    pub fn spec_accept_greedy(
2824        &self,
2825        preds: &CudaSlice<u32>,
2826        draft: &CudaSlice<u32>,
2827        last_pred: u32,
2828        base: usize,
2829        k_round: usize,
2830        out: &mut CudaSlice<u32>,
2831    ) -> Result<(), Box<dyn std::error::Error>> {
2832        let f = self.func("spec_accept_greedy");
2833        let (b, k) = (base as i32, k_round as i32);
2834        let cfg = LaunchConfig {
2835            grid_dim: (1, 1, 1),
2836            block_dim: (32, 1, 1),
2837            shared_mem_bytes: 0,
2838        };
2839        let __s_bl = self.gpu.stream();
2840        let mut bl = __s_bl.launch_builder(&f);
2841        bl.arg(preds)
2842            .arg(draft)
2843            .arg(&last_pred)
2844            .arg(&b)
2845            .arg(&k)
2846            .arg(out);
2847        unsafe {
2848            bl.launch(cfg)?;
2849        }
2850        Ok(())
2851    }
2852
2853    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2854    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2855    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2856
2857    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2858    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2859    pub fn gumbel_perturb(
2860        &self,
2861        x: &CudaSlice<f32>,
2862        y: &mut CudaSlice<f32>,
2863        n: usize,
2864        seed: u64,
2865        stream_pos: u32,
2866        temp: f32,
2867    ) -> Result<(), Box<dyn std::error::Error>> {
2868        let f = self.func("gumbel_perturb_f32");
2869        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2870        let cfg = LaunchConfig {
2871            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2872            block_dim: (256, 1, 1),
2873            shared_mem_bytes: 0,
2874        };
2875        let __s_b = self.gpu.stream();
2876        let mut b = __s_b.launch_builder(&f);
2877        b.arg(x)
2878            .arg(&mut *y)
2879            .arg(&ni)
2880            .arg(&slo)
2881            .arg(&shi)
2882            .arg(&stream_pos)
2883            .arg(&temp);
2884        unsafe {
2885            b.launch(cfg)?;
2886        }
2887        Ok(())
2888    }
2889
2890    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2891    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2892    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2893    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2894    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2895    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2896    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2897    pub fn mask_logits_col(
2898        &self,
2899        logits: &mut CudaSlice<f32>,
2900        mask: &CudaSlice<u32>,
2901        col: usize,
2902        n: usize,
2903        mask_words: usize,
2904    ) -> Result<(), Box<dyn std::error::Error>> {
2905        let f = self.func("mask_logits_f32");
2906        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2907        let cfg = LaunchConfig {
2908            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2909            block_dim: (256, 1, 1),
2910            shared_mem_bytes: 0,
2911        };
2912        let __s_b = self.gpu.stream();
2913        let mut b = __s_b.launch_builder(&f);
2914        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2915        unsafe {
2916            b.launch(cfg)?;
2917        }
2918        Ok(())
2919    }
2920
2921    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2922    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2923    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2924    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2925    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2926    /// pointer-invariance IS the serving isolation contract for sampled rows.
2927    pub fn gumbel_perturb_col(
2928        &self,
2929        x: &CudaSlice<f32>,
2930        col: usize,
2931        y: &mut CudaSlice<f32>,
2932        n: usize,
2933        seed: u64,
2934        stream_pos: u32,
2935        temp: f32,
2936    ) -> Result<(), Box<dyn std::error::Error>> {
2937        let f = self.func("gumbel_perturb_f32");
2938        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2939        let col_view = x.slice(col * n..(col + 1) * n);
2940        let cfg = LaunchConfig {
2941            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2942            block_dim: (256, 1, 1),
2943            shared_mem_bytes: 0,
2944        };
2945        let __s_b = self.gpu.stream();
2946        let mut b = __s_b.launch_builder(&f);
2947        b.arg(&col_view)
2948            .arg(&mut *y)
2949            .arg(&ni)
2950            .arg(&slo)
2951            .arg(&shi)
2952            .arg(&stream_pos)
2953            .arg(&temp);
2954        unsafe {
2955            b.launch(cfg)?;
2956        }
2957        Ok(())
2958    }
2959
2960    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
2961    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
2962    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
2963    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
2964    /// the serving isolation contract for sampled rows).
2965    #[allow(clippy::too_many_arguments)]
2966    pub fn gumbel_perturb_filtered_col(
2967        &self,
2968        x: &CudaSlice<f32>,
2969        col: usize,
2970        y: &mut CudaSlice<f32>,
2971        n: usize,
2972        seed: u64,
2973        stream_pos: u32,
2974        temp: f32,
2975        stat_max: &CudaSlice<f32>,
2976        stat_th: &CudaSlice<f32>,
2977        stat_idx: usize,
2978    ) -> Result<(), Box<dyn std::error::Error>> {
2979        let f = self.func("gumbel_perturb_filtered_col_f32");
2980        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2981        let (ci, si) = (col as i32, stat_idx as i32);
2982        let cfg = LaunchConfig {
2983            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2984            block_dim: (256, 1, 1),
2985            shared_mem_bytes: 0,
2986        };
2987        let __s_b = self.gpu.stream();
2988        let mut b = __s_b.launch_builder(&f);
2989        b.arg(x)
2990            .arg(&ci)
2991            .arg(&mut *y)
2992            .arg(&ni)
2993            .arg(&slo)
2994            .arg(&shi)
2995            .arg(&stream_pos)
2996            .arg(&temp)
2997            .arg(stat_max)
2998            .arg(stat_th)
2999            .arg(&si);
3000        unsafe {
3001            b.launch(cfg)?;
3002        }
3003        Ok(())
3004    }
3005
3006    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
3007    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
3008    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
3009    /// reads it (counter is data, not state — graph-replay-safe).
3010    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
3011        let f = self.func("memra_sctr_inc");
3012        let cfg = LaunchConfig {
3013            grid_dim: (1, 1, 1),
3014            block_dim: (1, 1, 1),
3015            shared_mem_bytes: 0,
3016        };
3017        let __s_b = self.gpu.stream();
3018        let mut b = __s_b.launch_builder(&f);
3019        b.arg(&mut *ctr);
3020        unsafe {
3021            b.launch(cfg)?;
3022        }
3023        Ok(())
3024    }
3025
3026    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3027    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3028    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3029    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3030    pub fn gumbel_perturb_ctr(
3031        &self,
3032        x: &CudaSlice<f32>,
3033        y: &mut CudaSlice<f32>,
3034        n: usize,
3035        seed: u64,
3036        ctr: &CudaSlice<u32>,
3037        temp: f32,
3038    ) -> Result<(), Box<dyn std::error::Error>> {
3039        let f = self.func("gumbel_perturb_ctr_f32");
3040        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3041        let cfg = LaunchConfig {
3042            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3043            block_dim: (256, 1, 1),
3044            shared_mem_bytes: 0,
3045        };
3046        let __s_b = self.gpu.stream();
3047        let mut b = __s_b.launch_builder(&f);
3048        b.arg(x)
3049            .arg(&mut *y)
3050            .arg(&ni)
3051            .arg(&slo)
3052            .arg(&shi)
3053            .arg(ctr)
3054            .arg(&temp);
3055        unsafe {
3056            b.launch(cfg)?;
3057        }
3058        Ok(())
3059    }
3060
3061    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3062    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3063    /// (smallest-index tie-break — matches the argmax-gate contract).
3064    pub fn softmax_gather(
3065        &self,
3066        x: &CudaSlice<f32>,
3067        row_stride: usize,
3068        ids: &CudaSlice<u32>,
3069        rows: &CudaSlice<i32>,
3070        out: &mut CudaSlice<f32>,
3071        n: usize,
3072        npair: usize,
3073        temp: f32,
3074    ) -> Result<(), Box<dyn std::error::Error>> {
3075        let f = self.func("softmax_gather_f32");
3076        let (ni, rs) = (n as i32, row_stride as i64);
3077        let np = npair as i32;
3078        let cfg = LaunchConfig {
3079            grid_dim: (npair as u32, 1, 1),
3080            block_dim: (256, 1, 1),
3081            shared_mem_bytes: 0,
3082        };
3083        let __s_b = self.gpu.stream();
3084        let mut b = __s_b.launch_builder(&f);
3085        b.arg(x)
3086            .arg(&rs)
3087            .arg(ids)
3088            .arg(rows)
3089            .arg(&mut *out)
3090            .arg(&ni)
3091            .arg(&np)
3092            .arg(&temp);
3093        unsafe {
3094            b.launch(cfg)?;
3095        }
3096        Ok(())
3097    }
3098
3099    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3100    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3101    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3102    pub fn residual_sample(
3103        &self,
3104        p: &CudaSlice<f32>,
3105        q: Option<&CudaSlice<f32>>,
3106        n: usize,
3107        temp: f32,
3108        seed: u64,
3109        stream_pos: u32,
3110        out_tok: &mut CudaSlice<u32>,
3111    ) -> Result<(), Box<dyn std::error::Error>> {
3112        let f = self.func("residual_sample_f32");
3113        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3114        let nth = 1024u32;
3115        let cfg = LaunchConfig {
3116            grid_dim: (1, 1, 1),
3117            block_dim: (nth, 1, 1),
3118            shared_mem_bytes: 0,
3119        };
3120        let has_q: i32 = q.is_some() as i32;
3121        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3122        let __s_b = self.gpu.stream();
3123        let mut b = __s_b.launch_builder(&f);
3124        b.arg(p)
3125            .arg(qbuf)
3126            .arg(&has_q)
3127            .arg(&ni)
3128            .arg(&temp)
3129            .arg(&slo)
3130            .arg(&shi)
3131            .arg(&stream_pos)
3132            .arg(&mut *out_tok);
3133        unsafe {
3134            b.launch(cfg)?;
3135        }
3136        Ok(())
3137    }
3138
3139    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3140    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3141    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3142    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3143    pub fn with_moe_cache<R>(
3144        &self,
3145        max_block_bytes: usize,
3146        f: impl FnOnce(
3147            &mut crate::moe_cache::MoeSlotCache,
3148            &Engine,
3149        ) -> Result<R, Box<dyn std::error::Error>>,
3150    ) -> Result<R, Box<dyn std::error::Error>> {
3151        let mut guard = self.moe_cache.lock().unwrap();
3152        if guard.is_none() {
3153            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3154        }
3155        let cache = guard.as_mut().unwrap();
3156        f(cache, self)
3157    }
3158
3159    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3160    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3161    pub fn freeze_moe_cache(&self) {
3162        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3163            cache.freeze();
3164        }
3165    }
3166
3167    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3168    /// Never constructs a cache.
3169    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3170        self.moe_cache
3171            .lock()
3172            .unwrap()
3173            .as_ref()
3174            .map(crate::moe_cache::MoeSlotCache::export_residency)
3175    }
3176
3177    pub(crate) fn moe_cache_frozen(&self) -> bool {
3178        self.moe_cache
3179            .lock()
3180            .unwrap()
3181            .as_ref()
3182            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3183    }
3184
3185    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3186    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3187    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3188    /// while leaving the profiling warmup's established batched behavior untouched.
3189    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3190    /// tokenwise arm anyway.)
3191    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3192        crate::cpu_experts::configured()
3193            && self.moe_cache_frozen()
3194            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3195    }
3196
3197    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3198    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3199        assert!(
3200            self.moe_cache.lock().unwrap().is_none(),
3201            "MoE cache layout configured after cache construction"
3202        );
3203        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3204    }
3205
3206    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3207        self.moe_cache_layout.lock().unwrap().clone()
3208    }
3209
3210    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3211    pub fn moe_cache_enabled() -> bool {
3212        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3213    }
3214
3215    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3216    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3217    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3218        let guard = self.moe_cache.lock().unwrap();
3219        guard
3220            .as_ref()
3221            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3222    }
3223
3224    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3225    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3226    /// callers compare a before/after snapshot around a decode window.
3227    pub fn cpu_expert_stats(
3228        &self,
3229    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3230        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3231    }
3232
3233    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3234    /// the backend tail that resident-GPU expert work did not hide.
3235    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3236        crate::cpu_experts::predictor_stats()
3237    }
3238
3239    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3240        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3241    }
3242
3243    /// CPU-routed expert selections grouped by how many of their three projections were already
3244    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3245    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3246        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3247    }
3248
3249    /// Positioned-read proof-backend counters:
3250    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3251    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3252        let guard = self.moe_cache.lock().unwrap();
3253        guard
3254            .as_ref()
3255            .and_then(|cache| cache.pread_stats())
3256            .map(|stats| {
3257                (
3258                    stats.reads,
3259                    stats.bytes,
3260                    stats.read_errors,
3261                    stats.short_reads,
3262                    stats.fallbacks,
3263                    stats.buffer_waits,
3264                    stats.ring_full,
3265                )
3266            })
3267    }
3268
3269    /// Spill configuration values that warned and substituted their documented defaults.
3270    pub fn spill_config_fallbacks(&self) -> u64 {
3271        crate::spill_pread::config_fallbacks()
3272    }
3273
3274    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3275    pub fn moe_cache_reset_counters(&self) {
3276        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3277            c.reset_counters();
3278        }
3279    }
3280
3281    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3282        Ok(self.gpu.stream().clone_htod(v)?)
3283    }
3284
3285    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3286    /// past the final q4_0 block through their aligned window — the bytes never reach a
3287    /// result (funnelshift discards them) but must be mapped memory.
3288    pub fn htod_bytes_padded(
3289        &self,
3290        v: &[u8],
3291        pad: usize,
3292    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3293        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3294        {
3295            let mut view = d.slice_mut(0..v.len());
3296            self.gpu.stream().memcpy_htod(v, &mut view)?;
3297        }
3298        Ok(d)
3299    }
3300
3301    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3302    pub fn copy_into(
3303        &self,
3304        dst: &mut CudaSlice<f32>,
3305        off: usize,
3306        src: &CudaSlice<f32>,
3307        len: usize,
3308    ) -> Result<(), Box<dyn std::error::Error>> {
3309        let mut view = dst.slice_mut(off..off + len);
3310        self.gpu
3311            .stream()
3312            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3313        Ok(())
3314    }
3315
3316    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3317    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3318    pub fn copy_u8_into(
3319        &self,
3320        dst: &mut CudaSlice<u8>,
3321        off: usize,
3322        src: &CudaSlice<u8>,
3323        len: usize,
3324    ) -> Result<(), Box<dyn std::error::Error>> {
3325        let mut view = dst.slice_mut(off..off + len);
3326        self.gpu
3327            .stream()
3328            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3329        Ok(())
3330    }
3331
3332    /// D2D byte-range copy with explicit source and destination offsets.
3333    pub fn copy_u8_range_into(
3334        &self,
3335        dst: &mut CudaSlice<u8>,
3336        dst_off: usize,
3337        src: &CudaSlice<u8>,
3338        src_off: usize,
3339        len: usize,
3340    ) -> Result<(), Box<dyn std::error::Error>> {
3341        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3342        self.gpu
3343            .stream()
3344            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3345        Ok(())
3346    }
3347
3348    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3349    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3350    /// keeping the audited attention range contiguous without changing its absolute start.
3351    pub fn prepare_kv_append(
3352        &self,
3353        kv: &mut crate::cache::KvLayer,
3354        retain_from: usize,
3355        append_rows: usize,
3356    ) -> Result<usize, Box<dyn std::error::Error>> {
3357        let Some(plan) = kv
3358            .ring
3359            .as_ref()
3360            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3361            .transpose()?
3362        else {
3363            return Ok(kv.len);
3364        };
3365        match plan {
3366            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3367            crate::cache::KvRingAppend::Rebase {
3368                src_row,
3369                keep_rows,
3370                new_base,
3371                write_row,
3372            } => {
3373                if keep_rows > 0 {
3374                    let k_len = keep_rows * kv.k_tok_bytes;
3375                    let v_len = keep_rows * kv.v_tok_bytes;
3376                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3377                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3378                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3379                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3380                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3381                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3382                }
3383                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3384                Ok(write_row)
3385            }
3386        }
3387    }
3388
3389    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3390    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3391    pub fn htod_u8_into(
3392        &self,
3393        dst: &mut CudaSlice<u8>,
3394        off: usize,
3395        src: &[u8],
3396    ) -> Result<(), Box<dyn std::error::Error>> {
3397        let mut view = dst.slice_mut(off..off + src.len());
3398        self.gpu.stream().memcpy_htod(src, &mut view)?;
3399        Ok(())
3400    }
3401
3402    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3403        b.slice(0..len)
3404    }
3405
3406    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3407    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3408    pub fn view_u8_range<'a>(
3409        &self,
3410        b: &'a CudaSlice<u8>,
3411        start: usize,
3412        end: usize,
3413    ) -> cudarc::driver::CudaView<'a, u8> {
3414        b.slice(start..end)
3415    }
3416    pub fn view_u8<'a>(
3417        &self,
3418        b: &'a CudaSlice<u8>,
3419        len: usize,
3420    ) -> cudarc::driver::CudaView<'a, u8> {
3421        b.slice(0..len)
3422    }
3423
3424    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3425    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3426    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3427    pub fn append_kv_quantized(
3428        &self,
3429        k_row: &CudaSlice<f32>,
3430        v_row: &CudaSlice<f32>,
3431        kc: &mut CudaSlice<u8>,
3432        vc: &mut CudaSlice<u8>,
3433        t: usize,
3434        kv_dim_k: usize,
3435        kv_dim_v: usize,
3436        k_tok_bytes: usize,
3437        v_tok_bytes: usize,
3438        g: bool,
3439    ) -> Result<(), Box<dyn std::error::Error>> {
3440        let f = if g {
3441            self.func_g("append_quantize_kv_q8_0_q5_1")
3442        } else {
3443            self.func("append_quantize_kv_q8_0_q5_1")
3444        };
3445        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3446        let cfg = LaunchConfig {
3447            grid_dim: (nblk, 1, 1),
3448            block_dim: (32, 1, 1),
3449            shared_mem_bytes: 0,
3450        };
3451        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3452        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3453        let __s_b = self.gpu.stream();
3454        let mut b = __s_b.launch_builder(&f);
3455        b.arg(k_row)
3456            .arg(v_row)
3457            .arg(kc)
3458            .arg(vc)
3459            .arg(&ti)
3460            .arg(&kdk)
3461            .arg(&kdv)
3462            .arg(&ktb)
3463            .arg(&vtb);
3464        unsafe {
3465            b.launch(cfg)?;
3466        }
3467        Ok(())
3468    }
3469
3470    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3471    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3472    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3473    pub fn append_kv_quantized_dc(
3474        &self,
3475        k_row: &CudaSlice<f32>,
3476        v_row: &CudaSlice<f32>,
3477        kc: &mut CudaSlice<u8>,
3478        vc: &mut CudaSlice<u8>,
3479        t_dev: &CudaSlice<i32>,
3480        kv_dim_k: usize,
3481        kv_dim_v: usize,
3482        k_tok_bytes: usize,
3483        v_tok_bytes: usize,
3484        g: bool,
3485    ) -> Result<(), Box<dyn std::error::Error>> {
3486        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3487        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3488        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3489        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3490        if Self::pdl_on() && Self::pdl_wb_on() {
3491            use cudarc::driver::{DevicePtr, DevicePtrMut};
3492            let s = &self.gpu.stream();
3493            let (pk, _g0) = k_row.device_ptr(s);
3494            let (pv, _g1) = v_row.device_ptr(s);
3495            let (pkc, _g2) = kc.device_ptr_mut(s);
3496            let (pvc, _g3) = vc.device_ptr_mut(s);
3497            let (pt, _g4) = t_dev.device_ptr(s);
3498            let mut ps = [
3499                &pk as *const _ as *mut std::ffi::c_void,
3500                &pv as *const _ as *mut _,
3501                &pkc as *const _ as *mut _,
3502                &pvc as *const _ as *mut _,
3503                &pt as *const _ as *mut _,
3504                &kdk as *const _ as *mut _,
3505                &kdv as *const _ as *mut _,
3506                &ktb as *const _ as *mut _,
3507                &vtb as *const _ as *mut _,
3508            ];
3509            unsafe {
3510                self.launch_pdl_flash(
3511                    g,
3512                    "append_quantize_kv_q8_0_q5_1_dc",
3513                    (nblk, 1, 1),
3514                    (32, 1, 1),
3515                    0,
3516                    &mut ps,
3517                )?;
3518            }
3519            return Ok(());
3520        }
3521        let f = if g {
3522            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3523        } else {
3524            self.func("append_quantize_kv_q8_0_q5_1_dc")
3525        };
3526        let cfg = LaunchConfig {
3527            grid_dim: (nblk, 1, 1),
3528            block_dim: (32, 1, 1),
3529            shared_mem_bytes: 0,
3530        };
3531        let __s_b = self.gpu.stream();
3532        let mut b = __s_b.launch_builder(&f);
3533        b.arg(k_row)
3534            .arg(v_row)
3535            .arg(kc)
3536            .arg(vc)
3537            .arg(t_dev)
3538            .arg(&kdk)
3539            .arg(&kdv)
3540            .arg(&ktb)
3541            .arg(&vtb);
3542        unsafe {
3543            b.launch(cfg)?;
3544        }
3545        Ok(())
3546    }
3547
3548    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3549    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3550    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3551    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3552    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3553    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3554    #[allow(clippy::too_many_arguments)]
3555    pub fn append_kv_quantized_rows(
3556        &self,
3557        k_rows: &CudaSlice<f32>,
3558        v_rows: &CudaSlice<f32>,
3559        kc: &mut CudaSlice<u8>,
3560        vc: &mut CudaSlice<u8>,
3561        t0: usize,
3562        t: usize,
3563        kv_dim_k: usize,
3564        kv_dim_v: usize,
3565        k_tok_bytes: usize,
3566        v_tok_bytes: usize,
3567        g: bool,
3568    ) -> Result<(), Box<dyn std::error::Error>> {
3569        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3570            for i in 0..t {
3571                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3572                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3573                self.append_kv_quantized_view(
3574                    &k_row,
3575                    &v_row,
3576                    kc,
3577                    vc,
3578                    t0 + i,
3579                    kv_dim_k,
3580                    kv_dim_v,
3581                    k_tok_bytes,
3582                    v_tok_bytes,
3583                    g,
3584                )?;
3585            }
3586            return Ok(());
3587        }
3588        let f = if g {
3589            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3590        } else {
3591            self.func("append_quantize_kv_q8_0_q5_1_rows")
3592        };
3593        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3594        let cfg = LaunchConfig {
3595            grid_dim: (nblk, t as u32, 1),
3596            block_dim: (32, 1, 1),
3597            shared_mem_bytes: 0,
3598        };
3599        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3600        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3601        let __s_b = self.gpu.stream();
3602        let mut b = __s_b.launch_builder(&f);
3603        b.arg(k_rows)
3604            .arg(v_rows)
3605            .arg(kc)
3606            .arg(vc)
3607            .arg(&t0i)
3608            .arg(&kdk)
3609            .arg(&kdv)
3610            .arg(&ktb)
3611            .arg(&vtb);
3612        unsafe {
3613            b.launch(cfg)?;
3614        }
3615        Ok(())
3616    }
3617
3618    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3619    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3620    /// later, inside a captured graph) without a host round-trip.
3621    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3622        let f = self.func("inc_i32");
3623        let cfg = LaunchConfig {
3624            grid_dim: (1, 1, 1),
3625            block_dim: (1, 1, 1),
3626            shared_mem_bytes: 0,
3627        };
3628        let __s_b = self.gpu.stream();
3629        let mut b = __s_b.launch_builder(&f);
3630        b.arg(p);
3631        unsafe {
3632            b.launch(cfg)?;
3633        }
3634        Ok(())
3635    }
3636
3637    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3638    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3639    pub fn append_kv_quantized_view(
3640        &self,
3641        k_row: &cudarc::driver::CudaView<f32>,
3642        v_row: &cudarc::driver::CudaView<f32>,
3643        kc: &mut CudaSlice<u8>,
3644        vc: &mut CudaSlice<u8>,
3645        t: usize,
3646        kv_dim_k: usize,
3647        kv_dim_v: usize,
3648        k_tok_bytes: usize,
3649        v_tok_bytes: usize,
3650        g: bool,
3651    ) -> Result<(), Box<dyn std::error::Error>> {
3652        let f = if g {
3653            self.func_g("append_quantize_kv_q8_0_q5_1")
3654        } else {
3655            self.func("append_quantize_kv_q8_0_q5_1")
3656        };
3657        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3658        let cfg = LaunchConfig {
3659            grid_dim: (nblk, 1, 1),
3660            block_dim: (32, 1, 1),
3661            shared_mem_bytes: 0,
3662        };
3663        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3664        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3665        let __s_b = self.gpu.stream();
3666        let mut b = __s_b.launch_builder(&f);
3667        b.arg(k_row)
3668            .arg(v_row)
3669            .arg(kc)
3670            .arg(vc)
3671            .arg(&ti)
3672            .arg(&kdk)
3673            .arg(&kdv)
3674            .arg(&ktb)
3675            .arg(&vtb);
3676        unsafe {
3677            b.launch(cfg)?;
3678        }
3679        Ok(())
3680    }
3681
3682    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3683    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3684    pub fn copy_view_into(
3685        &self,
3686        dst: &mut CudaSlice<f32>,
3687        off: usize,
3688        src: &cudarc::driver::CudaView<f32>,
3689        len: usize,
3690    ) -> Result<(), Box<dyn std::error::Error>> {
3691        let mut view = dst.slice_mut(off..off + len);
3692        self.gpu
3693            .stream()
3694            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3695        Ok(())
3696    }
3697
3698    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3699    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3700    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3701    pub fn clone_dtod(
3702        &self,
3703        src: &CudaSlice<f32>,
3704    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3705        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3706        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3707        Ok(dst)
3708    }
3709
3710    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3711    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3712    pub fn dtod_copy_view(
3713        &self,
3714        src: &cudarc::driver::CudaView<f32>,
3715        dst: &mut CudaSlice<f32>,
3716    ) -> Result<(), Box<dyn std::error::Error>> {
3717        self.gpu.stream().memcpy_dtod(src, dst)?;
3718        Ok(())
3719    }
3720
3721    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3722    pub fn dtod_copy_view_i8(
3723        &self,
3724        src: &cudarc::driver::CudaView<i8>,
3725        dst: &mut CudaSlice<i8>,
3726    ) -> Result<(), Box<dyn std::error::Error>> {
3727        self.gpu.stream().memcpy_dtod(src, dst)?;
3728        Ok(())
3729    }
3730
3731    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3732    pub fn dtod_copy_into(
3733        &self,
3734        src: &CudaSlice<f32>,
3735        dst: &mut CudaSlice<f32>,
3736        offset: usize,
3737    ) -> Result<(), Box<dyn std::error::Error>> {
3738        let n = src.len();
3739        let mut dv = dst.slice_mut(offset..offset + n);
3740        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3741        Ok(())
3742    }
3743
3744    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3745    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3746        self.alloc_uninit::<i8>(n)
3747    }
3748
3749    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3750    pub fn qmatvec(
3751        &self,
3752        w: &CudaSlice<u8>,
3753        x: &CudaSlice<f32>,
3754        m: usize,
3755        in_f: usize,
3756        out_f: usize,
3757        qtype: i32,
3758        row_bytes: usize,
3759    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3760        let f = self.func("qmatvec_f32");
3761        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3762        let cfg = LaunchConfig {
3763            grid_dim: (out_f as u32, m as u32, 1),
3764            block_dim: (256, 1, 1),
3765            shared_mem_bytes: 0,
3766        };
3767        let (inf, outf, mi, qt, rb) =
3768            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3769        let __s_b = self.gpu.stream();
3770        let mut b = __s_b.launch_builder(&f);
3771        b.arg(w)
3772            .arg(x)
3773            .arg(&mut y)
3774            .arg(&inf)
3775            .arg(&outf)
3776            .arg(&mi)
3777            .arg(&qt)
3778            .arg(&rb);
3779        unsafe {
3780            b.launch(cfg)?;
3781        }
3782        Ok(y)
3783    }
3784
3785    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3786    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3787        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3788        self.keep_if_capturing(&s);
3789        Ok(s)
3790    }
3791
3792    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3793    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3794    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3795    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3796        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3797        self.keep_if_capturing(&s);
3798        Ok(s)
3799    }
3800
3801    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3802    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3803    pub fn memset_zeros_view(
3804        &self,
3805        dst: &mut cudarc::driver::CudaViewMut<f32>,
3806    ) -> Result<(), Box<dyn std::error::Error>> {
3807        self.gpu.stream().memset_zeros(dst)?;
3808        Ok(())
3809    }
3810
3811    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3812    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3813    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3814    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3815    /// stream would require an event).
3816    pub fn stage_expert(
3817        &self,
3818        host_bytes: &[u8],
3819        scratch: &mut CudaSlice<u8>,
3820        off: usize,
3821    ) -> Result<(), Box<dyn std::error::Error>> {
3822        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3823        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3824        Ok(())
3825    }
3826
3827    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3828    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3829    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3830    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3831    /// One CTA per token row, 256 threads (one per expert).
3832    pub fn moe_router_topk(
3833        &self,
3834        logits: &CudaSlice<f32>,
3835        t: usize,
3836        n_expert: usize,
3837        n_used: usize,
3838    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3839        let f = self.func("moe_router_topk_f32");
3840        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3841        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3842        let cfg = LaunchConfig {
3843            grid_dim: (t as u32, 1, 1),
3844            block_dim: (n_expert as u32, 1, 1),
3845            shared_mem_bytes: 0,
3846        };
3847        let (ne, nu) = (n_expert as i32, n_used as i32);
3848        let __s_b = self.gpu.stream();
3849        let mut b = __s_b.launch_builder(&f);
3850        b.arg(logits)
3851            .arg(&mut sel_idx)
3852            .arg(&mut sel_w)
3853            .arg(&ne)
3854            .arg(&nu);
3855        unsafe {
3856            b.launch(cfg)?;
3857        }
3858        Ok((sel_idx, sel_w))
3859    }
3860
3861    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3862    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3863    pub fn moe_router_topk_scaled(
3864        &self,
3865        logits: &CudaSlice<f32>,
3866        t: usize,
3867        n_expert: usize,
3868        n_used: usize,
3869        ex_scale: &CudaSlice<f32>,
3870    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3871        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3872        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3873        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3874        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3875        let f = self.func("moe_router_topk_scaled_f32");
3876        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3877        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3878        let cfg = LaunchConfig {
3879            grid_dim: (t as u32, 1, 1),
3880            block_dim: (n_expert as u32, 1, 1),
3881            shared_mem_bytes: 0,
3882        };
3883        let (ne, nu) = (n_expert as i32, n_used as i32);
3884        let __s_b = self.gpu.stream();
3885        let mut b = __s_b.launch_builder(&f);
3886        b.arg(logits)
3887            .arg(&mut sel_idx)
3888            .arg(&mut sel_w)
3889            .arg(&ne)
3890            .arg(&nu)
3891            .arg(ex_scale);
3892        unsafe {
3893            b.launch(cfg)?;
3894        }
3895        Ok((sel_idx, sel_w))
3896    }
3897
3898    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3899    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3900    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3901    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3902    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3903    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3904    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3905    pub fn moe_router_topk_host(
3906        &self,
3907        logits: &CudaSlice<f32>,
3908        t: usize,
3909        n_expert: usize,
3910        n_used: usize,
3911    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3912        let f = self.func("moe_router_topk_f32");
3913        let n = t * n_used;
3914        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
3915        let mut sel_w = self.alloc_uninit::<f32>(n)?;
3916        let cfg = LaunchConfig {
3917            grid_dim: (t as u32, 1, 1),
3918            block_dim: (n_expert as u32, 1, 1),
3919            shared_mem_bytes: 0,
3920        };
3921        let (ne, nu) = (n_expert as i32, n_used as i32);
3922        let __s_b = self.gpu.stream();
3923        let mut b = __s_b.launch_builder(&f);
3924        b.arg(logits)
3925            .arg(&mut sel_idx)
3926            .arg(&mut sel_w)
3927            .arg(&ne)
3928            .arg(&nu);
3929        unsafe {
3930            b.launch(cfg)?;
3931        }
3932        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
3933        let bytes = n * 8;
3934        let mut guard = self.router_stage.lock().unwrap();
3935        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
3936            *guard = Some(PinnedStage::new(bytes.max(4096))?);
3937        }
3938        let stage = guard.as_mut().unwrap();
3939        let (si, sw) = unsafe {
3940            (
3941                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
3942                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
3943            )
3944        };
3945        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
3946        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
3947        self.gpu.stream().synchronize()?; // ONE sync for both
3948        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
3949    }
3950
3951    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
3952    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
3953    /// original expert ids before top-k. Exact key ties choose the smaller original id.
3954    #[allow(clippy::too_many_arguments)]
3955    pub fn moe_router_sigmoid_topk(
3956        &self,
3957        logits: &CudaSlice<f32>,
3958        t: usize,
3959        n_expert: usize,
3960        n_used: usize,
3961        active_count: usize,
3962        correction_bias: &CudaSlice<f32>,
3963        active: &CudaSlice<u8>,
3964        scaling_factor: f32,
3965        route_norm: bool,
3966    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3967        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
3968        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
3969            return Err(format!(
3970                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
3971            )
3972            .into());
3973        }
3974        if logits.len() < t * n_expert
3975            || correction_bias.len() != n_expert
3976            || active.len() != n_expert
3977        {
3978            return Err(format!(
3979                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
3980                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
3981            ).into());
3982        }
3983        let f = self.func("moe_router_sigmoid_topk_f32");
3984        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3985        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3986        let threads = n_expert.div_ceil(32) * 32;
3987        let cfg = LaunchConfig {
3988            grid_dim: (t as u32, 1, 1),
3989            block_dim: (threads as u32, 1, 1),
3990            shared_mem_bytes: 0,
3991        };
3992        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
3993        let __s_b = self.gpu.stream();
3994        let mut b = __s_b.launch_builder(&f);
3995        b.arg(logits)
3996            .arg(correction_bias)
3997            .arg(active)
3998            .arg(&mut sel_idx)
3999            .arg(&mut sel_w)
4000            .arg(&ne)
4001            .arg(&nu)
4002            .arg(&scaling_factor)
4003            .arg(&rn);
4004        unsafe {
4005            b.launch(cfg)?;
4006        }
4007        Ok((sel_idx, sel_w))
4008    }
4009
4010    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
4011    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
4012    #[allow(clippy::too_many_arguments)]
4013    pub fn moe_router_sigmoid_topk_host(
4014        &self,
4015        logits: &CudaSlice<f32>,
4016        t: usize,
4017        n_expert: usize,
4018        n_used: usize,
4019        active_count: usize,
4020        correction_bias: &CudaSlice<f32>,
4021        active: &CudaSlice<u8>,
4022        scaling_factor: f32,
4023        route_norm: bool,
4024    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4025        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4026            logits,
4027            t,
4028            n_expert,
4029            n_used,
4030            active_count,
4031            correction_bias,
4032            active,
4033            scaling_factor,
4034            route_norm,
4035        )?;
4036        let n = t * n_used;
4037        let bytes = n * 8;
4038        let mut guard = self.router_stage.lock().unwrap();
4039        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4040            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4041        }
4042        let stage = guard.as_mut().unwrap();
4043        let (si, sw) = unsafe {
4044            (
4045                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4046                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4047            )
4048        };
4049        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4050        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4051        self.gpu.stream().synchronize()?;
4052        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4053    }
4054
4055    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4056    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4057    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4058    pub fn stage_expert_async(
4059        &self,
4060        host_bytes: &[u8],
4061        scratch: &mut CudaSlice<u8>,
4062        off: usize,
4063    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4064        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4065        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4066        Ok(self.copy_stream.record_event(None)?)
4067    }
4068
4069    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4070    pub fn compute_wait(
4071        &self,
4072        ev: &cudarc::driver::CudaEvent,
4073    ) -> Result<(), Box<dyn std::error::Error>> {
4074        self.gpu.stream().wait(ev)?;
4075        Ok(())
4076    }
4077
4078    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4079    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4080    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4081    /// CudaView base+offset pointer is honored by the launch arg.
4082    pub fn qmatvec_view(
4083        &self,
4084        w: &CudaSlice<u8>,
4085        range: std::ops::Range<usize>,
4086        x: &cudarc::driver::CudaView<f32>,
4087        m: usize,
4088        in_f: usize,
4089        out_f: usize,
4090        qtype: i32,
4091        row_bytes: usize,
4092    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4093        let f = self.func("qmatvec_f32");
4094        let wv = w.slice(range); // CudaView<u8>, offset honored
4095        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4096        let cfg = LaunchConfig {
4097            grid_dim: (out_f as u32, m as u32, 1),
4098            block_dim: (256, 1, 1),
4099            shared_mem_bytes: 0,
4100        };
4101        let (inf, outf, mi, qt, rb) =
4102            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4103        let __s_b = self.gpu.stream();
4104        let mut b = __s_b.launch_builder(&f);
4105        b.arg(&wv)
4106            .arg(x)
4107            .arg(&mut y)
4108            .arg(&inf)
4109            .arg(&outf)
4110            .arg(&mi)
4111            .arg(&qt)
4112            .arg(&rb);
4113        unsafe {
4114            b.launch(cfg)?;
4115        }
4116        Ok(y)
4117    }
4118
4119    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4120    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4121    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4122    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4123    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4124    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4125    #[allow(clippy::too_many_arguments)]
4126    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4127    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4128    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4129    pub fn moe_gate_up_silu8_q8(
4130        &self,
4131        gp: WPtr8,
4132        up: WPtr8,
4133        aq: &CudaSlice<i8>,
4134        ad: &CudaSlice<f32>,
4135        in_f: usize,
4136        n_ff: usize,
4137        n_used: usize,
4138        qt_g: i32,
4139        qt_u: i32,
4140        rb_g: usize,
4141        rb_u: usize,
4142    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4143        let f = self.func("moe_gate_up_silu8_q8");
4144        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4145        let cfg = LaunchConfig {
4146            grid_dim: (n_ff as u32, n_used as u32, 1),
4147            block_dim: (32, 1, 1),
4148            shared_mem_bytes: 0,
4149        };
4150        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4151        let __s_b = self.gpu.stream();
4152        let mut b = __s_b.launch_builder(&f);
4153        b.arg(&gp)
4154            .arg(&up)
4155            .arg(aq)
4156            .arg(ad)
4157            .arg(&mut act)
4158            .arg(&inf)
4159            .arg(&nff)
4160            .arg(&qt_g)
4161            .arg(&qt_u)
4162            .arg(&rbg)
4163            .arg(&rbu);
4164        unsafe {
4165            b.launch(cfg)?;
4166        }
4167        Ok(act)
4168    }
4169
4170    #[allow(clippy::too_many_arguments)]
4171    pub fn moe_down8_fma_q8(
4172        &self,
4173        dp: WPtr8,
4174        w: F32x8,
4175        aq2: &CudaSlice<i8>,
4176        ad2: &CudaSlice<f32>,
4177        dst: &mut cudarc::driver::CudaViewMut<f32>,
4178        in_f: usize,
4179        out_f: usize,
4180        n_used: usize,
4181        qt: i32,
4182        rb: usize,
4183    ) -> Result<(), Box<dyn std::error::Error>> {
4184        let f = self.func("moe_down8_fma_q8");
4185        let cfg = LaunchConfig {
4186            grid_dim: (out_f as u32, 1, 1),
4187            block_dim: (32, 1, 1),
4188            shared_mem_bytes: 0,
4189        };
4190        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4191        let __s_b = self.gpu.stream();
4192        let mut b = __s_b.launch_builder(&f);
4193        b.arg(&dp)
4194            .arg(&w)
4195            .arg(aq2)
4196            .arg(ad2)
4197            .arg(dst)
4198            .arg(&inf)
4199            .arg(&outf)
4200            .arg(&nu)
4201            .arg(&qt)
4202            .arg(&rbi);
4203        unsafe {
4204            b.launch(cfg)?;
4205        }
4206        Ok(())
4207    }
4208
4209    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4210    pub fn qmatvec_expert_q8(
4211        &self,
4212        w: &CudaSlice<u8>,
4213        range: std::ops::Range<usize>,
4214        aq: &CudaSlice<i8>,
4215        ad: &CudaSlice<f32>,
4216        m: usize,
4217        in_f: usize,
4218        out_f: usize,
4219        qtype: i32,
4220        row_bytes: usize,
4221    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4222        let f = self.func("qmatvec_expert_q8");
4223        let wv = w.slice(range);
4224        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4225        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4226        let cfg = LaunchConfig {
4227            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4228            block_dim: (32, ROWS, 1),
4229            shared_mem_bytes: 0,
4230        };
4231        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4232        let __s_b = self.gpu.stream();
4233        let mut b = __s_b.launch_builder(&f);
4234        b.arg(&wv)
4235            .arg(aq)
4236            .arg(ad)
4237            .arg(&mut y)
4238            .arg(&inf)
4239            .arg(&outf)
4240            .arg(&mi)
4241            .arg(&qtype)
4242            .arg(&rbi);
4243        unsafe {
4244            b.launch(cfg)?;
4245        }
4246        Ok(y)
4247    }
4248
4249    pub fn moe_gate_up_silu8(
4250        &self,
4251        gp: WPtr8,
4252        up: WPtr8,
4253        x: &cudarc::driver::CudaView<f32>,
4254        in_f: usize,
4255        n_ff: usize,
4256        n_used: usize,
4257        qt_g: i32,
4258        qt_u: i32,
4259        rb_g: usize,
4260        rb_u: usize,
4261    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4262        let f = self.func("moe_gate_up_silu8_f32");
4263        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4264        let cfg = LaunchConfig {
4265            grid_dim: (n_ff as u32, n_used as u32, 1),
4266            block_dim: (256, 1, 1),
4267            shared_mem_bytes: 0,
4268        };
4269        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4270        let __s_b = self.gpu.stream();
4271        let mut b = __s_b.launch_builder(&f);
4272        b.arg(&gp)
4273            .arg(&up)
4274            .arg(x)
4275            .arg(&mut act)
4276            .arg(&inf)
4277            .arg(&nff)
4278            .arg(&qt_g)
4279            .arg(&qt_u)
4280            .arg(&rbg)
4281            .arg(&rbu);
4282        unsafe {
4283            b.launch(cfg)?;
4284        }
4285        Ok(act)
4286    }
4287
4288    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4289    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4290    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4291    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4292    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4293    #[allow(clippy::too_many_arguments)]
4294    pub fn moe_down8_fma_into(
4295        &self,
4296        dp: WPtr8,
4297        w: F32x8,
4298        act: &CudaSlice<f32>,
4299        dst: &mut cudarc::driver::CudaViewMut<f32>,
4300        in_f: usize,
4301        out_f: usize,
4302        n_used: usize,
4303        qt: i32,
4304        rb: usize,
4305    ) -> Result<(), Box<dyn std::error::Error>> {
4306        let f = self.func("moe_down8_fma_f32");
4307        let cfg = LaunchConfig {
4308            grid_dim: (out_f as u32, 1, 1),
4309            block_dim: (256, 1, 1),
4310            shared_mem_bytes: 0,
4311        };
4312        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4313        let __s_b = self.gpu.stream();
4314        let mut b = __s_b.launch_builder(&f);
4315        b.arg(&dp)
4316            .arg(&w)
4317            .arg(act)
4318            .arg(dst)
4319            .arg(&inf)
4320            .arg(&outf)
4321            .arg(&nu)
4322            .arg(&qt)
4323            .arg(&rbv);
4324        unsafe {
4325            b.launch(cfg)?;
4326        }
4327        Ok(())
4328    }
4329
4330    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4331    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4332    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4333    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4334    #[allow(clippy::too_many_arguments)]
4335    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4336    ///
4337    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4338    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4339    /// down's FMA chain stays slot-ordered serial). Seams:
4340    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4341    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4342    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4343    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4344    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4345    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4346    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4347    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4348    ///                       only) | w8h2 (h2 x slot-parallel)
4349    #[allow(clippy::too_many_arguments)]
4350    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4351    #[allow(clippy::too_many_arguments)]
4352    pub fn moe_pairs_matvec_q8(
4353        &self,
4354        table: &CudaSlice<u64>,
4355        proj: i32,
4356        pair_tok: &CudaSlice<i32>,
4357        pair_ex: &CudaSlice<i32>,
4358        aq: &CudaSlice<i8>,
4359        ad: &CudaSlice<f32>,
4360        in_f: usize,
4361        out_f: usize,
4362        n_expert: usize,
4363        n_pairs: usize,
4364        qtype: i32,
4365        row_bytes: usize,
4366    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4367        let f = self.func("moe_pairs_matvec_q8");
4368        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4369        const ROWS: u32 = 4;
4370        let cfg = LaunchConfig {
4371            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4372            block_dim: (32, ROWS, 1),
4373            shared_mem_bytes: 0,
4374        };
4375        let (inf, outf, ne, np, rbi) = (
4376            in_f as i32,
4377            out_f as i32,
4378            n_expert as i32,
4379            n_pairs as i32,
4380            row_bytes as i64,
4381        );
4382        let __s_b = self.gpu.stream();
4383        let mut b = __s_b.launch_builder(&f);
4384        b.arg(table)
4385            .arg(&proj)
4386            .arg(pair_tok)
4387            .arg(pair_ex)
4388            .arg(aq)
4389            .arg(ad)
4390            .arg(&mut y)
4391            .arg(&inf)
4392            .arg(&outf)
4393            .arg(&ne)
4394            .arg(&np)
4395            .arg(&qtype)
4396            .arg(&rbi);
4397        unsafe {
4398            b.launch(cfg)?;
4399        }
4400        Ok(y)
4401    }
4402
4403    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4404    #[allow(clippy::too_many_arguments)]
4405    pub fn moe_pairs_matvec_q8_em(
4406        &self,
4407        table: &CudaSlice<u64>,
4408        proj: i32,
4409        ex_ids: &CudaSlice<i32>,
4410        ex_off: &CudaSlice<i32>,
4411        ex_pairs: &CudaSlice<i32>,
4412        pair_tok: &CudaSlice<i32>,
4413        aq: &CudaSlice<i8>,
4414        ad: &CudaSlice<f32>,
4415        in_f: usize,
4416        out_f: usize,
4417        n_expert: usize,
4418        n_active: usize,
4419        n_pairs: usize,
4420        qtype: i32,
4421        row_bytes: usize,
4422    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4423        let f = self.func("moe_pairs_matvec_q8_em");
4424        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4425        const ROWS: u32 = 4;
4426        let cfg = LaunchConfig {
4427            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4428            block_dim: (32, ROWS, 1),
4429            shared_mem_bytes: 0,
4430        };
4431        let (inf, outf, ne, na, rbi) = (
4432            in_f as i32,
4433            out_f as i32,
4434            n_expert as i32,
4435            n_active as i32,
4436            row_bytes as i64,
4437        );
4438        let __s_b = self.gpu.stream();
4439        let mut b = __s_b.launch_builder(&f);
4440        b.arg(table)
4441            .arg(&proj)
4442            .arg(ex_ids)
4443            .arg(ex_off)
4444            .arg(ex_pairs)
4445            .arg(pair_tok)
4446            .arg(aq)
4447            .arg(ad)
4448            .arg(&mut y)
4449            .arg(&inf)
4450            .arg(&outf)
4451            .arg(&ne)
4452            .arg(&na)
4453            .arg(&qtype)
4454            .arg(&rbi);
4455        unsafe {
4456            b.launch(cfg)?;
4457        }
4458        Ok(y)
4459    }
4460
4461    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4462    // weight group once per (row,group) then dp4a's across the expert's token group.
4463    #[allow(clippy::too_many_arguments)]
4464    pub fn moe_pairs_matvec_q8_dec(
4465        &self,
4466        table: &CudaSlice<u64>,
4467        proj: i32,
4468        ex_ids: &CudaSlice<i32>,
4469        ex_off: &CudaSlice<i32>,
4470        ex_pairs: &CudaSlice<i32>,
4471        pair_tok: &CudaSlice<i32>,
4472        aq: &CudaSlice<i8>,
4473        ad: &CudaSlice<f32>,
4474        in_f: usize,
4475        out_f: usize,
4476        n_expert: usize,
4477        n_active: usize,
4478        n_pairs: usize,
4479        qtype: i32,
4480        row_bytes: usize,
4481    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4482        let f = self.func("moe_pairs_matvec_q8_dec");
4483        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4484        const ROWS: u32 = 4;
4485        let cfg = LaunchConfig {
4486            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4487            block_dim: (32, ROWS, 1),
4488            shared_mem_bytes: 0,
4489        };
4490        let (inf, outf, ne, na, rbi) = (
4491            in_f as i32,
4492            out_f as i32,
4493            n_expert as i32,
4494            n_active as i32,
4495            row_bytes as i64,
4496        );
4497        let __s_b = self.gpu.stream();
4498        let mut b = __s_b.launch_builder(&f);
4499        b.arg(table)
4500            .arg(&proj)
4501            .arg(ex_ids)
4502            .arg(ex_off)
4503            .arg(ex_pairs)
4504            .arg(pair_tok)
4505            .arg(aq)
4506            .arg(ad)
4507            .arg(&mut y)
4508            .arg(&inf)
4509            .arg(&outf)
4510            .arg(&ne)
4511            .arg(&na)
4512            .arg(&qtype)
4513            .arg(&rbi);
4514        unsafe {
4515            b.launch(cfg)?;
4516        }
4517        Ok(y)
4518    }
4519
4520    pub fn moe_pairs_gelu_mul(
4521        &self,
4522        gate: &CudaSlice<f32>,
4523        up: &CudaSlice<f32>,
4524        n: usize,
4525    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4526        let f = self.func("moe_pairs_gelu_mul");
4527        let mut act = self.alloc_uninit::<f32>(n)?;
4528        let cfg = LaunchConfig::for_num_elems(n as u32);
4529        let nl = n as i64;
4530        let __s_b = self.gpu.stream();
4531        let mut b = __s_b.launch_builder(&f);
4532        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4533        unsafe {
4534            b.launch(cfg)?;
4535        }
4536        Ok(act)
4537    }
4538
4539    pub fn moe_pairs_silu_mul(
4540        &self,
4541        gate: &CudaSlice<f32>,
4542        up: &CudaSlice<f32>,
4543        n: usize,
4544    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4545        let f = self.func("moe_pairs_silu_mul");
4546        let mut act = self.alloc_uninit::<f32>(n)?;
4547        let cfg = LaunchConfig::for_num_elems(n as u32);
4548        let nl = n as i64;
4549        let __s_b = self.gpu.stream();
4550        let mut b = __s_b.launch_builder(&f);
4551        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4552        unsafe {
4553            b.launch(cfg)?;
4554        }
4555        Ok(act)
4556    }
4557
4558    #[allow(clippy::too_many_arguments)]
4559    pub fn moe_pairs_scatter(
4560        &self,
4561        y_down: &CudaSlice<f32>,
4562        pair_w: &CudaSlice<f32>,
4563        tok_pair_off: &CudaSlice<i32>,
4564        tok_pair_ids: &CudaSlice<i32>,
4565        moe_out: &mut CudaSlice<f32>,
4566        t: usize,
4567        n_embd: usize,
4568    ) -> Result<(), Box<dyn std::error::Error>> {
4569        let f = self.func("moe_pairs_scatter");
4570        let cfg = LaunchConfig {
4571            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4572            block_dim: (256, 1, 1),
4573            shared_mem_bytes: 0,
4574        };
4575        let ne = n_embd as i32;
4576        let __s_b = self.gpu.stream();
4577        let mut b = __s_b.launch_builder(&f);
4578        b.arg(y_down)
4579            .arg(pair_w)
4580            .arg(tok_pair_off)
4581            .arg(tok_pair_ids)
4582            .arg(moe_out)
4583            .arg(&ne);
4584        unsafe {
4585            b.launch(cfg)?;
4586        }
4587        Ok(())
4588    }
4589
4590    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4591    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4592    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4593    #[allow(clippy::too_many_arguments)]
4594    pub fn moe_gate_up_gelu8_dev_q8(
4595        &self,
4596        table: &CudaSlice<u64>,
4597        sel: &cudarc::driver::CudaView<i32>,
4598        aq: &CudaSlice<i8>,
4599        ad: &CudaSlice<f32>,
4600        in_f: usize,
4601        n_ff: usize,
4602        n_used: usize,
4603        n_expert: usize,
4604        qt_g: i32,
4605        qt_u: i32,
4606        rb_g: usize,
4607        rb_u: usize,
4608    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4609        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4610        let (inf, nff, ne, rbg, rbu) = (
4611            in_f as i32,
4612            n_ff as i32,
4613            n_expert as i32,
4614            rb_g as i64,
4615            rb_u as i64,
4616        );
4617        let f = self.func("moe_gate_up_gelu8_dev_q8");
4618        let cfg = LaunchConfig {
4619            grid_dim: (n_ff as u32, n_used as u32, 1),
4620            block_dim: (32, 1, 1),
4621            shared_mem_bytes: 0,
4622        };
4623        let __s_b = self.gpu.stream();
4624        let mut b = __s_b.launch_builder(&f);
4625        b.arg(table)
4626            .arg(sel)
4627            .arg(aq)
4628            .arg(ad)
4629            .arg(&mut act)
4630            .arg(&inf)
4631            .arg(&nff)
4632            .arg(&ne)
4633            .arg(&qt_g)
4634            .arg(&qt_u)
4635            .arg(&rbg)
4636            .arg(&rbu);
4637        unsafe {
4638            b.launch(cfg)?;
4639        }
4640        Ok(act)
4641    }
4642
4643    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4644    #[allow(clippy::too_many_arguments)]
4645    pub fn moe_gate_up_gelu8_dev_q8_rows(
4646        &self,
4647        table: &CudaSlice<u64>,
4648        sel: &CudaSlice<i32>,
4649        aq: &CudaSlice<i8>,
4650        ad: &CudaSlice<f32>,
4651        t: usize,
4652        in_f: usize,
4653        n_ff: usize,
4654        n_used: usize,
4655        n_expert: usize,
4656        qt_g: i32,
4657        qt_u: i32,
4658        rb_g: usize,
4659        rb_u: usize,
4660    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4661        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4662        let (inf, nff, ne, rbg, rbu, nu) = (
4663            in_f as i32,
4664            n_ff as i32,
4665            n_expert as i32,
4666            rb_g as i64,
4667            rb_u as i64,
4668            n_used as i32,
4669        );
4670        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4671        let cfg = LaunchConfig {
4672            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4673            block_dim: (32, 1, 1),
4674            shared_mem_bytes: 0,
4675        };
4676        let __s_b = self.gpu.stream();
4677        let mut b = __s_b.launch_builder(&f);
4678        b.arg(table)
4679            .arg(sel)
4680            .arg(aq)
4681            .arg(ad)
4682            .arg(&mut act)
4683            .arg(&inf)
4684            .arg(&nff)
4685            .arg(&ne)
4686            .arg(&qt_g)
4687            .arg(&qt_u)
4688            .arg(&rbg)
4689            .arg(&rbu)
4690            .arg(&nu);
4691        unsafe {
4692            b.launch(cfg)?;
4693        }
4694        Ok(act)
4695    }
4696
4697    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4698    #[allow(clippy::too_many_arguments)]
4699    pub fn moe_gate_up_gelu8_dev_q8_csr(
4700        &self,
4701        table: &CudaSlice<u64>,
4702        sel: &CudaSlice<i32>,
4703        aq: &CudaSlice<i8>,
4704        ad: &CudaSlice<f32>,
4705        n_pairs: usize,
4706        in_f: usize,
4707        n_ff: usize,
4708        n_used: usize,
4709        n_expert: usize,
4710        qt_g: i32,
4711        qt_u: i32,
4712        rb_g: usize,
4713        rb_u: usize,
4714    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4715        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4716        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4717            in_f as i32,
4718            n_ff as i32,
4719            n_expert as i32,
4720            rb_g as i64,
4721            rb_u as i64,
4722            n_used as i32,
4723            n_pairs as i32,
4724        );
4725        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4726        let cfg = LaunchConfig {
4727            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4728            block_dim: (32, 1, 1),
4729            shared_mem_bytes: 0,
4730        };
4731        let __s_b = self.gpu.stream();
4732        let mut b = __s_b.launch_builder(&f);
4733        b.arg(table)
4734            .arg(sel)
4735            .arg(aq)
4736            .arg(ad)
4737            .arg(&mut act)
4738            .arg(&inf)
4739            .arg(&nff)
4740            .arg(&ne)
4741            .arg(&qt_g)
4742            .arg(&qt_u)
4743            .arg(&rbg)
4744            .arg(&rbu)
4745            .arg(&nu)
4746            .arg(&npi);
4747        unsafe {
4748            b.launch(cfg)?;
4749        }
4750        Ok(act)
4751    }
4752
4753    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4754    #[allow(clippy::too_many_arguments)]
4755    pub fn moe_down8_fma_dev_q8_rows_g(
4756        &self,
4757        table: &CudaSlice<u64>,
4758        sel: &CudaSlice<i32>,
4759        w: &CudaSlice<f32>,
4760        aq2: &CudaSlice<i8>,
4761        ad2: &CudaSlice<f32>,
4762        dst: &mut CudaSlice<f32>,
4763        t: usize,
4764        in_f: usize,
4765        out_f: usize,
4766        n_used: usize,
4767        n_expert: usize,
4768        qt: i32,
4769        rb: usize,
4770    ) -> Result<(), Box<dyn std::error::Error>> {
4771        let (inf, outf, nu, ne, rbi) = (
4772            in_f as i32,
4773            out_f as i32,
4774            n_used as i32,
4775            n_expert as i32,
4776            rb as i64,
4777        );
4778        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4779        // eight warps, then replay the original slot-ordered FMA chain. Every
4780        // other shape retains the generic one-warp rows kernel.
4781        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4782        let f = self.func(if step_b1_w8 {
4783            "moe_down8_fma_dev_q8_rows_w8"
4784        } else {
4785            "moe_down8_fma_dev_q8_rows_g"
4786        });
4787        let cfg = LaunchConfig {
4788            grid_dim: (out_f as u32, 1, t as u32),
4789            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4790            shared_mem_bytes: 0,
4791        };
4792        let __s_b = self.gpu.stream();
4793        let mut b = __s_b.launch_builder(&f);
4794        b.arg(table)
4795            .arg(sel)
4796            .arg(w)
4797            .arg(aq2)
4798            .arg(ad2)
4799            .arg(dst)
4800            .arg(&inf)
4801            .arg(&outf)
4802            .arg(&nu)
4803            .arg(&ne)
4804            .arg(&qt)
4805            .arg(&rbi);
4806        unsafe {
4807            b.launch(cfg)?;
4808        }
4809        Ok(())
4810    }
4811
4812    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4813    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4814    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4815    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4816        let (out_f, in_f) = (2048usize, 2816usize);
4817        let nblk = in_f / 32;
4818        let mut seed = 0x9E3779B97F4A7C15u64;
4819        let mut rng = move || {
4820            seed = seed
4821                .wrapping_mul(6364136223846793005)
4822                .wrapping_add(1442695040888963407);
4823            (seed >> 33) as u8
4824        };
4825        let mut w = vec![0u8; out_f * nblk * 18];
4826        for b in w.iter_mut() {
4827            *b = rng();
4828        }
4829        for r in 0..out_f {
4830            for g in 0..nblk {
4831                let off = (r * nblk + g) * 18;
4832                w[off] = 0x00;
4833                w[off + 1] = 0x2C; // sane half d
4834            }
4835        }
4836        let qplane = out_f * nblk * 16;
4837        let mut wrp = vec![0u8; w.len()];
4838        for r in 0..out_f {
4839            for g in 0..nblk {
4840                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4841                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4842                    .copy_from_slice(&src[0..2]);
4843                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4844            }
4845        }
4846        let w_d = self.htod_bytes(&w)?;
4847        let wrp_d = self.htod_bytes(&wrp)?;
4848        let mut aq = vec![0i8; m * in_f];
4849        for v in aq.iter_mut() {
4850            *v = rng() as i8;
4851        }
4852        let aq_d = self.htod_i8(&aq)?;
4853        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4854        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4855        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4856        const RPB: u32 = 4;
4857        let cfg = LaunchConfig {
4858            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4859            block_dim: (32, RPB, 1),
4860            shared_mem_bytes: 0,
4861        };
4862        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4863        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4864        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4865        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4866        {
4867            let __s_b = self.gpu.stream();
4868            let mut b = __s_b.launch_builder(&fb);
4869            b.arg(&w_d)
4870                .arg(&aq_d)
4871                .arg(&ad_d)
4872                .arg(&mut y0)
4873                .arg(&inf)
4874                .arg(&outf)
4875                .arg(&mi)
4876                .arg(&rb);
4877            unsafe {
4878                b.launch(cfg)?;
4879            }
4880            let __s_b = self.gpu.stream();
4881            let mut b = __s_b.launch_builder(&fr);
4882            b.arg(&wrp_d)
4883                .arg(&aq_d)
4884                .arg(&ad_d)
4885                .arg(&mut y1)
4886                .arg(&inf)
4887                .arg(&outf)
4888                .arg(&mi)
4889                .arg(&qp);
4890            unsafe {
4891                b.launch(cfg)?;
4892            }
4893        }
4894        self.gpu.stream().synchronize()?;
4895        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4896        let nd = h0
4897            .iter()
4898            .zip(&h1)
4899            .filter(|(a, b)| a.to_bits() != b.to_bits())
4900            .count();
4901        if nd != 0 {
4902            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4903        }
4904        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4905            self.gpu.stream().synchronize()?;
4906            let t0 = std::time::Instant::now();
4907            for _ in 0..500 {
4908                if rp {
4909                    let __s_b = self.gpu.stream();
4910                    let mut b = __s_b.launch_builder(&fr);
4911                    b.arg(&wrp_d)
4912                        .arg(&aq_d)
4913                        .arg(&ad_d)
4914                        .arg(&mut y1)
4915                        .arg(&inf)
4916                        .arg(&outf)
4917                        .arg(&mi)
4918                        .arg(&qp);
4919                    unsafe {
4920                        b.launch(cfg)?;
4921                    }
4922                } else {
4923                    let __s_b = self.gpu.stream();
4924                    let mut b = __s_b.launch_builder(&fb);
4925                    b.arg(&w_d)
4926                        .arg(&aq_d)
4927                        .arg(&ad_d)
4928                        .arg(&mut y0)
4929                        .arg(&inf)
4930                        .arg(&outf)
4931                        .arg(&mi)
4932                        .arg(&rb);
4933                    unsafe {
4934                        b.launch(cfg)?;
4935                    }
4936                }
4937            }
4938            self.gpu.stream().synchronize()?;
4939            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
4940        };
4941        let _ = time(false)?;
4942        let _ = time(true)?; // warm
4943        Ok((time(false)?, time(true)?))
4944    }
4945
4946    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
4947    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
4948    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
4949    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
4950    pub fn build_q4_rp4(
4951        &self,
4952        t: &mut crate::model::GpuTensor,
4953    ) -> Result<(), Box<dyn std::error::Error>> {
4954        use crate::model::GpuTensor;
4955        let GpuTensor::Quant {
4956            bytes,
4957            qtype,
4958            row_bytes,
4959            ne,
4960            rp4,
4961            ..
4962        } = t
4963        else {
4964            return Ok(());
4965        };
4966        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
4967            return Ok(());
4968        }
4969        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
4970        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
4971            return Ok(());
4972        }
4973        let nblk = in_f / 32;
4974        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
4975        let f = self.func("q4_0_split_rp_build");
4976        let n = (out_f * nblk) as i32;
4977        let cfg = LaunchConfig {
4978            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
4979            block_dim: (256, 1, 1),
4980            shared_mem_bytes: 0,
4981        };
4982        let (of, nb) = (out_f as i32, nblk as i32);
4983        let _ = n;
4984        let __s_b = self.gpu.stream();
4985        let mut b = __s_b.launch_builder(&f);
4986        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
4987        unsafe {
4988            b.launch(cfg)?;
4989        }
4990        *rp4 = Some(dst);
4991        Ok(())
4992    }
4993
4994    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
4995    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
4996    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
4997    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
4998    pub fn build_q8_rp4(
4999        &self,
5000        t: &mut crate::model::GpuTensor,
5001    ) -> Result<(), Box<dyn std::error::Error>> {
5002        use crate::model::GpuTensor;
5003        let GpuTensor::Quant {
5004            bytes,
5005            qtype,
5006            row_bytes,
5007            ne,
5008            rp4,
5009            ..
5010        } = t
5011        else {
5012            return Ok(());
5013        };
5014        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
5015            return Ok(());
5016        }
5017        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5018        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5019            return Ok(());
5020        }
5021        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5022        Ok(())
5023    }
5024
5025    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5026    /// mirror without a GpuTensor (same kernel the loader path above uses).
5027    pub fn build_q8_rp4_raw(
5028        &self,
5029        bytes: &CudaSlice<u8>,
5030        in_f: usize,
5031        out_f: usize,
5032    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5033        assert!(in_f % 32 == 0);
5034        let nblk = in_f / 32;
5035        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5036        let f = self.func("q8_0_split_rp_build");
5037        let cfg = LaunchConfig {
5038            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5039            block_dim: (256, 1, 1),
5040            shared_mem_bytes: 0,
5041        };
5042        let (of, nb) = (out_f as i32, nblk as i32);
5043        let __s_b = self.gpu.stream();
5044        let mut b = __s_b.launch_builder(&f);
5045        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5046        unsafe {
5047            b.launch(cfg)?;
5048        }
5049        Ok(dst)
5050    }
5051
5052    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5053    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5054    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5055    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5056    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5057    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5058    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5059    pub fn build_q4k_rp4(
5060        &self,
5061        t: &mut crate::model::GpuTensor,
5062    ) -> Result<(), Box<dyn std::error::Error>> {
5063        use crate::model::GpuTensor;
5064        let GpuTensor::Quant {
5065            bytes,
5066            qtype,
5067            row_bytes,
5068            ne,
5069            rp4,
5070            ..
5071        } = t
5072        else {
5073            return Ok(());
5074        };
5075        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5076            return Ok(());
5077        }
5078        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5079        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5080            return Ok(());
5081        }
5082        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5083        Ok(())
5084    }
5085
5086    pub fn build_q6k_rp4(
5087        &self,
5088        t: &mut crate::model::GpuTensor,
5089    ) -> Result<(), Box<dyn std::error::Error>> {
5090        use crate::model::GpuTensor;
5091        let GpuTensor::Quant {
5092            bytes,
5093            qtype,
5094            row_bytes,
5095            ne,
5096            rp4,
5097            ..
5098        } = t
5099        else {
5100            return Ok(());
5101        };
5102        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5103            return Ok(());
5104        }
5105        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5106        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5107            return Ok(());
5108        }
5109        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5110        Ok(())
5111    }
5112
5113    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5114    pub fn build_kq_rp4_raw(
5115        &self,
5116        bytes: &CudaSlice<u8>,
5117        in_f: usize,
5118        out_f: usize,
5119        qtype: i32,
5120    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5121        assert!(in_f % 256 == 0);
5122        let nsbk = in_f / 256;
5123        let (sb_bytes, kname) = match qtype {
5124            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5125            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5126            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5127        };
5128        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5129        let f = self.func(kname);
5130        let cfg = LaunchConfig {
5131            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5132            block_dim: (256, 1, 1),
5133            shared_mem_bytes: 0,
5134        };
5135        let (of, nb) = (out_f as i32, nsbk as i32);
5136        let __s_b = self.gpu.stream();
5137        let mut b = __s_b.launch_builder(&f);
5138        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5139        unsafe {
5140            b.launch(cfg)?;
5141        }
5142        Ok(dst)
5143    }
5144
5145    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5146    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5147    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5148    pub fn kqrp_enabled() -> bool {
5149        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5150        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5151            Ok("0") => false,
5152            Ok(_) => true,
5153            Err(_) => cfg!(memra_hopper_mma),
5154        })
5155    }
5156
5157    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5158    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5159    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5160    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5161    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5162    pub fn build_q4_rp_swap(
5163        &self,
5164        t: &mut crate::model::GpuTensor,
5165    ) -> Result<bool, Box<dyn std::error::Error>> {
5166        use crate::model::GpuTensor;
5167        // QTYPE GUARD IN THE SWAP ITSELF (gemma4 NVFP4mix prefill-NaN, 2026-08-17):
5168        // `rp4` is a SHARED field — the Q8RP walk parks Q8_0 MIRRORS there, and this
5169        // fn used to `take()` whatever it found. On a Q8_0-carrying gemma4-dense trunk
5170        // the swap hijacked those mirrors: `bytes` became split-plane in place, the
5171        // m<=16 `_rp` dispatch read them correctly (masking the corruption from every
5172        // decode pin), and every GGUF-layout prefill consumer (MMQ + GEMM) read the
5173        // fp16 d-plane as weights -> layer-0 NaN, <pad>-spam serving. Only a tensor
5174        // this fn's OWN builder serves may ever be swapped; everything else refuses
5175        // here, regardless of walk ordering.
5176        if !matches!(t, GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0) {
5177            return Ok(false);
5178        }
5179        self.build_q4_rp4(t)?;
5180        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5181        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5182            return Ok(false);
5183        };
5184        match rp4.take() {
5185            Some(split) => {
5186                *bytes = split; // the GGUF-layout buffer drops here
5187                *rp = true;
5188                Ok(true)
5189            }
5190            None => Ok(false),
5191        }
5192    }
5193
5194    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5195    pub fn q4rp_enabled() -> bool {
5196        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5197        *ON.get_or_init(|| {
5198            std::env::var("MEMRA_Q4RP")
5199                .map(|v| v != "0")
5200                .unwrap_or(true)
5201        })
5202    }
5203
5204    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5205    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5206    pub fn copy_rows_strided(
5207        &self,
5208        src: &CudaSlice<f32>,
5209        dst: &mut CudaSlice<f32>,
5210        row_elems: usize,
5211        n_rows: usize,
5212        src_stride: usize,
5213        src_off: usize,
5214    ) -> Result<(), Box<dyn std::error::Error>> {
5215        let f = self.func("copy_rows_strided_f32");
5216        let cfg = LaunchConfig {
5217            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5218            block_dim: (256, 1, 1),
5219            shared_mem_bytes: 0,
5220        };
5221        let (re, nr) = (row_elems as i32, n_rows as i32);
5222        let (st, off) = (src_stride as i64, src_off as i64);
5223        let __s_b = self.gpu.stream();
5224        let mut b = __s_b.launch_builder(&f);
5225        b.arg(src)
5226            .arg(&mut *dst)
5227            .arg(&re)
5228            .arg(&nr)
5229            .arg(&st)
5230            .arg(&off);
5231        unsafe {
5232            b.launch(cfg)?;
5233        }
5234        Ok(())
5235    }
5236
5237    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5238    pub fn u32_set_k(
5239        &self,
5240        dst: &mut CudaSlice<u32>,
5241        v: u32,
5242        idx: usize,
5243    ) -> Result<(), Box<dyn std::error::Error>> {
5244        let f = self.func("u32_set_k");
5245        let cfg = LaunchConfig {
5246            grid_dim: (1, 1, 1),
5247            block_dim: (1, 1, 1),
5248            shared_mem_bytes: 0,
5249        };
5250        let ii = idx as i32;
5251        let __s_b = self.gpu.stream();
5252        let mut b = __s_b.launch_builder(&f);
5253        b.arg(dst).arg(&v).arg(&ii);
5254        unsafe {
5255            b.launch(cfg)?;
5256        }
5257        Ok(())
5258    }
5259
5260    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5261    pub fn i32_add_k(
5262        &self,
5263        d: &mut CudaSlice<i32>,
5264        v: i32,
5265    ) -> Result<(), Box<dyn std::error::Error>> {
5266        let f = self.func("i32_add_k");
5267        let cfg = LaunchConfig {
5268            grid_dim: (1, 1, 1),
5269            block_dim: (32, 1, 1),
5270            shared_mem_bytes: 0,
5271        };
5272        let __s_b = self.gpu.stream();
5273        let mut b = __s_b.launch_builder(&f);
5274        b.arg(d).arg(&v);
5275        unsafe {
5276            b.launch(cfg)?;
5277        }
5278        Ok(())
5279    }
5280
5281    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5282    pub fn i32_iota_from(
5283        &self,
5284        ctr: &CudaSlice<i32>,
5285        dst: &mut CudaSlice<i32>,
5286        n: usize,
5287    ) -> Result<(), Box<dyn std::error::Error>> {
5288        let f = self.func("i32_iota_from");
5289        let cfg = LaunchConfig::for_num_elems(n as u32);
5290        let ni = n as i32;
5291        let __s_b = self.gpu.stream();
5292        let mut b = __s_b.launch_builder(&f);
5293        b.arg(ctr).arg(dst).arg(&ni);
5294        unsafe {
5295            b.launch(cfg)?;
5296        }
5297        Ok(())
5298    }
5299
5300    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5301    pub fn u32_map_k(
5302        &self,
5303        buf: &mut CudaSlice<u32>,
5304        map: &CudaSlice<u32>,
5305        idx: usize,
5306    ) -> Result<(), Box<dyn std::error::Error>> {
5307        let f = self.func("u32_map_k");
5308        let cfg = LaunchConfig {
5309            grid_dim: (1, 1, 1),
5310            block_dim: (1, 1, 1),
5311            shared_mem_bytes: 0,
5312        };
5313        let ii = idx as i32;
5314        let __s_b = self.gpu.stream();
5315        let mut b = __s_b.launch_builder(&f);
5316        b.arg(buf).arg(map).arg(&ii);
5317        unsafe {
5318            b.launch(cfg)?;
5319        }
5320        Ok(())
5321    }
5322
5323    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5324    #[allow(clippy::too_many_arguments)]
5325    pub fn u32_pack2(
5326        &self,
5327        a: &CudaSlice<u32>,
5328        off_a: usize,
5329        n1: usize,
5330        b_in: &CudaSlice<u32>,
5331        n2: usize,
5332        out: &mut CudaSlice<u32>,
5333    ) -> Result<(), Box<dyn std::error::Error>> {
5334        let f = self.func("u32_pack2");
5335        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5336        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5337        let __s_b = self.gpu.stream();
5338        let mut b = __s_b.launch_builder(&f);
5339        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5340        unsafe {
5341            b.launch(cfg)?;
5342        }
5343        Ok(())
5344    }
5345
5346    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5347    pub fn moe_w_exscale(
5348        &self,
5349        w: &mut CudaSlice<f32>,
5350        sel: &CudaSlice<i32>,
5351        s: &CudaSlice<f32>,
5352        n: usize,
5353    ) -> Result<(), Box<dyn std::error::Error>> {
5354        let f = self.func("moe_w_exscale");
5355        let cfg = LaunchConfig::for_num_elems(n as u32);
5356        let ni = n as i32;
5357        let __s_b = self.gpu.stream();
5358        let mut b = __s_b.launch_builder(&f);
5359        b.arg(w).arg(sel).arg(s).arg(&ni);
5360        unsafe {
5361            b.launch(cfg)?;
5362        }
5363        Ok(())
5364    }
5365
5366    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5367    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5368    pub fn moe_w_scale_by_expert(
5369        &self,
5370        w: &mut CudaSlice<f32>,
5371        sel: &CudaSlice<i32>,
5372        macros: &CudaSlice<f32>,
5373        n_expert: usize,
5374        n: usize,
5375    ) -> Result<(), Box<dyn std::error::Error>> {
5376        let f = self.func("moe_w_scale_by_expert");
5377        let cfg = LaunchConfig {
5378            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5379            block_dim: (64, 1, 1),
5380            shared_mem_bytes: 0,
5381        };
5382        let (ne, nn) = (n_expert as i32, n as i32);
5383        let __s_b = self.gpu.stream();
5384        let mut b = __s_b.launch_builder(&f);
5385        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5386        unsafe {
5387            b.launch(cfg)?;
5388        }
5389        Ok(())
5390    }
5391
5392    pub fn moe_gate_up_silu8_dev_q8(
5393        &self,
5394        table: &CudaSlice<u64>,
5395        sel: &cudarc::driver::CudaView<i32>,
5396        aq: &CudaSlice<i8>,
5397        ad: &CudaSlice<f32>,
5398        in_f: usize,
5399        n_ff: usize,
5400        n_used: usize,
5401        n_expert: usize,
5402        qt_g: i32,
5403        qt_u: i32,
5404        rb_g: usize,
5405        rb_u: usize,
5406        macros: &CudaSlice<f32>,
5407    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5408        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5409        let (mode, wpb) = GU.get_or_init(|| {
5410            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5411            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5412                .ok()
5413                .and_then(|v| v.parse().ok())
5414                .unwrap_or(4u32)
5415                .clamp(1, 16);
5416            (mode, wpb)
5417        });
5418        let (mode, wpb) = (mode.as_str(), *wpb);
5419        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5420        let (inf, nff, ne, rbg, rbu) = (
5421            in_f as i32,
5422            n_ff as i32,
5423            n_expert as i32,
5424            rb_g as i64,
5425            rb_u as i64,
5426        );
5427        let (f, cfg) = match mode {
5428            "1" | "2" | "4" => {
5429                let rpw: u32 = mode.parse().unwrap();
5430                let f = self.func(match rpw {
5431                    1 => "moe_gate_up_silu8_dev_q8_r1",
5432                    2 => "moe_gate_up_silu8_dev_q8_r2",
5433                    _ => "moe_gate_up_silu8_dev_q8_r4",
5434                });
5435                let rows_per_block = (rpw * wpb) as usize;
5436                let gx = n_ff.div_ceil(rows_per_block) as u32;
5437                (
5438                    f,
5439                    LaunchConfig {
5440                        grid_dim: (gx, n_used as u32, 1),
5441                        block_dim: (32, wpb, 1),
5442                        shared_mem_bytes: 0,
5443                    },
5444                )
5445            }
5446            "j8" if n_used <= 32 => (
5447                self.func("moe_gate_up_silu8_dev_q8_j8"),
5448                LaunchConfig {
5449                    grid_dim: (n_ff as u32, 1, 1),
5450                    block_dim: (32, n_used as u32, 1),
5451                    shared_mem_bytes: 0,
5452                },
5453            ),
5454            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5455            "vsm2" => {
5456                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5457                let sh = (rb_g + rb_u) as u32;
5458                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5459                f.set_attribute(
5460                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5461                    sh as i32,
5462                )?;
5463                (
5464                    f,
5465                    LaunchConfig {
5466                        grid_dim: (n_ff as u32, n_used as u32, 1),
5467                        block_dim: (32, 1, 1),
5468                        shared_mem_bytes: sh,
5469                    },
5470                )
5471            }
5472            "vsm" => {
5473                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5474                let sh = (rb_g + rb_u) as u32;
5475                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5476                f.set_attribute(
5477                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5478                    sh as i32,
5479                )?;
5480                (
5481                    f,
5482                    LaunchConfig {
5483                        grid_dim: (n_ff as u32, n_used as u32, 1),
5484                        block_dim: (32, 1, 1),
5485                        shared_mem_bytes: sh,
5486                    },
5487                )
5488            }
5489            "sg" => (
5490                self.func("moe_gate_up_silu8_dev_q8_sg"),
5491                LaunchConfig {
5492                    grid_dim: (n_ff as u32, n_used as u32, 1),
5493                    block_dim: (32, 1, 1),
5494                    shared_mem_bytes: 0,
5495                },
5496            ),
5497            "j8sg" if n_used <= 32 => (
5498                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5499                LaunchConfig {
5500                    grid_dim: (n_ff as u32, 1, 1),
5501                    block_dim: (32, n_used as u32, 1),
5502                    shared_mem_bytes: 0,
5503                },
5504            ),
5505            "u64" if in_f == 2048 => (
5506                self.func("moe_gate_up_silu8_dev_q8_u64"),
5507                LaunchConfig {
5508                    grid_dim: (n_ff as u32, n_used as u32, 1),
5509                    block_dim: (32, 1, 1),
5510                    shared_mem_bytes: 0,
5511                },
5512            ),
5513            "gs4" if in_f == 2048 => (
5514                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5515                LaunchConfig {
5516                    grid_dim: (n_ff as u32, n_used as u32, 1),
5517                    block_dim: (32, 4, 1),
5518                    shared_mem_bytes: 0,
5519                },
5520            ),
5521            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5522            "v" | "" => (
5523                self.func("moe_gate_up_silu8_dev_q8_v"),
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            "s2" => (
5531                self.func("moe_gate_up_silu8_dev_q8_s2"),
5532                LaunchConfig {
5533                    grid_dim: (n_ff as u32, n_used as u32, 1),
5534                    block_dim: (32, 2, 1),
5535                    shared_mem_bytes: 0,
5536                },
5537            ),
5538            "s2z" => {
5539                let rz = wpb.min(16); // s2z smem tile is [16][2]
5540                (
5541                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5542                    LaunchConfig {
5543                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5544                        block_dim: (32, 2, rz),
5545                        shared_mem_bytes: 0,
5546                    },
5547                )
5548            }
5549            _ => (
5550                self.func("moe_gate_up_silu8_dev_q8"),
5551                LaunchConfig {
5552                    grid_dim: (n_ff as u32, n_used as u32, 1),
5553                    block_dim: (32, 1, 1),
5554                    shared_mem_bytes: 0,
5555                },
5556            ),
5557        };
5558        let __s_b = self.gpu.stream();
5559        let mut b = __s_b.launch_builder(&f);
5560        b.arg(table)
5561            .arg(sel)
5562            .arg(aq)
5563            .arg(ad)
5564            .arg(&mut act)
5565            .arg(&inf)
5566            .arg(&nff)
5567            .arg(&ne)
5568            .arg(&qt_g)
5569            .arg(&qt_u)
5570            .arg(&rbg)
5571            .arg(&rbu)
5572            .arg(macros);
5573        unsafe {
5574            b.launch(cfg)?;
5575        }
5576        Ok(act)
5577    }
5578
5579    #[allow(clippy::too_many_arguments)]
5580    pub fn moe_down8_fma_dev_q8(
5581        &self,
5582        table: &CudaSlice<u64>,
5583        sel: &cudarc::driver::CudaView<i32>,
5584        w: &cudarc::driver::CudaView<f32>,
5585        aq2: &CudaSlice<i8>,
5586        ad2: &CudaSlice<f32>,
5587        dst: &mut cudarc::driver::CudaViewMut<f32>,
5588        in_f: usize,
5589        out_f: usize,
5590        n_used: usize,
5591        n_expert: usize,
5592        qt: i32,
5593        rb: usize,
5594    ) -> Result<(), Box<dyn std::error::Error>> {
5595        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5596        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5597        let (inf, outf, nu, ne, rbi) = (
5598            in_f as i32,
5599            out_f as i32,
5600            n_used as i32,
5601            n_expert as i32,
5602            rb as i64,
5603        );
5604        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5605        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5606        let (f, cfg) = match mode.as_str() {
5607            m @ ("1" | "2" | "4") if n_used <= 8 => {
5608                let rpw: usize = m.parse().unwrap();
5609                let f = self.func(match rpw {
5610                    1 => "moe_down8_fma_dev_q8_w8r1",
5611                    2 => "moe_down8_fma_dev_q8_w8r2",
5612                    _ => "moe_down8_fma_dev_q8_w8r4",
5613                });
5614                (
5615                    f,
5616                    LaunchConfig {
5617                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5618                        block_dim: (32, n_used as u32, 1),
5619                        shared_mem_bytes: 0,
5620                    },
5621                )
5622            }
5623            "h2" if in_f == 512 => (
5624                self.func("moe_down8_fma_dev_q8_h2"),
5625                LaunchConfig {
5626                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5627                    block_dim: (32, 1, 1),
5628                    shared_mem_bytes: 0,
5629                },
5630            ),
5631            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5632            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5633            "" if in_f == 704 && n_used <= 8 => (
5634                self.func("moe_down8_fma_dev_q8_w8r2"),
5635                LaunchConfig {
5636                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5637                    block_dim: (32, n_used as u32, 1),
5638                    shared_mem_bytes: 0,
5639                },
5640            ),
5641            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5642            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5643            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5644            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5645                self.func("moe_down8_fma_dev_q8_w8h2v"),
5646                LaunchConfig {
5647                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5648                    block_dim: (32, n_used as u32, 1),
5649                    shared_mem_bytes: 0,
5650                },
5651            ),
5652            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5653                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5654                LaunchConfig {
5655                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5656                    block_dim: (32, n_used as u32, 1),
5657                    shared_mem_bytes: 0,
5658                },
5659            ),
5660            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5661                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5662                LaunchConfig {
5663                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5664                    block_dim: (32, n_used as u32, 1),
5665                    shared_mem_bytes: 0,
5666                },
5667            ),
5668            "w8h2" if in_f == 512 && n_used <= 8 => (
5669                self.func("moe_down8_fma_dev_q8_w8h2"),
5670                LaunchConfig {
5671                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5672                    block_dim: (32, n_used as u32, 1),
5673                    shared_mem_bytes: 0,
5674                },
5675            ),
5676            _ => (
5677                self.func("moe_down8_fma_dev_q8"),
5678                LaunchConfig {
5679                    grid_dim: (out_f as u32, 1, 1),
5680                    block_dim: (32, 1, 1),
5681                    shared_mem_bytes: 0,
5682                },
5683            ),
5684        };
5685        let __s_b = self.gpu.stream();
5686        let mut b = __s_b.launch_builder(&f);
5687        b.arg(table)
5688            .arg(sel)
5689            .arg(w)
5690            .arg(aq2)
5691            .arg(ad2)
5692            .arg(dst)
5693            .arg(&inf)
5694            .arg(&outf)
5695            .arg(&nu)
5696            .arg(&ne)
5697            .arg(&qt)
5698            .arg(&rbi);
5699        unsafe {
5700            b.launch(cfg)?;
5701        }
5702        Ok(())
5703    }
5704
5705    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5706    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5707    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5708    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5709    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5710    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5711    #[allow(clippy::too_many_arguments)]
5712    pub fn moe_gate_up_silu8_dev_q8_rows(
5713        &self,
5714        table: &CudaSlice<u64>,
5715        sel: &CudaSlice<i32>,
5716        aq: &CudaSlice<i8>,
5717        ad: &CudaSlice<f32>,
5718        t: usize,
5719        in_f: usize,
5720        n_ff: usize,
5721        n_used: usize,
5722        n_expert: usize,
5723        qt_g: i32,
5724        qt_u: i32,
5725        rb_g: usize,
5726        rb_u: usize,
5727        macros: &CudaSlice<f32>,
5728    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5729        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5730        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5731        let cfg = LaunchConfig {
5732            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5733            block_dim: (32, 1, 1),
5734            shared_mem_bytes: 0,
5735        };
5736        let (inf, nff, ne, nu, rbg, rbu) = (
5737            in_f as i32,
5738            n_ff as i32,
5739            n_expert as i32,
5740            n_used as i32,
5741            rb_g as i64,
5742            rb_u as i64,
5743        );
5744        let __s_b = self.gpu.stream();
5745        let mut b = __s_b.launch_builder(&f);
5746        b.arg(table)
5747            .arg(sel)
5748            .arg(aq)
5749            .arg(ad)
5750            .arg(&mut act)
5751            .arg(&inf)
5752            .arg(&nff)
5753            .arg(&ne)
5754            .arg(&qt_g)
5755            .arg(&qt_u)
5756            .arg(&rbg)
5757            .arg(&rbu)
5758            .arg(&nu)
5759            .arg(macros);
5760        unsafe {
5761            b.launch(cfg)?;
5762        }
5763        Ok(act)
5764    }
5765
5766    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5767    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5768    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5769    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5770    #[allow(clippy::too_many_arguments)]
5771    pub fn moe_down8_fma_dev_q8_rows(
5772        &self,
5773        table: &CudaSlice<u64>,
5774        sel: &CudaSlice<i32>,
5775        w: &CudaSlice<f32>,
5776        aq2: &CudaSlice<i8>,
5777        ad2: &CudaSlice<f32>,
5778        dst: &mut CudaSlice<f32>,
5779        t: usize,
5780        in_f: usize,
5781        out_f: usize,
5782        n_used: usize,
5783        n_expert: usize,
5784        qt: i32,
5785        rb: usize,
5786    ) -> Result<(), Box<dyn std::error::Error>> {
5787        assert!(
5788            in_f == 512 && n_used <= 8,
5789            "down rows twin is w8h2v shape-gated"
5790        );
5791        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5792        let cfg = LaunchConfig {
5793            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5794            block_dim: (32, n_used as u32, 1),
5795            shared_mem_bytes: 0,
5796        };
5797        let (inf, outf, nu, ne, rbi) = (
5798            in_f as i32,
5799            out_f as i32,
5800            n_used as i32,
5801            n_expert as i32,
5802            rb as i64,
5803        );
5804        let __s_b = self.gpu.stream();
5805        let mut b = __s_b.launch_builder(&f);
5806        b.arg(table)
5807            .arg(sel)
5808            .arg(w)
5809            .arg(aq2)
5810            .arg(ad2)
5811            .arg(dst)
5812            .arg(&inf)
5813            .arg(&outf)
5814            .arg(&nu)
5815            .arg(&ne)
5816            .arg(&qt)
5817            .arg(&rbi);
5818        unsafe {
5819            b.launch(cfg)?;
5820        }
5821        Ok(())
5822    }
5823
5824    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5825    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5826    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5827    #[allow(clippy::too_many_arguments)]
5828    pub fn moe_gate_up_silu8_dev_q8_csr(
5829        &self,
5830        table: &CudaSlice<u64>,
5831        sel: &CudaSlice<i32>,
5832        aq: &CudaSlice<i8>,
5833        ad: &CudaSlice<f32>,
5834        n_pairs: usize,
5835        in_f: usize,
5836        n_ff: usize,
5837        n_used: usize,
5838        n_expert: usize,
5839        qt_g: i32,
5840        qt_u: i32,
5841        rb_g: usize,
5842        rb_u: usize,
5843    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5844        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
5845        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5846        let cfg = LaunchConfig {
5847            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5848            block_dim: (32, 1, 1),
5849            shared_mem_bytes: 0,
5850        };
5851        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5852            in_f as i32,
5853            n_ff as i32,
5854            n_expert as i32,
5855            n_used as i32,
5856            n_pairs as i32,
5857            rb_g as i64,
5858            rb_u as i64,
5859        );
5860        let __s_b = self.gpu.stream();
5861        let mut b = __s_b.launch_builder(&f);
5862        b.arg(table)
5863            .arg(sel)
5864            .arg(aq)
5865            .arg(ad)
5866            .arg(&mut act)
5867            .arg(&inf)
5868            .arg(&nff)
5869            .arg(&ne)
5870            .arg(&qt_g)
5871            .arg(&qt_u)
5872            .arg(&rbg)
5873            .arg(&rbu)
5874            .arg(&nu)
5875            .arg(&npi);
5876        unsafe {
5877            b.launch(cfg)?;
5878        }
5879        Ok(act)
5880    }
5881
5882    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5883    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5884    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5885    #[allow(clippy::too_many_arguments)]
5886    pub fn moe_down8_fma_dev_q8_variant(
5887        &self,
5888        variant: &str,
5889        table: &CudaSlice<u64>,
5890        sel: &cudarc::driver::CudaView<i32>,
5891        w: &cudarc::driver::CudaView<f32>,
5892        aq2: &CudaSlice<i8>,
5893        ad2: &CudaSlice<f32>,
5894        dst: &mut cudarc::driver::CudaViewMut<f32>,
5895        in_f: usize,
5896        out_f: usize,
5897        n_used: usize,
5898        n_expert: usize,
5899        qt: i32,
5900        rb: usize,
5901    ) -> Result<(), Box<dyn std::error::Error>> {
5902        let (inf, outf, nu, ne, rbi) = (
5903            in_f as i32,
5904            out_f as i32,
5905            n_used as i32,
5906            n_expert as i32,
5907            rb as i64,
5908        );
5909        let (f, cfg) = match variant {
5910            "w8h2" | "w8h2v" => (
5911                self.func(if variant == "w8h2" {
5912                    "moe_down8_fma_dev_q8_w8h2"
5913                } else {
5914                    "moe_down8_fma_dev_q8_w8h2v"
5915                }),
5916                LaunchConfig {
5917                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5918                    block_dim: (32, n_used as u32, 1),
5919                    shared_mem_bytes: 0,
5920                },
5921            ),
5922            "w8h2r2" | "w8h2r2v" => (
5923                self.func(if variant == "w8h2r2" {
5924                    "moe_down8_fma_dev_q8_w8h2r2"
5925                } else {
5926                    "moe_down8_fma_dev_q8_w8h2r2v"
5927                }),
5928                LaunchConfig {
5929                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5930                    block_dim: (32, n_used as u32, 1),
5931                    shared_mem_bytes: 0,
5932                },
5933            ),
5934            _ => (
5935                self.func("moe_down8_fma_dev_q8"),
5936                LaunchConfig {
5937                    grid_dim: (out_f as u32, 1, 1),
5938                    block_dim: (32, 1, 1),
5939                    shared_mem_bytes: 0,
5940                },
5941            ),
5942        };
5943        let __s_b = self.gpu.stream();
5944        let mut b = __s_b.launch_builder(&f);
5945        b.arg(table)
5946            .arg(sel)
5947            .arg(w)
5948            .arg(aq2)
5949            .arg(ad2)
5950            .arg(dst)
5951            .arg(&inf)
5952            .arg(&outf)
5953            .arg(&nu)
5954            .arg(&ne)
5955            .arg(&qt)
5956            .arg(&rbi);
5957        unsafe {
5958            b.launch(cfg)?;
5959        }
5960        Ok(())
5961    }
5962
5963    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
5964    #[allow(clippy::too_many_arguments)]
5965    pub fn moe_gate_up_silu8_dev_q8_variant(
5966        &self,
5967        variant: &str,
5968        table: &CudaSlice<u64>,
5969        sel: &cudarc::driver::CudaView<i32>,
5970        aq: &CudaSlice<i8>,
5971        ad: &CudaSlice<f32>,
5972        in_f: usize,
5973        n_ff: usize,
5974        n_used: usize,
5975        n_expert: usize,
5976        qt_g: i32,
5977        qt_u: i32,
5978        rb_g: usize,
5979        rb_u: usize,
5980    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5981        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5982        let (inf, nff, ne, rbg, rbu) = (
5983            in_f as i32,
5984            n_ff as i32,
5985            n_expert as i32,
5986            rb_g as i64,
5987            rb_u as i64,
5988        );
5989        let f = self.func(if variant == "v" {
5990            "moe_gate_up_silu8_dev_q8_v"
5991        } else {
5992            "moe_gate_up_silu8_dev_q8"
5993        });
5994        let cfg = LaunchConfig {
5995            grid_dim: (n_ff as u32, n_used as u32, 1),
5996            block_dim: (32, 1, 1),
5997            shared_mem_bytes: 0,
5998        };
5999        let __s_b = self.gpu.stream();
6000        let mut b = __s_b.launch_builder(&f);
6001        b.arg(table)
6002            .arg(sel)
6003            .arg(aq)
6004            .arg(ad)
6005            .arg(&mut act)
6006            .arg(&inf)
6007            .arg(&nff)
6008            .arg(&ne)
6009            .arg(&qt_g)
6010            .arg(&qt_u)
6011            .arg(&rbg)
6012            .arg(&rbu);
6013        unsafe {
6014            b.launch(cfg)?;
6015        }
6016        Ok(act)
6017    }
6018
6019    pub fn moe_gate_up_silu8_dev(
6020        &self,
6021        table: &CudaSlice<u64>,
6022        sel: &cudarc::driver::CudaView<i32>,
6023        x: &cudarc::driver::CudaView<f32>,
6024        in_f: usize,
6025        n_ff: usize,
6026        n_used: usize,
6027        n_expert: usize,
6028        qt_g: i32,
6029        qt_u: i32,
6030        rb_g: usize,
6031        rb_u: usize,
6032        macros: &CudaSlice<f32>,
6033    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6034        let f = self.func("moe_gate_up_silu8_dev");
6035        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6036        let cfg = LaunchConfig {
6037            grid_dim: (n_ff as u32, n_used as u32, 1),
6038            block_dim: (256, 1, 1),
6039            shared_mem_bytes: 0,
6040        };
6041        let (inf, nff, ne, rbg, rbu) = (
6042            in_f as i32,
6043            n_ff as i32,
6044            n_expert as i32,
6045            rb_g as i64,
6046            rb_u as i64,
6047        );
6048        let __s_b = self.gpu.stream();
6049        let mut b = __s_b.launch_builder(&f);
6050        b.arg(table)
6051            .arg(sel)
6052            .arg(x)
6053            .arg(&mut act)
6054            .arg(&inf)
6055            .arg(&nff)
6056            .arg(&ne)
6057            .arg(&qt_g)
6058            .arg(&qt_u)
6059            .arg(&rbg)
6060            .arg(&rbu)
6061            .arg(macros);
6062        unsafe {
6063            b.launch(cfg)?;
6064        }
6065        Ok(act)
6066    }
6067
6068    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6069    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6070    #[allow(clippy::too_many_arguments)]
6071    pub fn moe_down8_fma_dev(
6072        &self,
6073        table: &CudaSlice<u64>,
6074        sel: &cudarc::driver::CudaView<i32>,
6075        w: &cudarc::driver::CudaView<f32>,
6076        act: &CudaSlice<f32>,
6077        dst: &mut cudarc::driver::CudaViewMut<f32>,
6078        in_f: usize,
6079        out_f: usize,
6080        n_used: usize,
6081        n_expert: usize,
6082        qt: i32,
6083        rb: usize,
6084    ) -> Result<(), Box<dyn std::error::Error>> {
6085        let f = self.func("moe_down8_fma_dev");
6086        let cfg = LaunchConfig {
6087            grid_dim: (out_f as u32, 1, 1),
6088            block_dim: (256, 1, 1),
6089            shared_mem_bytes: 0,
6090        };
6091        let (inf, outf, nu, ne, rbv) = (
6092            in_f as i32,
6093            out_f as i32,
6094            n_used as i32,
6095            n_expert as i32,
6096            rb as i64,
6097        );
6098        let __s_b = self.gpu.stream();
6099        let mut b = __s_b.launch_builder(&f);
6100        b.arg(table)
6101            .arg(sel)
6102            .arg(w)
6103            .arg(act)
6104            .arg(dst)
6105            .arg(&inf)
6106            .arg(&outf)
6107            .arg(&nu)
6108            .arg(&ne)
6109            .arg(&qt)
6110            .arg(&rbv);
6111        unsafe {
6112            b.launch(cfg)?;
6113        }
6114        Ok(())
6115    }
6116
6117    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6118    pub fn axpy_into(
6119        &self,
6120        src: &CudaSlice<f32>,
6121        alpha: f32,
6122        dst: &mut cudarc::driver::CudaViewMut<f32>,
6123        n: usize,
6124    ) -> Result<(), Box<dyn std::error::Error>> {
6125        let f = self.func("axpy_f32");
6126        let cfg = LaunchConfig::for_num_elems(n as u32);
6127        let (a, ni) = (alpha, n as i32);
6128        let __s_b = self.gpu.stream();
6129        let mut b = __s_b.launch_builder(&f);
6130        b.arg(src).arg(dst).arg(&a).arg(&ni);
6131        unsafe {
6132            b.launch(cfg)?;
6133        }
6134        Ok(())
6135    }
6136
6137    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6138    pub fn add_scaled_rows(
6139        &self,
6140        src: &CudaSlice<f32>,
6141        scale: &CudaSlice<f32>,
6142        dst: &mut CudaSlice<f32>,
6143        ncols: usize,
6144        nrows: usize,
6145    ) -> Result<(), Box<dyn std::error::Error>> {
6146        let f = self.func("add_scaled_rows_f32");
6147        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6148        let (nc, nr) = (ncols as i32, nrows as i32);
6149        let __s_b = self.gpu.stream();
6150        let mut b = __s_b.launch_builder(&f);
6151        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6152        unsafe {
6153            b.launch(cfg)?;
6154        }
6155        Ok(())
6156    }
6157
6158    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6159
6160    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6161    pub fn gather_rows(
6162        &self,
6163        src: &CudaSlice<f32>,
6164        idx: &CudaSlice<i32>,
6165        dst: &mut CudaSlice<f32>,
6166        ncols: usize,
6167        m_e: usize,
6168    ) -> Result<(), Box<dyn std::error::Error>> {
6169        let f = self.func("gather_rows_f32");
6170        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6171        let (nc, me) = (ncols as i32, m_e as i32);
6172        let __s_b = self.gpu.stream();
6173        let mut b = __s_b.launch_builder(&f);
6174        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6175        unsafe {
6176            b.launch(cfg)?;
6177        }
6178        Ok(())
6179    }
6180
6181    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6182    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6183    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6184    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6185    pub fn scatter_slot(
6186        &self,
6187        src: &CudaSlice<f32>,
6188        tok_idx: &CudaSlice<i32>,
6189        slot_idx: &CudaSlice<i32>,
6190        weight: &CudaSlice<f32>,
6191        dst: &mut CudaSlice<f32>,
6192        wbuf: &mut CudaSlice<f32>,
6193        ncols: usize,
6194        n_used: usize,
6195        m_e: usize,
6196    ) -> Result<(), Box<dyn std::error::Error>> {
6197        let f = self.func("scatter_add_slot_f32");
6198        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6199        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6200        let __s_b = self.gpu.stream();
6201        let mut b = __s_b.launch_builder(&f);
6202        b.arg(src)
6203            .arg(tok_idx)
6204            .arg(slot_idx)
6205            .arg(weight)
6206            .arg(dst)
6207            .arg(wbuf)
6208            .arg(&nc)
6209            .arg(&nu)
6210            .arg(&me);
6211        unsafe {
6212            b.launch(cfg)?;
6213        }
6214        Ok(())
6215    }
6216
6217    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6218    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6219    /// Uses FMA for bit-identity with the sequential axpy path.
6220    pub fn reduce_slots(
6221        &self,
6222        slots: &CudaSlice<f32>,
6223        wbuf: &CudaSlice<f32>,
6224        dst: &mut CudaSlice<f32>,
6225        ncols: usize,
6226        n_used: usize,
6227        t: usize,
6228    ) -> Result<(), Box<dyn std::error::Error>> {
6229        let f = self.func("reduce_slots_f32");
6230        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6231        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6232        let __s_b = self.gpu.stream();
6233        let mut b = __s_b.launch_builder(&f);
6234        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6235        unsafe {
6236            b.launch(cfg)?;
6237        }
6238        Ok(())
6239    }
6240
6241    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6242    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6243    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6244    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6245    /// GPU time, ~half of it redundant re-quantization of the same row.
6246    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6247    pub fn quantize_q8_1_view(
6248        &self,
6249        x: &cudarc::driver::CudaView<f32>,
6250        m: usize,
6251        in_f: usize,
6252    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6253        let f = self.func("quantize_q8_1");
6254        let nblk = in_f / 32;
6255        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6256        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6257        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6258        let (inf, mi) = (in_f as i32, m as i32);
6259        let __s_b = self.gpu.stream();
6260        let mut b = __s_b.launch_builder(&f);
6261        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6262        unsafe {
6263            b.launch(cfg)?;
6264        }
6265        Ok((q, d))
6266    }
6267
6268    pub fn quantize_q8_1(
6269        &self,
6270        x: &CudaSlice<f32>,
6271        m: usize,
6272        in_f: usize,
6273    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6274        let nblk = in_f / 32;
6275        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6276        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6277        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6278        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6279        let (inf, mi) = (in_f as i32, m as i32);
6280        if Self::pdl_on() && Self::pdl_wb_on() {
6281            {
6282                use cudarc::driver::{DevicePtr, DevicePtrMut};
6283                let s = &self.gpu.stream();
6284                let (px, _g0) = x.device_ptr(s);
6285                let (pq, _g1) = q.device_ptr_mut(s);
6286                let (pd, _g2) = d.device_ptr_mut(s);
6287                let mut ps = [
6288                    &px as *const _ as *mut std::ffi::c_void,
6289                    &pq as *const _ as *mut _,
6290                    &pd as *const _ as *mut _,
6291                    &inf as *const _ as *mut _,
6292                    &mi as *const _ as *mut _,
6293                ];
6294                unsafe {
6295                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6296                }
6297            }
6298            return Ok((q, d));
6299        }
6300        let f = self.func("quantize_q8_1");
6301        let __s_b = self.gpu.stream();
6302        let mut b = __s_b.launch_builder(&f);
6303        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6304        unsafe {
6305            b.launch(cfg)?;
6306        }
6307        Ok((q, d))
6308    }
6309
6310    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6311    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6312    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6313    pub fn quantize_fp4_act(
6314        &self,
6315        x: &CudaSlice<f32>,
6316        m: usize,
6317        in_f: usize,
6318    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6319        let f = self.func("quantize_fp4_act");
6320        let nb16 = in_f / 16;
6321        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6322        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6323        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6324        let (inf, mi) = (in_f as i32, m as i32);
6325        let __s_b = self.gpu.stream();
6326        let mut b = __s_b.launch_builder(&f);
6327        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6328        unsafe {
6329            b.launch(cfg)?;
6330        }
6331        Ok((aq4, ad4))
6332    }
6333
6334    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6335    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6336    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6337    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6338    pub fn qmatvec_gemm_nvfp4_fp4(
6339        &self,
6340        bytes: &CudaSlice<u8>,
6341        x: &CudaSlice<f32>,
6342        m: usize,
6343        in_f: usize,
6344        out_f: usize,
6345        row_bytes: usize,
6346        scale: f32,
6347    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6348        assert!(
6349            in_f % 64 == 0,
6350            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6351        );
6352        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6353        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6354        if scale != 1.0 {
6355            self.scale_inplace(&mut y, scale, m * out_f)?;
6356        }
6357        Ok(y)
6358    }
6359
6360    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6361    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6362    fn fp4_gemm_launch(
6363        &self,
6364        bytes: &CudaSlice<u8>,
6365        aq4: &CudaSlice<u32>,
6366        ad4: &CudaSlice<u8>,
6367        m: usize,
6368        in_f: usize,
6369        out_f: usize,
6370        row_bytes: usize,
6371    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6372        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6373        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6374        const BM: u32 = 64;
6375        const BN: u32 = 256;
6376        let cfg = LaunchConfig {
6377            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6378            block_dim: (32, 4, 1),
6379            shared_mem_bytes: 0,
6380        };
6381        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6382        let __s_b = self.gpu.stream();
6383        let mut b = __s_b.launch_builder(&f);
6384        b.arg(bytes)
6385            .arg(aq4)
6386            .arg(ad4)
6387            .arg(&mut y)
6388            .arg(&inf)
6389            .arg(&outf)
6390            .arg(&mi)
6391            .arg(&rb);
6392        unsafe {
6393            b.launch(cfg)?;
6394        }
6395        Ok(y)
6396    }
6397
6398    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6399    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6400        &self,
6401        bytes: &CudaSlice<u8>,
6402        x: &CudaSlice<f32>,
6403        m: usize,
6404        in_f: usize,
6405        out_f: usize,
6406        row_bytes: usize,
6407    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6408        assert!(
6409            in_f % 64 == 0,
6410            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6411        );
6412        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6413        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6414    }
6415
6416    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6417    pub fn qmatvec_q8_0_fast(
6418        &self,
6419        w: &CudaSlice<u8>,
6420        x: &CudaSlice<f32>,
6421        m: usize,
6422        in_f: usize,
6423        out_f: usize,
6424        row_bytes: usize,
6425    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6426        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6427        let f = self.func("qmatvec_q8_0_dp4a");
6428        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6429        let cfg = LaunchConfig {
6430            grid_dim: (out_f as u32, m as u32, 1),
6431            block_dim: (128, 1, 1),
6432            shared_mem_bytes: 0,
6433        };
6434        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6435        let __s_b = self.gpu.stream();
6436        let mut b = __s_b.launch_builder(&f);
6437        b.arg(w)
6438            .arg(&aq)
6439            .arg(&ad)
6440            .arg(&mut y)
6441            .arg(&inf)
6442            .arg(&outf)
6443            .arg(&mi)
6444            .arg(&rb);
6445        unsafe {
6446            b.launch(cfg)?;
6447        }
6448        Ok(y)
6449    }
6450
6451    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6452    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6453    pub fn qmatvec_q4_K_fast(
6454        &self,
6455        w: &CudaSlice<u8>,
6456        x: &CudaSlice<f32>,
6457        m: usize,
6458        in_f: usize,
6459        out_f: usize,
6460        row_bytes: usize,
6461    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6462        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6463        let f = self.func("qmatvec_q4_K_dp4a");
6464        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6465        let cfg = LaunchConfig {
6466            grid_dim: (out_f as u32, m as u32, 1),
6467            block_dim: (128, 1, 1),
6468            shared_mem_bytes: 0,
6469        };
6470        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6471        let __s_b = self.gpu.stream();
6472        let mut b = __s_b.launch_builder(&f);
6473        b.arg(w)
6474            .arg(&aq)
6475            .arg(&ad)
6476            .arg(&mut y)
6477            .arg(&inf)
6478            .arg(&outf)
6479            .arg(&mi)
6480            .arg(&rb);
6481        unsafe {
6482            b.launch(cfg)?;
6483        }
6484        Ok(y)
6485    }
6486
6487    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6488    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6489    pub fn qmatvec_q6_K_fast(
6490        &self,
6491        w: &CudaSlice<u8>,
6492        x: &CudaSlice<f32>,
6493        m: usize,
6494        in_f: usize,
6495        out_f: usize,
6496        row_bytes: usize,
6497    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6498        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6499        let f = self.func("qmatvec_q6_K_dp4a");
6500        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6501        let cfg = LaunchConfig {
6502            grid_dim: (out_f as u32, m as u32, 1),
6503            block_dim: (128, 1, 1),
6504            shared_mem_bytes: 0,
6505        };
6506        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6507        let __s_b = self.gpu.stream();
6508        let mut b = __s_b.launch_builder(&f);
6509        b.arg(w)
6510            .arg(&aq)
6511            .arg(&ad)
6512            .arg(&mut y)
6513            .arg(&inf)
6514            .arg(&outf)
6515            .arg(&mi)
6516            .arg(&rb);
6517        unsafe {
6518            b.launch(cfg)?;
6519        }
6520        Ok(y)
6521    }
6522
6523    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6524    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6525    pub fn qmatvec_q5_K_fast(
6526        &self,
6527        w: &CudaSlice<u8>,
6528        x: &CudaSlice<f32>,
6529        m: usize,
6530        in_f: usize,
6531        out_f: usize,
6532        row_bytes: usize,
6533    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6534        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6535    }
6536    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6537    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6538    pub fn qmatvec_q3_K_fast(
6539        &self,
6540        w: &CudaSlice<u8>,
6541        x: &CudaSlice<f32>,
6542        m: usize,
6543        in_f: usize,
6544        out_f: usize,
6545        row_bytes: usize,
6546    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6547        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6548    }
6549    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6550    pub fn qmatvec_nvfp4_fast_rp(
6551        &self,
6552        w: &CudaSlice<u8>,
6553        x: &CudaSlice<f32>,
6554        m: usize,
6555        in_f: usize,
6556        out_f: usize,
6557        row_bytes: usize,
6558    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6559        assert!(
6560            in_f % 64 == 0,
6561            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6562        );
6563        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6564    }
6565    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6566    pub fn qmatvec_nvfp4_fast(
6567        &self,
6568        w: &CudaSlice<u8>,
6569        x: &CudaSlice<f32>,
6570        m: usize,
6571        in_f: usize,
6572        out_f: usize,
6573        row_bytes: usize,
6574    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6575        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6576        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6577        assert!(
6578            in_f % 64 == 0,
6579            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6580        );
6581        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6582    }
6583    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6584    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6585    pub fn qmatvec_iq4_XS_fast(
6586        &self,
6587        w: &CudaSlice<u8>,
6588        x: &CudaSlice<f32>,
6589        m: usize,
6590        in_f: usize,
6591        out_f: usize,
6592        row_bytes: usize,
6593    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6594        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6595    }
6596
6597    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6598    fn qmatvec_dp4a_named(
6599        &self,
6600        name: &str,
6601        w: &CudaSlice<u8>,
6602        x: &CudaSlice<f32>,
6603        m: usize,
6604        in_f: usize,
6605        out_f: usize,
6606        row_bytes: usize,
6607    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6608        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6609        let f = self.func(name);
6610        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6611        let cfg = LaunchConfig {
6612            grid_dim: (out_f as u32, m as u32, 1),
6613            block_dim: (128, 1, 1),
6614            shared_mem_bytes: 0,
6615        };
6616        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6617        let __s_b = self.gpu.stream();
6618        let mut b = __s_b.launch_builder(&f);
6619        b.arg(w)
6620            .arg(&aq)
6621            .arg(&ad)
6622            .arg(&mut y)
6623            .arg(&inf)
6624            .arg(&outf)
6625            .arg(&mi)
6626            .arg(&rb);
6627        unsafe {
6628            b.launch(cfg)?;
6629        }
6630        Ok(y)
6631    }
6632
6633    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6634        Ok(self.gpu.stream().clone_htod(v)?)
6635    }
6636    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6637        Ok(self.gpu.stream().clone_htod(v)?)
6638    }
6639    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6640    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6641        Ok(self.gpu.stream().clone_htod(v)?)
6642    }
6643    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6644        Ok(self.gpu.stream().clone_htod(v)?)
6645    }
6646    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6647    pub fn dtoh_view(
6648        &self,
6649        d: &cudarc::driver::CudaView<f32>,
6650    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6651        let v = self.gpu.stream().clone_dtoh(d)?;
6652        self.gpu.stream().synchronize()?;
6653        Ok(v)
6654    }
6655    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6656        let v = self.gpu.stream().clone_dtoh(d)?;
6657        self.gpu.stream().synchronize()?;
6658        Ok(v)
6659    }
6660    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6661    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6662    /// issuing them together avoids a second stream synchronization in every trunk layer.
6663    pub fn dtoh_pair(
6664        &self,
6665        a: &CudaSlice<f32>,
6666        b: &CudaSlice<f32>,
6667    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6668        let av = self.gpu.stream().clone_dtoh(a)?;
6669        let bv = self.gpu.stream().clone_dtoh(b)?;
6670        self.gpu.stream().synchronize()?;
6671        Ok((av, bv))
6672    }
6673    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6674    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6675        let v = self.gpu.stream().clone_dtoh(d)?;
6676        self.gpu.stream().synchronize()?;
6677        Ok(v)
6678    }
6679    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6680    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6681        let v = self.gpu.stream().clone_dtoh(d)?;
6682        self.gpu.stream().synchronize()?;
6683        Ok(v)
6684    }
6685    pub fn dtoh_u8_view(
6686        &self,
6687        d: &cudarc::driver::CudaView<u8>,
6688    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6689        let v = self.gpu.stream().clone_dtoh(d)?;
6690        self.gpu.stream().synchronize()?;
6691        Ok(v)
6692    }
6693    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6694        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6695        self.keep_if_capturing(&s);
6696        Ok(s)
6697    }
6698
6699    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6700    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6701    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6702    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6703    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6704    /// back (or kept resident for graph replay). Returns the device token buffer.
6705    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6706    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6707    pub fn prob_of_token_device(
6708        &self,
6709        logits: &CudaSlice<f32>,
6710        tok: &CudaSlice<u32>,
6711        n_vocab: usize,
6712    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6713        let nb = ARGMAX_NB;
6714        let mut part = self.alloc_uninit::<f32>(nb)?;
6715        let mut p = self.alloc_uninit::<f32>(1)?;
6716        let f1 = self.func("prob_of_token_partial_f32");
6717        let cfg1 = LaunchConfig {
6718            grid_dim: (nb as u32, 1, 1),
6719            block_dim: (256, 1, 1),
6720            shared_mem_bytes: 0,
6721        };
6722        let nv = n_vocab as i32;
6723        let __s_b1 = self.gpu.stream();
6724        let mut b1 = __s_b1.launch_builder(&f1);
6725        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6726        unsafe {
6727            b1.launch(cfg1)?;
6728        }
6729        let f2 = self.func("prob_of_token_final_f32");
6730        let cfg2 = LaunchConfig {
6731            grid_dim: (1, 1, 1),
6732            block_dim: (256, 1, 1),
6733            shared_mem_bytes: 0,
6734        };
6735        let nbi = nb as i32;
6736        let __s_b2 = self.gpu.stream();
6737        let mut b2 = __s_b2.launch_builder(&f2);
6738        b2.arg(&part).arg(&mut p).arg(&nbi);
6739        unsafe {
6740            b2.launch(cfg2)?;
6741        }
6742        Ok(p)
6743    }
6744
6745    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6746    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6747    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6748    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6749    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6750    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6751    pub fn prob_of_token_device_col(
6752        &self,
6753        logits: &CudaSlice<f32>,
6754        tok_all: &CudaSlice<u32>,
6755        tok_idx: usize,
6756        p_out: &mut CudaSlice<f32>,
6757        p_idx: usize,
6758        n_vocab: usize,
6759    ) -> Result<(), Box<dyn std::error::Error>> {
6760        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6761        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6762        let nb = ARGMAX_NB;
6763        let mut part = self.alloc_uninit::<f32>(nb)?;
6764        let f1 = self.func("prob_of_token_partial_f32");
6765        let cfg1 = LaunchConfig {
6766            grid_dim: (nb as u32, 1, 1),
6767            block_dim: (256, 1, 1),
6768            shared_mem_bytes: 0,
6769        };
6770        let nv = n_vocab as i32;
6771        let __s_b1 = self.gpu.stream();
6772        let mut b1 = __s_b1.launch_builder(&f1);
6773        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6774        unsafe {
6775            b1.launch(cfg1)?;
6776        }
6777        let f2 = self.func("prob_of_token_final_f32");
6778        let cfg2 = LaunchConfig {
6779            grid_dim: (1, 1, 1),
6780            block_dim: (256, 1, 1),
6781            shared_mem_bytes: 0,
6782        };
6783        let nbi = nb as i32;
6784        let __s_b2 = self.gpu.stream();
6785        let mut b2 = __s_b2.launch_builder(&f2);
6786        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6787        unsafe {
6788            b2.launch(cfg2)?;
6789        }
6790        Ok(())
6791    }
6792
6793    pub fn prob_of_token_device_into(
6794        &self,
6795        logits: &CudaSlice<f32>,
6796        tok: &CudaSlice<u32>,
6797        p_out: &mut CudaSlice<f32>,
6798        n_vocab: usize,
6799    ) -> Result<(), Box<dyn std::error::Error>> {
6800        let nb = ARGMAX_NB;
6801        let mut part = self.alloc_uninit::<f32>(nb)?;
6802        let f1 = self.func("prob_of_token_partial_f32");
6803        let cfg1 = LaunchConfig {
6804            grid_dim: (nb as u32, 1, 1),
6805            block_dim: (256, 1, 1),
6806            shared_mem_bytes: 0,
6807        };
6808        let nv = n_vocab as i32;
6809        let __s_b1 = self.gpu.stream();
6810        let mut b1 = __s_b1.launch_builder(&f1);
6811        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6812        unsafe {
6813            b1.launch(cfg1)?;
6814        }
6815        let f2 = self.func("prob_of_token_final_f32");
6816        let cfg2 = LaunchConfig {
6817            grid_dim: (1, 1, 1),
6818            block_dim: (256, 1, 1),
6819            shared_mem_bytes: 0,
6820        };
6821        let nbi = nb as i32;
6822        let __s_b2 = self.gpu.stream();
6823        let mut b2 = __s_b2.launch_builder(&f2);
6824        b2.arg(&part).arg(p_out).arg(&nbi);
6825        unsafe {
6826            b2.launch(cfg2)?;
6827        }
6828        Ok(())
6829    }
6830
6831    pub fn argmax_token_device(
6832        &self,
6833        logits: &CudaSlice<f32>,
6834        n_vocab: usize,
6835    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6836        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6837        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6838        Ok(tok)
6839    }
6840    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6841    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6842    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6843    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6844    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6845    /// captured passes bake fixed addresses.
6846    pub fn argmax_token_device_into(
6847        &self,
6848        logits: &CudaSlice<f32>,
6849        tok: &mut CudaSlice<u32>,
6850        n_vocab: usize,
6851    ) -> Result<(), Box<dyn std::error::Error>> {
6852        let nb = ARGMAX_NB;
6853        let f1 = self.func("argmax_partial_f32");
6854        let f2 = self.func("argmax_final_f32");
6855        let mut guard = self.argmax_partials.lock().unwrap();
6856        if guard.is_none() {
6857            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6858            // buffers carry no cudarc events (illegal inside capture).
6859            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6860            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6861            *guard = Some((pv, pi));
6862        }
6863        let (part_v, part_i) = guard.as_mut().unwrap();
6864        let nv = n_vocab as i32;
6865        let nbi = nb as i32;
6866        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6867        let cfg1 = LaunchConfig {
6868            grid_dim: (nb as u32, 1, 1),
6869            block_dim: (256, 1, 1),
6870            shared_mem_bytes: 0,
6871        };
6872        let __s_b1 = self.gpu.stream();
6873        let mut b1 = __s_b1.launch_builder(&f1);
6874        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6875        unsafe {
6876            b1.launch(cfg1)?;
6877        }
6878        // pass 2: one block reduces NB partials -> token_out[0].
6879        let cfg2 = LaunchConfig {
6880            grid_dim: (1, 1, 1),
6881            block_dim: (256, 1, 1),
6882            shared_mem_bytes: 0,
6883        };
6884        let __s_b2 = self.gpu.stream();
6885        let mut b2 = __s_b2.launch_builder(&f2);
6886        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6887        unsafe {
6888            b2.launch(cfg2)?;
6889        }
6890        Ok(())
6891    }
6892    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6893    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6894    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6895    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6896    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6897    pub fn argmax_token_device_col(
6898        &self,
6899        logits: &CudaSlice<f32>,
6900        col: usize,
6901        n_vocab: usize,
6902        toks: &mut CudaSlice<u32>,
6903        out_idx: usize,
6904    ) -> Result<(), Box<dyn std::error::Error>> {
6905        let nb = ARGMAX_NB;
6906        let f1 = self.func("argmax_partial_f32");
6907        let f2 = self.func("argmax_final_f32");
6908        let mut guard = self.argmax_partials.lock().unwrap();
6909        if guard.is_none() {
6910            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6911            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6912            *guard = Some((pv, pi));
6913        }
6914        let (part_v, part_i) = guard.as_mut().unwrap();
6915        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
6916        let nv = n_vocab as i32;
6917        let nbi = nb as i32;
6918        let cfg1 = LaunchConfig {
6919            grid_dim: (nb as u32, 1, 1),
6920            block_dim: (256, 1, 1),
6921            shared_mem_bytes: 0,
6922        };
6923        let __s_b1 = self.gpu.stream();
6924        let mut b1 = __s_b1.launch_builder(&f1);
6925        b1.arg(&col_view)
6926            .arg(&mut *part_v)
6927            .arg(&mut *part_i)
6928            .arg(&nv);
6929        unsafe {
6930            b1.launch(cfg1)?;
6931        }
6932        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
6933        let cfg2 = LaunchConfig {
6934            grid_dim: (1, 1, 1),
6935            block_dim: (256, 1, 1),
6936            shared_mem_bytes: 0,
6937        };
6938        let __s_b2 = self.gpu.stream();
6939        let mut b2 = __s_b2.launch_builder(&f2);
6940        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
6941        unsafe {
6942            b2.launch(cfg2)?;
6943        }
6944        Ok(())
6945    }
6946    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
6947    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6948        Ok(self.gpu.stream().clone_htod(v)?)
6949    }
6950    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
6951        let v = self.gpu.stream().clone_dtoh(d)?;
6952        self.gpu.stream().synchronize()?;
6953        Ok(v)
6954    }
6955    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
6956    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
6957    /// contents change every step, the address must not, so a captured graph can read it).
6958    pub fn htod_u32_into(
6959        &self,
6960        dst: &mut CudaSlice<u32>,
6961        src: &[u32],
6962    ) -> Result<(), Box<dyn std::error::Error>> {
6963        let mut view = dst.slice_mut(0..src.len());
6964        self.gpu.stream().memcpy_htod(src, &mut view)?;
6965        Ok(())
6966    }
6967
6968    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
6969    /// table without changing the device address its reconcile kernel consumes.
6970    pub fn htod_i32_into(
6971        &self,
6972        dst: &mut CudaSlice<i32>,
6973        src: &[i32],
6974    ) -> Result<(), Box<dyn std::error::Error>> {
6975        let mut view = dst.slice_mut(0..src.len());
6976        self.gpu.stream().memcpy_htod(src, &mut view)?;
6977        Ok(())
6978    }
6979
6980    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6981        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
6982        self.keep_if_capturing(&s);
6983        Ok(s)
6984    }
6985    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
6986    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
6987    pub fn embed_gather_device_into(
6988        &self,
6989        embd: &CudaSlice<u8>,
6990        token_d: &CudaSlice<u32>,
6991        x_out: &mut CudaSlice<f32>,
6992        n_embd: usize,
6993        qtype: i32,
6994        row_bytes: usize,
6995    ) -> Result<(), Box<dyn std::error::Error>> {
6996        let f = self.func("embed_gather_u32");
6997        let cfg = LaunchConfig {
6998            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
6999            block_dim: (256, 1, 1),
7000            shared_mem_bytes: 0,
7001        };
7002        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7003        let __s_b = self.gpu.stream();
7004        let mut b = __s_b.launch_builder(&f);
7005        b.arg(embd)
7006            .arg(token_d)
7007            .arg(x_out)
7008            .arg(&ne)
7009            .arg(&qt)
7010            .arg(&rb);
7011        unsafe {
7012            b.launch(cfg)?;
7013        }
7014        Ok(())
7015    }
7016    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
7017    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
7018        let v = self.gpu.stream().clone_dtoh(d)?;
7019        self.gpu.stream().synchronize()?;
7020        Ok(v[0])
7021    }
7022    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
7023    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
7024    /// the counter value after the throwaway capture warmups corrupt it.
7025    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
7026    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
7027    /// copy (fine at stream-idle boundaries, poison mid-round).
7028    pub fn i32_set_k(
7029        &self,
7030        dst: &mut CudaSlice<i32>,
7031        v: i32,
7032    ) -> Result<(), Box<dyn std::error::Error>> {
7033        let f = self.func("i32_set_k");
7034        let cfg = LaunchConfig {
7035            grid_dim: (1, 1, 1),
7036            block_dim: (1, 1, 1),
7037            shared_mem_bytes: 0,
7038        };
7039        let idx = 0i32;
7040        let __s_b = self.gpu.stream();
7041        let mut b = __s_b.launch_builder(&f);
7042        b.arg(dst).arg(&v).arg(&idx);
7043        unsafe {
7044            b.launch(cfg)?;
7045        }
7046        Ok(())
7047    }
7048
7049    pub fn set_i32_one(
7050        &self,
7051        d: &mut CudaSlice<i32>,
7052        v: i32,
7053    ) -> Result<(), Box<dyn std::error::Error>> {
7054        self.gpu.stream().memcpy_htod(&[v], d)?;
7055        Ok(())
7056    }
7057    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
7058    /// during priming / capture-state restore.
7059    pub fn set_u32_one(
7060        &self,
7061        d: &mut CudaSlice<u32>,
7062        v: u32,
7063    ) -> Result<(), Box<dyn std::error::Error>> {
7064        self.gpu.stream().memcpy_htod(&[v], d)?;
7065        Ok(())
7066    }
7067    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
7068    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
7069        let v = self.gpu.stream().clone_dtoh(d)?;
7070        self.gpu.stream().synchronize()?;
7071        Ok(v[0])
7072    }
7073    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
7074    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
7075        Ok(self.gpu.stream().clone_htod(bytes)?)
7076    }
7077    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7078    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7079    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7080    pub fn embed_gather_device(
7081        &self,
7082        embd: &CudaSlice<u8>,
7083        token_d: &CudaSlice<u32>,
7084        n_embd: usize,
7085        qtype: i32,
7086        row_bytes: usize,
7087    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7088        let f = self.func("embed_gather_u32");
7089        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7090        let cfg = LaunchConfig {
7091            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7092            block_dim: (256, 1, 1),
7093            shared_mem_bytes: 0,
7094        };
7095        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7096        let __s_b = self.gpu.stream();
7097        let mut b = __s_b.launch_builder(&f);
7098        b.arg(embd)
7099            .arg(token_d)
7100            .arg(&mut x)
7101            .arg(&ne)
7102            .arg(&qt)
7103            .arg(&rb);
7104        unsafe {
7105            b.launch(cfg)?;
7106        }
7107        Ok(x)
7108    }
7109
7110    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7111    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7112    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7113    pub fn embed_gather_device_t(
7114        &self,
7115        embd: &CudaSlice<u8>,
7116        tokens: &[u32],
7117        n_embd: usize,
7118        qtype: i32,
7119        row_bytes: usize,
7120    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7121        let t = tokens.len();
7122        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7123        let f = self.func("embed_gather_u32_t");
7124        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7125        let cfg = LaunchConfig {
7126            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7127            block_dim: (256, 1, 1),
7128            shared_mem_bytes: 0,
7129        };
7130        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7131        let __s_b = self.gpu.stream();
7132        let mut b = __s_b.launch_builder(&f);
7133        b.arg(embd)
7134            .arg(&tok_d)
7135            .arg(&mut x)
7136            .arg(&ne)
7137            .arg(&qt)
7138            .arg(&rb)
7139            .arg(&ti);
7140        unsafe {
7141            b.launch(cfg)?;
7142        }
7143        Ok(x)
7144    }
7145
7146    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7147    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7148    /// as embed_gather_device_t — bit-identical rows.
7149    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7150    pub fn embed_gather_device_tv(
7151        &self,
7152        embd: &CudaSlice<u8>,
7153        tok_v: &cudarc::driver::CudaView<u32>,
7154        t: usize,
7155        n_embd: usize,
7156        qtype: i32,
7157        row_bytes: usize,
7158    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7159        let f = self.func("embed_gather_u32_t");
7160        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7161        let cfg = LaunchConfig {
7162            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7163            block_dim: (256, 1, 1),
7164            shared_mem_bytes: 0,
7165        };
7166        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7167        let __s_b = self.gpu.stream();
7168        let mut b = __s_b.launch_builder(&f);
7169        b.arg(embd)
7170            .arg(tok_v)
7171            .arg(&mut x)
7172            .arg(&ne)
7173            .arg(&qt)
7174            .arg(&rb)
7175            .arg(&ti);
7176        unsafe {
7177            b.launch(cfg)?;
7178        }
7179        Ok(x)
7180    }
7181
7182    pub fn embed_gather_device_td(
7183        &self,
7184        embd: &CudaSlice<u8>,
7185        tok_d: &CudaSlice<u32>,
7186        t: usize,
7187        n_embd: usize,
7188        qtype: i32,
7189        row_bytes: usize,
7190    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7191        let f = self.func("embed_gather_u32_t");
7192        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7193        let cfg = LaunchConfig {
7194            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7195            block_dim: (256, 1, 1),
7196            shared_mem_bytes: 0,
7197        };
7198        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7199        let __s_b = self.gpu.stream();
7200        let mut b = __s_b.launch_builder(&f);
7201        b.arg(embd)
7202            .arg(tok_d)
7203            .arg(&mut x)
7204            .arg(&ne)
7205            .arg(&qt)
7206            .arg(&rb)
7207            .arg(&ti);
7208        unsafe {
7209            b.launch(cfg)?;
7210        }
7211        Ok(x)
7212    }
7213
7214    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7215    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7216    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7217    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7218    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7219    #[inline]
7220    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7221    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7222        if self
7223            .capture_keep_on
7224            .load(std::sync::atomic::Ordering::Relaxed)
7225        {
7226            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7227        }
7228    }
7229
7230    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7231        &self,
7232        n: usize,
7233    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7234        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7235        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7236        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7237        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7238        {
7239            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7240            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7241                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7242                use cudarc::driver::DevicePtrMut;
7243                let n_bytes = s.len() * std::mem::size_of::<T>();
7244                let stream = self.gpu.stream();
7245                let (p_, _g) = s.device_ptr_mut(&stream);
7246                unsafe {
7247                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7248                        .result()?;
7249                }
7250            }
7251        }
7252        self.keep_if_capturing(&s);
7253        Ok(s)
7254    }
7255
7256    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7257    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7258    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7259    /// consumers alloc through this (m=1 decode arms).
7260    pub fn uninit_q8_pair(
7261        &self,
7262        n: usize,
7263    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7264        Ok((
7265            self.alloc_uninit::<i8>(n)?,
7266            self.alloc_uninit::<f32>(n / 32)?,
7267        ))
7268    }
7269
7270    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7271        self.alloc_uninit::<f32>(n)
7272    }
7273
7274    /// i8 uninitialized scratch (same contract as `uninit`).
7275    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7276        self.alloc_uninit::<i8>(n)
7277    }
7278
7279    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7280    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7281    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7282    #[allow(clippy::too_many_arguments)]
7283    pub fn rms_norm3(
7284        &self,
7285        x: &CudaSlice<f32>,
7286        w0: &CudaSlice<f32>,
7287        w1: &CudaSlice<f32>,
7288        w2: &CudaSlice<f32>,
7289        d0: &mut CudaSlice<f32>,
7290        d1: &mut CudaSlice<f32>,
7291        d2: &mut CudaSlice<f32>,
7292        ncols: usize,
7293        nrows: usize,
7294        eps: f32,
7295    ) -> Result<(), Box<dyn std::error::Error>> {
7296        let f = self.func("rms_norm3_f32");
7297        let cfg = LaunchConfig {
7298            grid_dim: (nrows as u32, 1, 1),
7299            block_dim: (rms_block(), 1, 1),
7300            shared_mem_bytes: 0,
7301        };
7302        let (nc, e) = (ncols as i32, eps);
7303        let __s_b = self.gpu.stream();
7304        let mut b = __s_b.launch_builder(&f);
7305        b.arg(x)
7306            .arg(w0)
7307            .arg(w1)
7308            .arg(w2)
7309            .arg(d0)
7310            .arg(d1)
7311            .arg(d2)
7312            .arg(&nc)
7313            .arg(&e);
7314        unsafe {
7315            b.launch(cfg)?;
7316        }
7317        Ok(())
7318    }
7319
7320    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7321    #[allow(clippy::too_many_arguments)]
7322    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7323    /// piggybacks on the same conditions.
7324    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7325        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7326        *WARP_ON.get_or_init(|| {
7327            std::env::var("MEMRA_QKVNORM_W")
7328                .map(|v| v != "0")
7329                .unwrap_or(true)
7330        }) && ncols % 4 == 0
7331            && rows >= 64
7332    }
7333
7334    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7335    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7336    #[allow(clippy::too_many_arguments)]
7337    pub fn rms_norm_qkv_w4b(
7338        &self,
7339        q: &CudaSlice<f32>,
7340        k: &CudaSlice<f32>,
7341        v: &CudaSlice<f32>,
7342        wq: &CudaSlice<f32>,
7343        wk: &CudaSlice<f32>,
7344        wv: &CudaSlice<f32>,
7345        dq: &mut CudaSlice<f32>,
7346        dk: &mut CudaSlice<f32>,
7347        dv: &mut CudaSlice<f32>,
7348        dvb: &mut CudaSlice<u8>,
7349        ncols: usize,
7350        rq: usize,
7351        rk: usize,
7352        eps: f32,
7353        vf16: bool,
7354    ) -> Result<(), Box<dyn std::error::Error>> {
7355        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7356        let f = self.func("rms_norm_qkv_w4b_f32");
7357        let rows = (rq + 2 * rk) as u32;
7358        let cfg = LaunchConfig {
7359            grid_dim: (rows.div_ceil(8), 1, 1),
7360            block_dim: (256, 1, 1),
7361            shared_mem_bytes: 0,
7362        };
7363        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7364        let vf = vf16 as i32;
7365        let __s_b = self.gpu.stream();
7366        let mut b = __s_b.launch_builder(&f);
7367        b.arg(q)
7368            .arg(k)
7369            .arg(v)
7370            .arg(wq)
7371            .arg(wk)
7372            .arg(wv)
7373            .arg(dq)
7374            .arg(dk)
7375            .arg(dv)
7376            .arg(&mut *dvb)
7377            .arg(&nc)
7378            .arg(&rqi)
7379            .arg(&rki)
7380            .arg(&rvi)
7381            .arg(&e)
7382            .arg(&vf);
7383        unsafe {
7384            b.launch(cfg)?;
7385        }
7386        Ok(())
7387    }
7388
7389    pub fn rms_norm_qkv(
7390        &self,
7391        q: &CudaSlice<f32>,
7392        k: &CudaSlice<f32>,
7393        v: &CudaSlice<f32>,
7394        wq: &CudaSlice<f32>,
7395        wk: &CudaSlice<f32>,
7396        wv: &CudaSlice<f32>,
7397        dq: &mut CudaSlice<f32>,
7398        dk: &mut CudaSlice<f32>,
7399        dv: &mut CudaSlice<f32>,
7400        ncols: usize,
7401        rq: usize,
7402        rk: usize,
7403        eps: f32,
7404    ) -> Result<(), Box<dyn std::error::Error>> {
7405        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7406        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7407        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7408        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7409        let warp_on = *WARP_ON.get_or_init(|| {
7410            std::env::var("MEMRA_QKVNORM_W")
7411                .map(|v| v != "0")
7412                .unwrap_or(true)
7413        });
7414        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7415        // replay numerics are untouched on every model; only prefill depth takes the new config.
7416        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7417            let f = self.func("rms_norm_qkv_w4_f32");
7418            let rows = (rq + 2 * rk) as u32;
7419            let cfg = LaunchConfig {
7420                grid_dim: (rows.div_ceil(8), 1, 1),
7421                block_dim: (256, 1, 1),
7422                shared_mem_bytes: 0,
7423            };
7424            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7425            let __s_b = self.gpu.stream();
7426            let mut b = __s_b.launch_builder(&f);
7427            b.arg(q)
7428                .arg(k)
7429                .arg(v)
7430                .arg(wq)
7431                .arg(wk)
7432                .arg(wv)
7433                .arg(dq)
7434                .arg(dk)
7435                .arg(dv)
7436                .arg(&nc)
7437                .arg(&rqi)
7438                .arg(&rki)
7439                .arg(&rvi)
7440                .arg(&e);
7441            unsafe {
7442                b.launch(cfg)?;
7443            }
7444            return Ok(());
7445        }
7446        let f = self.func("rms_norm_qkv_f32");
7447        let grid = (rq + 2 * rk) as u32;
7448        let cfg = LaunchConfig {
7449            grid_dim: (grid, 1, 1),
7450            block_dim: (rms_block(), 1, 1),
7451            shared_mem_bytes: 0,
7452        };
7453        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7454        let __s_b = self.gpu.stream();
7455        let mut b = __s_b.launch_builder(&f);
7456        b.arg(q)
7457            .arg(k)
7458            .arg(v)
7459            .arg(wq)
7460            .arg(wk)
7461            .arg(wv)
7462            .arg(dq)
7463            .arg(dk)
7464            .arg(dv)
7465            .arg(&nc)
7466            .arg(&rqi)
7467            .arg(&rki)
7468            .arg(&e);
7469        unsafe {
7470            b.launch(cfg)?;
7471        }
7472        Ok(())
7473    }
7474
7475    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7476    #[allow(clippy::too_many_arguments)]
7477    pub fn rms_norm2x(
7478        &self,
7479        a: &CudaSlice<f32>,
7480        bb: &CudaSlice<f32>,
7481        wa: &CudaSlice<f32>,
7482        wb: &CudaSlice<f32>,
7483        da: &mut CudaSlice<f32>,
7484        db: &mut CudaSlice<f32>,
7485        ncols: usize,
7486        nrows: usize,
7487        eps: f32,
7488    ) -> Result<(), Box<dyn std::error::Error>> {
7489        let f = self.func("rms_norm2x_f32");
7490        let cfg = LaunchConfig {
7491            grid_dim: (2 * nrows as u32, 1, 1),
7492            block_dim: (rms_block(), 1, 1),
7493            shared_mem_bytes: 0,
7494        };
7495        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7496        let __s_b = self.gpu.stream();
7497        let mut b = __s_b.launch_builder(&f);
7498        b.arg(a)
7499            .arg(bb)
7500            .arg(wa)
7501            .arg(wb)
7502            .arg(da)
7503            .arg(db)
7504            .arg(&nc)
7505            .arg(&nr)
7506            .arg(&e);
7507        unsafe {
7508            b.launch(cfg)?;
7509        }
7510        Ok(())
7511    }
7512
7513    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7514    pub fn softcap(
7515        &self,
7516        y: &mut CudaSlice<f32>,
7517        cap: f32,
7518        n: usize,
7519    ) -> Result<(), Box<dyn std::error::Error>> {
7520        let f = self.func("softcap_f32");
7521        let cfg = LaunchConfig::for_num_elems(n as u32);
7522        let ni = n as i32;
7523        let __s_b = self.gpu.stream();
7524        let mut b = __s_b.launch_builder(&f);
7525        b.arg(y).arg(&cap).arg(&ni);
7526        unsafe {
7527            b.launch(cfg)?;
7528        }
7529        Ok(())
7530    }
7531
7532    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7533    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7534    pub fn mask_ids_rows(
7535        &self,
7536        y: &mut CudaSlice<f32>,
7537        ids: &CudaSlice<i32>,
7538        n_ids: usize,
7539        n_vocab: usize,
7540        t: usize,
7541    ) -> Result<(), Box<dyn std::error::Error>> {
7542        let f = self.func("mask_ids_rows_f32");
7543        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7544        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7545        let __s_b = self.gpu.stream();
7546        let mut b = __s_b.launch_builder(&f);
7547        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7548        unsafe {
7549            b.launch(cfg)?;
7550        }
7551        Ok(())
7552    }
7553
7554    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7555    #[allow(clippy::too_many_arguments)]
7556    pub fn add_scale_rms_norm(
7557        &self,
7558        a: &CudaSlice<f32>,
7559        b_in: &CudaSlice<f32>,
7560        c: f32,
7561        w: &CudaSlice<f32>,
7562        res: &mut CudaSlice<f32>,
7563        dst: &mut CudaSlice<f32>,
7564        ncols: usize,
7565        nrows: usize,
7566        eps: f32,
7567    ) -> Result<(), Box<dyn std::error::Error>> {
7568        let f = self.func("add_scale_rms_norm_f32");
7569        let cfg = LaunchConfig {
7570            grid_dim: (nrows as u32, 1, 1),
7571            block_dim: (rms_block(), 1, 1),
7572            shared_mem_bytes: 0,
7573        };
7574        let (nc, e2) = (ncols as i32, eps);
7575        let __s_b = self.gpu.stream();
7576        let mut b = __s_b.launch_builder(&f);
7577        b.arg(a)
7578            .arg(b_in)
7579            .arg(&c)
7580            .arg(w)
7581            .arg(res)
7582            .arg(dst)
7583            .arg(&nc)
7584            .arg(&e2);
7585        unsafe {
7586            b.launch(cfg)?;
7587        }
7588        Ok(())
7589    }
7590
7591    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7592    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7593    #[allow(clippy::too_many_arguments)]
7594    pub fn add_scale_rms_norm_q8_1(
7595        &self,
7596        a: &CudaSlice<f32>,
7597        b_in: &CudaSlice<f32>,
7598        c: f32,
7599        w: &CudaSlice<f32>,
7600        res: &mut CudaSlice<f32>,
7601        ncols: usize,
7602        nrows: usize,
7603        eps: f32,
7604    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7605        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7606        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7607        let (nc, e2) = (ncols as i32, eps);
7608        if Self::pdl_on() && Self::pdl_wb_on() {
7609            {
7610                use cudarc::driver::{DevicePtr, DevicePtrMut};
7611                let s = &self.gpu.stream();
7612                let (pa, _g0) = a.device_ptr(s);
7613                let (pb, _g1) = b_in.device_ptr(s);
7614                let (pw, _g2) = w.device_ptr(s);
7615                let (pr, _g3) = res.device_ptr_mut(s);
7616                let (pq, _g4) = out_q.device_ptr_mut(s);
7617                let (pd, _g5) = out_d.device_ptr_mut(s);
7618                let mut ps = [
7619                    &pa as *const _ as *mut std::ffi::c_void,
7620                    &pb as *const _ as *mut _,
7621                    &c as *const _ as *mut _,
7622                    &pw as *const _ as *mut _,
7623                    &pr as *const _ as *mut _,
7624                    &pq as *const _ as *mut _,
7625                    &pd as *const _ as *mut _,
7626                    &nc as *const _ as *mut _,
7627                    &e2 as *const _ as *mut _,
7628                ];
7629                unsafe {
7630                    self.launch_pdl(
7631                        "add_scale_rms_norm_q8_1",
7632                        (nrows as u32, 1, 1),
7633                        (rms_block(), 1, 1),
7634                        &mut ps,
7635                    )?;
7636                }
7637            }
7638            return Ok((out_q, out_d));
7639        }
7640        let f = self.func("add_scale_rms_norm_q8_1");
7641        let cfg = LaunchConfig {
7642            grid_dim: (nrows as u32, 1, 1),
7643            block_dim: (rms_block(), 1, 1),
7644            shared_mem_bytes: 0,
7645        };
7646        let __s_b = self.gpu.stream();
7647        let mut b = __s_b.launch_builder(&f);
7648        b.arg(a)
7649            .arg(b_in)
7650            .arg(&c)
7651            .arg(w)
7652            .arg(res)
7653            .arg(&mut out_q)
7654            .arg(&mut out_d)
7655            .arg(&nc)
7656            .arg(&e2);
7657        unsafe {
7658            b.launch(cfg)?;
7659        }
7660        Ok((out_q, out_d))
7661    }
7662
7663    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7664    #[allow(clippy::too_many_arguments)]
7665    pub fn add_scale_rms_norm_q8_1_into(
7666        &self,
7667        a: &CudaSlice<f32>,
7668        b_in: &CudaSlice<f32>,
7669        c: f32,
7670        w: &CudaSlice<f32>,
7671        res: &mut CudaSlice<f32>,
7672        ncols: usize,
7673        nrows: usize,
7674        eps: f32,
7675        out_q: &mut CudaSlice<i8>,
7676        out_d: &mut CudaSlice<f32>,
7677    ) -> Result<(), Box<dyn std::error::Error>> {
7678        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7679        let (nc, e2) = (ncols as i32, eps);
7680        if Self::pdl_on() && Self::pdl_wb_on() {
7681            use cudarc::driver::{DevicePtr, DevicePtrMut};
7682            let s = &self.gpu.stream();
7683            let (pa, _g0) = a.device_ptr(s);
7684            let (pb, _g1) = b_in.device_ptr(s);
7685            let (pw, _g2) = w.device_ptr(s);
7686            let (pr, _g3) = res.device_ptr_mut(s);
7687            let (pq, _g4) = out_q.device_ptr_mut(s);
7688            let (pd, _g5) = out_d.device_ptr_mut(s);
7689            let mut ps = [
7690                &pa as *const _ as *mut std::ffi::c_void,
7691                &pb as *const _ as *mut _,
7692                &c as *const _ as *mut _,
7693                &pw as *const _ as *mut _,
7694                &pr as *const _ as *mut _,
7695                &pq as *const _ as *mut _,
7696                &pd as *const _ as *mut _,
7697                &nc as *const _ as *mut _,
7698                &e2 as *const _ as *mut _,
7699            ];
7700            unsafe {
7701                self.launch_pdl(
7702                    "add_scale_rms_norm_q8_1",
7703                    (nrows as u32, 1, 1),
7704                    (rms_block(), 1, 1),
7705                    &mut ps,
7706                )?;
7707            }
7708            return Ok(());
7709        }
7710        let f = self.func("add_scale_rms_norm_q8_1");
7711        let cfg = LaunchConfig {
7712            grid_dim: (nrows as u32, 1, 1),
7713            block_dim: (rms_block(), 1, 1),
7714            shared_mem_bytes: 0,
7715        };
7716        let __s_b = self.gpu.stream();
7717        let mut b = __s_b.launch_builder(&f);
7718        b.arg(a)
7719            .arg(b_in)
7720            .arg(&c)
7721            .arg(w)
7722            .arg(res)
7723            .arg(&mut *out_q)
7724            .arg(&mut *out_d)
7725            .arg(&nc)
7726            .arg(&e2);
7727        unsafe {
7728            b.launch(cfg)?;
7729        }
7730        Ok(())
7731    }
7732
7733    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7734    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7735    #[allow(clippy::too_many_arguments)]
7736    pub fn rms_pre_add_scale_rms_norm_q8_1(
7737        &self,
7738        a: &CudaSlice<f32>,
7739        wa: &CudaSlice<f32>,
7740        b_in: &CudaSlice<f32>,
7741        c: f32,
7742        w: &CudaSlice<f32>,
7743        res: &mut CudaSlice<f32>,
7744        ncols: usize,
7745        nrows: usize,
7746        eps: f32,
7747    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7748        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7749        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7750        let (nc, e2) = (ncols as i32, eps);
7751        if Self::pdl_on() {
7752            {
7753                use cudarc::driver::{DevicePtr, DevicePtrMut};
7754                let s = &self.gpu.stream();
7755                let (pa, _g0) = a.device_ptr(s);
7756                let (pwa, _g1) = wa.device_ptr(s);
7757                let (pb, _g2) = b_in.device_ptr(s);
7758                let (pw, _g3) = w.device_ptr(s);
7759                let (pr, _g4) = res.device_ptr_mut(s);
7760                let (pq, _g5) = out_q.device_ptr_mut(s);
7761                let (pd, _g6) = out_d.device_ptr_mut(s);
7762                let mut ps = [
7763                    &pa as *const _ as *mut std::ffi::c_void,
7764                    &pwa as *const _ as *mut _,
7765                    &pb as *const _ as *mut _,
7766                    &c as *const _ as *mut _,
7767                    &pw as *const _ as *mut _,
7768                    &pr as *const _ as *mut _,
7769                    &pq as *const _ as *mut _,
7770                    &pd as *const _ as *mut _,
7771                    &nc as *const _ as *mut _,
7772                    &e2 as *const _ as *mut _,
7773                ];
7774                unsafe {
7775                    self.launch_pdl(
7776                        "rms_pre_add_scale_rms_norm_q8_1",
7777                        (nrows as u32, 1, 1),
7778                        (rms_block(), 1, 1),
7779                        &mut ps,
7780                    )?;
7781                }
7782            }
7783            return Ok((out_q, out_d));
7784        }
7785        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7786        let cfg = LaunchConfig {
7787            grid_dim: (nrows as u32, 1, 1),
7788            block_dim: (rms_block(), 1, 1),
7789            shared_mem_bytes: 0,
7790        };
7791        let __s_b = self.gpu.stream();
7792        let mut b = __s_b.launch_builder(&f);
7793        b.arg(a)
7794            .arg(wa)
7795            .arg(b_in)
7796            .arg(&c)
7797            .arg(w)
7798            .arg(res)
7799            .arg(&mut out_q)
7800            .arg(&mut out_d)
7801            .arg(&nc)
7802            .arg(&e2);
7803        unsafe {
7804            b.launch(cfg)?;
7805        }
7806        Ok((out_q, out_d))
7807    }
7808
7809    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7810    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7811    pub fn gelu_tanh_mul_q8_1(
7812        &self,
7813        gate: &CudaSlice<f32>,
7814        up: &cudarc::driver::CudaView<f32>,
7815        act: &mut CudaSlice<f32>,
7816        ncols: usize,
7817        nrows: usize,
7818    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7819        debug_assert!(ncols % 128 == 0);
7820        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7821        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7822        let nc = ncols as i32;
7823        if Self::pdl_on() {
7824            {
7825                use cudarc::driver::{DevicePtr, DevicePtrMut};
7826                let s = &self.gpu.stream();
7827                let (pg, _g0) = gate.device_ptr(s);
7828                let (pu, _g1) = up.device_ptr(s);
7829                let (pact, _g2) = act.device_ptr_mut(s);
7830                let (pq, _g3) = out_q.device_ptr_mut(s);
7831                let (pd, _g4) = out_d.device_ptr_mut(s);
7832                let mut ps = [
7833                    &pg as *const _ as *mut std::ffi::c_void,
7834                    &pu as *const _ as *mut _,
7835                    &pact as *const _ as *mut _,
7836                    &pq as *const _ as *mut _,
7837                    &pd as *const _ as *mut _,
7838                    &nc as *const _ as *mut _,
7839                ];
7840                unsafe {
7841                    self.launch_pdl(
7842                        "gelu_tanh_mul_q8_1",
7843                        (nrows as u32, 1, 1),
7844                        (rms_block(), 1, 1),
7845                        &mut ps,
7846                    )?;
7847                }
7848            }
7849            return Ok((out_q, out_d));
7850        }
7851        let f = self.func("gelu_tanh_mul_q8_1");
7852        let cfg = LaunchConfig {
7853            grid_dim: (nrows as u32, 1, 1),
7854            block_dim: (rms_block(), 1, 1),
7855            shared_mem_bytes: 0,
7856        };
7857        let __s_b = self.gpu.stream();
7858        let mut b = __s_b.launch_builder(&f);
7859        b.arg(gate)
7860            .arg(up)
7861            .arg(act)
7862            .arg(&mut out_q)
7863            .arg(&mut out_d)
7864            .arg(&nc);
7865        unsafe {
7866            b.launch(cfg)?;
7867        }
7868        Ok((out_q, out_d))
7869    }
7870
7871    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7872    #[allow(clippy::too_many_arguments)]
7873    pub fn gelu_tanh_mul_q8_1_into(
7874        &self,
7875        gate: &CudaSlice<f32>,
7876        up: &cudarc::driver::CudaView<f32>,
7877        act: &mut CudaSlice<f32>,
7878        ncols: usize,
7879        nrows: usize,
7880        out_q: &mut CudaSlice<i8>,
7881        out_d: &mut CudaSlice<f32>,
7882    ) -> Result<(), Box<dyn std::error::Error>> {
7883        debug_assert!(ncols % 128 == 0);
7884        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7885        let nc = ncols as i32;
7886        if Self::pdl_on() {
7887            use cudarc::driver::{DevicePtr, DevicePtrMut};
7888            let s = &self.gpu.stream();
7889            let (pg, _g0) = gate.device_ptr(s);
7890            let (pu, _g1) = up.device_ptr(s);
7891            let (pact, _g2) = act.device_ptr_mut(s);
7892            let (pq, _g3) = out_q.device_ptr_mut(s);
7893            let (pd, _g4) = out_d.device_ptr_mut(s);
7894            let mut ps = [
7895                &pg as *const _ as *mut std::ffi::c_void,
7896                &pu as *const _ as *mut _,
7897                &pact as *const _ as *mut _,
7898                &pq as *const _ as *mut _,
7899                &pd as *const _ as *mut _,
7900                &nc as *const _ as *mut _,
7901            ];
7902            unsafe {
7903                self.launch_pdl(
7904                    "gelu_tanh_mul_q8_1",
7905                    (nrows as u32, 1, 1),
7906                    (rms_block(), 1, 1),
7907                    &mut ps,
7908                )?;
7909            }
7910            return Ok(());
7911        }
7912        let f = self.func("gelu_tanh_mul_q8_1");
7913        let cfg = LaunchConfig {
7914            grid_dim: (nrows as u32, 1, 1),
7915            block_dim: (rms_block(), 1, 1),
7916            shared_mem_bytes: 0,
7917        };
7918        let __s_b = self.gpu.stream();
7919        let mut b = __s_b.launch_builder(&f);
7920        b.arg(gate)
7921            .arg(up)
7922            .arg(&mut *act)
7923            .arg(&mut *out_q)
7924            .arg(&mut *out_d)
7925            .arg(&nc);
7926        unsafe {
7927            b.launch(cfg)?;
7928        }
7929        Ok(())
7930    }
7931
7932    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
7933    #[allow(clippy::too_many_arguments)]
7934    pub fn add_rms_norm3_q8z(
7935        &self,
7936        a: &CudaSlice<f32>,
7937        b_in: &CudaSlice<f32>,
7938        w0: &CudaSlice<f32>,
7939        w1: &CudaSlice<f32>,
7940        w2: &CudaSlice<f32>,
7941        res: &mut CudaSlice<f32>,
7942        out1: &mut CudaSlice<f32>,
7943        ncols: usize,
7944        nrows: usize,
7945        eps: f32,
7946    ) -> Result<
7947        (
7948            (CudaSlice<i8>, CudaSlice<f32>),
7949            (CudaSlice<i8>, CudaSlice<f32>),
7950        ),
7951        Box<dyn std::error::Error>,
7952    > {
7953        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
7954        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7955        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
7956        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7957        let f = self.func("add_rms_norm3_q8z_f32");
7958        let cfg = LaunchConfig {
7959            grid_dim: (nrows as u32, 1, 1),
7960            block_dim: (rms_block(), 1, 1),
7961            shared_mem_bytes: 0,
7962        };
7963        let (nc, e2) = (ncols as i32, eps);
7964        let __s_b = self.gpu.stream();
7965        let mut b = __s_b.launch_builder(&f);
7966        b.arg(a)
7967            .arg(b_in)
7968            .arg(w0)
7969            .arg(w1)
7970            .arg(w2)
7971            .arg(res)
7972            .arg(&mut q0)
7973            .arg(&mut d0)
7974            .arg(out1)
7975            .arg(&mut q2)
7976            .arg(&mut d2)
7977            .arg(&nc)
7978            .arg(&e2);
7979        unsafe {
7980            b.launch(cfg)?;
7981        }
7982        Ok(((q0, d0), (q2, d2)))
7983    }
7984
7985    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
7986    #[allow(clippy::too_many_arguments)]
7987    pub fn add_rms_norm3(
7988        &self,
7989        a: &CudaSlice<f32>,
7990        b_in: &CudaSlice<f32>,
7991        w0: &CudaSlice<f32>,
7992        w1: &CudaSlice<f32>,
7993        w2: &CudaSlice<f32>,
7994        res: &mut CudaSlice<f32>,
7995        d0: &mut CudaSlice<f32>,
7996        d1: &mut CudaSlice<f32>,
7997        d2: &mut CudaSlice<f32>,
7998        ncols: usize,
7999        nrows: usize,
8000        eps: f32,
8001    ) -> Result<(), Box<dyn std::error::Error>> {
8002        let f = self.func("add_rms_norm3_f32");
8003        let cfg = LaunchConfig {
8004            grid_dim: (nrows as u32, 1, 1),
8005            block_dim: (rms_block(), 1, 1),
8006            shared_mem_bytes: 0,
8007        };
8008        let (nc, e2) = (ncols as i32, eps);
8009        let __s_b = self.gpu.stream();
8010        let mut b = __s_b.launch_builder(&f);
8011        b.arg(a)
8012            .arg(b_in)
8013            .arg(w0)
8014            .arg(w1)
8015            .arg(w2)
8016            .arg(res)
8017            .arg(d0)
8018            .arg(d1)
8019            .arg(d2)
8020            .arg(&nc)
8021            .arg(&e2);
8022        unsafe {
8023            b.launch(cfg)?;
8024        }
8025        Ok(())
8026    }
8027
8028    /// dst = (a + b) * c (residual add + layer scale, one launch).
8029    pub fn add_scale(
8030        &self,
8031        a: &CudaSlice<f32>,
8032        b_in: &CudaSlice<f32>,
8033        c: f32,
8034        dst: &mut CudaSlice<f32>,
8035        n: usize,
8036    ) -> Result<(), Box<dyn std::error::Error>> {
8037        let f = self.func("add_scale_f32");
8038        let cfg = LaunchConfig::for_num_elems(n as u32);
8039        let ni = n as i32;
8040        let __s_b = self.gpu.stream();
8041        let mut b = __s_b.launch_builder(&f);
8042        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
8043        unsafe {
8044            b.launch(cfg)?;
8045        }
8046        Ok(())
8047    }
8048
8049    /// Vision-tower LayerNorm (with bias) over [nrows, ncols] — lane/vision.
8050    pub fn layer_norm_bias(
8051        &self,
8052        x: &CudaSlice<f32>,
8053        w: &CudaSlice<f32>,
8054        b: &CudaSlice<f32>,
8055        dst: &mut CudaSlice<f32>,
8056        ncols: usize,
8057        nrows: usize,
8058        eps: f32,
8059    ) -> Result<(), Box<dyn std::error::Error>> {
8060        let f = self.func("layer_norm_bias_f32");
8061        let (nc, e) = (ncols as i32, eps);
8062        let cfg = LaunchConfig {
8063            grid_dim: (nrows as u32, 1, 1),
8064            block_dim: (256, 1, 1),
8065            shared_mem_bytes: 0,
8066        };
8067        let __s_b = self.gpu.stream();
8068        let mut lb = __s_b.launch_builder(&f);
8069        lb.arg(x).arg(w).arg(b).arg(&mut *dst).arg(&nc).arg(&e);
8070        unsafe {
8071            lb.launch(cfg)?;
8072        }
8073        Ok(())
8074    }
8075
8076    /// gelu_pytorch_tanh elementwise (vision tower MLP activation).
8077    pub fn gelu_tanh(
8078        &self,
8079        x: &CudaSlice<f32>,
8080        dst: &mut CudaSlice<f32>,
8081        n: usize,
8082    ) -> Result<(), Box<dyn std::error::Error>> {
8083        let f = self.func("gelu_tanh_f32");
8084        let ni = n as i64;
8085        let cfg = LaunchConfig {
8086            grid_dim: (n.div_ceil(256) as u32, 1, 1),
8087            block_dim: (256, 1, 1),
8088            shared_mem_bytes: 0,
8089        };
8090        let __s_b = self.gpu.stream();
8091        let mut lb = __s_b.launch_builder(&f);
8092        lb.arg(x).arg(&mut *dst).arg(&ni);
8093        unsafe {
8094            lb.launch(cfg)?;
8095        }
8096        Ok(())
8097    }
8098
8099    /// In-place row softmax over [nrows, ncols] (bidirectional vision attention).
8100    pub fn row_softmax(
8101        &self,
8102        x: &mut CudaSlice<f32>,
8103        ncols: usize,
8104        nrows: usize,
8105    ) -> Result<(), Box<dyn std::error::Error>> {
8106        let f = self.func("row_softmax_f32");
8107        let nc = ncols as i32;
8108        let cfg = LaunchConfig {
8109            grid_dim: (nrows as u32, 1, 1),
8110            block_dim: (256, 1, 1),
8111            shared_mem_bytes: 0,
8112        };
8113        let __s_b = self.gpu.stream();
8114        let mut lb = __s_b.launch_builder(&f);
8115        lb.arg(&mut *x).arg(&nc);
8116        unsafe {
8117            lb.launch(cfg)?;
8118        }
8119        Ok(())
8120    }
8121
8122    pub fn rms_norm(
8123        &self,
8124        x: &CudaSlice<f32>,
8125        w: &CudaSlice<f32>,
8126        dst: &mut CudaSlice<f32>,
8127        ncols: usize,
8128        nrows: usize,
8129        eps: f32,
8130    ) -> Result<(), Box<dyn std::error::Error>> {
8131        let (nc, e) = (ncols as i32, eps);
8132        if Self::pdl_on() && Self::pdl_wb_on() {
8133            use cudarc::driver::{DevicePtr, DevicePtrMut};
8134            let s = &self.gpu.stream();
8135            let (px, _g0) = x.device_ptr(s);
8136            let (pw, _g1) = w.device_ptr(s);
8137            let (pd, _g2) = dst.device_ptr_mut(s);
8138            let mut ps = [
8139                &px as *const _ as *mut std::ffi::c_void,
8140                &pw as *const _ as *mut _,
8141                &pd as *const _ as *mut _,
8142                &nc as *const _ as *mut _,
8143                &e as *const _ as *mut _,
8144            ];
8145            unsafe {
8146                self.launch_pdl(
8147                    "rms_norm_f32",
8148                    (nrows as u32, 1, 1),
8149                    (rms_block(), 1, 1),
8150                    &mut ps,
8151                )?;
8152            }
8153            return Ok(());
8154        }
8155        let f = self.func("rms_norm_f32");
8156        let cfg = LaunchConfig {
8157            grid_dim: (nrows as u32, 1, 1),
8158            block_dim: (rms_block(), 1, 1),
8159            shared_mem_bytes: 0,
8160        };
8161        let __s_b = self.gpu.stream();
8162        let mut b = __s_b.launch_builder(&f);
8163        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8164        unsafe {
8165            b.launch(cfg)?;
8166        }
8167        Ok(())
8168    }
8169
8170    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8171    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8172    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8173    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8174    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8175    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8176    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8177    pub fn rms_norm_decode(
8178        &self,
8179        x: &CudaSlice<f32>,
8180        w: &CudaSlice<f32>,
8181        dst: &mut CudaSlice<f32>,
8182        ncols: usize,
8183        nrows: usize,
8184        eps: f32,
8185    ) -> Result<(), Box<dyn std::error::Error>> {
8186        let f = self.func("rms_norm_f32");
8187        let cfg = LaunchConfig {
8188            grid_dim: (nrows as u32, 1, 1),
8189            block_dim: (1024, 1, 1),
8190            shared_mem_bytes: 0,
8191        };
8192        let (nc, e) = (ncols as i32, eps);
8193        let __s_b = self.gpu.stream();
8194        let mut b = __s_b.launch_builder(&f);
8195        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8196        unsafe {
8197            b.launch(cfg)?;
8198        }
8199        Ok(())
8200    }
8201
8202    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8203    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8204    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8205    pub fn rms_norm_q8_1(
8206        &self,
8207        x: &CudaSlice<f32>,
8208        w: &CudaSlice<f32>,
8209        ncols: usize,
8210        nrows: usize,
8211        eps: f32,
8212    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8213        let nblk = ncols / 32;
8214        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8215        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8216        let (nc, e) = (ncols as i32, eps);
8217        if Self::pdl_on() {
8218            {
8219                use cudarc::driver::{DevicePtr, DevicePtrMut};
8220                let s = &self.gpu.stream();
8221                let (px, _g0) = x.device_ptr(s);
8222                let (pw, _g1) = w.device_ptr(s);
8223                let (pq, _g2) = q.device_ptr_mut(s);
8224                let (pd, _g3) = d.device_ptr_mut(s);
8225                let mut ps = [
8226                    &px as *const _ as *mut std::ffi::c_void,
8227                    &pw as *const _ as *mut _,
8228                    &pq as *const _ as *mut _,
8229                    &pd as *const _ as *mut _,
8230                    &nc as *const _ as *mut _,
8231                    &e as *const _ as *mut _,
8232                ];
8233                unsafe {
8234                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8235                }
8236            }
8237            return Ok((q, d));
8238        }
8239        let f = self.func("rms_norm_q8_1");
8240        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8241        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8242        let cfg = LaunchConfig {
8243            grid_dim: (nrows as u32, 1, 1),
8244            block_dim: (1024, 1, 1),
8245            shared_mem_bytes: 0,
8246        };
8247        let __s_b = self.gpu.stream();
8248        let mut b = __s_b.launch_builder(&f);
8249        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8250        unsafe {
8251            b.launch(cfg)?;
8252        }
8253        Ok((q, d))
8254    }
8255
8256    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8257    /// PDL arm), caller-owned outputs.
8258    pub fn rms_norm_q8_1_into(
8259        &self,
8260        x: &CudaSlice<f32>,
8261        w: &CudaSlice<f32>,
8262        ncols: usize,
8263        nrows: usize,
8264        eps: f32,
8265        q: &mut CudaSlice<i8>,
8266        d: &mut CudaSlice<f32>,
8267    ) -> Result<(), Box<dyn std::error::Error>> {
8268        let nblk = ncols / 32;
8269        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8270        let (nc, e) = (ncols as i32, eps);
8271        if Self::pdl_on() {
8272            use cudarc::driver::{DevicePtr, DevicePtrMut};
8273            let s = &self.gpu.stream();
8274            let (px, _g0) = x.device_ptr(s);
8275            let (pw, _g1) = w.device_ptr(s);
8276            let (pq, _g2) = q.device_ptr_mut(s);
8277            let (pd, _g3) = d.device_ptr_mut(s);
8278            let mut ps = [
8279                &px as *const _ as *mut std::ffi::c_void,
8280                &pw as *const _ as *mut _,
8281                &pq as *const _ as *mut _,
8282                &pd as *const _ as *mut _,
8283                &nc as *const _ as *mut _,
8284                &e as *const _ as *mut _,
8285            ];
8286            unsafe {
8287                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8288            }
8289            return Ok(());
8290        }
8291        let f = self.func("rms_norm_q8_1");
8292        let cfg = LaunchConfig {
8293            grid_dim: (nrows as u32, 1, 1),
8294            block_dim: (1024, 1, 1),
8295            shared_mem_bytes: 0,
8296        };
8297        let __s_b = self.gpu.stream();
8298        let mut b = __s_b.launch_builder(&f);
8299        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8300        unsafe {
8301            b.launch(cfg)?;
8302        }
8303        Ok(())
8304    }
8305
8306    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8307    pub fn quantize_q8_1_into(
8308        &self,
8309        x: &CudaSlice<f32>,
8310        m: usize,
8311        in_f: usize,
8312        q: &mut CudaSlice<i8>,
8313        d: &mut CudaSlice<f32>,
8314    ) -> Result<(), Box<dyn std::error::Error>> {
8315        let nblk = in_f / 32;
8316        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8317        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8318        let (inf, mi) = (in_f as i32, m as i32);
8319        if Self::pdl_on() && Self::pdl_wb_on() {
8320            use cudarc::driver::{DevicePtr, DevicePtrMut};
8321            let s = &self.gpu.stream();
8322            let (px, _g0) = x.device_ptr(s);
8323            let (pq, _g1) = q.device_ptr_mut(s);
8324            let (pd, _g2) = d.device_ptr_mut(s);
8325            let mut ps = [
8326                &px as *const _ as *mut std::ffi::c_void,
8327                &pq as *const _ as *mut _,
8328                &pd as *const _ as *mut _,
8329                &inf as *const _ as *mut _,
8330                &mi as *const _ as *mut _,
8331            ];
8332            unsafe {
8333                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8334            }
8335            return Ok(());
8336        }
8337        let f = self.func("quantize_q8_1");
8338        let __s_b = self.gpu.stream();
8339        let mut b = __s_b.launch_builder(&f);
8340        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8341        unsafe {
8342            b.launch(cfg)?;
8343        }
8344        Ok(())
8345    }
8346
8347    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8348    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8349    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8350    pub fn add_rms_norm_q8_1(
8351        &self,
8352        a: &CudaSlice<f32>,
8353        b_in: &CudaSlice<f32>,
8354        w: &CudaSlice<f32>,
8355        res: &mut CudaSlice<f32>,
8356        ncols: usize,
8357        nrows: usize,
8358        eps: f32,
8359    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8360        let nblk = ncols / 32;
8361        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8362        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8363        let f = self.func("add_rms_norm_q8_1");
8364        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8365        let cfg = LaunchConfig {
8366            grid_dim: (nrows as u32, 1, 1),
8367            block_dim: (1024, 1, 1),
8368            shared_mem_bytes: 0,
8369        };
8370        let (nc, e) = (ncols as i32, eps);
8371        let __s_bld = self.gpu.stream();
8372        let mut bld = __s_bld.launch_builder(&f);
8373        bld.arg(a)
8374            .arg(b_in)
8375            .arg(w)
8376            .arg(res)
8377            .arg(&mut q)
8378            .arg(&mut d)
8379            .arg(&nc)
8380            .arg(&e);
8381        unsafe {
8382            bld.launch(cfg)?;
8383        }
8384        Ok((q, d))
8385    }
8386
8387    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8388    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8389    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8390    pub fn add_rms_norm(
8391        &self,
8392        a: &CudaSlice<f32>,
8393        b: &CudaSlice<f32>,
8394        w: &CudaSlice<f32>,
8395        res: &mut CudaSlice<f32>,
8396        dst: &mut CudaSlice<f32>,
8397        ncols: usize,
8398        nrows: usize,
8399        eps: f32,
8400    ) -> Result<(), Box<dyn std::error::Error>> {
8401        let (nc, e) = (ncols as i32, eps);
8402        if Self::pdl_on() && Self::pdl_wb_on() {
8403            use cudarc::driver::{DevicePtr, DevicePtrMut};
8404            let s = &self.gpu.stream();
8405            let (pa, _g0) = a.device_ptr(s);
8406            let (pb, _g1) = b.device_ptr(s);
8407            let (pw, _g2) = w.device_ptr(s);
8408            let (pr, _g3) = res.device_ptr_mut(s);
8409            let (pd, _g4) = dst.device_ptr_mut(s);
8410            let mut ps = [
8411                &pa as *const _ as *mut std::ffi::c_void,
8412                &pb as *const _ as *mut _,
8413                &pw as *const _ as *mut _,
8414                &pr as *const _ as *mut _,
8415                &pd as *const _ as *mut _,
8416                &nc as *const _ as *mut _,
8417                &e as *const _ as *mut _,
8418            ];
8419            unsafe {
8420                self.launch_pdl(
8421                    "add_rms_norm_f32",
8422                    (nrows as u32, 1, 1),
8423                    (rms_block(), 1, 1),
8424                    &mut ps,
8425                )?;
8426            }
8427            return Ok(());
8428        }
8429        let f = self.func("add_rms_norm_f32");
8430        let cfg = LaunchConfig {
8431            grid_dim: (nrows as u32, 1, 1),
8432            block_dim: (rms_block(), 1, 1),
8433            shared_mem_bytes: 0,
8434        };
8435        let __s_b2 = self.gpu.stream();
8436        let mut b2 = __s_b2.launch_builder(&f);
8437        b2.arg(a)
8438            .arg(b)
8439            .arg(w)
8440            .arg(&mut *res)
8441            .arg(&mut *dst)
8442            .arg(&nc)
8443            .arg(&e);
8444        unsafe {
8445            b2.launch(cfg)?;
8446        }
8447        Ok(())
8448    }
8449
8450    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8451    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8452    #[allow(clippy::too_many_arguments)]
8453    pub fn rms_pre_add_rms_norm(
8454        &self,
8455        a: &CudaSlice<f32>,
8456        wa: &CudaSlice<f32>,
8457        b: &CudaSlice<f32>,
8458        w: &CudaSlice<f32>,
8459        res: &mut CudaSlice<f32>,
8460        dst: &mut CudaSlice<f32>,
8461        ncols: usize,
8462        nrows: usize,
8463        eps: f32,
8464    ) -> Result<(), Box<dyn std::error::Error>> {
8465        let f = self.func("rms_pre_add_rms_norm_f32");
8466        let cfg = LaunchConfig {
8467            grid_dim: (nrows as u32, 1, 1),
8468            block_dim: (rms_block(), 1, 1),
8469            shared_mem_bytes: 0,
8470        };
8471        let (nc, e) = (ncols as i32, eps);
8472        let __s_b2 = self.gpu.stream();
8473        let mut b2 = __s_b2.launch_builder(&f);
8474        b2.arg(a)
8475            .arg(wa)
8476            .arg(b)
8477            .arg(w)
8478            .arg(&mut *res)
8479            .arg(&mut *dst)
8480            .arg(&nc)
8481            .arg(&e);
8482        unsafe {
8483            b2.launch(cfg)?;
8484        }
8485        Ok(())
8486    }
8487
8488    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8489    #[allow(clippy::too_many_arguments)]
8490    pub fn rms_pre_add_rms_norm_q8z(
8491        &self,
8492        a: &CudaSlice<f32>,
8493        wa: &CudaSlice<f32>,
8494        b: &CudaSlice<f32>,
8495        w: &CudaSlice<f32>,
8496        res: &mut CudaSlice<f32>,
8497        dst: &mut CudaSlice<f32>,
8498        ncols: usize,
8499        nrows: usize,
8500        eps: f32,
8501    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8502        debug_assert!(ncols % 128 == 0);
8503        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8504        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8505        let (nc, e) = (ncols as i32, eps);
8506        if Self::pdl_on() {
8507            {
8508                use cudarc::driver::{DevicePtr, DevicePtrMut};
8509                let s = &self.gpu.stream();
8510                let (pa, _g0) = a.device_ptr(s);
8511                let (pwa, _g1) = wa.device_ptr(s);
8512                let (pb, _g2) = b.device_ptr(s);
8513                let (pw, _g3) = w.device_ptr(s);
8514                let (pr, _g4) = res.device_ptr_mut(s);
8515                let (pdst, _g5) = dst.device_ptr_mut(s);
8516                let (pq, _g6) = out_q.device_ptr_mut(s);
8517                let (pd, _g7) = out_d.device_ptr_mut(s);
8518                let mut ps = [
8519                    &pa as *const _ as *mut std::ffi::c_void,
8520                    &pwa as *const _ as *mut _,
8521                    &pb as *const _ as *mut _,
8522                    &pw as *const _ as *mut _,
8523                    &pr as *const _ as *mut _,
8524                    &pdst as *const _ as *mut _,
8525                    &pq as *const _ as *mut _,
8526                    &pd as *const _ as *mut _,
8527                    &nc as *const _ as *mut _,
8528                    &e as *const _ as *mut _,
8529                ];
8530                unsafe {
8531                    self.launch_pdl(
8532                        "rms_pre_add_rms_norm_q8z_f32",
8533                        (nrows as u32, 1, 1),
8534                        (rms_block(), 1, 1),
8535                        &mut ps,
8536                    )?;
8537                }
8538            }
8539            return Ok((out_q, out_d));
8540        }
8541        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8542        let cfg = LaunchConfig {
8543            grid_dim: (nrows as u32, 1, 1),
8544            block_dim: (rms_block(), 1, 1),
8545            shared_mem_bytes: 0,
8546        };
8547        let __s_b2 = self.gpu.stream();
8548        let mut b2 = __s_b2.launch_builder(&f);
8549        b2.arg(a)
8550            .arg(wa)
8551            .arg(b)
8552            .arg(w)
8553            .arg(&mut *res)
8554            .arg(&mut *dst)
8555            .arg(&mut out_q)
8556            .arg(&mut out_d)
8557            .arg(&nc)
8558            .arg(&e);
8559        unsafe {
8560            b2.launch(cfg)?;
8561        }
8562        Ok((out_q, out_d))
8563    }
8564
8565    /// Slot-fed twin of `rms_pre_add_rms_norm_q8z` (gemma4 pn-fold, slotted/graph arm):
8566    /// identical kernel, caller-owned outputs, PLAIN launch only — the dc_slotted capture
8567    /// body must stay attribute-free (the fused2_into precedent).
8568    #[allow(clippy::too_many_arguments)]
8569    pub fn rms_pre_add_rms_norm_q8z_into(
8570        &self,
8571        a: &CudaSlice<f32>,
8572        wa: &CudaSlice<f32>,
8573        b: &CudaSlice<f32>,
8574        w: &CudaSlice<f32>,
8575        res: &mut CudaSlice<f32>,
8576        dst: &mut CudaSlice<f32>,
8577        ncols: usize,
8578        nrows: usize,
8579        eps: f32,
8580        out_q: &mut CudaSlice<i8>,
8581        out_d: &mut CudaSlice<f32>,
8582    ) -> Result<(), Box<dyn std::error::Error>> {
8583        debug_assert!(ncols % 128 == 0);
8584        let (nc, e) = (ncols as i32, eps);
8585        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8586        let cfg = LaunchConfig {
8587            grid_dim: (nrows as u32, 1, 1),
8588            block_dim: (rms_block(), 1, 1),
8589            shared_mem_bytes: 0,
8590        };
8591        let __s_b = self.gpu.stream();
8592        let mut b2 = __s_b.launch_builder(&f);
8593        b2.arg(a)
8594            .arg(wa)
8595            .arg(b)
8596            .arg(w)
8597            .arg(&mut *res)
8598            .arg(&mut *dst)
8599            .arg(&mut *out_q)
8600            .arg(&mut *out_d)
8601            .arg(&nc)
8602            .arg(&e);
8603        unsafe {
8604            b2.launch(cfg)?;
8605        }
8606        Ok(())
8607    }
8608
8609    /// Slot-fed twin of `rms_pre_add_scale_rms_norm_q8_1` (gemma4 pn-fold exit, slotted
8610    /// arm): identical kernel, caller-owned outputs, PLAIN launch (capture-safe).
8611    #[allow(clippy::too_many_arguments)]
8612    pub fn rms_pre_add_scale_rms_norm_q8_1_into(
8613        &self,
8614        a: &CudaSlice<f32>,
8615        wa: &CudaSlice<f32>,
8616        b_in: &CudaSlice<f32>,
8617        c: f32,
8618        w: &CudaSlice<f32>,
8619        res: &mut CudaSlice<f32>,
8620        ncols: usize,
8621        nrows: usize,
8622        eps: f32,
8623        out_q: &mut CudaSlice<i8>,
8624        out_d: &mut CudaSlice<f32>,
8625    ) -> Result<(), Box<dyn std::error::Error>> {
8626        debug_assert!(ncols % 128 == 0);
8627        let (nc, e2) = (ncols as i32, eps);
8628        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
8629        let cfg = LaunchConfig {
8630            grid_dim: (nrows as u32, 1, 1),
8631            block_dim: (rms_block(), 1, 1),
8632            shared_mem_bytes: 0,
8633        };
8634        let __s_b = self.gpu.stream();
8635        let mut b2 = __s_b.launch_builder(&f);
8636        b2.arg(a)
8637            .arg(wa)
8638            .arg(b_in)
8639            .arg(&c)
8640            .arg(w)
8641            .arg(&mut *res)
8642            .arg(&mut *out_q)
8643            .arg(&mut *out_d)
8644            .arg(&nc)
8645            .arg(&e2);
8646        unsafe {
8647            b2.launch(cfg)?;
8648        }
8649        Ok(())
8650    }
8651
8652    /// gemma4 pn-fold seam (GAP-DIAGNOSIS verdict 7, the E4B glue backport): the dense
8653    /// decode/verify/slotted trio folds post_attn_norm into the tail entry
8654    /// (rms_pre_add_rms_norm[_q8z]) and post_ffw_norm into the residual exit
8655    /// (rms_pre_add_scale_rms_norm_q8_1). BITS-CHANGING vs the two-launch chain (the
8656    /// single-phase reduction's expansion rounding — E4B receipts); every arm moves
8657    /// together so decode == verify == graph parity holds BY CONSTRUCTION within either
8658    /// seam value. MEMRA_G4_PNFOLD=0 restores the unfused chain everywhere.
8659    pub fn g4_pnfold_on() -> bool {
8660        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
8661        *ON.get_or_init(|| {
8662            std::env::var("MEMRA_G4_PNFOLD")
8663                .map(|v| v != "0")
8664                .unwrap_or(true)
8665        })
8666    }
8667
8668    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8669    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8670    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8671    pub fn build_q4_out_concat3(
8672        &self,
8673        w0: &crate::model::GpuTensor,
8674        w1: &crate::model::GpuTensor,
8675        w2: &crate::model::GpuTensor,
8676    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8677        use crate::model::GpuTensor;
8678        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8679            match w {
8680                GpuTensor::Quant {
8681                    qtype,
8682                    row_bytes,
8683                    rp,
8684                    ..
8685                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8686                _ => None,
8687            }
8688        };
8689        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8690        else {
8691            return Ok(None);
8692        };
8693        if rb0 != rb1
8694            || rb0 != rb2
8695            || w0.in_features() != w1.in_features()
8696            || w0.in_features() != w2.in_features()
8697        {
8698            return Ok(None);
8699        }
8700        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8701            match w {
8702                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8703                _ => unreachable!(),
8704            }
8705        }
8706        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8707        let total = rb0 * (o0 + o1 + o2);
8708        let mut cat = self.alloc_u8(total)?;
8709        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8710        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8711        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8712        Ok(Some(GpuTensor::Quant {
8713            bytes: cat,
8714            qtype: QT_Q4_0,
8715            row_bytes: rb0,
8716            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8717            scale: 1.0,
8718            rp: false,
8719            #[cfg(memra_cutlass)]
8720            cutlass: None,
8721            fp8: None,
8722            blk: None,
8723            rp4: None,
8724            f16: None,
8725        }))
8726    }
8727
8728    /// FULL-WIDTH-ROPE CONTRACT for the fused rms_norm+qkv+rope kernels
8729    /// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up).
8730    ///
8731    /// `rms_norm_qkv_rope_f32` / `_cat_f32` (`cu/kernels.cu`) and
8732    /// `rms_norm_qkv_rope_append_body` (`cu/flash_attn.cu`) take NO `n_dims`/`n_rot` argument.
8733    /// They compute `int half = ncols / 2` and rotate the FULL head width by construction — the
8734    /// standalone `rope_neox*` kernels take `n_dims` and early-return above it, these do not.
8735    ///
8736    /// Every call site today is a gemma-4 arm, where `n_rot == head_dim`, so nothing is wrong
8737    /// now. But this is the n_rot bug ONE FUSION away: fuse a partial-rotary arch onto these
8738    /// kernels (qwen3.5 = 64 rotary dims of a 256-wide head; step35 full-attn = 64 of 128) and
8739    /// 192 dims that must pass through unrotated get rotated silently — no error, no NaN, just a
8740    /// wrong model. The n_rot lane already paid for that class once, in the config derivation.
8741    ///
8742    /// So the fusions now take the layer's DERIVED rope width and refuse anything but full
8743    /// width. A future partial-rotary caller fails at its first launch with the geometry named
8744    /// instead of serving quietly wrong logits.
8745    fn full_width_rope_only(
8746        kernel: &str,
8747        n_rot: usize,
8748        head_dim: usize,
8749    ) -> Result<(), Box<dyn std::error::Error>> {
8750        if n_rot == head_dim {
8751            return Ok(());
8752        }
8753        Err(format!(
8754            "{kernel}: PARTIAL ROTARY REFUSED — n_rot {n_rot} != head_dim {head_dim}. This fused \
8755             rms_norm+qkv+rope kernel carries no n_dims parameter and rotates the full head \
8756             width (half = ncols/2), so it would rotate dims {n_rot}..{head_dim} that must pass \
8757             through unrotated. Use the split path (rms_norm_qkv + rope_neox/rope_neox2 with \
8758             n_dims={n_rot}), or add an n_dims early-return to the kernel and widen this guard."
8759        )
8760        .into())
8761    }
8762
8763    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8764    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8765    /// ([`Engine::full_width_rope_only`]).
8766    #[allow(clippy::too_many_arguments)]
8767    pub fn rms_norm_qkv_rope_cat(
8768        &self,
8769        qkv: &CudaSlice<f32>,
8770        wq: &CudaSlice<f32>,
8771        wk: &CudaSlice<f32>,
8772        wv: &CudaSlice<f32>,
8773        q: &mut CudaSlice<f32>,
8774        k: &mut CudaSlice<f32>,
8775        v: &mut CudaSlice<f32>,
8776        head_dim: usize,
8777        n_rot: usize,
8778        rq: usize,
8779        rk: usize,
8780        pos: &CudaSlice<i32>,
8781        nh_q: usize,
8782        nh_k: usize,
8783        base: f32,
8784        freq_scale: f32,
8785        ff: Option<&CudaSlice<f32>>,
8786        eps: f32,
8787    ) -> Result<(), Box<dyn std::error::Error>> {
8788        Self::full_width_rope_only("rms_norm_qkv_rope_cat", n_rot, head_dim)?;
8789        let rows = rq + rk + rk;
8790        let theta_scale = base.powf(-2.0 / head_dim as f32);
8791        let (nc, rqi, rki, nhq, nhk) = (
8792            head_dim as i32,
8793            rq as i32,
8794            rk as i32,
8795            nh_q as i32,
8796            nh_k as i32,
8797        );
8798        if Self::pdl_on() {
8799            use cudarc::driver::{DevicePtr, DevicePtrMut};
8800            let s = &self.gpu.stream();
8801            let (pqkv, _g0) = qkv.device_ptr(s);
8802            let (pwq, _g1) = wq.device_ptr(s);
8803            let (pwk, _g2) = wk.device_ptr(s);
8804            let (pwv, _g3) = wv.device_ptr(s);
8805            let (pq, _g4) = q.device_ptr_mut(s);
8806            let (pk, _g5) = k.device_ptr_mut(s);
8807            let (pv, _g6) = v.device_ptr_mut(s);
8808            let (ppos, _g7) = pos.device_ptr(s);
8809            let (pff, _g8) = match ff {
8810                Some(t) => {
8811                    let (p, g) = t.device_ptr(s);
8812                    (p, Some(g))
8813                }
8814                None => (0, None),
8815            };
8816            let mut ps = [
8817                &pqkv as *const _ as *mut std::ffi::c_void,
8818                &pwq as *const _ as *mut _,
8819                &pwk as *const _ as *mut _,
8820                &pwv as *const _ as *mut _,
8821                &pq as *const _ as *mut _,
8822                &pk as *const _ as *mut _,
8823                &pv as *const _ as *mut _,
8824                &nc as *const _ as *mut _,
8825                &rqi as *const _ as *mut _,
8826                &rki as *const _ as *mut _,
8827                &ppos as *const _ as *mut _,
8828                &nhq as *const _ as *mut _,
8829                &nhk as *const _ as *mut _,
8830                &theta_scale as *const _ as *mut _,
8831                &freq_scale as *const _ as *mut _,
8832                &pff as *const _ as *mut _,
8833                &eps as *const _ as *mut _,
8834            ];
8835            unsafe {
8836                self.launch_pdl(
8837                    "rms_norm_qkv_rope_cat_f32",
8838                    (rows as u32, 1, 1),
8839                    (rms_block(), 1, 1),
8840                    &mut ps,
8841                )?;
8842            }
8843            return Ok(());
8844        }
8845        let f = self.func("rms_norm_qkv_rope_cat_f32");
8846        let cfg = LaunchConfig {
8847            grid_dim: (rows as u32, 1, 1),
8848            block_dim: (rms_block(), 1, 1),
8849            shared_mem_bytes: 0,
8850        };
8851        let __s_b = self.gpu.stream();
8852        let mut b = __s_b.launch_builder(&f);
8853        match ff {
8854            Some(t) => {
8855                b.arg(qkv)
8856                    .arg(wq)
8857                    .arg(wk)
8858                    .arg(wv)
8859                    .arg(&mut *q)
8860                    .arg(&mut *k)
8861                    .arg(&mut *v)
8862                    .arg(&nc)
8863                    .arg(&rqi)
8864                    .arg(&rki)
8865                    .arg(pos)
8866                    .arg(&nhq)
8867                    .arg(&nhk)
8868                    .arg(&theta_scale)
8869                    .arg(&freq_scale)
8870                    .arg(t)
8871                    .arg(&eps);
8872                unsafe {
8873                    b.launch(cfg)?;
8874                }
8875            }
8876            None => {
8877                let null: u64 = 0;
8878                b.arg(qkv)
8879                    .arg(wq)
8880                    .arg(wk)
8881                    .arg(wv)
8882                    .arg(&mut *q)
8883                    .arg(&mut *k)
8884                    .arg(&mut *v)
8885                    .arg(&nc)
8886                    .arg(&rqi)
8887                    .arg(&rki)
8888                    .arg(pos)
8889                    .arg(&nhq)
8890                    .arg(&nhk)
8891                    .arg(&theta_scale)
8892                    .arg(&freq_scale)
8893                    .arg(&null)
8894                    .arg(&eps);
8895                unsafe {
8896                    b.launch(cfg)?;
8897                }
8898            }
8899        }
8900        Ok(())
8901    }
8902
8903    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8904    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
8905    /// ([`Engine::full_width_rope_only`]).
8906    #[allow(clippy::too_many_arguments)]
8907    pub fn rms_norm_qkv_rope(
8908        &self,
8909        q0: &CudaSlice<f32>,
8910        k0: &CudaSlice<f32>,
8911        v0: &CudaSlice<f32>,
8912        wq: &CudaSlice<f32>,
8913        wk: &CudaSlice<f32>,
8914        wv: &CudaSlice<f32>,
8915        q: &mut CudaSlice<f32>,
8916        k: &mut CudaSlice<f32>,
8917        v: &mut CudaSlice<f32>,
8918        head_dim: usize,
8919        n_rot: usize,
8920        rq: usize,
8921        rk: usize,
8922        pos: &CudaSlice<i32>,
8923        nh_q: usize,
8924        nh_k: usize,
8925        base: f32,
8926        freq_scale: f32,
8927        ff: Option<&CudaSlice<f32>>,
8928        eps: f32,
8929    ) -> Result<(), Box<dyn std::error::Error>> {
8930        Self::full_width_rope_only("rms_norm_qkv_rope", n_rot, head_dim)?;
8931        let f = self.func("rms_norm_qkv_rope_f32");
8932        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
8933        let cfg = LaunchConfig {
8934            grid_dim: (rows as u32, 1, 1),
8935            block_dim: (rms_block(), 1, 1),
8936            shared_mem_bytes: 0,
8937        };
8938        let theta_scale = base.powf(-2.0 / head_dim as f32);
8939        let (nc, rqi, rki, nhq, nhk) = (
8940            head_dim as i32,
8941            rq as i32,
8942            rk as i32,
8943            nh_q as i32,
8944            nh_k as i32,
8945        );
8946        let __s_b = self.gpu.stream();
8947        let mut b = __s_b.launch_builder(&f);
8948        match ff {
8949            Some(t) => {
8950                b.arg(q0)
8951                    .arg(k0)
8952                    .arg(v0)
8953                    .arg(wq)
8954                    .arg(wk)
8955                    .arg(wv)
8956                    .arg(&mut *q)
8957                    .arg(&mut *k)
8958                    .arg(&mut *v)
8959                    .arg(&nc)
8960                    .arg(&rqi)
8961                    .arg(&rki)
8962                    .arg(pos)
8963                    .arg(&nhq)
8964                    .arg(&nhk)
8965                    .arg(&theta_scale)
8966                    .arg(&freq_scale)
8967                    .arg(t)
8968                    .arg(&eps);
8969                unsafe {
8970                    b.launch(cfg)?;
8971                }
8972            }
8973            None => {
8974                let null: u64 = 0;
8975                b.arg(q0)
8976                    .arg(k0)
8977                    .arg(v0)
8978                    .arg(wq)
8979                    .arg(wk)
8980                    .arg(wv)
8981                    .arg(&mut *q)
8982                    .arg(&mut *k)
8983                    .arg(&mut *v)
8984                    .arg(&nc)
8985                    .arg(&rqi)
8986                    .arg(&rki)
8987                    .arg(pos)
8988                    .arg(&nhq)
8989                    .arg(&nhk)
8990                    .arg(&theta_scale)
8991                    .arg(&freq_scale)
8992                    .arg(&null)
8993                    .arg(&eps);
8994                unsafe {
8995                    b.launch(cfg)?;
8996                }
8997            }
8998        }
8999        Ok(())
9000    }
9001
9002    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
9003    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
9004    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
9005    /// `n_rot` is the layer's derived rotary width and MUST equal `head_dim`
9006    /// ([`Engine::full_width_rope_only`]).
9007    #[allow(clippy::too_many_arguments)]
9008    pub fn rms_norm_qkv_rope_append_dc(
9009        &self,
9010        q0: &CudaSlice<f32>,
9011        k0: &CudaSlice<f32>,
9012        v0: &CudaSlice<f32>,
9013        wq: &CudaSlice<f32>,
9014        wk: &CudaSlice<f32>,
9015        wv: &CudaSlice<f32>,
9016        q: &mut CudaSlice<f32>,
9017        k: &mut CudaSlice<f32>,
9018        v: &mut CudaSlice<f32>,
9019        head_dim: usize,
9020        n_rot: usize,
9021        rq: usize,
9022        rk: usize,
9023        pos: &CudaSlice<i32>,
9024        nh_q: usize,
9025        nh_k: usize,
9026        base: f32,
9027        freq_scale: f32,
9028        ff: Option<&CudaSlice<f32>>,
9029        eps: f32,
9030        kc: &mut CudaSlice<u8>,
9031        vc: &mut CudaSlice<u8>,
9032        t_dev: &CudaSlice<i32>,
9033        k_tok_bytes: usize,
9034        v_tok_bytes: usize,
9035        g: bool,
9036    ) -> Result<(), Box<dyn std::error::Error>> {
9037        Self::full_width_rope_only("rms_norm_qkv_rope_append_dc", n_rot, head_dim)?;
9038        let rows = rq + rk + rk;
9039        let theta_scale = base.powf(-2.0 / head_dim as f32);
9040        let (nc, rqi, rki, nhq, nhk) = (
9041            head_dim as i32,
9042            rq as i32,
9043            rk as i32,
9044            nh_q as i32,
9045            nh_k as i32,
9046        );
9047        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9048        if Self::pdl_on() && Self::pdl_wb_on() {
9049            use cudarc::driver::{DevicePtr, DevicePtrMut};
9050            let s = &self.gpu.stream();
9051            let (p0, _a0) = q0.device_ptr(s);
9052            let (p1, _a1) = k0.device_ptr(s);
9053            let (p2, _a2) = v0.device_ptr(s);
9054            let (pwq, _a3) = wq.device_ptr(s);
9055            let (pwk, _a4) = wk.device_ptr(s);
9056            let (pwv, _a5) = wv.device_ptr(s);
9057            let (pq, _a6) = q.device_ptr_mut(s);
9058            let (pk, _a7) = k.device_ptr_mut(s);
9059            let (pv, _a8) = v.device_ptr_mut(s);
9060            let (pp, _a9) = pos.device_ptr(s);
9061            let pff: u64 = match ff {
9062                Some(t) => {
9063                    let (p, _gg) = t.device_ptr(s);
9064                    p as u64
9065                }
9066                None => 0,
9067            };
9068            let (pkc, _a10) = kc.device_ptr_mut(s);
9069            let (pvc, _a11) = vc.device_ptr_mut(s);
9070            let (pt, _a12) = t_dev.device_ptr(s);
9071            let mut ps = [
9072                &p0 as *const _ as *mut std::ffi::c_void,
9073                &p1 as *const _ as *mut _,
9074                &p2 as *const _ as *mut _,
9075                &pwq as *const _ as *mut _,
9076                &pwk as *const _ as *mut _,
9077                &pwv as *const _ as *mut _,
9078                &pq as *const _ as *mut _,
9079                &pk as *const _ as *mut _,
9080                &pv as *const _ as *mut _,
9081                &nc as *const _ as *mut _,
9082                &rqi as *const _ as *mut _,
9083                &rki as *const _ as *mut _,
9084                &pp as *const _ as *mut _,
9085                &nhq as *const _ as *mut _,
9086                &nhk as *const _ as *mut _,
9087                &theta_scale as *const _ as *mut _,
9088                &freq_scale as *const _ as *mut _,
9089                &pff as *const _ as *mut _,
9090                &eps as *const _ as *mut _,
9091                &pkc as *const _ as *mut _,
9092                &pvc as *const _ as *mut _,
9093                &pt as *const _ as *mut _,
9094                &ktb as *const _ as *mut _,
9095                &vtb as *const _ as *mut _,
9096            ];
9097            unsafe {
9098                self.launch_pdl_flash(
9099                    g,
9100                    "rms_norm_qkv_rope_append_dc_f32",
9101                    (rows as u32, 1, 1),
9102                    (rms_block(), 1, 1),
9103                    0,
9104                    &mut ps,
9105                )?;
9106            }
9107            return Ok(());
9108        }
9109        let f = if g {
9110            self.func_g("rms_norm_qkv_rope_append_dc_f32")
9111        } else {
9112            self.func("rms_norm_qkv_rope_append_dc_f32")
9113        };
9114        let cfg = LaunchConfig {
9115            grid_dim: (rows as u32, 1, 1),
9116            block_dim: (rms_block(), 1, 1),
9117            shared_mem_bytes: 0,
9118        };
9119        let __s_b = self.gpu.stream();
9120        let mut b = __s_b.launch_builder(&f);
9121        match ff {
9122            Some(t) => {
9123                b.arg(q0)
9124                    .arg(k0)
9125                    .arg(v0)
9126                    .arg(wq)
9127                    .arg(wk)
9128                    .arg(wv)
9129                    .arg(&mut *q)
9130                    .arg(&mut *k)
9131                    .arg(&mut *v)
9132                    .arg(&nc)
9133                    .arg(&rqi)
9134                    .arg(&rki)
9135                    .arg(pos)
9136                    .arg(&nhq)
9137                    .arg(&nhk)
9138                    .arg(&theta_scale)
9139                    .arg(&freq_scale)
9140                    .arg(t)
9141                    .arg(&eps)
9142                    .arg(&mut *kc)
9143                    .arg(&mut *vc)
9144                    .arg(t_dev)
9145                    .arg(&ktb)
9146                    .arg(&vtb);
9147                unsafe {
9148                    b.launch(cfg)?;
9149                }
9150            }
9151            None => {
9152                let null: u64 = 0;
9153                b.arg(q0)
9154                    .arg(k0)
9155                    .arg(v0)
9156                    .arg(wq)
9157                    .arg(wk)
9158                    .arg(wv)
9159                    .arg(&mut *q)
9160                    .arg(&mut *k)
9161                    .arg(&mut *v)
9162                    .arg(&nc)
9163                    .arg(&rqi)
9164                    .arg(&rki)
9165                    .arg(pos)
9166                    .arg(&nhq)
9167                    .arg(&nhk)
9168                    .arg(&theta_scale)
9169                    .arg(&freq_scale)
9170                    .arg(&null)
9171                    .arg(&eps)
9172                    .arg(&mut *kc)
9173                    .arg(&mut *vc)
9174                    .arg(t_dev)
9175                    .arg(&ktb)
9176                    .arg(&vtb);
9177                unsafe {
9178                    b.launch(cfg)?;
9179                }
9180            }
9181        }
9182        Ok(())
9183    }
9184
9185    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
9186    /// Host-len twin of `rms_norm_qkv_rope_append_dc` (zoo-fusion arc): the eager decode
9187    /// arm tracks kv length on host (`kvl.len`), so the append slot rides the launch arg
9188    /// instead of the device counter. Kernel body is SHARED with the _dc entry (one
9189    /// inlined body — bit-identical to the rms_norm_qkv_rope + append pair it replaces,
9190    /// same law as the dc fold). `n_rot` is the layer's derived rotary width and MUST equal
9191    /// `head_dim` ([`Engine::full_width_rope_only`]).
9192    #[allow(clippy::too_many_arguments)]
9193    pub fn rms_norm_qkv_rope_append(
9194        &self,
9195        q0: &CudaSlice<f32>,
9196        k0: &CudaSlice<f32>,
9197        v0: &CudaSlice<f32>,
9198        wq: &CudaSlice<f32>,
9199        wk: &CudaSlice<f32>,
9200        wv: &CudaSlice<f32>,
9201        q: &mut CudaSlice<f32>,
9202        k: &mut CudaSlice<f32>,
9203        v: &mut CudaSlice<f32>,
9204        head_dim: usize,
9205        n_rot: usize,
9206        rq: usize,
9207        rk: usize,
9208        pos: &CudaSlice<i32>,
9209        nh_q: usize,
9210        nh_k: usize,
9211        base: f32,
9212        freq_scale: f32,
9213        ff: Option<&CudaSlice<f32>>,
9214        eps: f32,
9215        kc: &mut CudaSlice<u8>,
9216        vc: &mut CudaSlice<u8>,
9217        t: usize,
9218        k_tok_bytes: usize,
9219        v_tok_bytes: usize,
9220        g: bool,
9221    ) -> Result<(), Box<dyn std::error::Error>> {
9222        Self::full_width_rope_only("rms_norm_qkv_rope_append", n_rot, head_dim)?;
9223        let rows = rq + rk + rk;
9224        let theta_scale = base.powf(-2.0 / head_dim as f32);
9225        let (nc, rqi, rki, nhq, nhk) = (
9226            head_dim as i32,
9227            rq as i32,
9228            rk as i32,
9229            nh_q as i32,
9230            nh_k as i32,
9231        );
9232        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
9233        let ti = t as i32;
9234        if Self::pdl_on() && Self::pdl_wb_on() {
9235            use cudarc::driver::{DevicePtr, DevicePtrMut};
9236            let s = &self.gpu.stream();
9237            let (p0, _a0) = q0.device_ptr(s);
9238            let (p1, _a1) = k0.device_ptr(s);
9239            let (p2, _a2) = v0.device_ptr(s);
9240            let (pwq, _a3) = wq.device_ptr(s);
9241            let (pwk, _a4) = wk.device_ptr(s);
9242            let (pwv, _a5) = wv.device_ptr(s);
9243            let (pq, _a6) = q.device_ptr_mut(s);
9244            let (pk, _a7) = k.device_ptr_mut(s);
9245            let (pv, _a8) = v.device_ptr_mut(s);
9246            let (pp, _a9) = pos.device_ptr(s);
9247            let pff: u64 = match ff {
9248                Some(t) => {
9249                    let (p, _gg) = t.device_ptr(s);
9250                    p as u64
9251                }
9252                None => 0,
9253            };
9254            let (pkc, _a10) = kc.device_ptr_mut(s);
9255            let (pvc, _a11) = vc.device_ptr_mut(s);
9256            let mut ps = [
9257                &p0 as *const _ as *mut std::ffi::c_void,
9258                &p1 as *const _ as *mut _,
9259                &p2 as *const _ as *mut _,
9260                &pwq as *const _ as *mut _,
9261                &pwk as *const _ as *mut _,
9262                &pwv as *const _ as *mut _,
9263                &pq as *const _ as *mut _,
9264                &pk as *const _ as *mut _,
9265                &pv as *const _ as *mut _,
9266                &nc as *const _ as *mut _,
9267                &rqi as *const _ as *mut _,
9268                &rki as *const _ as *mut _,
9269                &pp as *const _ as *mut _,
9270                &nhq as *const _ as *mut _,
9271                &nhk as *const _ as *mut _,
9272                &theta_scale as *const _ as *mut _,
9273                &freq_scale as *const _ as *mut _,
9274                &pff as *const _ as *mut _,
9275                &eps as *const _ as *mut _,
9276                &pkc as *const _ as *mut _,
9277                &pvc as *const _ as *mut _,
9278                &ti as *const _ as *mut _,
9279                &ktb as *const _ as *mut _,
9280                &vtb as *const _ as *mut _,
9281            ];
9282            unsafe {
9283                self.launch_pdl_flash(
9284                    g,
9285                    "rms_norm_qkv_rope_append_f32",
9286                    (rows as u32, 1, 1),
9287                    (rms_block(), 1, 1),
9288                    0,
9289                    &mut ps,
9290                )?;
9291            }
9292            return Ok(());
9293        }
9294        let f = if g {
9295            self.func_g("rms_norm_qkv_rope_append_f32")
9296        } else {
9297            self.func("rms_norm_qkv_rope_append_f32")
9298        };
9299        let cfg = LaunchConfig {
9300            grid_dim: (rows as u32, 1, 1),
9301            block_dim: (rms_block(), 1, 1),
9302            shared_mem_bytes: 0,
9303        };
9304        let __s_b = self.gpu.stream();
9305        let mut b = __s_b.launch_builder(&f);
9306        let null: u64 = 0;
9307        b.arg(q0)
9308            .arg(k0)
9309            .arg(v0)
9310            .arg(wq)
9311            .arg(wk)
9312            .arg(wv)
9313            .arg(&mut *q)
9314            .arg(&mut *k)
9315            .arg(&mut *v)
9316            .arg(&nc)
9317            .arg(&rqi)
9318            .arg(&rki)
9319            .arg(pos)
9320            .arg(&nhq)
9321            .arg(&nhk)
9322            .arg(&theta_scale)
9323            .arg(&freq_scale);
9324        match ff {
9325            Some(t) => {
9326                b.arg(t);
9327            }
9328            None => {
9329                b.arg(&null);
9330            }
9331        }
9332        b.arg(&eps)
9333            .arg(&mut *kc)
9334            .arg(&mut *vc)
9335            .arg(&ti)
9336            .arg(&ktb)
9337            .arg(&vtb);
9338        unsafe {
9339            b.launch(cfg)?;
9340        }
9341        Ok(())
9342    }
9343
9344    pub fn add_q8_1(
9345        &self,
9346        a: &CudaSlice<f32>,
9347        b: &CudaSlice<f32>,
9348        res: &mut CudaSlice<f32>,
9349        ncols: usize,
9350        nrows: usize,
9351    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9352        debug_assert!(ncols % 128 == 0);
9353        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9354        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9355        let f = self.func("add_q8_1_f32");
9356        let cfg = LaunchConfig {
9357            grid_dim: (nrows as u32, 1, 1),
9358            block_dim: (rms_block(), 1, 1),
9359            shared_mem_bytes: 0,
9360        };
9361        let nc = ncols as i32;
9362        let __s_b2 = self.gpu.stream();
9363        let mut b2 = __s_b2.launch_builder(&f);
9364        b2.arg(a)
9365            .arg(b)
9366            .arg(&mut *res)
9367            .arg(&mut out_q)
9368            .arg(&mut out_d)
9369            .arg(&nc);
9370        unsafe {
9371            b2.launch(cfg)?;
9372        }
9373        Ok((out_q, out_d))
9374    }
9375
9376    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
9377    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
9378    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
9379    pub fn rms_pre_add_q8_1(
9380        &self,
9381        a: &CudaSlice<f32>,
9382        wa: &CudaSlice<f32>,
9383        b: &CudaSlice<f32>,
9384        res: &mut CudaSlice<f32>,
9385        ncols: usize,
9386        nrows: usize,
9387        eps: f32,
9388    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9389        debug_assert!(ncols % 128 == 0);
9390        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
9391        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
9392        let f = self.func("rms_pre_add_q8_1_f32");
9393        let cfg = LaunchConfig {
9394            grid_dim: (nrows as u32, 1, 1),
9395            block_dim: (rms_block(), 1, 1),
9396            shared_mem_bytes: 0,
9397        };
9398        let (nc, ep) = (ncols as i32, eps);
9399        let __s_b2 = self.gpu.stream();
9400        let mut b2 = __s_b2.launch_builder(&f);
9401        b2.arg(a)
9402            .arg(wa)
9403            .arg(b)
9404            .arg(&mut *res)
9405            .arg(&mut out_q)
9406            .arg(&mut out_d)
9407            .arg(&nc)
9408            .arg(&ep);
9409        unsafe {
9410            b2.launch(cfg)?;
9411        }
9412        Ok((out_q, out_d))
9413    }
9414
9415    /// L2 norm per row (head_dim), no weight.
9416    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
9417    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
9418    pub fn l2_v2_on(ncols: usize) -> bool {
9419        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
9420    }
9421
9422    pub fn l2_norm_pp(
9423        &self,
9424        x: &CudaSlice<f32>,
9425        dst: &mut CudaSlice<f32>,
9426        dst16: Option<&mut CudaSlice<u8>>,
9427        ncols: usize,
9428        nrows: usize,
9429        eps: f32,
9430    ) -> Result<(), Box<dyn std::error::Error>> {
9431        if Self::l2_v2_on(ncols) {
9432            let f = self.func("l2_norm_pp_v2_f32");
9433            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
9434            let cfg = LaunchConfig {
9435                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
9436                block_dim: (256, 1, 1),
9437                shared_mem_bytes: 0,
9438            };
9439            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9440            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
9441            let d16: u64 = match dst16 {
9442                Some(d) => self.addr_u8(d),
9443                None => 0,
9444            };
9445            let __s_b = self.gpu.stream();
9446            let mut b = __s_b.launch_builder(&f);
9447            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
9448            unsafe {
9449                b.launch(cfg)?;
9450            }
9451            return Ok(());
9452        }
9453        self.l2_norm(x, dst, ncols, nrows, eps)
9454    }
9455
9456    pub fn l2_norm(
9457        &self,
9458        x: &CudaSlice<f32>,
9459        dst: &mut CudaSlice<f32>,
9460        ncols: usize,
9461        nrows: usize,
9462        eps: f32,
9463    ) -> Result<(), Box<dyn std::error::Error>> {
9464        let f = self.func("l2_norm_f32");
9465        let cfg = LaunchConfig {
9466            grid_dim: (nrows as u32, 1, 1),
9467            block_dim: (256, 1, 1),
9468            shared_mem_bytes: 0,
9469        };
9470        let (nc, e) = (ncols as i32, eps);
9471        let __s_b = self.gpu.stream();
9472        let mut b = __s_b.launch_builder(&f);
9473        b.arg(x).arg(dst).arg(&nc).arg(&e);
9474        unsafe {
9475            b.launch(cfg)?;
9476        }
9477        Ok(())
9478    }
9479
9480    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9481    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9482    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9483    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9484    /// propagate through gdn_scan and flip argmax on marginal logits.
9485    pub fn l2_norm_decode(
9486        &self,
9487        x: &CudaSlice<f32>,
9488        dst: &mut CudaSlice<f32>,
9489        ncols: usize,
9490        nrows: usize,
9491        eps: f32,
9492    ) -> Result<(), Box<dyn std::error::Error>> {
9493        let f = self.func("l2_norm_f32");
9494        let cfg = LaunchConfig {
9495            grid_dim: (nrows as u32, 1, 1),
9496            block_dim: (32, 1, 1),
9497            shared_mem_bytes: 0,
9498        };
9499        let (nc, e) = (ncols as i32, eps);
9500        let __s_b = self.gpu.stream();
9501        let mut b = __s_b.launch_builder(&f);
9502        b.arg(x).arg(dst).arg(&nc).arg(&e);
9503        unsafe {
9504            b.launch(cfg)?;
9505        }
9506        Ok(())
9507    }
9508
9509    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9510    pub fn rope_neox(
9511        &self,
9512        x: &mut CudaSlice<f32>,
9513        pos: &CudaSlice<i32>,
9514        head_dim: usize,
9515        n_dims: usize,
9516        n_heads: usize,
9517        n_tokens: usize,
9518        freq_base: f32,
9519        freq_scale: f32,
9520    ) -> Result<(), Box<dyn std::error::Error>> {
9521        let f = self.func("rope_neox_f32");
9522        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9523        let grid = (n_heads * n_tokens) as u32;
9524        let cfg = LaunchConfig {
9525            grid_dim: (grid, 1, 1),
9526            block_dim: ((head_dim / 2) as u32, 1, 1),
9527            shared_mem_bytes: 0,
9528        };
9529        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9530        let __s_b = self.gpu.stream();
9531        let mut b = __s_b.launch_builder(&f);
9532        b.arg(x)
9533            .arg(pos)
9534            .arg(&hd)
9535            .arg(&nd)
9536            .arg(&nh)
9537            .arg(&theta_scale)
9538            .arg(&freq_scale);
9539        unsafe {
9540            b.launch(cfg)?;
9541        }
9542        Ok(())
9543    }
9544
9545    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9546    pub fn rope_neox_ff(
9547        &self,
9548        x: &mut CudaSlice<f32>,
9549        pos: &CudaSlice<i32>,
9550        head_dim: usize,
9551        n_dims: usize,
9552        n_heads: usize,
9553        n_tokens: usize,
9554        freq_base: f32,
9555        freq_scale: f32,
9556        ff: &CudaSlice<f32>,
9557    ) -> Result<(), Box<dyn std::error::Error>> {
9558        let f = self.func("rope_neox_ff_f32");
9559        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9560        let grid = (n_heads * n_tokens) as u32;
9561        let cfg = LaunchConfig {
9562            grid_dim: (grid, 1, 1),
9563            block_dim: ((head_dim / 2) as u32, 1, 1),
9564            shared_mem_bytes: 0,
9565        };
9566        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9567        let __s_b = self.gpu.stream();
9568        let mut b = __s_b.launch_builder(&f);
9569        b.arg(x)
9570            .arg(pos)
9571            .arg(&hd)
9572            .arg(&nd)
9573            .arg(&nh)
9574            .arg(&theta_scale)
9575            .arg(&freq_scale)
9576            .arg(ff);
9577        unsafe {
9578            b.launch(cfg)?;
9579        }
9580        Ok(())
9581    }
9582
9583    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9584    #[allow(clippy::too_many_arguments)]
9585    pub fn rope_neox2(
9586        &self,
9587        q: &mut CudaSlice<f32>,
9588        k: &mut CudaSlice<f32>,
9589        pos: &CudaSlice<i32>,
9590        head_dim: usize,
9591        n_dims: usize,
9592        nh_q: usize,
9593        nh_k: usize,
9594        n_tokens: usize,
9595        freq_base: f32,
9596        freq_scale: f32,
9597        ff: Option<&CudaSlice<f32>>,
9598    ) -> Result<(), Box<dyn std::error::Error>> {
9599        let f = self.func("rope_neox2_f32");
9600        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9601        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9602        let cfg = LaunchConfig {
9603            grid_dim: (grid, 1, 1),
9604            block_dim: ((head_dim / 2) as u32, 1, 1),
9605            shared_mem_bytes: 0,
9606        };
9607        let (hd, nd, nq, nk, nt) = (
9608            head_dim as i32,
9609            n_dims as i32,
9610            nh_q as i32,
9611            nh_k as i32,
9612            n_tokens as i32,
9613        );
9614        let __s_b = self.gpu.stream();
9615        let mut b = __s_b.launch_builder(&f);
9616        b.arg(q)
9617            .arg(k)
9618            .arg(pos)
9619            .arg(&hd)
9620            .arg(&nd)
9621            .arg(&nq)
9622            .arg(&nk)
9623            .arg(&nt)
9624            .arg(&theta_scale)
9625            .arg(&freq_scale);
9626        match ff {
9627            Some(ffv) => {
9628                b.arg(ffv);
9629                unsafe {
9630                    b.launch(cfg)?;
9631                }
9632            }
9633            None => {
9634                let null: u64 = 0;
9635                b.arg(&null);
9636                unsafe {
9637                    b.launch(cfg)?;
9638                }
9639            }
9640        }
9641        Ok(())
9642    }
9643
9644    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9645    pub fn gelu_tanh_mul(
9646        &self,
9647        gate: &CudaSlice<f32>,
9648        up: &CudaSlice<f32>,
9649        dst: &mut CudaSlice<f32>,
9650        n: usize,
9651    ) -> Result<(), Box<dyn std::error::Error>> {
9652        let f = self.func("gelu_tanh_mul_f32");
9653        let cfg = LaunchConfig::for_num_elems(n as u32);
9654        let ni = n as i32;
9655        let __s_b = self.gpu.stream();
9656        let mut b = __s_b.launch_builder(&f);
9657        b.arg(gate).arg(up).arg(dst).arg(&ni);
9658        unsafe {
9659            b.launch(cfg)?;
9660        }
9661        Ok(())
9662    }
9663
9664    pub fn silu_mul(
9665        &self,
9666        gate: &CudaSlice<f32>,
9667        up: &CudaSlice<f32>,
9668        dst: &mut CudaSlice<f32>,
9669        n: usize,
9670    ) -> Result<(), Box<dyn std::error::Error>> {
9671        let f = self.func("silu_mul_f32");
9672        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9673        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9674        let ni = n as i32;
9675        let __s_b = self.gpu.stream();
9676        let mut b = __s_b.launch_builder(&f);
9677        b.arg(gate).arg(up).arg(dst).arg(&ni);
9678        unsafe {
9679            b.launch(cfg)?;
9680        }
9681        Ok(())
9682    }
9683
9684    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9685    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9686    pub fn silu_mul_f16out(
9687        &self,
9688        gate: &CudaSlice<f32>,
9689        up: &CudaSlice<f32>,
9690        dst: &mut CudaSlice<f32>,
9691        dst16: &mut CudaSlice<u8>,
9692        n: usize,
9693    ) -> Result<(), Box<dyn std::error::Error>> {
9694        let f = self.func("silu_mul_f16out_f32");
9695        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9696        let ni = n as i32;
9697        let __s_b = self.gpu.stream();
9698        let mut b = __s_b.launch_builder(&f);
9699        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9700        unsafe {
9701            b.launch(cfg)?;
9702        }
9703        Ok(())
9704    }
9705
9706    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9707    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9708    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9709    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9710    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9711    /// launches per dense FFN layer (the gate+up post-matmul scales).
9712    pub fn silu_mul_scaled(
9713        &self,
9714        gate: &CudaSlice<f32>,
9715        up: &CudaSlice<f32>,
9716        gs: f32,
9717        us: f32,
9718        dst: &mut CudaSlice<f32>,
9719        n: usize,
9720    ) -> Result<(), Box<dyn std::error::Error>> {
9721        let f = self.func("silu_mul_scaled_f32");
9722        let cfg = LaunchConfig::for_num_elems(n as u32);
9723        let ni = n as i32;
9724        let (gsf, usf) = (gs, us);
9725        let __s_b = self.gpu.stream();
9726        let mut b = __s_b.launch_builder(&f);
9727        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9728        unsafe {
9729            b.launch(cfg)?;
9730        }
9731        Ok(())
9732    }
9733
9734    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9735    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9736    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9737    #[allow(clippy::too_many_arguments)]
9738    pub fn swigluoai_mul_scaled(
9739        &self,
9740        gate: &CudaSlice<f32>,
9741        up: &CudaSlice<f32>,
9742        gs: f32,
9743        us: f32,
9744        alpha: f32,
9745        limit: f32,
9746        dst: &mut CudaSlice<f32>,
9747        n: usize,
9748    ) -> Result<(), Box<dyn std::error::Error>> {
9749        let f = self.func("swigluoai_mul_scaled_f32");
9750        let cfg = LaunchConfig::for_num_elems(n as u32);
9751        let ni = n as i32;
9752        let __s_b = self.gpu.stream();
9753        let mut b = __s_b.launch_builder(&f);
9754        b.arg(gate)
9755            .arg(up)
9756            .arg(&gs)
9757            .arg(&us)
9758            .arg(&alpha)
9759            .arg(&limit)
9760            .arg(dst)
9761            .arg(&ni);
9762        unsafe {
9763            b.launch(cfg)?;
9764        }
9765        Ok(())
9766    }
9767
9768    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9769    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9770    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9771    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9772    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9773    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9774    /// n must be a multiple of 32 (n_ff always is).
9775    pub fn silu_mul_scaled_q8_1(
9776        &self,
9777        gate: &CudaSlice<f32>,
9778        up: &CudaSlice<f32>,
9779        gs: f32,
9780        us: f32,
9781        n: usize,
9782    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9783        let f = self.func("silu_mul_scaled_q8_1");
9784        let nblk = n / 32;
9785        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9786        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9787        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9788        let cfg = LaunchConfig::for_num_elems(n as u32);
9789        let (gsf, usf, ni) = (gs, us, n as i32);
9790        let __s_b = self.gpu.stream();
9791        let mut b = __s_b.launch_builder(&f);
9792        b.arg(gate)
9793            .arg(up)
9794            .arg(&gsf)
9795            .arg(&usf)
9796            .arg(&mut aq)
9797            .arg(&mut ad)
9798            .arg(&ni);
9799        unsafe {
9800            b.launch(cfg)?;
9801        }
9802        Ok((aq, ad))
9803    }
9804
9805    pub fn add(
9806        &self,
9807        a: &CudaSlice<f32>,
9808        b_in: &CudaSlice<f32>,
9809        dst: &mut CudaSlice<f32>,
9810        n: usize,
9811    ) -> Result<(), Box<dyn std::error::Error>> {
9812        let f = self.func("add_f32");
9813        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9814        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9815        let ni = n as i32;
9816        let __s_bld = self.gpu.stream();
9817        let mut bld = __s_bld.launch_builder(&f);
9818        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9819        unsafe {
9820            bld.launch(cfg)?;
9821        }
9822        Ok(())
9823    }
9824
9825    pub fn mul(
9826        &self,
9827        a: &CudaSlice<f32>,
9828        b_in: &CudaSlice<f32>,
9829        dst: &mut CudaSlice<f32>,
9830        n: usize,
9831    ) -> Result<(), Box<dyn std::error::Error>> {
9832        let f = self.func("mul_f32");
9833        let cfg = LaunchConfig::for_num_elems(n as u32);
9834        let ni = n as i32;
9835        let __s_bld = self.gpu.stream();
9836        let mut bld = __s_bld.launch_builder(&f);
9837        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9838        unsafe {
9839            bld.launch(cfg)?;
9840        }
9841        Ok(())
9842    }
9843
9844    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9845    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9846    pub fn matmul(
9847        &self,
9848        w: &crate::model::GpuTensor,
9849        x: &CudaSlice<f32>,
9850        m: usize,
9851    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9852        use crate::model::GpuTensor;
9853        let in_f = w.in_features();
9854        let out_f = w.out_features();
9855        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9856        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9857        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9858        // gives nothing). Quantize the activation once here then call the GEMM.
9859        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9860        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9861        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9862        #[allow(non_snake_case)]
9863        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9864        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9865        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9866            usize::MAX
9867        } else {
9868            16usize
9869        };
9870
9871        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9872        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9873        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9874        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9875        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9876        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9877        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9878        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9879        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9880        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9881        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9882        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9883        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9884        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9885        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9886        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9887        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9888        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9889        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9890        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9891        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9892        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9893        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9894        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9895        if m >= GEMM_M_THRESHOLD {
9896            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9897                return Ok(y);
9898            }
9899            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9900            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9901            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9902            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9903            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9904            // tile defaults differently by operand source.
9905            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9906                return Ok(y);
9907            }
9908            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
9909            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
9910            if let Some(y) = self.try_f16_gemm(w, x, m)? {
9911                return Ok(y);
9912            }
9913        }
9914        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
9915        // m threshold the rest of this method uses:
9916        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
9917        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
9918        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
9919        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
9920        //     across every tier by construction with no batched twin needed.
9921        //
9922        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
9923        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
9924        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
9925        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
9926        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
9927        // arms is what makes sure it never gets there.
9928        if let GpuTensor::Quant { qtype, .. } = w {
9929            if *qtype == QT_F8_E4M3_BLK {
9930                if m >= GEMM_M_THRESHOLD {
9931                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
9932                        return Ok(y);
9933                    }
9934                }
9935                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9936                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
9937                    return Ok(y);
9938                }
9939            }
9940        }
9941        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
9942            return self.qmatvec_mmq(w, x, m);
9943        }
9944        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
9945            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9946            return self.qmatvec_gemm(w, &aq, &ad, m);
9947        }
9948        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
9949        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
9950        if m >= GEMM_M_THRESHOLD {
9951            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
9952                return Ok(y);
9953            }
9954        }
9955        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
9956        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
9957        // to Stage-A f32-dequant (the correctness oracle path).
9958        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
9959        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
9960        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
9961        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
9962        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
9963        if m == 1 && fast {
9964            if let GpuTensor::Quant {
9965                bytes,
9966                qtype,
9967                row_bytes,
9968                rp,
9969                rp4,
9970                scale,
9971                ..
9972            } = w
9973            {
9974                if self.mmvq_supports(*qtype) {
9975                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
9976                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
9977                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
9978                    let (bytes, rp) = match rp4 {
9979                        Some(m4) => (m4, true),
9980                        None => (bytes, *rp),
9981                    };
9982                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9983                    return self.qmatvec_mmvq(
9984                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
9985                    );
9986                }
9987            }
9988        }
9989        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
9990        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
9991        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
9992        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
9993        // block below. MEMRA_NO_BATCHED -> per-m path.
9994        //
9995        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
9996        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
9997        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
9998        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
9999        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
10000        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
10001        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
10002        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
10003        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
10004        if (2..=16).contains(&m)
10005            && fast
10006            && std::env::var("MEMRA_NO_BATCHED").is_err()
10007            && (m <= 4 || Self::b8_enabled())
10008        {
10009            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
10010            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
10011            // is present (rp4) — the mirror pick below then routes to the _rp family.
10012            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
10013            // because the native e4m3 row layout is already aligned and needs no mirror.
10014            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
10015            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
10016            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
10017            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
10018            let m_ok = m <= 8
10019                || matches!(w, GpuTensor::Quant { qtype, .. }
10020                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
10021                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
10022            if m_ok {
10023                if let GpuTensor::Quant {
10024                    bytes,
10025                    qtype,
10026                    row_bytes,
10027                    rp,
10028                    rp4,
10029                    ..
10030                } = w
10031                {
10032                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
10033                        let (bytes, rp) = match rp4 {
10034                            Some(m4) => (m4, true),
10035                            None => (bytes, *rp),
10036                        };
10037                        let mcols = Self::batched_mcols(m);
10038                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10039                        let mut y = self.qmatvec_mmvq_batched(
10040                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
10041                        )?;
10042                        if let GpuTensor::Quant { scale, .. } = w {
10043                            if *scale != 1.0 {
10044                                self.scale_inplace(&mut y, *scale, m * out_f)?;
10045                            }
10046                        }
10047                        return Ok(y);
10048                    }
10049                }
10050            }
10051        }
10052        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
10053        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
10054        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
10055        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
10056        // for this dtype, so the generic match below must never see it under `fast`.
10057        if fast {
10058            if let GpuTensor::Quant {
10059                bytes,
10060                qtype,
10061                row_bytes,
10062                scale,
10063                ..
10064            } = w
10065            {
10066                if *qtype == QT_F8_E4M3 {
10067                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10068                    return self.qmatvec_mmvq(
10069                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
10070                    );
10071                }
10072            }
10073        }
10074        let mut y = match w {
10075            GpuTensor::Quant {
10076                bytes,
10077                qtype,
10078                row_bytes,
10079                ..
10080            } if fast && *qtype == QT_Q8_0 => {
10081                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10082            }
10083            GpuTensor::Quant {
10084                bytes,
10085                qtype,
10086                row_bytes,
10087                ..
10088            } if fast && *qtype == QT_Q4_K => {
10089                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10090            }
10091            GpuTensor::Quant {
10092                bytes,
10093                qtype,
10094                row_bytes,
10095                ..
10096            } if fast && *qtype == QT_Q6_K => {
10097                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10098            }
10099            GpuTensor::Quant {
10100                bytes,
10101                qtype,
10102                row_bytes,
10103                ..
10104            } if fast && *qtype == QT_Q5_K => {
10105                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10106            }
10107            GpuTensor::Quant {
10108                bytes,
10109                qtype,
10110                row_bytes,
10111                ..
10112            } if fast && *qtype == QT_Q3_K => {
10113                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10114            }
10115            GpuTensor::Quant {
10116                bytes,
10117                qtype,
10118                row_bytes,
10119                rp,
10120                ..
10121            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
10122                if *rp {
10123                    "qmatvec_nvfp4_dp4a_rp"
10124                } else {
10125                    "qmatvec_nvfp4_dp4a"
10126                },
10127                bytes,
10128                x,
10129                m,
10130                in_f,
10131                out_f,
10132                *row_bytes,
10133            )?,
10134            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
10135            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
10136            // anomaly (research/kat-anomaly-20260802/).
10137            GpuTensor::Quant {
10138                bytes,
10139                qtype,
10140                row_bytes,
10141                ..
10142            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
10143                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
10144            }
10145            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
10146            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
10147            // without first writing the matching kernel, or func() will panic
10148            // "kernel ... not in any fatbin".
10149            GpuTensor::Quant {
10150                bytes,
10151                qtype,
10152                row_bytes,
10153                rp,
10154                ..
10155            } =>
10156            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
10157            // deq(row,j) form cannot address the planes; same value/product order).
10158            {
10159                self.qmatvec(
10160                    bytes,
10161                    x,
10162                    m,
10163                    in_f,
10164                    out_f,
10165                    if *rp && *qtype == QT_NVFP4 {
10166                        QT_NVFP4_RP
10167                    } else {
10168                        *qtype
10169                    },
10170                    *row_bytes,
10171                )?
10172            }
10173            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
10174            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
10175            // cuBLASLt f32 GEMV as the Float arm.
10176            GpuTensor::FloatBf16 { data, .. } => {
10177                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
10178            }
10179        };
10180        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
10181        if let GpuTensor::Quant { scale, .. } = w {
10182            if *scale != 1.0 {
10183                self.scale_inplace(&mut y, *scale, m * out_f)?;
10184            }
10185        }
10186        Ok(y)
10187    }
10188
10189    /// True when `MEMRA_FAST=0`, i.e. the Stage-A f32 oracle is the requested arithmetic and every
10190    /// `matmul_pre` call will take the raw-f32 escape rather than the q8_1 pair.
10191    ///
10192    /// WHY THIS EXISTS AS ITS OWN PREDICATE: `uses_q8_1_fast` needs a weight, but the callers that
10193    /// have to DECIDE WHETHER TO MATERIALIZE an f32 activation sit one level above any weight — the
10194    /// gemma-4 decode trunk emits a q8_1 pair per layer and hands it down, so the f32 has to be
10195    /// produced (or not) by the loop that owns the residual. Cached in a OnceLock like every other
10196    /// `*_on()` flag here: this is read once per layer per token on the decode path, and the daily
10197    /// path must not pay an env lookup for a flag that is off.
10198    pub fn stage_a_raw_needed() -> bool {
10199        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10200        *ON.get_or_init(|| std::env::var("MEMRA_FAST").as_deref() == Ok("0"))
10201    }
10202
10203    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
10204    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
10205    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
10206        use crate::model::GpuTensor;
10207        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
10208            return false;
10209        }
10210        match w {
10211            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
10212            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
10213            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
10214            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
10215            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
10216            // block class has no fused twin yet, so each of its projections takes its own launch.
10217            GpuTensor::Quant { qtype, .. } => {
10218                matches!(
10219                    *qtype,
10220                    QT_Q8_0
10221                        | QT_Q4_K
10222                        | QT_Q6_K
10223                        | QT_Q5_K
10224                        | QT_Q3_K
10225                        | QT_NVFP4
10226                        | QT_F8_E4M3
10227                        | QT_F8_E4M3_BLK
10228                        | QT_Q4_0
10229                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
10230            }
10231            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
10232        }
10233    }
10234
10235    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
10236    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
10237    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
10238    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
10239    pub fn matmul_pre(
10240        &self,
10241        w: &crate::model::GpuTensor,
10242        aq: &CudaSlice<i8>,
10243        ad: &CudaSlice<f32>,
10244        x_fallback: &CudaSlice<f32>,
10245        m: usize,
10246    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10247        use crate::model::GpuTensor;
10248        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
10249        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
10250        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
10251        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
10252        // rc=30013 dig, 2026-07-31).
10253        let x_raw_ok = x_fallback.len() >= m * w.in_features();
10254        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
10255        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
10256        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10257            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
10258                return Ok(y);
10259            }
10260            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
10261            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
10262            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
10263                return Ok(y);
10264            }
10265            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
10266            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
10267                return Ok(y);
10268            }
10269        }
10270        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
10271        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
10272        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
10273        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
10274        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
10275        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10276            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
10277                return Ok(y);
10278            }
10279        }
10280        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10281            return Ok(y);
10282        }
10283        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
10284        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
10285        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
10286        // aq/ad.
10287        if m >= 16
10288            && w.out_features() >= 128
10289            && self.mmq_supports(w)
10290            && !self.verify_exact_on()
10291            && x_raw_ok
10292        {
10293            return self.qmatvec_mmq(w, x_fallback, m);
10294        }
10295        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
10296        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
10297        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
10298            if let Some(y) =
10299                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
10300            {
10301                return Ok(y);
10302            }
10303        }
10304        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
10305        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
10306        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
10307            return self.qmatvec_gemm(w, aq, ad, m);
10308        }
10309        // THE SEVENTH ARM. Every raw-f32 arm above is guarded by `x_raw_ok`; this one — the
10310        // Stage-A / Float escape — was not, and it is the ONLY one `MEMRA_FAST=0` opens. So the
10311        // 2026-07-31 E4B rc=30013 fix (add the length guard, keep empty-fallback callers off the
10312        // raw-f32 arms) protected the six arms the FAST path can reach and missed the one the
10313        // ORACLE path reaches. Consequence before this guard: the gemma-4 decode arms, which pass
10314        // `e.zeros(0)` because their trunk emits only a q8_1 pair and never materializes an f32
10315        // attn-normed activation, fell through to Stage-A `qmatvec_f32` (cu/qmatvec.cu:5442),
10316        // which reads `m * in_f` floats out of a 0-byte allocation ->
10317        // CUDA_ERROR_ILLEGAL_ADDRESS at layer 0 of the first decode token. The fault is STICKY:
10318        // it poisons the context, so every LATER request in that process fails with an unrelated
10319        // message ("cache alloc failed: ...") and the true cause appears exactly once, in the
10320        // first failure. That is what made `MEMRA_FAST=0` — the reference named in
10321        // `tools/argmax-margin-gate.sh`'s own header — return an opaque HTTP 500 on the gemma
10322        // dense artifact and left the arm with no working truth instrument.
10323        //
10324        // Refuse loudly instead of reading out of bounds. A named error at the true call site is
10325        // strictly better than an illegal address surfacing later at an unrelated sync point, and
10326        // an oracle that cannot run must say so rather than corrupt the context it runs in.
10327        if !self.uses_q8_1_fast(w) {
10328            if !x_raw_ok {
10329                return Err(format!(
10330                    "matmul_pre: q8_1-fast is off for this weight but x_fallback holds {} f32 \
10331                     (need m*in_f = {}*{} = {}). This call site pre-quantized its activation and \
10332                     dropped the f32, so there is nothing to fall back to — pass the real f32 \
10333                     activation (see Engine::rms_norm_decode, which is bit-identical to \
10334                     rms_norm_q8_1's reduction) or keep the weight on the q8_1 path.",
10335                    x_fallback.len(),
10336                    m,
10337                    w.in_features(),
10338                    m * w.in_features()
10339                )
10340                .into());
10341            }
10342            return self.matmul(w, x_fallback, m);
10343        }
10344        let in_f = w.in_features();
10345        let out_f = w.out_features();
10346        let (bytes, qtype, row_bytes, scale, rp) = match w {
10347            GpuTensor::Quant {
10348                bytes,
10349                qtype,
10350                row_bytes,
10351                scale,
10352                rp,
10353                ..
10354            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10355            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
10356        };
10357        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
10358        // the dp4a/oracle tails below keep the raw GGUF bytes.
10359        let (mbytes, mrp) = match w {
10360            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10361            _ => (bytes, rp),
10362        };
10363        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
10364        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
10365        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
10366        if m == 1 && self.mmvq_supports(qtype) {
10367            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
10368        }
10369        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
10370        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
10371        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
10372        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
10373        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
10374        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
10375        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
10376        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
10377        // m=5..8 on the old per-m path (b8-tier-only seam).
10378        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
10379        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
10380        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
10381        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10382            && std::env::var("MEMRA_NO_BATCHED").is_err()
10383            && (m <= 4 || Self::b8_enabled())
10384            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
10385            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
10386            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
10387            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
10388                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
10389        {
10390            let mcols = Self::batched_mcols(m);
10391            return self.qmatvec_mmvq_batched(
10392                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
10393            );
10394        }
10395        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
10396        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
10397        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
10398        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
10399        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
10400        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
10401            let (b2, r2) = if qtype == QT_Q4_0 {
10402                (mbytes, mrp)
10403            } else {
10404                (bytes, rp)
10405            };
10406            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
10407        }
10408        let name = match qtype {
10409            QT_Q8_0 => "qmatvec_q8_0_dp4a",
10410            QT_Q4_K => "qmatvec_q4_K_dp4a",
10411            QT_Q6_K => "qmatvec_q6_K_dp4a",
10412            QT_Q5_K => "qmatvec_q5_K_dp4a",
10413            QT_Q3_K => "qmatvec_q3_K_dp4a",
10414            QT_NVFP4 => {
10415                if rp {
10416                    "qmatvec_nvfp4_dp4a_rp"
10417                } else {
10418                    "qmatvec_nvfp4_dp4a"
10419                }
10420            }
10421            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
10422            _ => unreachable!(),
10423        };
10424        let f = self.func(name);
10425        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
10426        let cfg = LaunchConfig {
10427            grid_dim: (out_f as u32, m as u32, 1),
10428            block_dim: (128, 1, 1),
10429            shared_mem_bytes: 0,
10430        };
10431        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10432        let __s_b = self.gpu.stream();
10433        let mut b = __s_b.launch_builder(&f);
10434        b.arg(bytes)
10435            .arg(aq)
10436            .arg(ad)
10437            .arg(&mut y)
10438            .arg(&inf)
10439            .arg(&outf)
10440            .arg(&mi)
10441            .arg(&rb);
10442        unsafe {
10443            b.launch(cfg)?;
10444        }
10445        if scale != 1.0 {
10446            self.scale_inplace(&mut y, scale, m * out_f)?;
10447        }
10448        Ok(y)
10449    }
10450
10451    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
10452    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
10453    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
10454    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
10455    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
10456    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
10457    /// reduce as m=1); this method just forces that path unconditionally.
10458    pub fn matmul_decode_exact(
10459        &self,
10460        w: &crate::model::GpuTensor,
10461        x: &CudaSlice<f32>,
10462        m: usize,
10463    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10464        use crate::model::GpuTensor;
10465        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
10466        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
10467        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
10468        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
10469        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
10470        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
10471        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
10472        if let GpuTensor::Float { data, .. } = w {
10473            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
10474        }
10475        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
10476        // float linear (same n-independent reduction contract as the Float arm above).
10477        if let GpuTensor::FloatBf16 { data, .. } = w {
10478            let (in_f, out_f) = (w.in_features(), w.out_features());
10479            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
10480        }
10481        if !self.uses_q8_1_fast(w) {
10482            return self.matmul(w, x, m);
10483        }
10484        let in_f = w.in_features();
10485        let out_f = w.out_features();
10486        let (bytes, qtype, row_bytes, scale, rp) = match w {
10487            GpuTensor::Quant {
10488                bytes,
10489                qtype,
10490                row_bytes,
10491                scale,
10492                rp,
10493                ..
10494            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10495            _ => return self.matmul(w, x, m),
10496        };
10497        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
10498        // which does its own mirror pick).
10499        let (bytes, rp) = match w {
10500            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10501            _ => (bytes, rp),
10502        };
10503        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10504        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10505        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10506        // (token,row) by construction, which is exactly what this method exists to guarantee.
10507        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10508            return Ok(y);
10509        }
10510        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10511        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10512        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10513        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10514        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10515        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10516        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10517        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10518        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10519            && std::env::var("MEMRA_NO_BATCHED").is_err()
10520            && (m <= 4 || Self::b8_enabled())
10521            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10522            // no mirror precondition, `rp` selects the layout only.
10523            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10524                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10525        {
10526            let mcols = Self::batched_mcols(m);
10527            return self.qmatvec_mmvq_batched(
10528                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10529            );
10530        }
10531        if self.mmvq_supports(qtype) {
10532            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10533            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10534            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10535        }
10536        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10537        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10538        self.matmul_pre(w, &aq, &ad, x, m)
10539    }
10540
10541    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10542    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10543    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10544    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10545    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10546    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10547    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10548    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10549    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10550    pub fn matmul_decode_exact_pre(
10551        &self,
10552        w: &crate::model::GpuTensor,
10553        aq: &CudaSlice<i8>,
10554        ad: &CudaSlice<f32>,
10555        m: usize,
10556    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10557        use crate::model::GpuTensor;
10558        debug_assert!(
10559            self.uses_q8_1_fast(w),
10560            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10561        );
10562        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10563        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10564            return Ok(y);
10565        }
10566        let in_f = w.in_features();
10567        let out_f = w.out_features();
10568        let (bytes, qtype, row_bytes, scale, rp) = match w {
10569            GpuTensor::Quant {
10570                bytes,
10571                qtype,
10572                row_bytes,
10573                scale,
10574                rp,
10575                ..
10576            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10577            _ => {
10578                return Err(
10579                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10580                );
10581            }
10582        };
10583        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10584        let (bytes, rp) = match w {
10585            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10586            _ => (bytes, rp),
10587        };
10588        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10589        if (2..=16).contains(&m)
10590            && self.batched_supports(qtype)
10591            && self.mmvq_supports(qtype)
10592            && std::env::var("MEMRA_NO_BATCHED").is_err()
10593            && (m <= 4 || Self::b8_enabled())
10594            && (m <= 8
10595                || qtype == QT_Q4_0
10596                || qtype == QT_Q6_K
10597                || qtype == QT_F8_E4M3
10598                || qtype == QT_NVFP4
10599                || qtype == QT_Q4_K
10600                || qtype == QT_Q5_K
10601                || qtype == QT_Q8_0)
10602        {
10603            let mcols = Self::batched_mcols(m);
10604            return self.qmatvec_mmvq_batched(
10605                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10606            );
10607        }
10608        if self.mmvq_supports(qtype) {
10609            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10610        }
10611        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10612        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10613        let x0 = self.zeros(0)?;
10614        self.matmul_pre(w, aq, ad, &x0, m)
10615    }
10616
10617    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10618    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10619    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10620    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10621    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10622    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10623    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10624    /// per-tensor path.
10625    pub fn matmul_decode_exact_dual_pre(
10626        &self,
10627        w0: &crate::model::GpuTensor,
10628        w1: &crate::model::GpuTensor,
10629        aq: &CudaSlice<i8>,
10630        ad: &CudaSlice<f32>,
10631        m: usize,
10632    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10633    {
10634        use crate::model::GpuTensor;
10635        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10636        let on = *ON.get_or_init(|| {
10637            std::env::var("MEMRA_SPEC_DUAL_T")
10638                .map(|v| v != "0")
10639                .unwrap_or(true)
10640        });
10641        if !on
10642            || !(2..=7).contains(&m)
10643            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10644            || !self.uses_q8_1_fast(w0)
10645            || !self.uses_q8_1_fast(w1)
10646        {
10647            return Ok(None);
10648        }
10649        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10650        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10651        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10652        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10653        if !self.mmvq_supports(QT_NVFP4) {
10654            return Ok(None);
10655        }
10656        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10657        if w1.in_features() != in_f || w1.out_features() != out_f {
10658            return Ok(None);
10659        }
10660        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10661            (
10662                GpuTensor::Quant {
10663                    bytes: b0,
10664                    qtype: q0,
10665                    row_bytes: rb0,
10666                    scale: s0,
10667                    rp: rp0,
10668                    rp4: None,
10669                    ..
10670                },
10671                GpuTensor::Quant {
10672                    bytes: b1,
10673                    qtype: q1,
10674                    row_bytes: rb1,
10675                    scale: s1,
10676                    rp: rp1,
10677                    rp4: None,
10678                    ..
10679                },
10680            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10681                (b0, b1, *rb0, *s0, *s1, *rp0)
10682            }
10683            _ => return Ok(None),
10684        };
10685        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10686        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10687        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10688        {
10689            return Ok(None);
10690        }
10691        let (y0, y1) =
10692            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10693        Ok(Some(((y0, s0), (y1, s1))))
10694    }
10695
10696    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10697    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10698    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10699    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10700    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10701    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10702    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10703    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10704    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10705    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10706    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10707    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10708    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10709    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10710    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10711    pub fn matmul_decode_exact_dual(
10712        &self,
10713        w0: &crate::model::GpuTensor,
10714        w1: &crate::model::GpuTensor,
10715        x: &CudaSlice<f32>,
10716        m: usize,
10717    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10718        use crate::model::GpuTensor;
10719        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10720        let on = *ON.get_or_init(|| {
10721            std::env::var("MEMRA_SPEC_DUAL_T")
10722                .map(|v| v != "0")
10723                .unwrap_or(true)
10724        });
10725        if !on
10726            || !(2..=4).contains(&m)
10727            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10728            || !self.uses_q8_1_fast(w0)
10729            || !self.uses_q8_1_fast(w1)
10730        {
10731            return Ok(None);
10732        }
10733        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10734        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10735        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10736        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10737        if !self.mmvq_supports(QT_NVFP4) {
10738            return Ok(None);
10739        }
10740        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10741        if w1.in_features() != in_f || w1.out_features() != out_f {
10742            return Ok(None);
10743        }
10744        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10745            (
10746                GpuTensor::Quant {
10747                    bytes: b0,
10748                    qtype: q0,
10749                    row_bytes: rb0,
10750                    scale: s0,
10751                    rp: rp0,
10752                    rp4: None,
10753                    ..
10754                },
10755                GpuTensor::Quant {
10756                    bytes: b1,
10757                    qtype: q1,
10758                    row_bytes: rb1,
10759                    scale: s1,
10760                    rp: rp1,
10761                    rp4: None,
10762                    ..
10763                },
10764            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10765                (b0, b1, *rb0, *s0, *s1, *rp0)
10766            }
10767            _ => return Ok(None),
10768        };
10769        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10770        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10771        if std::env::var("MEMRA_DEBUG").is_ok() {
10772            static ONCE: std::sync::Once = std::sync::Once::new();
10773            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10774        }
10775        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10776        let (y0, y1) =
10777            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10778        let mut y0 = y0;
10779        let mut y1 = y1;
10780        if s0 != 1.0 {
10781            self.scale_inplace(&mut y0, s0, m * out_f)?;
10782        }
10783        if s1 != 1.0 {
10784            self.scale_inplace(&mut y1, s1, m * out_f)?;
10785        }
10786        Ok(Some((y0, y1)))
10787    }
10788
10789    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10790    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10791    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10792    /// twins (both buffers must be the repacked layout).
10793    #[allow(clippy::too_many_arguments)]
10794    pub fn qmatvec_batched_dual_raw(
10795        &self,
10796        b0: &CudaSlice<u8>,
10797        b1: &CudaSlice<u8>,
10798        aq: &CudaSlice<i8>,
10799        ad: &CudaSlice<f32>,
10800        m: usize,
10801        in_f: usize,
10802        out_f: usize,
10803        row_bytes: usize,
10804        rp: bool,
10805    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10806        const ROWS_PER_BLOCK: u32 = 4;
10807        let mcols = Self::batched_mcols(m);
10808        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10809        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10810        let tiny_rp1 = rp
10811            && mcols == 4
10812            && out_f <= 128
10813            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10814        let (name, rows_per_block) = if tiny_rp1 {
10815            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10816        } else {
10817            match (mcols, rp, m) {
10818                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10819                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10820                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10821                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10822                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10823                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10824                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10825                _ => {
10826                    return Err(
10827                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10828                    );
10829                }
10830            }
10831        };
10832        let f = self.func(name);
10833        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10834        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10835        let cfg = LaunchConfig {
10836            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10837            block_dim: (32, ROWS_PER_BLOCK, 1),
10838            shared_mem_bytes: 0,
10839        };
10840        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10841        let __s_b = self.gpu.stream();
10842        let mut b = __s_b.launch_builder(&f);
10843        b.arg(b0)
10844            .arg(b1)
10845            .arg(aq)
10846            .arg(ad)
10847            .arg(&mut y0)
10848            .arg(&mut y1)
10849            .arg(&inf)
10850            .arg(&outf)
10851            .arg(&mi)
10852            .arg(&rb);
10853        unsafe {
10854            b.launch(cfg)?;
10855        }
10856        Ok((y0, y1))
10857    }
10858
10859    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10860    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10861    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10862    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10863    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10864    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10865    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10866    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10867    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10868    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10869    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10870    pub fn matmul_pre_dual_noscale(
10871        &self,
10872        w0: &crate::model::GpuTensor,
10873        w1: &crate::model::GpuTensor,
10874        aq: &CudaSlice<i8>,
10875        ad: &CudaSlice<f32>,
10876        m: usize,
10877    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10878    {
10879        use crate::model::GpuTensor;
10880        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10881            return Ok(None);
10882        }
10883        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10884        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10885        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10886        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10887        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10888        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10889        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10890        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10891        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10892        if !self.mmvq_supports(QT_NVFP4) {
10893            return Ok(None);
10894        }
10895        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10896        if w1.in_features() != in_f || w1.out_features() != out_f {
10897            return Ok(None);
10898        }
10899        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10900        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10901        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10902        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10903        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10904        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10905        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10906        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10907        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
10908        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
10909        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
10910        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
10911        let no_mirror =
10912            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
10913        if self.q8_ffn_fuse2_on()
10914            && no_mirror(w0)
10915            && no_mirror(w1)
10916            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
10917        {
10918            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
10919            return Ok(Some(((y0, 1.0), (y1, 1.0))));
10920        }
10921        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
10922        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
10923        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
10924        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
10925        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
10926        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
10927        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
10928        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
10929        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
10930        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10931            let (y0, y1) =
10932                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
10933            return Ok(Some(((y0, p0.3), (y1, p1.3))));
10934        }
10935        let (b0, q0, rb0, s0, rp0) = match w0 {
10936            GpuTensor::Quant {
10937                bytes,
10938                qtype,
10939                row_bytes,
10940                scale,
10941                rp,
10942                ..
10943            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10944            _ => return Ok(None),
10945        };
10946        let (b1, q1, rb1, s1, rp1) = match w1 {
10947            GpuTensor::Quant {
10948                bytes,
10949                qtype,
10950                row_bytes,
10951                scale,
10952                rp,
10953                ..
10954            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10955            _ => return Ok(None),
10956        };
10957        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
10958            return Ok(None);
10959        }
10960        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10961        const RPW: u32 = 2;
10962        let rows_per_block = ROWS_PER_BLOCK * RPW;
10963        let f = self.func(if rp0 {
10964            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
10965        } else {
10966            "qmatvec_nvfp4_mmvq_dual_mr2"
10967        });
10968        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
10969        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
10970        let cfg = LaunchConfig {
10971            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10972            block_dim: (32, ROWS_PER_BLOCK, 1),
10973            shared_mem_bytes: 0,
10974        };
10975        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
10976        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
10977        // yscale args stay 1.0 here (they exist for the single-tensor callers).
10978        let one = 1.0f32;
10979        let __s_b = self.gpu.stream();
10980        let mut b = __s_b.launch_builder(&f);
10981        b.arg(b0)
10982            .arg(b1)
10983            .arg(aq)
10984            .arg(ad)
10985            .arg(&mut y0)
10986            .arg(&mut y1)
10987            .arg(&inf)
10988            .arg(&outf)
10989            .arg(&mi)
10990            .arg(&rb)
10991            .arg(&one)
10992            .arg(&one);
10993        unsafe {
10994            b.launch(cfg)?;
10995        }
10996        Ok(Some(((y0, s0), (y1, s1))))
10997    }
10998
10999    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
11000    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
11001    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
11002    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
11003    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
11004    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
11005    /// back to the three singles.
11006    #[allow(clippy::too_many_arguments)]
11007    pub fn matmul_nvfp4_fused3(
11008        &self,
11009        w0: &crate::model::GpuTensor,
11010        w1: &crate::model::GpuTensor,
11011        w2: &crate::model::GpuTensor,
11012        aq: &CudaSlice<i8>,
11013        ad: &CudaSlice<f32>,
11014        m: usize,
11015    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11016    {
11017        use crate::model::GpuTensor;
11018        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11019        // read serves all m rows); the fused segments would re-read the weight per row. The
11020        // fusion win is the B=1 decode tick.
11021        if m != 1
11022            || !self.mmvq_supports(QT_NVFP4)
11023            || !self.uses_q8_1_fast(w0)
11024            || !self.uses_q8_1_fast(w1)
11025            || !self.uses_q8_1_fast(w2)
11026        {
11027            return Ok(None);
11028        }
11029        let unpack = |w: &crate::model::GpuTensor| match w {
11030            GpuTensor::Quant {
11031                bytes,
11032                qtype,
11033                scale,
11034                rp,
11035                ..
11036            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11037            _ => None,
11038        };
11039        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
11040            return Ok(None);
11041        };
11042        let in_f = w0.in_features();
11043        if w1.in_features() != in_f || w2.in_features() != in_f {
11044            return Ok(None);
11045        }
11046        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
11047        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11048        const RPW: u32 = 2;
11049        let rows_pb = ROWS_PER_BLOCK * RPW;
11050        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11051        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
11052        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11053        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11054        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11055        let cfg = LaunchConfig {
11056            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
11057            block_dim: (32, ROWS_PER_BLOCK, 1),
11058            shared_mem_bytes: 0,
11059        };
11060        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
11061        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11062        // only dereferenced for the launch-arg build inside this call.
11063        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
11064        let __s_b = self.gpu.stream();
11065        let mut b = __s_b.launch_builder(&f);
11066        b.arg(b0)
11067            .arg(b1)
11068            .arg(b2)
11069            .arg(aq)
11070            .arg(ad)
11071            .arg(&mut y0)
11072            .arg(&mut y1)
11073            .arg(&mut y2)
11074            .arg(&inf)
11075            .arg(&oi0)
11076            .arg(&oi1)
11077            .arg(&oi2)
11078            .arg(&mi)
11079            .arg(&p0.1)
11080            .arg(&p1.1)
11081            .arg(&p2.1);
11082        unsafe {
11083            b.launch(cfg)?;
11084        }
11085        Ok(Some((y0, y1, y2)))
11086    }
11087
11088    /// fused2 twin of `matmul_nvfp4_fused3`, for MIXED-type weight groups: the gemma4
11089    /// dense NVFP4mix recipe keeps `attn_v` and `ffn_down` at Q8_0 (full-NVFP4 was
11090    /// measured garbage on this dense class), so its q/k/v trio and gate/up/down never
11091    /// satisfy an all-NVFP4 fused3 — the pairs that ARE uniformly NVFP4 (q,k and
11092    /// gate,up) fuse here instead. m==1 only, same law as fused3/fused4: per
11093    /// (tensor,row) the kernel seg body is VERBATIM, so the fusion is bit-identical to
11094    /// two separate launches. `MEMRA_NVFP4_FUSED2=0` is the rollback seam and the
11095    /// same-binary interleaved A/B arm.
11096    pub fn matmul_nvfp4_fused2(
11097        &self,
11098        w0: &crate::model::GpuTensor,
11099        w1: &crate::model::GpuTensor,
11100        aq: &CudaSlice<i8>,
11101        ad: &CudaSlice<f32>,
11102        m: usize,
11103    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11104        use crate::model::GpuTensor;
11105        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11106        let off =
11107            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11108        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
11109        // read serves all m rows); the fused segments would re-read the weight per row.
11110        if off
11111            || m != 1
11112            || !self.mmvq_supports(QT_NVFP4)
11113            || !self.uses_q8_1_fast(w0)
11114            || !self.uses_q8_1_fast(w1)
11115        {
11116            return Ok(None);
11117        }
11118        let unpack = |w: &crate::model::GpuTensor| match w {
11119            GpuTensor::Quant {
11120                bytes,
11121                qtype,
11122                scale,
11123                rp,
11124                ..
11125            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11126            _ => None,
11127        };
11128        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11129            return Ok(None);
11130        };
11131        let in_f = w0.in_features();
11132        if w1.in_features() != in_f {
11133            return Ok(None);
11134        }
11135        let (o0, o1) = (w0.out_features(), w1.out_features());
11136        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11137        const RPW: u32 = 2;
11138        let rows_pb = ROWS_PER_BLOCK * RPW;
11139        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11140        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11141        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11142        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11143        let cfg = LaunchConfig {
11144            grid_dim: (nb(o0) + nb(o1), m as u32, 1),
11145            block_dim: (32, ROWS_PER_BLOCK, 1),
11146            shared_mem_bytes: 0,
11147        };
11148        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, m as i32);
11149        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11150        // only dereferenced for the launch-arg build inside this call.
11151        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11152        // PDL wave-B: the kernel carries MEMRA_PDL_ENTRY — launch overlaps the producer's
11153        // drain (quantize/norm emitting aq/ad). Same math, same order, bit-identical.
11154        if Self::pdl_on() && Self::pdl_mmvq_on() && Self::pdl_nvfp4q8_on() {
11155            {
11156                use cudarc::driver::{DevicePtr, DevicePtrMut};
11157                let s = &self.gpu.stream();
11158                let (pw0, _g0) = b0.device_ptr(s);
11159                let (pw1, _g1) = b1.device_ptr(s);
11160                let (paq, _g2) = aq.device_ptr(s);
11161                let (pad, _g3) = ad.device_ptr(s);
11162                let (py0, _g4) = y0.device_ptr_mut(s);
11163                let (py1, _g5) = y1.device_ptr_mut(s);
11164                let (s0, s1) = (p0.1, p1.1);
11165                let mut ps = [
11166                    &pw0 as *const _ as *mut std::ffi::c_void,
11167                    &pw1 as *const _ as *mut _,
11168                    &paq as *const _ as *mut _,
11169                    &pad as *const _ as *mut _,
11170                    &py0 as *const _ as *mut _,
11171                    &py1 as *const _ as *mut _,
11172                    &inf as *const _ as *mut _,
11173                    &oi0 as *const _ as *mut _,
11174                    &oi1 as *const _ as *mut _,
11175                    &mi as *const _ as *mut _,
11176                    &s0 as *const _ as *mut _,
11177                    &s1 as *const _ as *mut _,
11178                ];
11179                unsafe {
11180                    self.launch_pdl(
11181                        "qmatvec_nvfp4_mmvq_fused2_rp",
11182                        cfg.grid_dim,
11183                        cfg.block_dim,
11184                        &mut ps,
11185                    )?;
11186                }
11187            }
11188            return Ok(Some((y0, y1)));
11189        }
11190        let __s_b = self.gpu.stream();
11191        let mut b = __s_b.launch_builder(&f);
11192        b.arg(b0)
11193            .arg(b1)
11194            .arg(aq)
11195            .arg(ad)
11196            .arg(&mut y0)
11197            .arg(&mut y1)
11198            .arg(&inf)
11199            .arg(&oi0)
11200            .arg(&oi1)
11201            .arg(&mi)
11202            .arg(&p0.1)
11203            .arg(&p1.1);
11204        unsafe {
11205            b.launch(cfg)?;
11206        }
11207        Ok(Some((y0, y1)))
11208    }
11209
11210    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch to
11211    /// `matmul_nvfp4_fused2`, caller-owned outputs — the gemma4 dc_slotted graph body
11212    /// needs zero mem nodes, so the allocating wrapper can't serve it. Returns false
11213    /// (decline) on any non-NVFP4/rp pair; the caller chains or refuses.
11214    pub fn matmul_nvfp4_fused2_into(
11215        &self,
11216        w0: &crate::model::GpuTensor,
11217        w1: &crate::model::GpuTensor,
11218        aq: &CudaSlice<i8>,
11219        ad: &CudaSlice<f32>,
11220        y0: &mut CudaSlice<f32>,
11221        y1: &mut CudaSlice<f32>,
11222    ) -> Result<bool, Box<dyn std::error::Error>> {
11223        use crate::model::GpuTensor;
11224        static FUSED2_OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11225        let off =
11226            *FUSED2_OFF.get_or_init(|| std::env::var("MEMRA_NVFP4_FUSED2").as_deref() == Ok("0"));
11227        if off
11228            || !self.mmvq_supports(QT_NVFP4)
11229            || !self.uses_q8_1_fast(w0)
11230            || !self.uses_q8_1_fast(w1)
11231        {
11232            return Ok(false);
11233        }
11234        let unpack = |w: &crate::model::GpuTensor| match w {
11235            GpuTensor::Quant {
11236                bytes,
11237                qtype,
11238                scale,
11239                rp,
11240                ..
11241            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11242            _ => None,
11243        };
11244        let (Some(p0), Some(p1)) = (unpack(w0), unpack(w1)) else {
11245            return Ok(false);
11246        };
11247        let in_f = w0.in_features();
11248        if w1.in_features() != in_f {
11249            return Ok(false);
11250        }
11251        let (o0, o1) = (w0.out_features(), w1.out_features());
11252        if y0.len() < o0 || y1.len() < o1 {
11253            return Ok(false);
11254        }
11255        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11256        const RPW: u32 = 2;
11257        let rows_pb = ROWS_PER_BLOCK * RPW;
11258        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11259        let f = self.func("qmatvec_nvfp4_mmvq_fused2_rp");
11260        let cfg = LaunchConfig {
11261            grid_dim: (nb(o0) + nb(o1), 1, 1),
11262            block_dim: (32, ROWS_PER_BLOCK, 1),
11263            shared_mem_bytes: 0,
11264        };
11265        let (inf, oi0, oi1, mi) = (in_f as i32, o0 as i32, o1 as i32, 1i32);
11266        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11267        // only dereferenced for the launch-arg build inside this call.
11268        let (b0, b1) = unsafe { (&*p0.0, &*p1.0) };
11269        let __s_b = self.gpu.stream();
11270        let mut b = __s_b.launch_builder(&f);
11271        b.arg(b0)
11272            .arg(b1)
11273            .arg(aq)
11274            .arg(ad)
11275            .arg(&mut *y0)
11276            .arg(&mut *y1)
11277            .arg(&inf)
11278            .arg(&oi0)
11279            .arg(&oi1)
11280            .arg(&mi)
11281            .arg(&p0.1)
11282            .arg(&p1.1);
11283        unsafe {
11284            b.launch(cfg)?;
11285        }
11286        Ok(true)
11287    }
11288
11289    /// fused4 twin of `matmul_nvfp4_fused3`: the Linear-mixer projection quartet
11290    /// (wqkv + wqkv_gate + ssm_beta + ssm_alpha) in one launch, m==1 only. Per
11291    /// (tensor,row) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM — bit-identical
11292    /// to four separate launches (rig-native decode increment 2, RIG-NATIVE-DECODE.md).
11293    #[allow(clippy::type_complexity)]
11294    pub fn matmul_nvfp4_fused4(
11295        &self,
11296        w0: &crate::model::GpuTensor,
11297        w1: &crate::model::GpuTensor,
11298        w2: &crate::model::GpuTensor,
11299        w3: &crate::model::GpuTensor,
11300        aq: &CudaSlice<i8>,
11301        ad: &CudaSlice<f32>,
11302        m: usize,
11303    ) -> Result<
11304        Option<(
11305            CudaSlice<f32>,
11306            CudaSlice<f32>,
11307            CudaSlice<f32>,
11308            CudaSlice<f32>,
11309        )>,
11310        Box<dyn std::error::Error>,
11311    > {
11312        use crate::model::GpuTensor;
11313        // MEMRA_NVFP4_FUSED4=0: rollback seam + the same-binary interleaved A/B arm.
11314        if m != 1
11315            || std::env::var("MEMRA_NVFP4_FUSED4").as_deref() == Ok("0")
11316            || !self.mmvq_supports(QT_NVFP4)
11317            || !self.uses_q8_1_fast(w0)
11318            || !self.uses_q8_1_fast(w1)
11319            || !self.uses_q8_1_fast(w2)
11320            || !self.uses_q8_1_fast(w3)
11321        {
11322            return Ok(None);
11323        }
11324        let unpack = |w: &crate::model::GpuTensor| match w {
11325            GpuTensor::Quant {
11326                bytes,
11327                qtype,
11328                scale,
11329                rp,
11330                ..
11331            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
11332            _ => None,
11333        };
11334        let (Some(p0), Some(p1), Some(p2), Some(p3)) =
11335            (unpack(w0), unpack(w1), unpack(w2), unpack(w3))
11336        else {
11337            return Ok(None);
11338        };
11339        let in_f = w0.in_features();
11340        if w1.in_features() != in_f || w2.in_features() != in_f || w3.in_features() != in_f {
11341            return Ok(None);
11342        }
11343        let (o0, o1, o2, o3) = (
11344            w0.out_features(),
11345            w1.out_features(),
11346            w2.out_features(),
11347            w3.out_features(),
11348        );
11349        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
11350        const RPW: u32 = 2;
11351        let rows_pb = ROWS_PER_BLOCK * RPW;
11352        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
11353        let f = self.func("qmatvec_nvfp4_mmvq_fused4_rp");
11354        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11355        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11356        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11357        let mut y3 = self.alloc_uninit::<f32>(m * o3)?;
11358        let cfg = LaunchConfig {
11359            grid_dim: (nb(o0) + nb(o1) + nb(o2) + nb(o3), m as u32, 1),
11360            block_dim: (32, ROWS_PER_BLOCK, 1),
11361            shared_mem_bytes: 0,
11362        };
11363        let (inf, oi0, oi1, oi2, oi3, mi) = (
11364            in_f as i32,
11365            o0 as i32,
11366            o1 as i32,
11367            o2 as i32,
11368            o3 as i32,
11369            m as i32,
11370        );
11371        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
11372        // only dereferenced for the launch-arg build inside this call.
11373        let (b0, b1, b2, b3) = unsafe { (&*p0.0, &*p1.0, &*p2.0, &*p3.0) };
11374        let __s_b = self.gpu.stream();
11375        let mut b = __s_b.launch_builder(&f);
11376        b.arg(b0)
11377            .arg(b1)
11378            .arg(b2)
11379            .arg(b3)
11380            .arg(aq)
11381            .arg(ad)
11382            .arg(&mut y0)
11383            .arg(&mut y1)
11384            .arg(&mut y2)
11385            .arg(&mut y3)
11386            .arg(&inf)
11387            .arg(&oi0)
11388            .arg(&oi1)
11389            .arg(&oi2)
11390            .arg(&oi3)
11391            .arg(&mi)
11392            .arg(&p0.1)
11393            .arg(&p1.1)
11394            .arg(&p2.1)
11395            .arg(&p3.1);
11396        unsafe {
11397            b.launch(cfg)?;
11398        }
11399        Ok(Some((y0, y1, y2, y3)))
11400    }
11401
11402    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
11403    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
11404    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
11405    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
11406    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
11407    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
11408    /// back to the per-tensor path.
11409    pub fn matmul_q8_fused2(
11410        &self,
11411        w0: &crate::model::GpuTensor,
11412        w1: &crate::model::GpuTensor,
11413        aq: &CudaSlice<i8>,
11414        ad: &CudaSlice<f32>,
11415    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11416        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
11417        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
11418        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
11419        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
11420        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
11421        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11422            return Ok(Some(self.e4m3_fused2_core(
11423                p0.0,
11424                p1.0,
11425                aq,
11426                ad,
11427                w0.in_features(),
11428                p0.1,
11429                p1.1,
11430                p0.2,
11431                p0.3,
11432                p1.3,
11433            )?));
11434        }
11435        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11436            return Ok(None);
11437        };
11438        Ok(Some(self.q8_fused2_core(
11439            p0.0,
11440            p1.0,
11441            aq,
11442            ad,
11443            w0.in_features(),
11444            p0.1,
11445            p1.1,
11446            p0.2,
11447        )?))
11448    }
11449
11450    #[allow(clippy::too_many_arguments)]
11451    fn q8_fused2_core(
11452        &self,
11453        b0: &CudaSlice<u8>,
11454        b1: &CudaSlice<u8>,
11455        aq: &CudaSlice<i8>,
11456        ad: &CudaSlice<f32>,
11457        in_f: usize,
11458        out0: usize,
11459        out1: usize,
11460        row_bytes: usize,
11461    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11462        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11463        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11464        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11465        let f = self.func("qmatvec_q8_0_mmvq_fused2");
11466        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11467        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11468        let cfg = LaunchConfig {
11469            grid_dim: (nb0 + nb1, 1, 1),
11470            block_dim: (32, ROWS_PER_BLOCK, 1),
11471            shared_mem_bytes: 0,
11472        };
11473        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
11474        let __s_b = self.gpu.stream();
11475        let mut b = __s_b.launch_builder(&f);
11476        b.arg(b0)
11477            .arg(b1)
11478            .arg(aq)
11479            .arg(ad)
11480            .arg(&mut y0)
11481            .arg(&mut y1)
11482            .arg(&inf)
11483            .arg(&o0)
11484            .arg(&o1)
11485            .arg(&rbl);
11486        unsafe {
11487            b.launch(cfg)?;
11488        }
11489        Ok((y0, y1))
11490    }
11491
11492    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
11493    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
11494    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
11495    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
11496    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
11497    pub fn matmul_q8_fused2_x(
11498        &self,
11499        w0: &crate::model::GpuTensor,
11500        w1: &crate::model::GpuTensor,
11501        x: &CudaSlice<f32>,
11502    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11503        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
11504            return Ok(None);
11505        }
11506        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11507            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11508            return Ok(Some(self.e4m3_fused2_core(
11509                p0.0,
11510                p1.0,
11511                &aq,
11512                &ad,
11513                w0.in_features(),
11514                p0.1,
11515                p1.1,
11516                p0.2,
11517                p0.3,
11518                p1.3,
11519            )?));
11520        }
11521        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11522            return Ok(None);
11523        };
11524        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
11525        Ok(Some(self.q8_fused2_core(
11526            p0.0,
11527            p1.0,
11528            &aq,
11529            &ad,
11530            w0.in_features(),
11531            p0.1,
11532            p1.1,
11533            p0.2,
11534        )?))
11535    }
11536
11537    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
11538    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
11539    #[allow(clippy::too_many_arguments)]
11540    pub fn qmatvec_q8_fused2_raw(
11541        &self,
11542        b0: &CudaSlice<u8>,
11543        b1: &CudaSlice<u8>,
11544        x: &CudaSlice<f32>,
11545        in_f: usize,
11546        out0: usize,
11547        out1: usize,
11548        row_bytes: usize,
11549    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11550        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11551        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
11552    }
11553
11554    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
11555    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
11556    /// (tensor,row) to three separate m=1 MMVQ launches.
11557    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
11558    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
11559    pub fn matmul_q4_fused3(
11560        &self,
11561        w0: &crate::model::GpuTensor,
11562        w1: &crate::model::GpuTensor,
11563        w2: &crate::model::GpuTensor,
11564        aq: &CudaSlice<i8>,
11565        ad: &CudaSlice<f32>,
11566    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11567    {
11568        use crate::model::GpuTensor;
11569        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11570            match w {
11571                GpuTensor::Quant {
11572                    qtype, row_bytes, ..
11573                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11574                _ => None,
11575            }
11576        };
11577        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11578            return Ok(None);
11579        };
11580        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11581            return Ok(None);
11582        }
11583        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
11584        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
11585        // the separate matvecs (each routes its own rp).
11586        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11587            match w {
11588                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11589                    Some(m) => (m, true),
11590                    None => (bytes, *rp),
11591                },
11592                _ => unreachable!(),
11593            }
11594        }
11595        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11596        if rp0 != rp1 || rp1 != rp2 {
11597            return Ok(None);
11598        }
11599        let rp = rp0;
11600        let rpb: u32 = 4;
11601        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
11602        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
11603        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
11604        let mr1 = rp && Self::q40_mr1_on();
11605        let nb = |o: usize| {
11606            if mr1 {
11607                (o as u32).div_ceil(rpb)
11608            } else {
11609                (o as u32).div_ceil(2).div_ceil(rpb)
11610            }
11611        };
11612        let grid = nb(o0) + nb(o1) + nb(o2);
11613        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11614        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11615        let mut y2 = self.alloc_uninit::<f32>(o2)?;
11616        let f = self.func(if mr1 {
11617            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11618        } else if rp {
11619            "qmatvec_q4_0_mmvq_fused3_rp"
11620        } else {
11621            "qmatvec_q4_0_mmvq_fused3"
11622        });
11623        let cfg = LaunchConfig {
11624            grid_dim: (grid, 1, 1),
11625            block_dim: (32, rpb, 1),
11626            shared_mem_bytes: 0,
11627        };
11628        let inf = w0.in_features() as i32;
11629        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11630        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11631        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
11632        // variant may take the programmatic-serialization launch.
11633        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11634            {
11635                use cudarc::driver::{DevicePtr, DevicePtrMut};
11636                let s = &self.gpu.stream();
11637                let (p0, _g0) = b0.device_ptr(s);
11638                let (p1, _g1) = b1.device_ptr(s);
11639                let (p2, _g2) = b2.device_ptr(s);
11640                let (paq, _g3) = aq.device_ptr(s);
11641                let (pad, _g4) = ad.device_ptr(s);
11642                let (py0, _g5) = y0.device_ptr_mut(s);
11643                let (py1, _g6) = y1.device_ptr_mut(s);
11644                let (py2, _g7) = y2.device_ptr_mut(s);
11645                let mut ps = [
11646                    &p0 as *const _ as *mut std::ffi::c_void,
11647                    &p1 as *const _ as *mut _,
11648                    &p2 as *const _ as *mut _,
11649                    &paq as *const _ as *mut _,
11650                    &pad as *const _ as *mut _,
11651                    &py0 as *const _ as *mut _,
11652                    &py1 as *const _ as *mut _,
11653                    &py2 as *const _ as *mut _,
11654                    &inf as *const _ as *mut _,
11655                    &oo0 as *const _ as *mut _,
11656                    &oo1 as *const _ as *mut _,
11657                    &oo2 as *const _ as *mut _,
11658                    &r0 as *const _ as *mut _,
11659                    &r1 as *const _ as *mut _,
11660                    &r2 as *const _ as *mut _,
11661                ];
11662                unsafe {
11663                    self.launch_pdl(
11664                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11665                        (grid, 1, 1),
11666                        (32, rpb, 1),
11667                        &mut ps,
11668                    )?;
11669                }
11670            }
11671            return Ok(Some((y0, y1, y2)));
11672        }
11673        let __s_b = self.gpu.stream();
11674        let mut b = __s_b.launch_builder(&f);
11675        b.arg(b0)
11676            .arg(b1)
11677            .arg(b2)
11678            .arg(aq)
11679            .arg(ad)
11680            .arg(&mut y0)
11681            .arg(&mut y1)
11682            .arg(&mut y2)
11683            .arg(&inf)
11684            .arg(&oo0)
11685            .arg(&oo1)
11686            .arg(&oo2)
11687            .arg(&r0)
11688            .arg(&r1)
11689            .arg(&r2);
11690        unsafe {
11691            b.launch(cfg)?;
11692        }
11693        Ok(Some((y0, y1, y2)))
11694    }
11695
11696    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11697    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
11698    #[allow(clippy::too_many_arguments)]
11699    pub fn matmul_q4_fused3_into(
11700        &self,
11701        w0: &crate::model::GpuTensor,
11702        w1: &crate::model::GpuTensor,
11703        w2: &crate::model::GpuTensor,
11704        aq: &CudaSlice<i8>,
11705        ad: &CudaSlice<f32>,
11706        y0: &mut CudaSlice<f32>,
11707        y1: &mut CudaSlice<f32>,
11708        y2: &mut CudaSlice<f32>,
11709    ) -> Result<bool, Box<dyn std::error::Error>> {
11710        use crate::model::GpuTensor;
11711        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11712            match w {
11713                GpuTensor::Quant {
11714                    qtype, row_bytes, ..
11715                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11716                _ => None,
11717            }
11718        };
11719        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
11720            return Ok(false);
11721        };
11722        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11723            return Ok(false);
11724        }
11725        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11726            match w {
11727                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11728                    Some(m) => (m, true),
11729                    None => (bytes, *rp),
11730                },
11731                _ => unreachable!(),
11732            }
11733        }
11734        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11735        if rp0 != rp1 || rp1 != rp2 {
11736            return Ok(false);
11737        }
11738        let rp = rp0;
11739        let rpb: u32 = 4;
11740        let mr1 = rp && Self::q40_mr1_on();
11741        let nb = |o: usize| {
11742            if mr1 {
11743                (o as u32).div_ceil(rpb)
11744            } else {
11745                (o as u32).div_ceil(2).div_ceil(rpb)
11746            }
11747        };
11748        let grid = nb(o0) + nb(o1) + nb(o2);
11749        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
11750        let f = self.func(if mr1 {
11751            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
11752        } else if rp {
11753            "qmatvec_q4_0_mmvq_fused3_rp"
11754        } else {
11755            "qmatvec_q4_0_mmvq_fused3"
11756        });
11757        let cfg = LaunchConfig {
11758            grid_dim: (grid, 1, 1),
11759            block_dim: (32, rpb, 1),
11760            shared_mem_bytes: 0,
11761        };
11762        let inf = w0.in_features() as i32;
11763        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
11764        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
11765        // PDL wave-A: identical to the owned twin (capture-lane parity).
11766        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11767            use cudarc::driver::{DevicePtr, DevicePtrMut};
11768            let s = &self.gpu.stream();
11769            let (p0, _g0) = b0.device_ptr(s);
11770            let (p1, _g1) = b1.device_ptr(s);
11771            let (p2, _g2) = b2.device_ptr(s);
11772            let (paq, _g3) = aq.device_ptr(s);
11773            let (pad, _g4) = ad.device_ptr(s);
11774            let (py0, _g5) = y0.device_ptr_mut(s);
11775            let (py1, _g6) = y1.device_ptr_mut(s);
11776            let (py2, _g7) = y2.device_ptr_mut(s);
11777            let mut ps = [
11778                &p0 as *const _ as *mut std::ffi::c_void,
11779                &p1 as *const _ as *mut _,
11780                &p2 as *const _ as *mut _,
11781                &paq as *const _ as *mut _,
11782                &pad as *const _ as *mut _,
11783                &py0 as *const _ as *mut _,
11784                &py1 as *const _ as *mut _,
11785                &py2 as *const _ as *mut _,
11786                &inf as *const _ as *mut _,
11787                &oo0 as *const _ as *mut _,
11788                &oo1 as *const _ as *mut _,
11789                &oo2 as *const _ as *mut _,
11790                &r0 as *const _ as *mut _,
11791                &r1 as *const _ as *mut _,
11792                &r2 as *const _ as *mut _,
11793            ];
11794            unsafe {
11795                self.launch_pdl(
11796                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11797                    (grid, 1, 1),
11798                    (32, rpb, 1),
11799                    &mut ps,
11800                )?;
11801            }
11802            return Ok(true);
11803        }
11804        let __s_b = self.gpu.stream();
11805        let mut b = __s_b.launch_builder(&f);
11806        b.arg(b0)
11807            .arg(b1)
11808            .arg(b2)
11809            .arg(aq)
11810            .arg(ad)
11811            .arg(&mut *y0)
11812            .arg(&mut *y1)
11813            .arg(&mut *y2)
11814            .arg(&inf)
11815            .arg(&oo0)
11816            .arg(&oo1)
11817            .arg(&oo2)
11818            .arg(&r0)
11819            .arg(&r1)
11820            .arg(&r2);
11821        unsafe {
11822            b.launch(cfg)?;
11823        }
11824        Ok(true)
11825    }
11826
11827    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
11828    pub fn matmul_q4_fused2(
11829        &self,
11830        w0: &crate::model::GpuTensor,
11831        w1: &crate::model::GpuTensor,
11832        aq: &CudaSlice<i8>,
11833        ad: &CudaSlice<f32>,
11834    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11835        use crate::model::GpuTensor;
11836        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11837            match w {
11838                GpuTensor::Quant {
11839                    qtype, row_bytes, ..
11840                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11841                _ => None,
11842            }
11843        };
11844        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11845            return Ok(None);
11846        };
11847        if w0.in_features() != w1.in_features() {
11848            return Ok(None);
11849        }
11850        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
11851        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11852            match w {
11853                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11854                    Some(m) => (m, true),
11855                    None => (bytes, *rp),
11856                },
11857                _ => unreachable!(),
11858            }
11859        }
11860        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11861        if rp0 != rp1 {
11862            return Ok(None);
11863        }
11864        let rp = rp0;
11865        let rpb: u32 = 4;
11866        // mr1 twin — see matmul_q4_fused3.
11867        let mr1 = rp && Self::q40_mr1_on();
11868        let nb = |o: usize| {
11869            if mr1 {
11870                (o as u32).div_ceil(rpb)
11871            } else {
11872                (o as u32).div_ceil(2).div_ceil(rpb)
11873            }
11874        };
11875        let grid = nb(o0) + nb(o1);
11876        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11877        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11878        let f = self.func(if mr1 {
11879            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11880        } else if rp {
11881            "qmatvec_q4_0_mmvq_fused2_rp"
11882        } else {
11883            "qmatvec_q4_0_mmvq_fused2"
11884        });
11885        let cfg = LaunchConfig {
11886            grid_dim: (grid, 1, 1),
11887            block_dim: (32, rpb, 1),
11888            shared_mem_bytes: 0,
11889        };
11890        let inf = w0.in_features() as i32;
11891        let (oo0, oo1) = (o0 as i32, o1 as i32);
11892        let (r0, r1) = (rb0 as i64, rb1 as i64);
11893        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
11894        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11895            {
11896                use cudarc::driver::{DevicePtr, DevicePtrMut};
11897                let s = &self.gpu.stream();
11898                let (p0, _g0) = b0.device_ptr(s);
11899                let (p1, _g1) = b1.device_ptr(s);
11900                let (paq, _g2) = aq.device_ptr(s);
11901                let (pad, _g3) = ad.device_ptr(s);
11902                let (py0, _g4) = y0.device_ptr_mut(s);
11903                let (py1, _g5) = y1.device_ptr_mut(s);
11904                let mut ps = [
11905                    &p0 as *const _ as *mut std::ffi::c_void,
11906                    &p1 as *const _ as *mut _,
11907                    &paq as *const _ as *mut _,
11908                    &pad as *const _ as *mut _,
11909                    &py0 as *const _ as *mut _,
11910                    &py1 as *const _ as *mut _,
11911                    &inf as *const _ as *mut _,
11912                    &oo0 as *const _ as *mut _,
11913                    &oo1 as *const _ as *mut _,
11914                    &r0 as *const _ as *mut _,
11915                    &r1 as *const _ as *mut _,
11916                ];
11917                unsafe {
11918                    self.launch_pdl(
11919                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11920                        (grid, 1, 1),
11921                        (32, rpb, 1),
11922                        &mut ps,
11923                    )?;
11924                }
11925            }
11926            return Ok(Some((y0, y1)));
11927        }
11928        let __s_b = self.gpu.stream();
11929        let mut b = __s_b.launch_builder(&f);
11930        b.arg(b0)
11931            .arg(b1)
11932            .arg(aq)
11933            .arg(ad)
11934            .arg(&mut y0)
11935            .arg(&mut y1)
11936            .arg(&inf)
11937            .arg(&oo0)
11938            .arg(&oo1)
11939            .arg(&r0)
11940            .arg(&r1);
11941        unsafe {
11942            b.launch(cfg)?;
11943        }
11944        Ok(Some((y0, y1)))
11945    }
11946
11947    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11948    pub fn matmul_q4_fused2_into(
11949        &self,
11950        w0: &crate::model::GpuTensor,
11951        w1: &crate::model::GpuTensor,
11952        aq: &CudaSlice<i8>,
11953        ad: &CudaSlice<f32>,
11954        y0: &mut CudaSlice<f32>,
11955        y1: &mut CudaSlice<f32>,
11956    ) -> Result<bool, Box<dyn std::error::Error>> {
11957        use crate::model::GpuTensor;
11958        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11959            match w {
11960                GpuTensor::Quant {
11961                    qtype, row_bytes, ..
11962                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11963                _ => None,
11964            }
11965        };
11966        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11967            return Ok(false);
11968        };
11969        if w0.in_features() != w1.in_features() {
11970            return Ok(false);
11971        }
11972        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11973            match w {
11974                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11975                    Some(m) => (m, true),
11976                    None => (bytes, *rp),
11977                },
11978                _ => unreachable!(),
11979            }
11980        }
11981        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11982        if rp0 != rp1 {
11983            return Ok(false);
11984        }
11985        let rp = rp0;
11986        let rpb: u32 = 4;
11987        let mr1 = rp && Self::q40_mr1_on();
11988        let nb = |o: usize| {
11989            if mr1 {
11990                (o as u32).div_ceil(rpb)
11991            } else {
11992                (o as u32).div_ceil(2).div_ceil(rpb)
11993            }
11994        };
11995        let grid = nb(o0) + nb(o1);
11996        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
11997        let f = self.func(if mr1 {
11998            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11999        } else if rp {
12000            "qmatvec_q4_0_mmvq_fused2_rp"
12001        } else {
12002            "qmatvec_q4_0_mmvq_fused2"
12003        });
12004        let cfg = LaunchConfig {
12005            grid_dim: (grid, 1, 1),
12006            block_dim: (32, rpb, 1),
12007            shared_mem_bytes: 0,
12008        };
12009        let inf = w0.in_features() as i32;
12010        let (oo0, oo1) = (o0 as i32, o1 as i32);
12011        let (r0, r1) = (rb0 as i64, rb1 as i64);
12012        // PDL wave-A: identical to the owned twin (capture-lane parity).
12013        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
12014            use cudarc::driver::{DevicePtr, DevicePtrMut};
12015            let s = &self.gpu.stream();
12016            let (p0, _g0) = b0.device_ptr(s);
12017            let (p1, _g1) = b1.device_ptr(s);
12018            let (paq, _g2) = aq.device_ptr(s);
12019            let (pad, _g3) = ad.device_ptr(s);
12020            let (py0, _g4) = y0.device_ptr_mut(s);
12021            let (py1, _g5) = y1.device_ptr_mut(s);
12022            let mut ps = [
12023                &p0 as *const _ as *mut std::ffi::c_void,
12024                &p1 as *const _ as *mut _,
12025                &paq as *const _ as *mut _,
12026                &pad as *const _ as *mut _,
12027                &py0 as *const _ as *mut _,
12028                &py1 as *const _ as *mut _,
12029                &inf as *const _ as *mut _,
12030                &oo0 as *const _ as *mut _,
12031                &oo1 as *const _ as *mut _,
12032                &r0 as *const _ as *mut _,
12033                &r1 as *const _ as *mut _,
12034            ];
12035            unsafe {
12036                self.launch_pdl(
12037                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
12038                    (grid, 1, 1),
12039                    (32, rpb, 1),
12040                    &mut ps,
12041                )?;
12042            }
12043            return Ok(true);
12044        }
12045        let __s_b = self.gpu.stream();
12046        let mut b = __s_b.launch_builder(&f);
12047        b.arg(b0)
12048            .arg(b1)
12049            .arg(aq)
12050            .arg(ad)
12051            .arg(&mut *y0)
12052            .arg(&mut *y1)
12053            .arg(&inf)
12054            .arg(&oo0)
12055            .arg(&oo1)
12056            .arg(&r0)
12057            .arg(&r1);
12058        unsafe {
12059            b.launch(cfg)?;
12060        }
12061        Ok(true)
12062    }
12063
12064    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
12065    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
12066    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
12067    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
12068    pub fn matmul_q4_fused2_batched(
12069        &self,
12070        w0: &crate::model::GpuTensor,
12071        w1: &crate::model::GpuTensor,
12072        aq: &CudaSlice<i8>,
12073        ad: &CudaSlice<f32>,
12074        m: usize,
12075    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12076        use crate::model::GpuTensor;
12077        if m < 2 || m > 8 {
12078            return Ok(None);
12079        }
12080        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
12081            match w {
12082                GpuTensor::Quant {
12083                    qtype, row_bytes, ..
12084                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
12085                _ => None,
12086            }
12087        };
12088        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
12089            return Ok(None);
12090        };
12091        if w0.in_features() != w1.in_features() {
12092            return Ok(None);
12093        }
12094        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12095            match w {
12096                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12097                    Some(mr) => (mr, true),
12098                    None => (bytes, *rp),
12099                },
12100                _ => unreachable!(),
12101            }
12102        }
12103        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
12104        if !rp0 || !rp1 {
12105            return Ok(None);
12106        }
12107        let mcols = Self::batched_mcols(m);
12108        let rpb: u32 = 4;
12109        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12110        let grid = nb(o0) + nb(o1);
12111        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12112        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12113        let f = self.func(match mcols {
12114            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
12115            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
12116            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
12117        });
12118        let cfg = LaunchConfig {
12119            grid_dim: (grid, 1, 1),
12120            block_dim: (32, rpb, 1),
12121            shared_mem_bytes: 0,
12122        };
12123        let inf = w0.in_features() as i32;
12124        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
12125        let rb = rb0 as i64;
12126        let __s_b = self.gpu.stream();
12127        let mut b = __s_b.launch_builder(&f);
12128        b.arg(b0)
12129            .arg(b1)
12130            .arg(aq)
12131            .arg(ad)
12132            .arg(&mut y0)
12133            .arg(&mut y1)
12134            .arg(&inf)
12135            .arg(&oo0)
12136            .arg(&oo1)
12137            .arg(&mi)
12138            .arg(&rb);
12139        unsafe {
12140            b.launch(cfg)?;
12141        }
12142        Ok(Some((y0, y1)))
12143    }
12144
12145    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
12146    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
12147    #[allow(clippy::too_many_arguments)]
12148    pub fn matmul_q4_fused3_batched(
12149        &self,
12150        w0: &crate::model::GpuTensor,
12151        w1: &crate::model::GpuTensor,
12152        w2: &crate::model::GpuTensor,
12153        aq: &CudaSlice<i8>,
12154        ad: &CudaSlice<f32>,
12155        m: usize,
12156    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12157    {
12158        use crate::model::GpuTensor;
12159        if m < 2 || m > 8 {
12160            return Ok(None);
12161        }
12162        let q4 = |w: &GpuTensor| -> Option<usize> {
12163            match w {
12164                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
12165                _ => None,
12166            }
12167        };
12168        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
12169            return Ok(None);
12170        };
12171        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
12172            return Ok(None);
12173        }
12174        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
12175            match w {
12176                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
12177                    Some(mr) => (mr, true),
12178                    None => (bytes, *rp),
12179                },
12180                _ => unreachable!(),
12181            }
12182        }
12183        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
12184        if !rp0 || !rp1 || !rp2 {
12185            return Ok(None);
12186        }
12187        let mcols = Self::batched_mcols(m);
12188        let rpb: u32 = 4;
12189        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
12190        let grid = nb(o0) + nb(o1) + nb(o2);
12191        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
12192        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
12193        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
12194        let f = self.func(match mcols {
12195            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
12196            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
12197            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
12198        });
12199        let cfg = LaunchConfig {
12200            grid_dim: (grid, 1, 1),
12201            block_dim: (32, rpb, 1),
12202            shared_mem_bytes: 0,
12203        };
12204        let inf = w0.in_features() as i32;
12205        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
12206        let rb = 0i64;
12207        let __s_b = self.gpu.stream();
12208        let mut b = __s_b.launch_builder(&f);
12209        b.arg(b0)
12210            .arg(b1)
12211            .arg(b2)
12212            .arg(aq)
12213            .arg(ad)
12214            .arg(&mut y0)
12215            .arg(&mut y1)
12216            .arg(&mut y2)
12217            .arg(&inf)
12218            .arg(&oo0)
12219            .arg(&oo1)
12220            .arg(&oo2)
12221            .arg(&mi)
12222            .arg(&rb);
12223        unsafe {
12224            b.launch(cfg)?;
12225        }
12226        Ok(Some((y0, y1, y2)))
12227    }
12228
12229    pub fn matmul_q8_fused3(
12230        &self,
12231        w0: &crate::model::GpuTensor,
12232        w1: &crate::model::GpuTensor,
12233        w2: &crate::model::GpuTensor,
12234        aq: &CudaSlice<i8>,
12235        ad: &CudaSlice<f32>,
12236    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12237    {
12238        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
12239        // are per-tensor FP8, so native residency without this arm meant three separate launches.
12240        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12241            return Ok(Some(self.e4m3_fused3_core(
12242                p0.0,
12243                p1.0,
12244                p2.0,
12245                aq,
12246                ad,
12247                w0.in_features(),
12248                p0.1,
12249                p1.1,
12250                p2.1,
12251                p0.2,
12252                p0.3,
12253                p1.3,
12254                p2.3,
12255            )?));
12256        }
12257        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12258            return Ok(None);
12259        };
12260        Ok(Some(self.q8_fused3_core(
12261            p0.0,
12262            p1.0,
12263            p2.0,
12264            aq,
12265            ad,
12266            w0.in_features(),
12267            p0.1,
12268            p1.1,
12269            p2.1,
12270            p0.2,
12271        )?))
12272    }
12273
12274    #[allow(clippy::too_many_arguments)]
12275    fn q8_fused3_core(
12276        &self,
12277        b0: &CudaSlice<u8>,
12278        b1: &CudaSlice<u8>,
12279        b2: &CudaSlice<u8>,
12280        aq: &CudaSlice<i8>,
12281        ad: &CudaSlice<f32>,
12282        in_f: usize,
12283        out0: usize,
12284        out1: usize,
12285        out2: usize,
12286        row_bytes: usize,
12287    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12288        const ROWS_PER_BLOCK: u32 = 4;
12289        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12290        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12291        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12292        let f = self.func("qmatvec_q8_0_mmvq_fused3");
12293        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12294        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12295        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12296        let cfg = LaunchConfig {
12297            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12298            block_dim: (32, ROWS_PER_BLOCK, 1),
12299            shared_mem_bytes: 0,
12300        };
12301        let (inf, o0, o1, o2, rbl) = (
12302            in_f as i32,
12303            out0 as i32,
12304            out1 as i32,
12305            out2 as i32,
12306            row_bytes as i64,
12307        );
12308        let __s_b = self.gpu.stream();
12309        let mut b = __s_b.launch_builder(&f);
12310        b.arg(b0)
12311            .arg(b1)
12312            .arg(b2)
12313            .arg(aq)
12314            .arg(ad)
12315            .arg(&mut y0)
12316            .arg(&mut y1)
12317            .arg(&mut y2)
12318            .arg(&inf)
12319            .arg(&o0)
12320            .arg(&o1)
12321            .arg(&o2)
12322            .arg(&rbl);
12323        unsafe {
12324            b.launch(cfg)?;
12325        }
12326        Ok((y0, y1, y2))
12327    }
12328
12329    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
12330    #[allow(clippy::too_many_arguments)]
12331    pub fn qmatvec_q8_fused3_raw(
12332        &self,
12333        b0: &CudaSlice<u8>,
12334        b1: &CudaSlice<u8>,
12335        b2: &CudaSlice<u8>,
12336        x: &CudaSlice<f32>,
12337        in_f: usize,
12338        out0: usize,
12339        out1: usize,
12340        out2: usize,
12341        row_bytes: usize,
12342    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12343        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12344        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
12345    }
12346
12347    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
12348    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
12349    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
12350    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
12351    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
12352    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
12353    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
12354    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
12355    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
12356    /// twin must not introduce a batched program the reference path would not run).
12357    pub fn matmul_q8_fused2_t(
12358        &self,
12359        w0: &crate::model::GpuTensor,
12360        w1: &crate::model::GpuTensor,
12361        aq: &CudaSlice<i8>,
12362        ad: &CudaSlice<f32>,
12363        m: usize,
12364    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
12365        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
12366        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
12367        // fuses too — same template body, still bit-identical to the two _b8 launches.
12368        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12369            return Ok(None);
12370        }
12371        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
12372        // so the fused b8 launch would introduce a batched program the reference path would not run.
12373        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
12374            if m > 4 && !Self::b8_enabled() {
12375                return Ok(None);
12376            }
12377            return Ok(Some(self.e4m3_fused2_t_core(
12378                p0.0,
12379                p1.0,
12380                aq,
12381                ad,
12382                m,
12383                w0.in_features(),
12384                p0.1,
12385                p1.1,
12386                p0.2,
12387                p0.3,
12388                p1.3,
12389            )?));
12390        }
12391        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
12392            return Ok(None);
12393        };
12394        Ok(Some(self.q8_fused2_t_core(
12395            p0.0,
12396            p1.0,
12397            aq,
12398            ad,
12399            m,
12400            w0.in_features(),
12401            p0.1,
12402            p1.1,
12403            p0.2,
12404        )?))
12405    }
12406
12407    #[allow(clippy::too_many_arguments)]
12408    fn q8_fused2_t_core(
12409        &self,
12410        b0: &CudaSlice<u8>,
12411        b1: &CudaSlice<u8>,
12412        aq: &CudaSlice<i8>,
12413        ad: &CudaSlice<f32>,
12414        m: usize,
12415        in_f: usize,
12416        out0: usize,
12417        out1: usize,
12418        row_bytes: usize,
12419    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12420        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12421        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12422        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12423        let f = self.func(match Self::batched_mcols(m) {
12424            2 => "qmatvec_q8_0_mmvq_fused2_b2",
12425            4 => "qmatvec_q8_0_mmvq_fused2_b4",
12426            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
12427            _ => "qmatvec_q8_0_mmvq_fused2_b8",
12428        });
12429        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12430        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12431        let cfg = LaunchConfig {
12432            grid_dim: (nb0 + nb1, 1, 1),
12433            block_dim: (32, ROWS_PER_BLOCK, 1),
12434            shared_mem_bytes: 0,
12435        };
12436        let (inf, o0, o1, mi, rbl) = (
12437            in_f as i32,
12438            out0 as i32,
12439            out1 as i32,
12440            m as i32,
12441            row_bytes as i64,
12442        );
12443        let __s_b = self.gpu.stream();
12444        let mut b = __s_b.launch_builder(&f);
12445        b.arg(b0)
12446            .arg(b1)
12447            .arg(aq)
12448            .arg(ad)
12449            .arg(&mut y0)
12450            .arg(&mut y1)
12451            .arg(&inf)
12452            .arg(&o0)
12453            .arg(&o1)
12454            .arg(&mi)
12455            .arg(&rbl);
12456        unsafe {
12457            b.launch(cfg)?;
12458        }
12459        Ok((y0, y1))
12460    }
12461
12462    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
12463    /// q8_1 quant of the [m, in_f] activation), no env gating.
12464    #[allow(clippy::too_many_arguments)]
12465    pub fn qmatvec_q8_fused2_t_raw(
12466        &self,
12467        b0: &CudaSlice<u8>,
12468        b1: &CudaSlice<u8>,
12469        x: &CudaSlice<f32>,
12470        m: usize,
12471        in_f: usize,
12472        out0: usize,
12473        out1: usize,
12474        row_bytes: usize,
12475    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12476        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12477        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
12478    }
12479
12480    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
12481    /// `matmul_q8_fused2_t` with three ranges.
12482    #[allow(clippy::too_many_arguments)]
12483    pub fn matmul_q8_fused3_t(
12484        &self,
12485        w0: &crate::model::GpuTensor,
12486        w1: &crate::model::GpuTensor,
12487        w2: &crate::model::GpuTensor,
12488        aq: &CudaSlice<i8>,
12489        ad: &CudaSlice<f32>,
12490        m: usize,
12491    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
12492    {
12493        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
12494            return Ok(None);
12495        }
12496        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
12497            return Ok(Some(self.e4m3_fused3_t_core(
12498                p0.0,
12499                p1.0,
12500                p2.0,
12501                aq,
12502                ad,
12503                m,
12504                w0.in_features(),
12505                p0.1,
12506                p1.1,
12507                p2.1,
12508                p0.2,
12509                p0.3,
12510                p1.3,
12511                p2.3,
12512            )?));
12513        }
12514        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
12515            return Ok(None);
12516        };
12517        Ok(Some(self.q8_fused3_t_core(
12518            p0.0,
12519            p1.0,
12520            p2.0,
12521            aq,
12522            ad,
12523            m,
12524            w0.in_features(),
12525            p0.1,
12526            p1.1,
12527            p2.1,
12528            p0.2,
12529        )?))
12530    }
12531
12532    #[allow(clippy::too_many_arguments)]
12533    fn q8_fused3_t_core(
12534        &self,
12535        b0: &CudaSlice<u8>,
12536        b1: &CudaSlice<u8>,
12537        b2: &CudaSlice<u8>,
12538        aq: &CudaSlice<i8>,
12539        ad: &CudaSlice<f32>,
12540        m: usize,
12541        in_f: usize,
12542        out0: usize,
12543        out1: usize,
12544        out2: usize,
12545        row_bytes: usize,
12546    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12547        const ROWS_PER_BLOCK: u32 = 4;
12548        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12549        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12550        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12551        let f = self.func(if Self::batched_mcols(m) == 2 {
12552            "qmatvec_q8_0_mmvq_fused3_b2"
12553        } else {
12554            "qmatvec_q8_0_mmvq_fused3_b4"
12555        });
12556        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12557        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12558        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12559        let cfg = LaunchConfig {
12560            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12561            block_dim: (32, ROWS_PER_BLOCK, 1),
12562            shared_mem_bytes: 0,
12563        };
12564        let (inf, o0, o1, o2, mi, rbl) = (
12565            in_f as i32,
12566            out0 as i32,
12567            out1 as i32,
12568            out2 as i32,
12569            m as i32,
12570            row_bytes as i64,
12571        );
12572        let __s_b = self.gpu.stream();
12573        let mut b = __s_b.launch_builder(&f);
12574        b.arg(b0)
12575            .arg(b1)
12576            .arg(b2)
12577            .arg(aq)
12578            .arg(ad)
12579            .arg(&mut y0)
12580            .arg(&mut y1)
12581            .arg(&mut y2)
12582            .arg(&inf)
12583            .arg(&o0)
12584            .arg(&o1)
12585            .arg(&o2)
12586            .arg(&mi)
12587            .arg(&rbl);
12588        unsafe {
12589            b.launch(cfg)?;
12590        }
12591        Ok((y0, y1, y2))
12592    }
12593
12594    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
12595    #[allow(clippy::too_many_arguments)]
12596    pub fn qmatvec_q8_fused3_t_raw(
12597        &self,
12598        b0: &CudaSlice<u8>,
12599        b1: &CudaSlice<u8>,
12600        b2: &CudaSlice<u8>,
12601        x: &CudaSlice<f32>,
12602        m: usize,
12603        in_f: usize,
12604        out0: usize,
12605        out1: usize,
12606        out2: usize,
12607        row_bytes: usize,
12608    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12609        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12610        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
12611    }
12612
12613    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
12614    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
12615    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
12616    pub fn q8_ffn_fuse2_on(&self) -> bool {
12617        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12618        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
12619    }
12620
12621    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
12622    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
12623    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
12624    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
12625    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
12626    #[allow(clippy::type_complexity)]
12627    fn q8_fused_params<'w, const N: usize>(
12628        &self,
12629        ws: &[&'w crate::model::GpuTensor; N],
12630    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
12631        use crate::model::GpuTensor;
12632        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12633            return None;
12634        }
12635        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
12636            return None;
12637        }
12638        let in_f = ws[0].in_features();
12639        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
12640        for (i, w) in ws.iter().enumerate() {
12641            match w {
12642                GpuTensor::Quant {
12643                    bytes,
12644                    qtype,
12645                    row_bytes,
12646                    scale,
12647                    ..
12648                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
12649                    out[i] = Some((bytes, w.out_features(), *row_bytes))
12650                }
12651                _ => return None,
12652            }
12653        }
12654        Some(out.map(|o| o.unwrap()))
12655    }
12656
12657    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
12658    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
12659    pub fn e4m3_dual_on(&self) -> bool {
12660        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12661        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
12662    }
12663
12664    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
12665    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
12666    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
12667    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
12668    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
12669    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
12670    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
12671    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
12672    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
12673    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
12674    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
12675    #[allow(clippy::type_complexity)]
12676    fn e4m3_fused_params<'w, const N: usize>(
12677        &self,
12678        ws: &[&'w crate::model::GpuTensor; N],
12679    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
12680        use crate::model::GpuTensor;
12681        if !self.e4m3_dual_on() {
12682            return None;
12683        }
12684        let in_f = ws[0].in_features();
12685        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
12686        for (i, w) in ws.iter().enumerate() {
12687            match w {
12688                GpuTensor::Quant {
12689                    bytes,
12690                    qtype,
12691                    row_bytes,
12692                    scale,
12693                    rp,
12694                    rp4,
12695                    ..
12696                } if *qtype == QT_F8_E4M3
12697                    && w.in_features() == in_f
12698                    && *row_bytes == in_f
12699                    && !*rp
12700                    && rp4.is_none() =>
12701                {
12702                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
12703                }
12704                _ => return None,
12705            }
12706        }
12707        Some(out.map(|o| o.unwrap()))
12708    }
12709
12710    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
12711    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
12712    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
12713    #[allow(clippy::too_many_arguments)]
12714    fn e4m3_fused2_core(
12715        &self,
12716        b0: &CudaSlice<u8>,
12717        b1: &CudaSlice<u8>,
12718        aq: &CudaSlice<i8>,
12719        ad: &CudaSlice<f32>,
12720        in_f: usize,
12721        out0: usize,
12722        out1: usize,
12723        row_bytes: usize,
12724        ws0: f32,
12725        ws1: f32,
12726    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12727        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12728        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12729        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12730        let f = self.func("qmatvec_e4m3_mmvq_fused2");
12731        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12732        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12733        let cfg = LaunchConfig {
12734            grid_dim: (nb0 + nb1, 1, 1),
12735            block_dim: (32, ROWS_PER_BLOCK, 1),
12736            shared_mem_bytes: 0,
12737        };
12738        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
12739        let __s_b = self.gpu.stream();
12740        let mut b = __s_b.launch_builder(&f);
12741        b.arg(b0)
12742            .arg(b1)
12743            .arg(aq)
12744            .arg(ad)
12745            .arg(&mut y0)
12746            .arg(&mut y1)
12747            .arg(&inf)
12748            .arg(&o0)
12749            .arg(&o1)
12750            .arg(&rbl)
12751            .arg(&ws0)
12752            .arg(&ws1);
12753        unsafe {
12754            b.launch(cfg)?;
12755        }
12756        Ok((y0, y1))
12757    }
12758
12759    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
12760    #[allow(clippy::too_many_arguments)]
12761    fn e4m3_fused3_core(
12762        &self,
12763        b0: &CudaSlice<u8>,
12764        b1: &CudaSlice<u8>,
12765        b2: &CudaSlice<u8>,
12766        aq: &CudaSlice<i8>,
12767        ad: &CudaSlice<f32>,
12768        in_f: usize,
12769        out0: usize,
12770        out1: usize,
12771        out2: usize,
12772        row_bytes: usize,
12773        ws0: f32,
12774        ws1: f32,
12775        ws2: f32,
12776    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12777        const ROWS_PER_BLOCK: u32 = 4;
12778        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12779        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12780        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12781        let f = self.func("qmatvec_e4m3_mmvq_fused3");
12782        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12783        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12784        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12785        let cfg = LaunchConfig {
12786            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12787            block_dim: (32, ROWS_PER_BLOCK, 1),
12788            shared_mem_bytes: 0,
12789        };
12790        let (inf, o0, o1, o2, rbl) = (
12791            in_f as i32,
12792            out0 as i32,
12793            out1 as i32,
12794            out2 as i32,
12795            row_bytes as i64,
12796        );
12797        let __s_b = self.gpu.stream();
12798        let mut b = __s_b.launch_builder(&f);
12799        b.arg(b0)
12800            .arg(b1)
12801            .arg(b2)
12802            .arg(aq)
12803            .arg(ad)
12804            .arg(&mut y0)
12805            .arg(&mut y1)
12806            .arg(&mut y2)
12807            .arg(&inf)
12808            .arg(&o0)
12809            .arg(&o1)
12810            .arg(&o2)
12811            .arg(&rbl)
12812            .arg(&ws0)
12813            .arg(&ws1)
12814            .arg(&ws2);
12815        unsafe {
12816            b.launch(cfg)?;
12817        }
12818        Ok((y0, y1, y2))
12819    }
12820
12821    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
12822    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
12823    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
12824    #[allow(clippy::too_many_arguments)]
12825    fn e4m3_fused2_t_core(
12826        &self,
12827        b0: &CudaSlice<u8>,
12828        b1: &CudaSlice<u8>,
12829        aq: &CudaSlice<i8>,
12830        ad: &CudaSlice<f32>,
12831        m: usize,
12832        in_f: usize,
12833        out0: usize,
12834        out1: usize,
12835        row_bytes: usize,
12836        ws0: f32,
12837        ws1: f32,
12838    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12839        const ROWS_PER_BLOCK: u32 = 4;
12840        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12841        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12842        let f = self.func(match Self::batched_mcols(m) {
12843            2 => "qmatvec_e4m3_mmvq_fused2_b2",
12844            4 => "qmatvec_e4m3_mmvq_fused2_b4",
12845            _ => "qmatvec_e4m3_mmvq_fused2_b8",
12846        });
12847        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12848        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12849        let cfg = LaunchConfig {
12850            grid_dim: (nb0 + nb1, 1, 1),
12851            block_dim: (32, ROWS_PER_BLOCK, 1),
12852            shared_mem_bytes: 0,
12853        };
12854        let (inf, o0, o1, mi, rbl) = (
12855            in_f as i32,
12856            out0 as i32,
12857            out1 as i32,
12858            m as i32,
12859            row_bytes as i64,
12860        );
12861        let __s_b = self.gpu.stream();
12862        let mut b = __s_b.launch_builder(&f);
12863        b.arg(b0)
12864            .arg(b1)
12865            .arg(aq)
12866            .arg(ad)
12867            .arg(&mut y0)
12868            .arg(&mut y1)
12869            .arg(&inf)
12870            .arg(&o0)
12871            .arg(&o1)
12872            .arg(&mi)
12873            .arg(&rbl);
12874        unsafe {
12875            b.launch(cfg)?;
12876        }
12877        if ws0 != 1.0 {
12878            self.scale_inplace(&mut y0, ws0, m * out0)?;
12879        }
12880        if ws1 != 1.0 {
12881            self.scale_inplace(&mut y1, ws1, m * out1)?;
12882        }
12883        Ok((y0, y1))
12884    }
12885
12886    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
12887    #[allow(clippy::too_many_arguments)]
12888    fn e4m3_fused3_t_core(
12889        &self,
12890        b0: &CudaSlice<u8>,
12891        b1: &CudaSlice<u8>,
12892        b2: &CudaSlice<u8>,
12893        aq: &CudaSlice<i8>,
12894        ad: &CudaSlice<f32>,
12895        m: usize,
12896        in_f: usize,
12897        out0: usize,
12898        out1: usize,
12899        out2: usize,
12900        row_bytes: usize,
12901        ws0: f32,
12902        ws1: f32,
12903        ws2: f32,
12904    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12905        const ROWS_PER_BLOCK: u32 = 4;
12906        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12907        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12908        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12909        let f = self.func(if Self::batched_mcols(m) == 2 {
12910            "qmatvec_e4m3_mmvq_fused3_b2"
12911        } else {
12912            "qmatvec_e4m3_mmvq_fused3_b4"
12913        });
12914        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12915        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12916        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12917        let cfg = LaunchConfig {
12918            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12919            block_dim: (32, ROWS_PER_BLOCK, 1),
12920            shared_mem_bytes: 0,
12921        };
12922        let (inf, o0, o1, o2, mi, rbl) = (
12923            in_f as i32,
12924            out0 as i32,
12925            out1 as i32,
12926            out2 as i32,
12927            m as i32,
12928            row_bytes as i64,
12929        );
12930        let __s_b = self.gpu.stream();
12931        let mut b = __s_b.launch_builder(&f);
12932        b.arg(b0)
12933            .arg(b1)
12934            .arg(b2)
12935            .arg(aq)
12936            .arg(ad)
12937            .arg(&mut y0)
12938            .arg(&mut y1)
12939            .arg(&mut y2)
12940            .arg(&inf)
12941            .arg(&o0)
12942            .arg(&o1)
12943            .arg(&o2)
12944            .arg(&mi)
12945            .arg(&rbl);
12946        unsafe {
12947            b.launch(cfg)?;
12948        }
12949        if ws0 != 1.0 {
12950            self.scale_inplace(&mut y0, ws0, m * out0)?;
12951        }
12952        if ws1 != 1.0 {
12953            self.scale_inplace(&mut y1, ws1, m * out1)?;
12954        }
12955        if ws2 != 1.0 {
12956            self.scale_inplace(&mut y2, ws2, m * out2)?;
12957        }
12958        Ok((y0, y1, y2))
12959    }
12960
12961    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
12962    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
12963    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
12964    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
12965    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
12966    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
12967    ///
12968    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
12969    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
12970    pub fn qmatvec_e4m3_blk_mmvq(
12971        &self,
12972        bytes: &CudaSlice<u8>,
12973        aq: &CudaSlice<i8>,
12974        ad: &CudaSlice<f32>,
12975        scales: &CudaSlice<f32>,
12976        m: usize,
12977        in_f: usize,
12978        out_f: usize,
12979        row_bytes: usize,
12980        scale_cols: usize,
12981    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12982        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
12983        self.qmatvec_e4m3_blk_mmvq_into(
12984            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
12985        )?;
12986        Ok(y)
12987    }
12988
12989    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
12990    #[allow(clippy::too_many_arguments)]
12991    pub fn qmatvec_e4m3_blk_mmvq_into(
12992        &self,
12993        bytes: &CudaSlice<u8>,
12994        aq: &CudaSlice<i8>,
12995        ad: &CudaSlice<f32>,
12996        scales: &CudaSlice<f32>,
12997        m: usize,
12998        in_f: usize,
12999        out_f: usize,
13000        row_bytes: usize,
13001        scale_cols: usize,
13002        y: &mut CudaSlice<f32>,
13003    ) -> Result<(), Box<dyn std::error::Error>> {
13004        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13005        let f = self.func("qmatvec_e4m3_blk_mmvq");
13006        let cfg = LaunchConfig {
13007            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
13008            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
13009            shared_mem_bytes: 0,                // warp-only reduce
13010        };
13011        let (inf, outf, mi, rb, sc) = (
13012            in_f as i32,
13013            out_f as i32,
13014            m as i32,
13015            row_bytes as i64,
13016            scale_cols as i32,
13017        );
13018        let __s_b = self.gpu.stream();
13019        let mut b = __s_b.launch_builder(&f);
13020        b.arg(bytes)
13021            .arg(aq)
13022            .arg(ad)
13023            .arg(scales)
13024            .arg(&mut *y)
13025            .arg(&inf)
13026            .arg(&outf)
13027            .arg(&mi)
13028            .arg(&rb)
13029            .arg(&sc);
13030        unsafe {
13031            b.launch(cfg)?;
13032        }
13033        Ok(())
13034    }
13035
13036    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
13037    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
13038    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
13039    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
13040    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
13041    #[allow(clippy::too_many_arguments)]
13042    pub fn qmatvec_e4m3_blk_mmvq_batched(
13043        &self,
13044        bytes: &CudaSlice<u8>,
13045        aq: &CudaSlice<i8>,
13046        ad: &CudaSlice<f32>,
13047        scales: &CudaSlice<f32>,
13048        m: usize,
13049        in_f: usize,
13050        out_f: usize,
13051        row_bytes: usize,
13052        scale_cols: usize,
13053        mcols: usize,
13054    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13055        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13056        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
13057        let name = match mcols {
13058            2 => "qmatvec_e4m3_blk_mmvq_b2",
13059            4 => "qmatvec_e4m3_blk_mmvq_b4",
13060            8 => "qmatvec_e4m3_blk_mmvq_b8",
13061            16 => "qmatvec_e4m3_blk_mmvq_b16",
13062            _ => {
13063                return Err(
13064                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
13065                );
13066            }
13067        };
13068        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13069        let f = self.func(name);
13070        let cfg = LaunchConfig {
13071            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
13072            block_dim: (32, ROWS_PER_BLOCK, 1),
13073            shared_mem_bytes: 0,
13074        };
13075        let (inf, outf, mi, rb, sc) = (
13076            in_f as i32,
13077            out_f as i32,
13078            m as i32,
13079            row_bytes as i64,
13080            scale_cols as i32,
13081        );
13082        let __s_b = self.gpu.stream();
13083        let mut b = __s_b.launch_builder(&f);
13084        b.arg(bytes)
13085            .arg(aq)
13086            .arg(ad)
13087            .arg(scales)
13088            .arg(&mut y)
13089            .arg(&inf)
13090            .arg(&outf)
13091            .arg(&mi)
13092            .arg(&rb)
13093            .arg(&sc);
13094        unsafe {
13095            b.launch(cfg)?;
13096        }
13097        Ok(y)
13098    }
13099
13100    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
13101    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
13102    #[allow(clippy::too_many_arguments)]
13103    pub fn qmatvec_e4m3_blk_batched_raw(
13104        &self,
13105        bytes: &CudaSlice<u8>,
13106        x: &CudaSlice<f32>,
13107        scales: &CudaSlice<f32>,
13108        m: usize,
13109        in_f: usize,
13110        out_f: usize,
13111        row_bytes: usize,
13112        scale_cols: usize,
13113        mcols: usize,
13114    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13115        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13116        self.qmatvec_e4m3_blk_mmvq_batched(
13117            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
13118        )
13119    }
13120
13121    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
13122    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
13123    #[allow(clippy::too_many_arguments)]
13124    pub fn qmatvec_e4m3_blk_mmvq_raw(
13125        &self,
13126        bytes: &CudaSlice<u8>,
13127        x: &CudaSlice<f32>,
13128        scales: &CudaSlice<f32>,
13129        m: usize,
13130        in_f: usize,
13131        out_f: usize,
13132        row_bytes: usize,
13133        scale_cols: usize,
13134    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13135        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13136        self.qmatvec_e4m3_blk_mmvq(
13137            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
13138        )
13139    }
13140
13141    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
13142    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
13143    #[allow(clippy::too_many_arguments)]
13144    pub fn qmatvec_e4m3_fused2_raw(
13145        &self,
13146        b0: &CudaSlice<u8>,
13147        b1: &CudaSlice<u8>,
13148        x: &CudaSlice<f32>,
13149        in_f: usize,
13150        out0: usize,
13151        out1: usize,
13152        row_bytes: usize,
13153        ws0: f32,
13154        ws1: f32,
13155    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13156        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13157        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
13158    }
13159
13160    #[allow(clippy::too_many_arguments)]
13161    pub fn qmatvec_e4m3_fused3_raw(
13162        &self,
13163        b0: &CudaSlice<u8>,
13164        b1: &CudaSlice<u8>,
13165        b2: &CudaSlice<u8>,
13166        x: &CudaSlice<f32>,
13167        in_f: usize,
13168        out0: usize,
13169        out1: usize,
13170        out2: usize,
13171        row_bytes: usize,
13172        ws0: f32,
13173        ws1: f32,
13174        ws2: f32,
13175    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13176        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
13177        self.e4m3_fused3_core(
13178            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13179        )
13180    }
13181
13182    #[allow(clippy::too_many_arguments)]
13183    pub fn qmatvec_e4m3_fused2_t_raw(
13184        &self,
13185        b0: &CudaSlice<u8>,
13186        b1: &CudaSlice<u8>,
13187        x: &CudaSlice<f32>,
13188        m: usize,
13189        in_f: usize,
13190        out0: usize,
13191        out1: usize,
13192        row_bytes: usize,
13193        ws0: f32,
13194        ws1: f32,
13195    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13196        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13197        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
13198    }
13199
13200    #[allow(clippy::too_many_arguments)]
13201    pub fn qmatvec_e4m3_fused3_t_raw(
13202        &self,
13203        b0: &CudaSlice<u8>,
13204        b1: &CudaSlice<u8>,
13205        b2: &CudaSlice<u8>,
13206        x: &CudaSlice<f32>,
13207        m: usize,
13208        in_f: usize,
13209        out0: usize,
13210        out1: usize,
13211        out2: usize,
13212        row_bytes: usize,
13213        ws0: f32,
13214        ws1: f32,
13215        ws2: f32,
13216    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
13217        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13218        self.e4m3_fused3_t_core(
13219            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
13220        )
13221    }
13222
13223    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
13224    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
13225    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
13226    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
13227    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
13228    ///
13229    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
13230    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
13231    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
13232    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
13233    fn try_e4m3_blk_pre(
13234        &self,
13235        w: &crate::model::GpuTensor,
13236        aq: &CudaSlice<i8>,
13237        ad: &CudaSlice<f32>,
13238        m: usize,
13239    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13240        use crate::model::GpuTensor;
13241        if let GpuTensor::Quant {
13242            bytes,
13243            qtype,
13244            row_bytes,
13245            blk: Some(g),
13246            ..
13247        } = w
13248        {
13249            if *qtype == QT_F8_E4M3_BLK {
13250                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
13251                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
13252                // below, so the decode-exactness contract is preserved at every width. Gated by
13253                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
13254                // one rollback door covers every dtype's batched tier.
13255                if (2..=16).contains(&m)
13256                    && std::env::var("MEMRA_NO_BATCHED").is_err()
13257                    && (m <= 4 || Self::b8_enabled())
13258                {
13259                    let mcols = Self::batched_mcols(m);
13260                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
13261                        bytes,
13262                        aq,
13263                        ad,
13264                        &g.scales,
13265                        m,
13266                        w.in_features(),
13267                        w.out_features(),
13268                        *row_bytes,
13269                        g.cols,
13270                        mcols,
13271                    )?));
13272                }
13273                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
13274                    bytes,
13275                    aq,
13276                    ad,
13277                    &g.scales,
13278                    m,
13279                    w.in_features(),
13280                    w.out_features(),
13281                    *row_bytes,
13282                    g.cols,
13283                )?));
13284            }
13285        }
13286        Ok(None)
13287    }
13288
13289    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
13290    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
13291    ///
13292    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
13293    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
13294    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
13295    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
13296    /// prefill keeps the floor's arithmetic and the floor's kernels.
13297    ///
13298    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
13299    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
13300    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
13301    /// (projection, prefill call) and frees immediately.
13302    ///
13303    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
13304    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
13305    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
13306    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
13307    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
13308    /// single-variable comparison instead of a two-variable one.
13309    ///
13310    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
13311    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
13312    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
13313    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
13314    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
13315    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
13316    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
13317    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
13318    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
13319    ///
13320    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
13321    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
13322    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
13323    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
13324    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
13325    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
13326    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
13327    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
13328    /// because v2's denominator had its slab already resident while this class's floor must build it
13329    /// every call; same tile, opposite sign, because the question changed.
13330    ///
13331    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
13332    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
13333    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
13334    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
13335    fn try_e4m3_blk_prefill(
13336        &self,
13337        w: &crate::model::GpuTensor,
13338        x: &CudaSlice<f32>,
13339        m: usize,
13340    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13341        use crate::model::GpuTensor;
13342        let GpuTensor::Quant {
13343            bytes,
13344            qtype,
13345            blk: Some(g),
13346            ..
13347        } = w
13348        else {
13349            return Ok(None);
13350        };
13351        if *qtype != QT_F8_E4M3_BLK {
13352            return Ok(None);
13353        }
13354        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
13355        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
13356        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
13357        // through to the dequant below when they do, never silently produce nothing.
13358        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
13359            return Ok(Some(y));
13360        }
13361        let (in_f, out_f) = (w.in_features(), w.out_features());
13362        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
13363        let tmp = GpuTensor::Quant {
13364            bytes: slab,
13365            qtype: QT_Q8_0,
13366            row_bytes: in_f / 32 * 34,
13367            ne: vec![in_f as u64, out_f as u64],
13368            scale: 1.0,
13369            rp: false,
13370            #[cfg(memra_cutlass)]
13371            cutlass: None,
13372            fp8: None,
13373            blk: None,
13374            f16: None,
13375            rp4: None,
13376        };
13377        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
13378        Ok(Some(self.matmul(&tmp, x, m)?))
13379    }
13380
13381    pub fn matmul_pre_noscale(
13382        &self,
13383        w: &crate::model::GpuTensor,
13384        aq: &CudaSlice<i8>,
13385        ad: &CudaSlice<f32>,
13386        m: usize,
13387    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
13388        use crate::model::GpuTensor;
13389        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
13390        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
13391        // rather than let the tail below refuse and cost the caller a re-dispatch.
13392        if m == 1 {
13393            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
13394                return Ok(Some((y, 1.0)));
13395            }
13396        }
13397        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
13398        if m != 1 || !self.uses_q8_1_fast(w) {
13399            return Ok(None);
13400        }
13401        let in_f = w.in_features();
13402        let out_f = w.out_features();
13403        let (bytes, qtype, row_bytes, scale, rp) = match w {
13404            GpuTensor::Quant {
13405                bytes,
13406                qtype,
13407                row_bytes,
13408                scale,
13409                rp,
13410                ..
13411            } => (bytes, *qtype, *row_bytes, *scale, *rp),
13412            _ => return Ok(None),
13413        };
13414        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
13415        if self.mmvq_supports(qtype) {
13416            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
13417            let (mbytes, mrp) = match w {
13418                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
13419                _ => (bytes, rp),
13420            };
13421            let y = self.qmatvec_mmvq(
13422                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
13423            )?;
13424            return Ok(Some((y, scale)));
13425        }
13426        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
13427        let name = match qtype {
13428            QT_Q8_0 => "qmatvec_q8_0_dp4a",
13429            QT_Q4_K => "qmatvec_q4_K_dp4a",
13430            QT_Q6_K => "qmatvec_q6_K_dp4a",
13431            QT_Q5_K => "qmatvec_q5_K_dp4a",
13432            QT_Q3_K => "qmatvec_q3_K_dp4a",
13433            QT_NVFP4 => {
13434                if rp {
13435                    "qmatvec_nvfp4_dp4a_rp"
13436                } else {
13437                    "qmatvec_nvfp4_dp4a"
13438                }
13439            }
13440            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
13441            _ => return Ok(None),
13442        };
13443        let f = self.func(name);
13444        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13445        let cfg = LaunchConfig {
13446            grid_dim: (out_f as u32, m as u32, 1),
13447            block_dim: (128, 1, 1),
13448            shared_mem_bytes: 0,
13449        };
13450        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13451        let __s_b = self.gpu.stream();
13452        let mut b = __s_b.launch_builder(&f);
13453        b.arg(bytes)
13454            .arg(aq)
13455            .arg(ad)
13456            .arg(&mut y)
13457            .arg(&inf)
13458            .arg(&outf)
13459            .arg(&mi)
13460            .arg(&rb);
13461        unsafe {
13462            b.launch(cfg)?;
13463        }
13464        Ok(Some((y, scale)))
13465    }
13466
13467    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
13468    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
13469    pub fn mmvq_supports(&self, qtype: i32) -> bool {
13470        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
13471        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
13472        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
13473        // is a pure function of the dtype — the decode-parity law holds under every env.
13474        if qtype == QT_F8_E4M3 {
13475            return true;
13476        }
13477        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
13478            return false;
13479        }
13480        matches!(
13481            qtype,
13482            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
13483        )
13484    }
13485
13486    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
13487    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
13488    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
13489    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
13490    pub fn qmatvec_mmvq(
13491        &self,
13492        bytes: &CudaSlice<u8>,
13493        aq: &CudaSlice<i8>,
13494        ad: &CudaSlice<f32>,
13495        m: usize,
13496        in_f: usize,
13497        out_f: usize,
13498        qtype: i32,
13499        row_bytes: usize,
13500        scale: f32,
13501        rp: bool,
13502    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13503        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
13504        self.qmatvec_mmvq_into(
13505            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
13506        )?;
13507        Ok(y)
13508    }
13509
13510    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
13511    #[allow(clippy::too_many_arguments)]
13512    pub fn qmatvec_mmvq_into(
13513        &self,
13514        bytes: &CudaSlice<u8>,
13515        aq: &CudaSlice<i8>,
13516        ad: &CudaSlice<f32>,
13517        m: usize,
13518        in_f: usize,
13519        out_f: usize,
13520        qtype: i32,
13521        row_bytes: usize,
13522        scale: f32,
13523        rp: bool,
13524        y: &mut CudaSlice<f32>,
13525    ) -> Result<(), Box<dyn std::error::Error>> {
13526        debug_assert!(y.len() >= m * out_f);
13527        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
13528        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
13529        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
13530        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
13531        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
13532        if qtype == QT_Q8_0
13533            && rp
13534            && m == 1
13535            && out_f >= 64
13536            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
13537            && {
13538                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13539                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
13540            }
13541        {
13542            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
13543            let cfg = LaunchConfig {
13544                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
13545                block_dim: (32, 2, 1),
13546                shared_mem_bytes: 0,
13547            };
13548            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
13549            let __s_b = self.gpu.stream();
13550            let mut b = __s_b.launch_builder(&f);
13551            b.arg(bytes)
13552                .arg(aq)
13553                .arg(ad)
13554                .arg(&mut *y)
13555                .arg(&inf)
13556                .arg(&outf)
13557                .arg(&mi)
13558                .arg(&rb);
13559            unsafe {
13560                b.launch(cfg)?;
13561            }
13562            if scale != 1.0 {
13563                self.scale_inplace(y, scale, out_f)?;
13564            }
13565            return Ok(());
13566        }
13567        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
13568        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
13569        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
13570        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
13571        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
13572        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
13573        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
13574        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
13575        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
13576            2
13577        } else {
13578            1
13579        };
13580        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
13581        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
13582        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
13583        // valid-window interleaved, bit-identical per row — same dot program).
13584        if m == 1 && qtype == QT_Q4_0 {
13585            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13586            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
13587            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
13588            mr = *Q40MR.get_or_init(|| {
13589                std::env::var("MEMRA_Q40_MR")
13590                    .ok()
13591                    .and_then(|v| v.parse().ok())
13592                    .unwrap_or(1)
13593            });
13594        }
13595        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
13596        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
13597        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
13598        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
13599        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
13600        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
13601        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
13602        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
13603        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
13604        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
13605        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
13606        let q5_force = q5_mode.as_deref() == Some("2");
13607        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
13608        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
13609        let q5_il = qtype == QT_Q5_K
13610            && m == 1
13611            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
13612        if q5_il && !q5_force && out_f > 65536 {
13613            mr = 1;
13614        }
13615        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
13616        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
13617        if qtype == QT_Q4_0 && rp && mr != 1 {
13618            mr = 2;
13619        }
13620        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
13621        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
13622        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
13623        if qtype == QT_Q8_0 && rp {
13624            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
13625            mr = *Q80MR.get_or_init(|| {
13626                std::env::var("MEMRA_Q80_MR")
13627                    .ok()
13628                    .and_then(|v| v.parse().ok())
13629                    .unwrap_or(1)
13630            });
13631        }
13632        let name = match (qtype, mr, rp) {
13633            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
13634            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
13635            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
13636            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
13637            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
13638            (QT_Q5_K, 2, _) => {
13639                if q5_il {
13640                    "qmatvec_q5_K_mmvq_mr2_il"
13641                } else {
13642                    "qmatvec_q5_K_mmvq_mr2"
13643                }
13644            }
13645            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
13646            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
13647            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
13648            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
13649            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
13650            (QT_Q8_0, _, true)
13651                if in_f % 1024 == 0 && {
13652                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13653                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
13654                } =>
13655            {
13656                "qmatvec_q8_0_mmvq_rpca"
13657            }
13658            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
13659            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
13660            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
13661            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
13662            // reach a GGUF-layout kernel or vice versa.
13663            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
13664            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
13665            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
13666            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
13667            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
13668            (QT_Q5_K, _, _) => {
13669                if q5_il {
13670                    "qmatvec_q5_K_mmvq_il"
13671                } else {
13672                    "qmatvec_q5_K_mmvq"
13673                }
13674            }
13675            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
13676            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
13677            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
13678            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
13679        };
13680        let f = self.func(name);
13681        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
13682        let rows_per_block = ROWS_PER_BLOCK * mr;
13683        let cfg = LaunchConfig {
13684            grid_dim: (
13685                (out_f as u32 + rows_per_block - 1) / rows_per_block,
13686                m as u32,
13687                1,
13688            ),
13689            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
13690            shared_mem_bytes: 0,                // warp-only reduce at m=1
13691        };
13692        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13693        let __s_b = self.gpu.stream();
13694        let mut b = __s_b.launch_builder(&f);
13695        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
13696        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
13697        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
13698        // weight_scale). Other mmvq kernels keep the 8-arg signature.
13699        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
13700            // PDL wave-B: the nvfp4 mr2_rp single (gemma wo / generic rp singles) joins
13701            // the wave-A launch class — 9-arg flavor (fused macro-scale epilogue).
13702            if Self::pdl_on()
13703                && Self::pdl_mmvq_on()
13704                && Self::pdl_nvfp4q8_on()
13705                && name == "qmatvec_nvfp4_mmvq_mr2_rp"
13706            {
13707                use cudarc::driver::{DevicePtr, DevicePtrMut};
13708                let s = &self.gpu.stream();
13709                let (pw, _g0) = bytes.device_ptr(s);
13710                let (paq, _g1) = aq.device_ptr(s);
13711                let (pad, _g2) = ad.device_ptr(s);
13712                let (py, _g3) = y.device_ptr_mut(s);
13713                let mut ps = [
13714                    &pw as *const _ as *mut std::ffi::c_void,
13715                    &paq as *const _ as *mut _,
13716                    &pad as *const _ as *mut _,
13717                    &py as *const _ as *mut _,
13718                    &inf as *const _ as *mut _,
13719                    &outf as *const _ as *mut _,
13720                    &mi as *const _ as *mut _,
13721                    &rb as *const _ as *mut _,
13722                    &scale as *const _ as *mut _,
13723                ];
13724                unsafe {
13725                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13726                }
13727                return Ok(());
13728            }
13729            b.arg(bytes)
13730                .arg(aq)
13731                .arg(ad)
13732                .arg(&mut *y)
13733                .arg(&inf)
13734                .arg(&outf)
13735                .arg(&mi)
13736                .arg(&rb)
13737                .arg(&scale);
13738            unsafe {
13739                b.launch(cfg)?;
13740            }
13741        } else if Self::pdl_on()
13742            && Self::pdl_mmvq_on()
13743            && (matches!(
13744                name,
13745                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
13746            ) || (Self::pdl_nvfp4q8_on()
13747                && matches!(name, "qmatvec_q8_0_mmvq_rp" | "qmatvec_q8_0_mmvq_mr2_rp")))
13748        {
13749            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
13750            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
13751            // names may take this launch (unmarked kernels would read unordered).
13752            {
13753                use cudarc::driver::{DevicePtr, DevicePtrMut};
13754                let s = &self.gpu.stream();
13755                let (pw, _g0) = bytes.device_ptr(s);
13756                let (paq, _g1) = aq.device_ptr(s);
13757                let (pad, _g2) = ad.device_ptr(s);
13758                let (py, _g3) = y.device_ptr_mut(s);
13759                let mut ps = [
13760                    &pw as *const _ as *mut std::ffi::c_void,
13761                    &paq as *const _ as *mut _,
13762                    &pad as *const _ as *mut _,
13763                    &py as *const _ as *mut _,
13764                    &inf as *const _ as *mut _,
13765                    &outf as *const _ as *mut _,
13766                    &mi as *const _ as *mut _,
13767                    &rb as *const _ as *mut _,
13768                ];
13769                unsafe {
13770                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
13771                }
13772            }
13773            if scale != 1.0 {
13774                self.scale_inplace(y, scale, m * out_f)?;
13775            }
13776        } else {
13777            b.arg(bytes)
13778                .arg(aq)
13779                .arg(ad)
13780                .arg(&mut *y)
13781                .arg(&inf)
13782                .arg(&outf)
13783                .arg(&mi)
13784                .arg(&rb);
13785            unsafe {
13786                b.launch(cfg)?;
13787            }
13788            if scale != 1.0 {
13789                self.scale_inplace(y, scale, m * out_f)?;
13790            }
13791        }
13792        Ok(())
13793    }
13794
13795    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
13796    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
13797    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
13798    pub fn qmatvec_mmvq_raw(
13799        &self,
13800        bytes: &CudaSlice<u8>,
13801        x: &CudaSlice<f32>,
13802        m: usize,
13803        in_f: usize,
13804        out_f: usize,
13805        qtype: i32,
13806        row_bytes: usize,
13807        rp: bool,
13808    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13809        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13810        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
13811    }
13812
13813    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
13814    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
13815    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
13816    pub fn batched_supports(&self, qtype: i32) -> bool {
13817        matches!(
13818            qtype,
13819            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
13820        )
13821    }
13822
13823    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
13824    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
13825    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
13826    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
13827    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
13828    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
13829    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
13830    pub fn iq_fast_enabled() -> bool {
13831        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13832        *ON.get_or_init(|| {
13833            std::env::var("MEMRA_IQ_FAST")
13834                .map(|v| v != "0")
13835                .unwrap_or(true)
13836        })
13837    }
13838
13839    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
13840    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
13841    pub fn b8_enabled() -> bool {
13842        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13843        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
13844    }
13845
13846    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
13847    pub fn batched_mcols(m: usize) -> usize {
13848        if m == 2 {
13849            2
13850        } else if m <= 4 {
13851            4
13852        } else if m <= 8 {
13853            8
13854        } else {
13855            16
13856        }
13857    }
13858
13859    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
13860    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
13861    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
13862    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
13863    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
13864        Some(match (qtype, mcols) {
13865            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
13866            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
13867            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
13868            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
13869            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
13870            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
13871            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
13872            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
13873            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
13874            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
13875            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
13876            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
13877            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
13878            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
13879            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
13880            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
13881            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
13882            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
13883            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
13884            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
13885            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
13886            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
13887            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
13888            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
13889            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
13890            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
13891            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
13892            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
13893            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
13894            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
13895            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
13896            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
13897            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
13898            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
13899            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
13900            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
13901            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
13902            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
13903            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
13904            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
13905            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
13906            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
13907            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
13908            _ => return None,
13909        })
13910    }
13911
13912    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
13913    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
13914    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
13915    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
13916    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
13917    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
13918    ///
13919    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
13920    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
13921    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
13922    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
13923    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
13924    /// msweep on all six 27B shapes (2026-07-03):
13925    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
13926    ///          it applies for b4 (-3..-14%), never loses;
13927    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
13928    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
13929    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
13930    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
13931    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
13932    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
13933    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
13934    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
13935    /// b2: in_f>=6144 -> r2, else base.
13936    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
13937    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
13938    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
13939    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
13940    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
13941    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
13942    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
13943    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
13944    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
13945    /// Device SM count (cached) — grid-fill policy input.
13946    pub fn sm_count(&self) -> i32 {
13947        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13948        *SMS.get_or_init(|| {
13949            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13950            self.gpu
13951                .ctx
13952                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13953                .unwrap_or(82)
13954        })
13955    }
13956
13957    pub fn batched_variant(
13958        &self,
13959        _m: usize,
13960        in_f: usize,
13961        out_f: usize,
13962        qtype: i32,
13963        row_bytes: usize,
13964        mcols: usize,
13965        rp: bool,
13966    ) -> &'static str {
13967        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
13968        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
13969        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
13970        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
13971        if qtype == QT_Q8_0 {
13972            return if rp { "rp" } else { "base" };
13973        }
13974        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13975        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
13976            Ok("base") => "base",
13977            Ok("pf") => "pf",
13978            Ok("r2") => "r2",
13979            Ok("r2w8") => "r2w8",
13980            Ok("pfr2") => "pfr2",
13981            Ok("ca") => "ca",
13982            Ok("car2") => "car2",
13983            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
13984            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
13985            Ok("rp") => "rp",
13986            Ok("rpr2") => "rpr2",
13987            Ok("rpr2w8") => "rpr2w8",
13988            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
13989            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
13990            Ok("rpca") => "rpca",
13991            Ok("rpcar2") => "rpcar2",
13992            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
13993            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
13994            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
13995            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
13996            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
13997            // bit-identical to the decode path — measurement corpus ONLY, never auto).
13998            Ok("rpsc") => "rpsc",
13999            Ok("rpms") => "rpms",
14000            Ok("rpmsc") => "rpmsc",
14001            Ok("rpks") => "rpks",
14002            Ok("rpksc") => "rpksc",
14003            _ => "auto",
14004        });
14005        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
14006        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
14007        // shapes qualify; anything else falls back to the register variants.
14008        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
14009        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
14010        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
14011        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
14012        // forced MEMRA_MMVQ_BV values still work).
14013        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14014        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
14015        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
14016        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
14017        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
14018        let sms = *SMS.get_or_init(|| {
14019            use cudarc::driver::sys::CUdevice_attribute_enum as A;
14020            self.gpu
14021                .ctx
14022                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
14023                .unwrap_or(82)
14024        });
14025        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
14026        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
14027        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
14028        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
14029        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
14030        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
14031        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
14032        // AUTO RULE = the measured winners table (differs from NVFP4's!):
14033        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
14034        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
14035        //     r2 1258us) — kernels kept behind the force seam for the corpus;
14036        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
14037        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
14038        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
14039        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
14040        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
14041        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
14042        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
14043        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
14044        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
14045        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
14046        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
14047        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14048        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
14049            Ok("base") => "base",
14050            Ok("r2") => "r2",
14051            Ok("r2w8") => "r2w8",
14052            _ => "auto",
14053        });
14054        let variant: &'static str = if qtype == QT_Q4_0 {
14055            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
14056            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
14057            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
14058            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
14059            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
14060                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
14061                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
14062                // + syncs cost more than the stalls, bank-pad made no difference);
14063                // register load-ahead flat (nvcc already reorders). The b-tier limiter
14064                // is still unidentified — see the jsonl row.
14065                Ok("base") => "base",
14066                Ok("r2") => "r2",
14067                Ok("ms") => "ms",
14068                Ok("sm") => "sm",
14069                Ok("la") => "la",
14070                _ => "auto",
14071            });
14072            let v = if q40 != "auto" {
14073                q40
14074            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
14075                "r2"
14076            } else {
14077                "base"
14078            };
14079            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
14080            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
14081            // and the limiter is the per-column activation load chain (long_scoreboard
14082            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
14083            if rp {
14084                match v {
14085                    "ms" => "r2ms_rp",
14086                    "sm" => "r2sm_rp",
14087                    "la" => "r2la_rp",
14088                    "r2" => "r2_rp",
14089                    _ => "rp",
14090                }
14091            } else if matches!(v, "ms" | "sm" | "la") {
14092                "r2"
14093            } else {
14094                v
14095            }
14096        } else if qtype != QT_NVFP4 && !kq_r2 {
14097            "base"
14098        } else if kq_r2 && rp {
14099            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
14100            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
14101            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
14102            "rp"
14103        } else if kq_r2 {
14104            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
14105            // mcols != 4 forced r2w8 falls to unbounded r2.
14106            if kq_bv != "auto" {
14107                if kq_bv == "r2w8" && mcols != 4 {
14108                    "r2"
14109                } else {
14110                    kq_bv
14111                }
14112            } else if bv != "auto" {
14113                match bv {
14114                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
14115                    "r2w8" | "rpr2w8" => {
14116                        if mcols != 4 {
14117                            "r2"
14118                        } else {
14119                            "r2w8"
14120                        }
14121                    }
14122                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
14123                }
14124            } else {
14125                let blocks = (out_f + 7) / 8;
14126                let waves = blocks as f64 / (7 * sms as usize) as f64;
14127                let filled = blocks >= 4 * sms as usize;
14128                let use_r2 = if qtype == QT_Q4_K {
14129                    filled
14130                } else {
14131                    waves >= 2.0
14132                };
14133                if use_r2 { "r2" } else { "base" }
14134            }
14135        } else if bv != "auto" {
14136            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
14137            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
14138            // unsupported (shape, mcols) combos fall back to pf/r2.
14139            // On rp buffers, forced legacy names map to their rp twins (layout law).
14140            let v = if bv == "r2w8" && mcols == 2 {
14141                "r2"
14142            } else if bv == "ca" && (!ca_ok || mcols == 8) {
14143                "pf"
14144            } else if bv == "car2" && (!ca_ok || mcols == 8) {
14145                "r2"
14146            } else if bv == "pfr2" && mcols == 8 {
14147                "r2"
14148            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
14149                "rpr2"
14150            }
14151            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
14152            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
14153                if mcols == 8 { "rpr2w8" } else { "rpr2" }
14154            } else if bv == "rpcar2" && mcols == 2 {
14155                "rpca"
14156            }
14157            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
14158            // (rpms has no smem and no alignment need — always valid on rp buffers).
14159            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
14160                "rpr2"
14161            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
14162                "rpr2"
14163            } else {
14164                bv
14165            };
14166            if rp {
14167                match v {
14168                    "base" | "pf" | "ca" | "rp" => "rp",
14169                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
14170                    "r2w8" | "rpr2w8" => {
14171                        if mcols == 2 {
14172                            "rpr2"
14173                        } else {
14174                            "rpr2w8"
14175                        }
14176                    }
14177                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
14178                }
14179            } else {
14180                v
14181            }
14182        } else if mcols == 8 {
14183            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
14184            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
14185            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
14186            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
14187            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
14188            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
14189            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
14190            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
14191            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
14192            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
14193            if rp {
14194                if sc_ok { "rpsc" } else { "rpr2w8" }
14195            } else {
14196                "r2w8"
14197            }
14198        } else if mcols >= 4 {
14199            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
14200            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
14201            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
14202            let blocks = (out_f + 7) / 8;
14203            let r7 = 7 * sms as usize;
14204            let r8 = 8 * sms as usize;
14205            let waves = blocks as f64 / r7 as f64;
14206            let filled = blocks >= 4 * sms as usize;
14207            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
14208            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
14209            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
14210            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
14211                // the extra residency drops the INTEGER wave count -> the straggler wave a
14212                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
14213                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
14214                if rp { "rpr2w8" } else { "r2w8" }
14215            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
14216                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
14217                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
14218                if rp { "rpr2" } else { "r2" }
14219            } else {
14220                // fractional straggler-wave window with no crossing, or grid too small to fill
14221                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
14222                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
14223                if rp { "rp" } else { "pf" }
14224            }
14225        } else if in_f >= 6144 {
14226            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
14227            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
14228            // stays.
14229            if rp { "rpr2" } else { "r2" }
14230        } else if rp {
14231            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
14232            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
14233            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
14234            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
14235            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
14236            if sc_ok && waves >= 0.9 && waves <= 1.1 {
14237                "rpsc"
14238            } else {
14239                "rp"
14240            }
14241        } else {
14242            "base"
14243        };
14244        variant
14245    }
14246
14247    pub fn qmatvec_mmvq_batched(
14248        &self,
14249        bytes: &CudaSlice<u8>,
14250        aq: &CudaSlice<i8>,
14251        ad: &CudaSlice<f32>,
14252        m: usize,
14253        in_f: usize,
14254        out_f: usize,
14255        qtype: i32,
14256        row_bytes: usize,
14257        mcols: usize,
14258        scale: f32,
14259        rp: bool,
14260    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14261        const ROWS_PER_BLOCK: u32 = 4;
14262        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
14263        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
14264        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
14265        // weight keeps its rp-layout kernel family regardless of the override.
14266        let forced: Option<&'static str> = {
14267            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
14268            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
14269                .as_deref()
14270                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
14271        };
14272        let variant = match forced {
14273            Some(v) if !rp || v.contains("rp") => v,
14274            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
14275        };
14276        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
14277            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
14278        })?;
14279        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
14280        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
14281        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
14282        let variant = if mcols == 16 {
14283            if rp { "rp" } else { "base" }
14284        } else {
14285            variant
14286        };
14287        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
14288        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
14289        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
14290        // per-(token,row) chain (columns c >= m never execute in either form) ->
14291        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
14292        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
14293        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14294        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
14295        if b567
14296            && qtype == QT_NVFP4
14297            && rp
14298            && mcols == 8
14299            && (5..=7).contains(&m)
14300            && matches!(variant, "rpsc" | "rpr2w8")
14301        {
14302            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
14303            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
14304            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14305            let cfg = LaunchConfig {
14306                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14307                block_dim: (32, ROWS_PER_BLOCK, 1),
14308                shared_mem_bytes: 0,
14309            };
14310            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14311            let __s_b = self.gpu.stream();
14312            let mut b = __s_b.launch_builder(&f);
14313            b.arg(bytes)
14314                .arg(aq)
14315                .arg(ad)
14316                .arg(&mut y)
14317                .arg(&inf)
14318                .arg(&outf)
14319                .arg(&mi)
14320                .arg(&rb);
14321            unsafe {
14322                b.launch(cfg)?;
14323            }
14324            if scale != 1.0 {
14325                self.scale_inplace(&mut y, scale, m * out_f)?;
14326            }
14327            return Ok(y);
14328        }
14329        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
14330            "base" => (base_name.into(), ROWS_PER_BLOCK),
14331            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
14332            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
14333            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
14334            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
14335            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
14336            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
14337            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
14338            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
14339            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
14340            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
14341            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
14342            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
14343            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
14344            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
14345        };
14346        debug_assert!(
14347            !rp || name.contains("_rp"),
14348            "rp weight dispatched to a GGUF-layout kernel"
14349        );
14350        let f = self.func(&name);
14351        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14352        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
14353        let smem = if name.contains("_r2sm_rp") {
14354            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
14355        } else {
14356            0
14357        };
14358        let cfg = LaunchConfig {
14359            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
14360            block_dim: (32, ROWS_PER_BLOCK, 1),
14361            shared_mem_bytes: smem,
14362        };
14363        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14364        let __s_b = self.gpu.stream();
14365        let mut b = __s_b.launch_builder(&f);
14366        b.arg(bytes)
14367            .arg(aq)
14368            .arg(ad)
14369            .arg(&mut y)
14370            .arg(&inf)
14371            .arg(&outf)
14372            .arg(&mi)
14373            .arg(&rb);
14374        unsafe {
14375            b.launch(cfg)?;
14376        }
14377        if scale != 1.0 {
14378            self.scale_inplace(&mut y, scale, m * out_f)?;
14379        }
14380        Ok(y)
14381    }
14382
14383    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
14384    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
14385    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
14386    pub fn qmatvec_batched_raw(
14387        &self,
14388        bytes: &CudaSlice<u8>,
14389        x: &CudaSlice<f32>,
14390        m: usize,
14391        in_f: usize,
14392        out_f: usize,
14393        qtype: i32,
14394        row_bytes: usize,
14395        mcols: usize,
14396        rp: bool,
14397    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14398        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14399        self.qmatvec_mmvq_batched(
14400            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
14401        )
14402    }
14403
14404    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
14405    pub fn qmatvec_nvfp4_batched_raw(
14406        &self,
14407        bytes: &CudaSlice<u8>,
14408        x: &CudaSlice<f32>,
14409        m: usize,
14410        in_f: usize,
14411        out_f: usize,
14412        row_bytes: usize,
14413        mcols: usize,
14414        rp: bool,
14415    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14416        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
14417    }
14418
14419    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
14420    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
14421    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
14422    fn try_fp4_gemm(
14423        &self,
14424        w: &crate::model::GpuTensor,
14425        x: &CudaSlice<f32>,
14426        m: usize,
14427        in_f: usize,
14428        out_f: usize,
14429    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14430        use crate::model::GpuTensor;
14431        if cfg!(memra_portable_cuda) {
14432            return Ok(None);
14433        }
14434        if std::env::var("MEMRA_FP4").is_err() {
14435            return Ok(None);
14436        }
14437        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
14438        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
14439        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
14440        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
14441        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
14442        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
14443        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
14444        // for the common no-macro-scale case.
14445        #[cfg(memra_cutlass)]
14446        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
14447            if let GpuTensor::Quant {
14448                bytes,
14449                qtype,
14450                scale,
14451                row_bytes,
14452                cutlass,
14453                ..
14454            } = w
14455            {
14456                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
14457                    if let Some(cw) = cutlass {
14458                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
14459                        let y = self.cutlass_fp4_gemm(
14460                            &cw.b_packed,
14461                            &cw.sfb_swizzled,
14462                            x,
14463                            *scale,
14464                            m,
14465                            out_f,
14466                            in_f,
14467                        )?;
14468                        return Ok(Some(y));
14469                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
14470                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
14471                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
14472                        // (the load-time repack ~doubles it) — needed for models that don't fit the
14473                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
14474                        let (b_packed, sfb_sw) =
14475                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
14476                        let y =
14477                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
14478                        return Ok(Some(y));
14479                    }
14480                }
14481            }
14482        }
14483        if let GpuTensor::Quant {
14484            bytes,
14485            qtype,
14486            row_bytes,
14487            scale,
14488            rp,
14489            ..
14490        } = w
14491        {
14492            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
14493            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
14494            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
14495                let y =
14496                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
14497                return Ok(Some(y));
14498            }
14499        }
14500        Ok(None)
14501    }
14502
14503    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
14504    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
14505    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
14506    pub fn rms_norm_f16out(
14507        &self,
14508        x: &CudaSlice<f32>,
14509        w: &CudaSlice<f32>,
14510        dst: &mut CudaSlice<f32>,
14511        dst16: &mut CudaSlice<u8>,
14512        ncols: usize,
14513        nrows: usize,
14514        eps: f32,
14515    ) -> Result<(), Box<dyn std::error::Error>> {
14516        let f = self.func("rms_norm_f16out_f32");
14517        let cfg = LaunchConfig {
14518            grid_dim: (nrows as u32, 1, 1),
14519            block_dim: (rms_block(), 1, 1),
14520            shared_mem_bytes: 0,
14521        };
14522        let (nc, e) = (ncols as i32, eps);
14523        let __s_b = self.gpu.stream();
14524        let mut b = __s_b.launch_builder(&f);
14525        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
14526        unsafe {
14527            b.launch(cfg)?;
14528        }
14529        Ok(())
14530    }
14531
14532    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
14533    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
14534    #[allow(clippy::too_many_arguments)]
14535    pub fn add_rms_norm_f16out(
14536        &self,
14537        a: &CudaSlice<f32>,
14538        b: &CudaSlice<f32>,
14539        w: &CudaSlice<f32>,
14540        res: &mut CudaSlice<f32>,
14541        dst: &mut CudaSlice<f32>,
14542        dst16: &mut CudaSlice<u8>,
14543        ncols: usize,
14544        nrows: usize,
14545        eps: f32,
14546    ) -> Result<(), Box<dyn std::error::Error>> {
14547        let f = self.func("add_rms_norm_f16out_f32");
14548        let cfg = LaunchConfig {
14549            grid_dim: (nrows as u32, 1, 1),
14550            block_dim: (rms_block(), 1, 1),
14551            shared_mem_bytes: 0,
14552        };
14553        let (nc, e) = (ncols as i32, eps);
14554        let __s_lb = self.gpu.stream();
14555        let mut lb = __s_lb.launch_builder(&f);
14556        lb.arg(a)
14557            .arg(b)
14558            .arg(w)
14559            .arg(res)
14560            .arg(dst)
14561            .arg(dst16)
14562            .arg(&nc)
14563            .arg(&e);
14564        unsafe {
14565            lb.launch(cfg)?;
14566        }
14567        Ok(())
14568    }
14569
14570    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
14571    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
14572    pub fn matmul_group_xh(
14573        &self,
14574        ws: &[&crate::model::GpuTensor],
14575        x: &CudaSlice<f32>,
14576        xh: &CudaSlice<u8>,
14577        m: usize,
14578    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14579        let mut out = Vec::with_capacity(ws.len());
14580        let in_f = ws[0].in_features();
14581        for w in ws {
14582            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
14583                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
14584                    out.push(y);
14585                    continue;
14586                }
14587            }
14588            out.push(self.matmul(w, x, m)?);
14589        }
14590        Ok(out)
14591    }
14592
14593    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
14594    /// GDN steps). Layouts [T, H].
14595    pub fn gdn_pad_mask(
14596        &self,
14597        beta: &mut CudaSlice<f32>,
14598        g_log: &mut CudaSlice<f32>,
14599        len_d: &CudaSlice<i32>,
14600        h: usize,
14601        t: usize,
14602    ) -> Result<(), Box<dyn std::error::Error>> {
14603        let f = self.func("gdn_pad_mask_f32");
14604        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
14605        let (hi, ti) = (h as i32, t as i32);
14606        let __s_b = self.gpu.stream();
14607        let mut b = __s_b.launch_builder(&f);
14608        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
14609        unsafe {
14610            b.launch(cfg)?;
14611        }
14612        Ok(())
14613    }
14614
14615    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
14616    /// gather for the padded prime graph's h_seed/hlast.
14617    pub fn row_gather_dev(
14618        &self,
14619        src: &CudaSlice<f32>,
14620        dst: &mut CudaSlice<f32>,
14621        len_d: &CudaSlice<i32>,
14622        ncols: usize,
14623    ) -> Result<(), Box<dyn std::error::Error>> {
14624        let f = self.func("row_gather_dev_f32");
14625        let cfg = LaunchConfig::for_num_elems(ncols as u32);
14626        let nc = ncols as i32;
14627        let __s_b = self.gpu.stream();
14628        let mut b = __s_b.launch_builder(&f);
14629        b.arg(src).arg(dst).arg(len_d).arg(&nc);
14630        unsafe {
14631            b.launch(cfg)?;
14632        }
14633        Ok(())
14634    }
14635
14636    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
14637    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
14638    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
14639    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
14640    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
14641    /// different in_f) falls back to its own `matmul` — behavior unchanged.
14642    pub fn matmul_group(
14643        &self,
14644        ws: &[&crate::model::GpuTensor],
14645        x: &CudaSlice<f32>,
14646        m: usize,
14647    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
14648        use crate::model::GpuTensor;
14649        let mut out = Vec::with_capacity(ws.len());
14650        let any_mirror = ws
14651            .iter()
14652            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
14653        if m >= 16 && any_mirror && !self.verify_exact_on() {
14654            let in_f = ws[0].in_features();
14655            let xh = self.f16_act(x, m * in_f, in_f)?;
14656            for w in ws {
14657                if w.in_features() == in_f {
14658                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
14659                        out.push(y);
14660                        continue;
14661                    }
14662                }
14663                out.push(self.matmul(w, x, m)?);
14664            }
14665            return Ok(out);
14666        }
14667        for w in ws {
14668            out.push(self.matmul(w, x, m)?);
14669        }
14670        Ok(out)
14671    }
14672
14673    /// Cross-request grouped matmul (task #13): run ONE projection group over the
14674    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
14675    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
14676    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
14677    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
14678    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
14679    pub fn matmul_group_multi(
14680        &self,
14681        ws: &[&crate::model::GpuTensor],
14682        xs: &[&CudaSlice<f32>],
14683        ms: &[usize],
14684    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
14685        assert_eq!(xs.len(), ms.len());
14686        let in_f = ws[0].in_features();
14687        let total: usize = ms.iter().sum();
14688        let mut xcat = self.uninit(total * in_f)?;
14689        let mut off = 0usize;
14690        for (x, &m) in xs.iter().zip(ms) {
14691            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
14692            off += m;
14693        }
14694        let ys = self.matmul_group(ws, &xcat, total)?;
14695        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
14696        for (w, y) in ws.iter().zip(ys) {
14697            let out_f = w.out_features();
14698            let mut off = 0usize;
14699            for (s, &m) in ms.iter().enumerate() {
14700                let mut ys_s = self.uninit(m * out_f)?;
14701                let src = y.slice(off * out_f..(off + m) * out_f);
14702                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
14703                out[s].push(ys_s);
14704                off += m;
14705            }
14706        }
14707        Ok(out)
14708    }
14709
14710    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
14711    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
14712    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
14713    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
14714    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
14715    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
14716    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
14717    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
14718    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
14719    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
14720        use crate::model::GpuTensor;
14721        if !legacy_quant_gemm_allowed(
14722            cfg!(memra_portable_cuda),
14723            cfg!(memra_hopper_mma),
14724            std::env::var_os("MEMRA_NO_GEMM").is_some(),
14725        ) {
14726            return false;
14727        }
14728        match w {
14729            GpuTensor::Quant { qtype, .. } => {
14730                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
14731                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
14732            }
14733            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
14734        }
14735    }
14736
14737    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
14738    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
14739    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
14740    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
14741    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
14742    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
14743    pub fn qmatvec_gemm(
14744        &self,
14745        w: &crate::model::GpuTensor,
14746        aq: &CudaSlice<i8>,
14747        ad: &CudaSlice<f32>,
14748        m: usize,
14749    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14750        use crate::model::GpuTensor;
14751        let in_f = w.in_features();
14752        let out_f = w.out_features();
14753        let (bytes, qtype, row_bytes, scale, rp) = match w {
14754            GpuTensor::Quant {
14755                bytes,
14756                qtype,
14757                row_bytes,
14758                scale,
14759                rp,
14760                ..
14761            } => (bytes, *qtype, *row_bytes, *scale, *rp),
14762            _ => unreachable!("gemm_supports guaranteed Quant"),
14763        };
14764        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
14765        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
14766        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
14767        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
14768        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
14769        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
14770            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
14771                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
14772                if scale != 1.0 {
14773                    self.scale_inplace(&mut y, scale, m * out_f)?;
14774                }
14775                return Ok(y);
14776            }
14777        }
14778        let name = match qtype {
14779            QT_Q8_0 => "qmatvec_gemm_q8_0",
14780            QT_Q4_K => "qmatvec_gemm_q4_K",
14781            QT_Q4_0 => {
14782                if rp {
14783                    "qmatvec_gemm_q4_0_rp"
14784                } else {
14785                    "qmatvec_gemm_q4_0"
14786                }
14787            }
14788            QT_Q5_K => "qmatvec_gemm_q5_K",
14789            QT_Q6_K => "qmatvec_gemm_q6_K",
14790            QT_NVFP4 => {
14791                if rp {
14792                    "qmatvec_gemm_nvfp4_rp"
14793                } else {
14794                    "qmatvec_gemm_nvfp4"
14795                }
14796            }
14797            _ => unreachable!(),
14798        };
14799        let f = self.func(name);
14800        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14801        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
14802        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
14803        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
14804        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14805        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14806        let k1_tile = if is_k1 {
14807            k1_launch_override().unwrap_or((128, 128, 8))
14808        } else {
14809            (128, 128, 8)
14810        };
14811        let (bm, bn): (u32, u32) = if is_k1 {
14812            (k1_tile.0, k1_tile.1)
14813        } else {
14814            (64, 256)
14815        };
14816        let warps: u32 = if is_k1 {
14817            k1_tile.2
14818        } else {
14819            match qtype {
14820                QT_NVFP4 => 8,
14821                _ => 4,
14822            }
14823        };
14824        let cfg = LaunchConfig {
14825            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14826            block_dim: (32, warps, 1),
14827            shared_mem_bytes: 0,
14828        };
14829        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14830        let __s_b = self.gpu.stream();
14831        let mut b = __s_b.launch_builder(&f);
14832        b.arg(bytes)
14833            .arg(aq)
14834            .arg(ad)
14835            .arg(&mut y)
14836            .arg(&inf)
14837            .arg(&outf)
14838            .arg(&mi)
14839            .arg(&rb);
14840        unsafe {
14841            b.launch(cfg)?;
14842        }
14843        if scale != 1.0 {
14844            self.scale_inplace(&mut y, scale, m * out_f)?;
14845        }
14846        Ok(y)
14847    }
14848
14849    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
14850    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
14851    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
14852    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
14853    pub fn qmatvec_gemm_raw(
14854        &self,
14855        bytes: &CudaSlice<u8>,
14856        x: &CudaSlice<f32>,
14857        m: usize,
14858        in_f: usize,
14859        out_f: usize,
14860        qtype: i32,
14861        row_bytes: usize,
14862    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14863        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14864        let name = match qtype {
14865            QT_Q8_0 => "qmatvec_gemm_q8_0",
14866            QT_Q4_K => "qmatvec_gemm_q4_K",
14867            QT_Q4_0 => "qmatvec_gemm_q4_0",
14868            QT_Q5_K => "qmatvec_gemm_q5_K",
14869            QT_Q6_K => "qmatvec_gemm_q6_K",
14870            QT_NVFP4 => "qmatvec_gemm_nvfp4",
14871            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
14872            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
14873        };
14874        let f = self.func(name);
14875        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14876        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
14877        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
14878        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14879        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14880        let k1_tile = if is_k1 {
14881            k1_launch_override().unwrap_or((128, 128, 8))
14882        } else {
14883            (128, 128, 8)
14884        };
14885        let (bm, bn): (u32, u32) = if is_k1 {
14886            (k1_tile.0, k1_tile.1)
14887        } else {
14888            (64, 256)
14889        };
14890        let warps: u32 = if is_k1 {
14891            k1_tile.2
14892        } else {
14893            match qtype {
14894                QT_NVFP4 | QT_NVFP4_RP => 8,
14895                _ => 4,
14896            }
14897        };
14898        let cfg = LaunchConfig {
14899            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14900            block_dim: (32, warps, 1),
14901            shared_mem_bytes: 0,
14902        };
14903        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14904        let __s_b = self.gpu.stream();
14905        let mut b = __s_b.launch_builder(&f);
14906        b.arg(bytes)
14907            .arg(&aq)
14908            .arg(&ad)
14909            .arg(&mut y)
14910            .arg(&inf)
14911            .arg(&outf)
14912            .arg(&mi)
14913            .arg(&rb);
14914        unsafe {
14915            b.launch(cfg)?;
14916        }
14917        Ok(y)
14918    }
14919
14920    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
14921    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
14922    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
14923    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
14924    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
14925    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
14926    pub fn qmatvec_gemm_q8_0_wgmma_raw(
14927        &self,
14928        rp4: &CudaSlice<u8>,
14929        aq: &CudaSlice<i8>,
14930        ad: &CudaSlice<f32>,
14931        m: usize,
14932        in_f: usize,
14933        out_f: usize,
14934    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14935        assert!(
14936            out_f % 64 == 0 && in_f % 32 == 0,
14937            "wgmma GEMM needs out_f%64==0, in_f%32==0"
14938        );
14939        let f = self.func("qmatvec_gemm_q8_0_wgmma");
14940        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
14941        let cfg = LaunchConfig {
14942            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
14943            block_dim: (128, 1, 1),
14944            shared_mem_bytes: 0,
14945        };
14946        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
14947        let __s_b = self.gpu.stream();
14948        let mut b = __s_b.launch_builder(&f);
14949        b.arg(rp4)
14950            .arg(aq)
14951            .arg(ad)
14952            .arg(&mut y)
14953            .arg(&inf)
14954            .arg(&outf)
14955            .arg(&mi);
14956        unsafe {
14957            b.launch(cfg)?;
14958        }
14959        Ok(y)
14960    }
14961
14962    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
14963    pub fn scale_inplace(
14964        &self,
14965        y: &mut CudaSlice<f32>,
14966        s: f32,
14967        n: usize,
14968    ) -> Result<(), Box<dyn std::error::Error>> {
14969        let f = self.func("scale_f32");
14970        let cfg = LaunchConfig::for_num_elems(n as u32);
14971        let (sf, ni) = (s, n as i32);
14972        let __s_b = self.gpu.stream();
14973        let mut b = __s_b.launch_builder(&f);
14974        b.arg(y).arg(&sf).arg(&ni);
14975        unsafe {
14976            b.launch(cfg)?;
14977        }
14978        Ok(())
14979    }
14980
14981    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
14982    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
14983    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
14984    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
14985    pub fn bf16_to_f32(
14986        &self,
14987        data: &cudarc::driver::CudaView<'_, u8>,
14988        n: usize,
14989    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14990        let mut out = self.alloc_uninit::<f32>(n)?;
14991        let f = self.func("bf16_to_f32");
14992        let cfg = LaunchConfig::for_num_elems(n as u32);
14993        let ni = n as i32;
14994        let __s_b = self.gpu.stream();
14995        let mut b = __s_b.launch_builder(&f);
14996        b.arg(data).arg(&mut out).arg(&ni);
14997        unsafe {
14998            b.launch(cfg)?;
14999        }
15000        Ok(out)
15001    }
15002
15003    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
15004    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
15005    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
15006    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
15007    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
15008    /// calls, the spec-verify contract) vs plain linear.
15009    fn linear_bf16_chunked(
15010        &self,
15011        x: &CudaSlice<f32>,
15012        data: &CudaSlice<u8>,
15013        m: usize,
15014        in_f: usize,
15015        out_f: usize,
15016        exact: bool,
15017    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15018        const CHUNK_BYTES: usize = 256 << 20;
15019        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
15020        if chunk_rows >= out_f {
15021            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
15022            return if exact {
15023                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
15024            } else {
15025                self.linear(x, &wf32, m, in_f, out_f)
15026            };
15027        }
15028        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
15029        let mut r0 = 0usize;
15030        while r0 < out_f {
15031            let rows = chunk_rows.min(out_f - r0);
15032            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
15033            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
15034            let yc = if exact {
15035                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
15036            } else {
15037                self.linear(x, &wf32, m, in_f, rows)?
15038            };
15039            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
15040            for mi in 0..m {
15041                let src = yc.slice(mi * rows..(mi + 1) * rows);
15042                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
15043                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
15044            }
15045            r0 += rows;
15046        }
15047        Ok(y)
15048    }
15049
15050    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
15051    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
15052    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
15053    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
15054    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
15055    /// router/shexp sites and matmul_decode_exact's Float arm.
15056    pub fn linear_decode_exact(
15057        &self,
15058        x: &CudaSlice<f32>,
15059        w: &CudaSlice<f32>,
15060        m_tokens: usize,
15061        in_f: usize,
15062        out_f: usize,
15063    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15064        if m_tokens == 1 {
15065            return self.linear(x, w, 1, in_f, out_f);
15066        }
15067        let xv = self.view(x, m_tokens * in_f);
15068        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
15069        for t in 0..m_tokens {
15070            let row = xv.slice(t * in_f..(t + 1) * in_f);
15071            let mut xr = self.alloc_uninit::<f32>(in_f)?;
15072            self.copy_view_into(&mut xr, 0, &row, in_f)?;
15073            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
15074            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
15075        }
15076        Ok(y)
15077    }
15078
15079    pub fn linear(
15080        &self,
15081        x: &CudaSlice<f32>,
15082        w: &CudaSlice<f32>,
15083        m_tokens: usize,
15084        in_f: usize,
15085        out_f: usize,
15086    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
15087        use cudarc::cublaslt::{Matmul, MatmulConfig};
15088        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
15089        let cfg = MatmulConfig {
15090            transa: true,
15091            transb: false,
15092            transc: false,
15093            m: out_f as u64,
15094            n: m_tokens as u64,
15095            k: in_f as u64,
15096            alpha: 1.0,
15097            lda: in_f as i64,
15098            ldb: in_f as i64,
15099            beta: 0.0,
15100            ldc: out_f as i64,
15101            stride_a: None,
15102            stride_b: None,
15103            stride_c: None,
15104            stride_bias: None,
15105            batch_size: None,
15106        };
15107        unsafe {
15108            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
15109        }
15110        Ok(c)
15111    }
15112
15113    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
15114    pub fn sdpa_naive(
15115        &self,
15116        q: &CudaSlice<f32>,
15117        k: &CudaSlice<f32>,
15118        v: &CudaSlice<f32>,
15119        o: &mut CudaSlice<f32>,
15120        head_dim: usize,
15121        n_head: usize,
15122        n_head_kv: usize,
15123        t: usize,
15124        t_kv: usize,
15125        scale: f32,
15126        causal: bool,
15127    ) -> Result<(), Box<dyn std::error::Error>> {
15128        let f = self.func("sdpa_naive_f32");
15129        let cfg = LaunchConfig {
15130            grid_dim: (n_head as u32, t as u32, 1),
15131            block_dim: (128, 1, 1),
15132            shared_mem_bytes: (t_kv * 4) as u32,
15133        };
15134        let (hd, nh, nhkv, ti, tkvi, cz) = (
15135            head_dim as i32,
15136            n_head as i32,
15137            n_head_kv as i32,
15138            t as i32,
15139            t_kv as i32,
15140            causal as i32,
15141        );
15142        let __s_b = self.gpu.stream();
15143        let mut b = __s_b.launch_builder(&f);
15144        b.arg(q)
15145            .arg(k)
15146            .arg(v)
15147            .arg(o)
15148            .arg(&hd)
15149            .arg(&nh)
15150            .arg(&nhkv)
15151            .arg(&ti)
15152            .arg(&tkvi)
15153            .arg(&scale)
15154            .arg(&cz);
15155        unsafe {
15156            b.launch(cfg)?;
15157        }
15158        Ok(())
15159    }
15160
15161    /// Island twin (lane/gemma-vision): causal + sliding-window attention with
15162    /// bidirectional image islands. `span_id` labels each absolute kv position
15163    /// (-1 text, >=0 island id); same-island keys are visible unconditionally,
15164    /// reproducing the reference's non-causal image batch. window 0 = no window.
15165    #[allow(clippy::too_many_arguments)]
15166    pub fn sdpa_naive_island(
15167        &self,
15168        q: &CudaSlice<f32>,
15169        k: &CudaSlice<f32>,
15170        v: &CudaSlice<f32>,
15171        o: &mut CudaSlice<f32>,
15172        span_id: &CudaSlice<i32>,
15173        head_dim: usize,
15174        n_head: usize,
15175        n_head_kv: usize,
15176        t: usize,
15177        t_kv: usize,
15178        scale: f32,
15179        window: usize,
15180    ) -> Result<(), Box<dyn std::error::Error>> {
15181        let f = self.func("sdpa_naive_island_f32");
15182        let cfg = LaunchConfig {
15183            grid_dim: (n_head as u32, t as u32, 1),
15184            block_dim: (128, 1, 1),
15185            shared_mem_bytes: (t_kv * 4) as u32,
15186        };
15187        let (hd, nh, nhkv, ti, tkvi, 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            window as i32,
15194        );
15195        let __s_b = self.gpu.stream();
15196        let mut b = __s_b.launch_builder(&f);
15197        b.arg(q)
15198            .arg(k)
15199            .arg(v)
15200            .arg(o)
15201            .arg(span_id)
15202            .arg(&hd)
15203            .arg(&nh)
15204            .arg(&nhkv)
15205            .arg(&ti)
15206            .arg(&tkvi)
15207            .arg(&scale)
15208            .arg(&wi);
15209        unsafe {
15210            b.launch(cfg)?;
15211        }
15212        Ok(())
15213    }
15214
15215    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
15216    #[allow(clippy::too_many_arguments)]
15217    pub fn sdpa_naive_w(
15218        &self,
15219        q: &CudaSlice<f32>,
15220        k: &CudaSlice<f32>,
15221        v: &CudaSlice<f32>,
15222        o: &mut CudaSlice<f32>,
15223        head_dim: usize,
15224        n_head: usize,
15225        n_head_kv: usize,
15226        t: usize,
15227        t_kv: usize,
15228        scale: f32,
15229        causal: bool,
15230        window: usize,
15231    ) -> Result<(), Box<dyn std::error::Error>> {
15232        let f = self.func("sdpa_naive_w_f32");
15233        let cfg = LaunchConfig {
15234            grid_dim: (n_head as u32, t as u32, 1),
15235            block_dim: (128, 1, 1),
15236            shared_mem_bytes: (t_kv * 4) as u32,
15237        };
15238        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15239            head_dim as i32,
15240            n_head as i32,
15241            n_head_kv as i32,
15242            t as i32,
15243            t_kv as i32,
15244            causal as i32,
15245            window as i32,
15246        );
15247        let __s_b = self.gpu.stream();
15248        let mut b = __s_b.launch_builder(&f);
15249        b.arg(q)
15250            .arg(k)
15251            .arg(v)
15252            .arg(o)
15253            .arg(&hd)
15254            .arg(&nh)
15255            .arg(&nhkv)
15256            .arg(&ti)
15257            .arg(&tkvi)
15258            .arg(&scale)
15259            .arg(&cz)
15260            .arg(&wi);
15261        unsafe {
15262            b.launch(cfg)?;
15263        }
15264        Ok(())
15265    }
15266
15267    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
15268    pub fn sdpa_naive_view(
15269        &self,
15270        q: &CudaSlice<f32>,
15271        k: &cudarc::driver::CudaView<f32>,
15272        v: &cudarc::driver::CudaView<f32>,
15273        o: &mut CudaSlice<f32>,
15274        head_dim: usize,
15275        n_head: usize,
15276        n_head_kv: usize,
15277        t: usize,
15278        t_kv: usize,
15279        scale: f32,
15280        causal: bool,
15281    ) -> Result<(), Box<dyn std::error::Error>> {
15282        let f = self.func("sdpa_naive_f32");
15283        let cfg = LaunchConfig {
15284            grid_dim: (n_head as u32, t as u32, 1),
15285            block_dim: (128, 1, 1),
15286            shared_mem_bytes: (t_kv * 4) as u32,
15287        };
15288        let (hd, nh, nhkv, ti, tkvi, cz) = (
15289            head_dim as i32,
15290            n_head as i32,
15291            n_head_kv as i32,
15292            t as i32,
15293            t_kv as i32,
15294            causal as i32,
15295        );
15296        let __s_b = self.gpu.stream();
15297        let mut b = __s_b.launch_builder(&f);
15298        b.arg(q)
15299            .arg(k)
15300            .arg(v)
15301            .arg(o)
15302            .arg(&hd)
15303            .arg(&nh)
15304            .arg(&nhkv)
15305            .arg(&ti)
15306            .arg(&tkvi)
15307            .arg(&scale)
15308            .arg(&cz);
15309        unsafe {
15310            b.launch(cfg)?;
15311        }
15312        Ok(())
15313    }
15314
15315    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
15316    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
15317    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
15318    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
15319    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
15320    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
15321    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
15322    #[allow(clippy::too_many_arguments)]
15323    pub fn fa_dequant_kv_view_f32(
15324        &self,
15325        k: &cudarc::driver::CudaView<u8>,
15326        v: &cudarc::driver::CudaView<u8>,
15327        kf: &mut CudaSlice<f32>,
15328        vf: &mut CudaSlice<f32>,
15329        kv_dim_k: usize,
15330        kv_dim_v: usize,
15331        t_kv: usize,
15332        k_tok_bytes: usize,
15333        v_tok_bytes: usize,
15334        g: bool,
15335    ) -> Result<(), Box<dyn std::error::Error>> {
15336        let f = if g {
15337            self.func_g("fa_dequant_kv_ws_f32")
15338        } else {
15339            self.func("fa_dequant_kv_ws_f32")
15340        };
15341        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
15342        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15343        let cfg = LaunchConfig {
15344            grid_dim: (nblk.max(1), 1, 1),
15345            block_dim: (256, 1, 1),
15346            shared_mem_bytes: 0,
15347        };
15348        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
15349        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15350        let __s_b = self.gpu.stream();
15351        let mut b = __s_b.launch_builder(&f);
15352        b.arg(k)
15353            .arg(v)
15354            .arg(&mut *kf)
15355            .arg(&mut *vf)
15356            .arg(&kdk)
15357            .arg(&kdv)
15358            .arg(&tkvi)
15359            .arg(&ktb)
15360            .arg(&vtb);
15361        unsafe {
15362            b.launch(cfg)?;
15363        }
15364        Ok(())
15365    }
15366
15367    #[allow(clippy::too_many_arguments)]
15368    pub fn sdpa_naive_quantized_view(
15369        &self,
15370        q: &CudaSlice<f32>,
15371        k: &cudarc::driver::CudaView<u8>,
15372        v: &cudarc::driver::CudaView<u8>,
15373        o: &mut CudaSlice<f32>,
15374        head_dim: usize,
15375        n_head: usize,
15376        n_head_kv: usize,
15377        t: usize,
15378        t_kv: usize,
15379        scale: f32,
15380        causal: bool,
15381        k_tok_bytes: usize,
15382        v_tok_bytes: usize,
15383    ) -> Result<(), Box<dyn std::error::Error>> {
15384        let kv_dim = n_head_kv * head_dim;
15385        let mut kf = self.uninit(t_kv * kv_dim)?;
15386        let mut vf = self.uninit(t_kv * kv_dim)?;
15387        let f = self.func("fa_dequant_kv_ws_f32");
15388        let total = (2 * t_kv * kv_dim) as u64;
15389        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15390        let cfg = LaunchConfig {
15391            grid_dim: (nblk.max(1), 1, 1),
15392            block_dim: (256, 1, 1),
15393            shared_mem_bytes: 0,
15394        };
15395        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15396        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15397        let __s_b = self.gpu.stream();
15398        let mut b = __s_b.launch_builder(&f);
15399        b.arg(k)
15400            .arg(v)
15401            .arg(&mut kf)
15402            .arg(&mut vf)
15403            .arg(&kv_dim_i)
15404            .arg(&kv_dim_i)
15405            .arg(&t_kv_i)
15406            .arg(&k_tok_bytes_i)
15407            .arg(&v_tok_bytes_i);
15408        unsafe { b.launch(cfg)? };
15409        self.sdpa_naive(
15410            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15411        )
15412    }
15413
15414    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
15415    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
15416    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
15417    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
15418    /// unwindowed function above and produces bit-identical output at window == 0.
15419    ///
15420    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
15421    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
15422    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
15423    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
15424    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
15425    #[allow(clippy::too_many_arguments)]
15426    pub fn sdpa_naive_w_quantized_view(
15427        &self,
15428        q: &CudaSlice<f32>,
15429        k: &cudarc::driver::CudaView<u8>,
15430        v: &cudarc::driver::CudaView<u8>,
15431        o: &mut CudaSlice<f32>,
15432        head_dim: usize,
15433        n_head: usize,
15434        n_head_kv: usize,
15435        t: usize,
15436        t_kv: usize,
15437        scale: f32,
15438        causal: bool,
15439        window: usize,
15440        k_tok_bytes: usize,
15441        v_tok_bytes: usize,
15442    ) -> Result<(), Box<dyn std::error::Error>> {
15443        let kv_dim = n_head_kv * head_dim;
15444        let mut kf = self.uninit(t_kv * kv_dim)?;
15445        let mut vf = self.uninit(t_kv * kv_dim)?;
15446        let f = self.func("fa_dequant_kv_ws_f32");
15447        let total = (2 * t_kv * kv_dim) as u64;
15448        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
15449        let cfg = LaunchConfig {
15450            grid_dim: (nblk.max(1), 1, 1),
15451            block_dim: (256, 1, 1),
15452            shared_mem_bytes: 0,
15453        };
15454        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
15455        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
15456        let __s_b = self.gpu.stream();
15457        let mut b = __s_b.launch_builder(&f);
15458        b.arg(k)
15459            .arg(v)
15460            .arg(&mut kf)
15461            .arg(&mut vf)
15462            .arg(&kv_dim_i)
15463            .arg(&kv_dim_i)
15464            .arg(&t_kv_i)
15465            .arg(&k_tok_bytes_i)
15466            .arg(&v_tok_bytes_i);
15467        unsafe { b.launch(cfg)? };
15468        self.sdpa_naive_w(
15469            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15470        )
15471    }
15472
15473    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
15474    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
15475    /// Q/K/V/O [head_dim, n_head(_kv), T].
15476    pub fn fa_prefill(
15477        &self,
15478        q: &CudaSlice<f32>,
15479        k: &CudaSlice<f32>,
15480        v: &CudaSlice<f32>,
15481        o: &mut CudaSlice<f32>,
15482        head_dim: usize,
15483        n_head: usize,
15484        n_head_kv: usize,
15485        t: usize,
15486        t_kv: usize,
15487        scale: f32,
15488        causal: bool,
15489    ) -> Result<(), Box<dyn std::error::Error>> {
15490        if portable_mma_gated() {
15491            return self.sdpa_naive(
15492                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15493            );
15494        }
15495        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
15496        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
15497        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
15498        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
15499        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
15500        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
15501        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
15502        let fa3_on = head_dim == 256
15503            && causal
15504            && t == t_kv
15505            && match std::env::var("MEMRA_FA3").as_deref() {
15506                Ok("0") => false,
15507                Ok("1") => true,
15508                _ => cfg!(memra_hopper_mma),
15509            };
15510        if fa3_on {
15511            let n = t * n_head * head_dim;
15512            let nkv = t * n_head_kv * head_dim;
15513            let mut q16 = self.alloc_u8_uninit(n * 2)?;
15514            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
15515            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
15516            self.f32_to_bf16_into(q, &mut q16, n)?;
15517            self.f32_to_bf16_into(k, &mut k16, nkv)?;
15518            self.f32_to_bf16_into(v, &mut v16, nkv)?;
15519            let rc = {
15520                use cudarc::driver::{DevicePtr, DevicePtrMut};
15521                let stream = self.gpu.stream();
15522                let (qp, _g1) = q16.device_ptr(&stream);
15523                let (kp, _g2) = k16.device_ptr(&stream);
15524                let (vp, _g3) = v16.device_ptr(&stream);
15525                let (op, _g4) = o.device_ptr_mut(&stream);
15526                unsafe {
15527                    memra_fa3_prefill(
15528                        qp as *const core::ffi::c_void,
15529                        kp as *const core::ffi::c_void,
15530                        vp as *const core::ffi::c_void,
15531                        op as *mut f32,
15532                        t as i32,
15533                        n_head as i32,
15534                        n_head_kv as i32,
15535                        head_dim as i32,
15536                        scale,
15537                        stream.cu_stream() as *mut core::ffi::c_void,
15538                    )
15539                }
15540            };
15541            if rc != 0 {
15542                return Err(format!("memra_fa3_prefill rc={rc}").into());
15543            }
15544            return Ok(());
15545        }
15546        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
15547        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
15548        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
15549        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
15550        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15551        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
15552        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
15553            const BLOCK_Q: usize = 64;
15554            const BKX: usize = 32;
15555            let f = self.func("fa_prefill_bf16_p1");
15556            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
15557                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
15558            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15559            f.set_attribute(
15560                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15561                shmem as i32,
15562            )?;
15563            let cfg = LaunchConfig {
15564                grid_dim: (
15565                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15566                    n_head as u32,
15567                    1,
15568                ),
15569                block_dim: (32, 4, 1),
15570                shared_mem_bytes: shmem,
15571            };
15572            let (hd, nh, nhkv, ti, tkvi, cz) = (
15573                head_dim as i32,
15574                n_head as i32,
15575                n_head_kv as i32,
15576                t as i32,
15577                t_kv as i32,
15578                causal as i32,
15579            );
15580            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15581            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15582            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15583            let __s_b = self.gpu.stream();
15584            let mut b = __s_b.launch_builder(&f);
15585            b.arg(&qb)
15586                .arg(&kb)
15587                .arg(&vb)
15588                .arg(o)
15589                .arg(&hd)
15590                .arg(&nh)
15591                .arg(&nhkv)
15592                .arg(&ti)
15593                .arg(&tkvi)
15594                .arg(&scale)
15595                .arg(&cz);
15596            unsafe {
15597                b.launch(cfg)?;
15598            }
15599            return Ok(());
15600        }
15601        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
15602        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
15603        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
15604        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
15605        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
15606        const BK: usize = 32;
15607        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
15608        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
15609        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
15610        let (block_q, warps, w2_sfx): (usize, u32, &str) =
15611            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
15612        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
15613        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
15614        // other head_dims to sdpa_naive before reaching here.
15615        let hd_sfx = fa_hd_suffix(head_dim)?;
15616        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15617        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
15618        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
15619        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
15620        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
15621        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
15622        let (kb16, vb16) = if bf16kv {
15623            let n = t_kv * n_head_kv * head_dim;
15624            let mut kb = self.alloc_u8_uninit(n * 2)?;
15625            let mut vb = self.alloc_u8_uninit(n * 2)?;
15626            let fcv = self.func("f32_to_bf16_bulk");
15627            let ni = n as i64;
15628            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
15629            let __s_b = self.gpu.stream();
15630            let mut b = __s_b.launch_builder(&fcv);
15631            b.arg(k).arg(&mut kb).arg(&ni);
15632            unsafe {
15633                b.launch(cfgc)?;
15634            }
15635            let __s_b = self.gpu.stream();
15636            let mut b = __s_b.launch_builder(&fcv);
15637            b.arg(v).arg(&mut vb).arg(&ni);
15638            unsafe {
15639                b.launch(cfgc)?;
15640            }
15641            (Some(kb), Some(vb))
15642        } else {
15643            (None, None)
15644        };
15645        let f = self.func(&if bf16kv {
15646            format!("fa_prefill_bf16kv_pp{hd_sfx}")
15647        } else {
15648            format!(
15649                "fa_prefill_f32{}{}{hd_sfx}",
15650                if floor { "" } else { "_pp" },
15651                if floor { "" } else { w2_sfx }
15652            )
15653        });
15654        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
15655        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
15656        let kv_stages = if bf16kv { 2 } else { 1 };
15657        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
15658            + 4 * (block_q * BK + 2 * block_q)) as u32;
15659        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15660        f.set_attribute(
15661            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15662            shmem as i32,
15663        )?;
15664        let cfg = LaunchConfig {
15665            grid_dim: (
15666                (t as u32 + block_q as u32 - 1) / block_q as u32,
15667                n_head as u32,
15668                1,
15669            ),
15670            block_dim: (32, warps, 1),
15671            shared_mem_bytes: shmem,
15672        };
15673        let (hd, nh, nhkv, ti, tkvi, cz) = (
15674            head_dim as i32,
15675            n_head as i32,
15676            n_head_kv as i32,
15677            t as i32,
15678            t_kv as i32,
15679            causal as i32,
15680        );
15681        let __s_b = self.gpu.stream();
15682        let mut b = __s_b.launch_builder(&f);
15683        b.arg(q);
15684        match (&kb16, &vb16) {
15685            (Some(kb), Some(vb)) => {
15686                b.arg(kb).arg(vb);
15687            }
15688            _ => {
15689                b.arg(k).arg(v);
15690            }
15691        }
15692        b.arg(o)
15693            .arg(&hd)
15694            .arg(&nh)
15695            .arg(&nhkv)
15696            .arg(&ti)
15697            .arg(&tkvi)
15698            .arg(&scale)
15699            .arg(&cz);
15700        unsafe {
15701            b.launch(cfg)?;
15702        }
15703        Ok(())
15704    }
15705
15706    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
15707    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
15708    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
15709    #[allow(clippy::too_many_arguments)]
15710    pub fn fa_prefill_w(
15711        &self,
15712        q: &CudaSlice<f32>,
15713        k: &CudaSlice<f32>,
15714        v: &CudaSlice<f32>,
15715        o: &mut CudaSlice<f32>,
15716        head_dim: usize,
15717        n_head: usize,
15718        n_head_kv: usize,
15719        t: usize,
15720        t_kv: usize,
15721        scale: f32,
15722        causal: bool,
15723        window: usize,
15724    ) -> Result<(), Box<dyn std::error::Error>> {
15725        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
15726        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
15727        if portable_mma_gated() {
15728            return self.sdpa_naive_w(
15729                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
15730            );
15731        }
15732        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
15733        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
15734        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
15735        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15736        let faw_f32 =
15737            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
15738        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
15739        self.fa_prefill_w_arm(
15740            q,
15741            k,
15742            v,
15743            o,
15744            head_dim,
15745            n_head,
15746            n_head_kv,
15747            t,
15748            t_kv,
15749            scale,
15750            causal,
15751            window,
15752            floor || faw_f32,
15753            floor,
15754        )
15755    }
15756
15757    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
15758    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
15759    #[allow(clippy::too_many_arguments)]
15760    pub fn fa_prefill_w_pre(
15761        &self,
15762        qb: &CudaSlice<u8>,
15763        kb: &CudaSlice<u8>,
15764        vb: &CudaSlice<u8>,
15765        o: &mut CudaSlice<f32>,
15766        head_dim: usize,
15767        n_head: usize,
15768        n_head_kv: usize,
15769        t: usize,
15770        t_kv: usize,
15771        scale: f32,
15772        causal: bool,
15773        window: usize,
15774        v_f16: bool,
15775    ) -> Result<(), Box<dyn std::error::Error>> {
15776        const BLOCK_Q: usize = 64;
15777        const BK: usize = 32;
15778        debug_assert_eq!(head_dim, 256);
15779        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15780        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
15781        if hp {
15782            const BLOCK_QH: usize = 32;
15783            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
15784            // else re-encode through the pooled scratch (stream-ordered reuse).
15785            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15786            let vh: &CudaSlice<u8> = if v_f16 {
15787                vb
15788            } else {
15789                let n = t_kv * n_head_kv * head_dim;
15790                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
15791                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
15792                }
15793                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
15794                vguard.as_ref().unwrap()
15795            };
15796            let f = self.func("fa_prefill_w_bf16_p1h2");
15797            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15798            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15799            f.set_attribute(
15800                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15801                shmem as i32,
15802            )?;
15803            let cfg = LaunchConfig {
15804                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15805                block_dim: (32, 4, 1),
15806                shared_mem_bytes: shmem,
15807            };
15808            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15809                head_dim as i32,
15810                n_head as i32,
15811                n_head_kv as i32,
15812                t as i32,
15813                t_kv as i32,
15814                causal as i32,
15815                window as i32,
15816            );
15817            let __s_b = self.gpu.stream();
15818            let mut b = __s_b.launch_builder(&f);
15819            b.arg(qb)
15820                .arg(kb)
15821                .arg(vh)
15822                .arg(o)
15823                .arg(&hd)
15824                .arg(&nh)
15825                .arg(&nhkv)
15826                .arg(&ti)
15827                .arg(&tkvi)
15828                .arg(&scale)
15829                .arg(&cz)
15830                .arg(&wi);
15831            unsafe {
15832                b.launch(cfg)?;
15833            }
15834            return Ok(());
15835        }
15836        let f = self.func("fa_prefill_w_bf16_p1");
15837        let shmem =
15838            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15839        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15840        f.set_attribute(
15841            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15842            shmem as i32,
15843        )?;
15844        let cfg = LaunchConfig {
15845            grid_dim: (
15846                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15847                n_head as u32,
15848                1,
15849            ),
15850            block_dim: (32, 4, 1),
15851            shared_mem_bytes: shmem,
15852        };
15853        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15854            head_dim as i32,
15855            n_head as i32,
15856            n_head_kv as i32,
15857            t as i32,
15858            t_kv as i32,
15859            causal as i32,
15860            window as i32,
15861        );
15862        let __s_b = self.gpu.stream();
15863        let mut b = __s_b.launch_builder(&f);
15864        b.arg(qb)
15865            .arg(kb)
15866            .arg(vb)
15867            .arg(o)
15868            .arg(&hd)
15869            .arg(&nh)
15870            .arg(&nhkv)
15871            .arg(&ti)
15872            .arg(&tkvi)
15873            .arg(&scale)
15874            .arg(&cz)
15875            .arg(&wi);
15876        unsafe {
15877            b.launch(cfg)?;
15878        }
15879        Ok(())
15880    }
15881
15882    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
15883    #[allow(clippy::too_many_arguments)]
15884    pub fn fa_prefill_w_arm(
15885        &self,
15886        q: &CudaSlice<f32>,
15887        k: &CudaSlice<f32>,
15888        v: &CudaSlice<f32>,
15889        o: &mut CudaSlice<f32>,
15890        head_dim: usize,
15891        n_head: usize,
15892        n_head_kv: usize,
15893        t: usize,
15894        t_kv: usize,
15895        scale: f32,
15896        causal: bool,
15897        window: usize,
15898        f32_stage: bool,
15899        floor: bool,
15900    ) -> Result<(), Box<dyn std::error::Error>> {
15901        const BLOCK_Q: usize = 64;
15902        const BK: usize = 32;
15903        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
15904        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
15905        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
15906        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
15907        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15908        let p1 = !floor
15909            && !f32_stage
15910            && *P1_ON.get_or_init(|| {
15911                std::env::var("MEMRA_FAW_P1")
15912                    .map(|v| v != "0")
15913                    .unwrap_or(true)
15914            });
15915        let hp =
15916            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15917        if hp {
15918            const BLOCK_QH: usize = 32;
15919            let f = self.func("fa_prefill_w_bf16_p1h2");
15920            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15921            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15922            f.set_attribute(
15923                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15924                shmem as i32,
15925            )?;
15926            let cfg = LaunchConfig {
15927                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15928                block_dim: (32, 4, 1),
15929                shared_mem_bytes: shmem,
15930            };
15931            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15932                head_dim as i32,
15933                n_head as i32,
15934                n_head_kv as i32,
15935                t as i32,
15936                t_kv as i32,
15937                causal as i32,
15938                window as i32,
15939            );
15940            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15941            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15942            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
15943            let __s_b = self.gpu.stream();
15944            let mut b = __s_b.launch_builder(&f);
15945            b.arg(&qb)
15946                .arg(&kb)
15947                .arg(&vh)
15948                .arg(o)
15949                .arg(&hd)
15950                .arg(&nh)
15951                .arg(&nhkv)
15952                .arg(&ti)
15953                .arg(&tkvi)
15954                .arg(&scale)
15955                .arg(&cz)
15956                .arg(&wi);
15957            unsafe {
15958                b.launch(cfg)?;
15959            }
15960            return Ok(());
15961        }
15962        if p1 {
15963            let f = self.func("fa_prefill_w_bf16_p1");
15964            let shmem =
15965                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15966            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15967            f.set_attribute(
15968                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15969                shmem as i32,
15970            )?;
15971            let cfg = LaunchConfig {
15972                grid_dim: (
15973                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15974                    n_head as u32,
15975                    1,
15976                ),
15977                block_dim: (32, 4, 1),
15978                shared_mem_bytes: shmem,
15979            };
15980            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15981                head_dim as i32,
15982                n_head as i32,
15983                n_head_kv as i32,
15984                t as i32,
15985                t_kv as i32,
15986                causal as i32,
15987                window as i32,
15988            );
15989            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15990            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15991            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15992            let __s_b = self.gpu.stream();
15993            let mut b = __s_b.launch_builder(&f);
15994            b.arg(&qb)
15995                .arg(&kb)
15996                .arg(&vb)
15997                .arg(o)
15998                .arg(&hd)
15999                .arg(&nh)
16000                .arg(&nhkv)
16001                .arg(&ti)
16002                .arg(&tkvi)
16003                .arg(&scale)
16004                .arg(&cz)
16005                .arg(&wi);
16006            unsafe {
16007                b.launch(cfg)?;
16008            }
16009            return Ok(());
16010        }
16011        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
16012        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
16013        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16014        let g4 = !floor
16015            && !f32_stage
16016            && n_head_kv == 1
16017            && n_head % 4 == 0
16018            && *G4_ON.get_or_init(|| {
16019                std::env::var("MEMRA_FAW_G4")
16020                    .map(|v| v != "0")
16021                    .unwrap_or(true)
16022            });
16023        if g4 {
16024            const SP_M: usize = 16;
16025            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
16026            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
16027            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16028            let o2 = *O2_ON.get_or_init(|| {
16029                std::env::var("MEMRA_FAW_O2")
16030                    .map(|v| v != "0")
16031                    .unwrap_or(true)
16032            });
16033            let f = self.func(if o2 {
16034                "fa_prefill_w_bf16_g4o2"
16035            } else {
16036                "fa_prefill_w_bf16_g4"
16037            });
16038            let shmem = if o2 {
16039                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
16040            } else {
16041                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
16042                    as u32
16043            };
16044            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16045            f.set_attribute(
16046                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16047                shmem as i32,
16048            )?;
16049            let cfg = LaunchConfig {
16050                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
16051                block_dim: (32, 4, 1),
16052                shared_mem_bytes: shmem,
16053            };
16054            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16055                head_dim as i32,
16056                n_head as i32,
16057                n_head_kv as i32,
16058                t as i32,
16059                t_kv as i32,
16060                causal as i32,
16061                window as i32,
16062            );
16063            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16064            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16065            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16066            let __s_b = self.gpu.stream();
16067            let mut b = __s_b.launch_builder(&f);
16068            b.arg(&qb)
16069                .arg(&kb)
16070                .arg(&vb)
16071                .arg(o)
16072                .arg(&hd)
16073                .arg(&nh)
16074                .arg(&nhkv)
16075                .arg(&ti)
16076                .arg(&tkvi)
16077                .arg(&scale)
16078                .arg(&cz)
16079                .arg(&wi);
16080            unsafe {
16081                b.launch(cfg)?;
16082            }
16083            return Ok(());
16084        }
16085        let f = self.func(if floor {
16086            "fa_prefill_w_f32"
16087        } else if f32_stage {
16088            "fa_prefill_w_f32_pp"
16089        } else {
16090            "fa_prefill_w_bf16_pp"
16091        });
16092        let shmem =
16093            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16094        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16095        f.set_attribute(
16096            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16097            shmem as i32,
16098        )?;
16099        let cfg = LaunchConfig {
16100            grid_dim: (
16101                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16102                n_head as u32,
16103                1,
16104            ),
16105            block_dim: (32, 4, 1),
16106            shared_mem_bytes: shmem,
16107        };
16108        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
16109            head_dim as i32,
16110            n_head as i32,
16111            n_head_kv as i32,
16112            t as i32,
16113            t_kv as i32,
16114            causal as i32,
16115            window as i32,
16116        );
16117        if f32_stage {
16118            let __s_b = self.gpu.stream();
16119            let mut b = __s_b.launch_builder(&f);
16120            b.arg(q)
16121                .arg(k)
16122                .arg(v)
16123                .arg(o)
16124                .arg(&hd)
16125                .arg(&nh)
16126                .arg(&nhkv)
16127                .arg(&ti)
16128                .arg(&tkvi)
16129                .arg(&scale)
16130                .arg(&cz)
16131                .arg(&wi);
16132            unsafe {
16133                b.launch(cfg)?;
16134            }
16135        } else {
16136            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16137            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16138            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16139            let __s_b = self.gpu.stream();
16140            let mut b = __s_b.launch_builder(&f);
16141            b.arg(&qb)
16142                .arg(&kb)
16143                .arg(&vb)
16144                .arg(o)
16145                .arg(&hd)
16146                .arg(&nh)
16147                .arg(&nhkv)
16148                .arg(&ti)
16149                .arg(&tkvi)
16150                .arg(&scale)
16151                .arg(&cz)
16152                .arg(&wi);
16153            unsafe {
16154                b.launch(cfg)?;
16155            }
16156        }
16157        Ok(())
16158    }
16159
16160    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
16161    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
16162    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
16163    #[allow(clippy::too_many_arguments)]
16164    pub fn fa_prefill_hd512(
16165        &self,
16166        q: &CudaSlice<f32>,
16167        k: &CudaSlice<f32>,
16168        v: &CudaSlice<f32>,
16169        o: &mut CudaSlice<f32>,
16170        head_dim: usize,
16171        n_head: usize,
16172        n_head_kv: usize,
16173        t: usize,
16174        t_kv: usize,
16175        scale: f32,
16176        causal: bool,
16177    ) -> Result<(), Box<dyn std::error::Error>> {
16178        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
16179        if portable_mma_gated() {
16180            return self.sdpa_naive(
16181                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
16182            );
16183        }
16184        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
16185        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
16186        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
16187        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
16188        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
16189        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16190        let f32_stage =
16191            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
16192        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
16193        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
16194        // Own numeric config (partial-sum order) — battery-gated.
16195        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
16196        let sp = !f32_stage
16197            && *SP_ON.get_or_init(|| {
16198                std::env::var("MEMRA_FA512_SP")
16199                    .map(|v| v != "0")
16200                    .unwrap_or(true)
16201            });
16202        self.fa_prefill_hd512_arm(
16203            q,
16204            k,
16205            v,
16206            o,
16207            head_dim,
16208            n_head,
16209            n_head_kv,
16210            t,
16211            t_kv,
16212            scale,
16213            causal,
16214            f32_stage,
16215            sp,
16216            sp && fa_f16pv_on(),
16217        )
16218    }
16219
16220    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
16221    #[allow(clippy::too_many_arguments)]
16222    pub fn fa_prefill_hd512_pre(
16223        &self,
16224        qb: &CudaSlice<u8>,
16225        kb: &CudaSlice<u8>,
16226        vb: &CudaSlice<u8>,
16227        o: &mut CudaSlice<f32>,
16228        head_dim: usize,
16229        n_head: usize,
16230        n_head_kv: usize,
16231        t: usize,
16232        t_kv: usize,
16233        scale: f32,
16234        causal: bool,
16235        v_f16: bool,
16236    ) -> Result<(), Box<dyn std::error::Error>> {
16237        debug_assert_eq!(head_dim, 512);
16238        const SP_M: usize = 16;
16239        const BKS: usize = 32;
16240        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
16241        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
16242        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
16243        let f16pv = fa_f16pv_on();
16244        let nw = if f16pv { fa512_wide_warps() } else { 2 };
16245        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16246        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
16247        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
16248        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
16249            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
16250            let n = t_kv * n_head_kv * head_dim;
16251            let need = n * 2;
16252            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
16253                *vguard = Some(self.alloc_uninit::<u8>(need)?);
16254            }
16255            let dst = vguard.as_mut().unwrap();
16256            self.bf16_to_f16_into(vb, n, dst)?;
16257            vguard.as_ref().unwrap()
16258        } else {
16259            vb
16260        };
16261        let f = self.func(if hp {
16262            "fa_prefill_bf16_hd512_sp16h2"
16263        } else {
16264            match (f16pv, nw) {
16265                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16266                (true, _) => "fa_prefill_bf16_hd512_sp16",
16267                _ => "fa_prefill_bf16_hd512_sp",
16268            }
16269        });
16270        let (nwarp, npart) = if hp {
16271            (4usize, 4usize)
16272        } else if nw > 2 {
16273            (nw, nw)
16274        } else {
16275            (2, 1)
16276        };
16277        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
16278        let shmem = if hp {
16279            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
16280                as u32
16281        } else {
16282            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16283                + 4 * (npart * SP_M * BKS + SP_M)) as u32
16284        };
16285        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16286        f.set_attribute(
16287            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16288            shmem as i32,
16289        )?;
16290        let grid_y = if hp {
16291            (n_head / 2) as u32
16292        } else {
16293            n_head as u32
16294        };
16295        let cfg = LaunchConfig {
16296            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16297            block_dim: (32, nwarp as u32, 1),
16298            shared_mem_bytes: shmem,
16299        };
16300        let (hd, nh, nhkv, ti, tkvi, cz) = (
16301            head_dim as i32,
16302            n_head as i32,
16303            n_head_kv as i32,
16304            t as i32,
16305            t_kv as i32,
16306            causal as i32,
16307        );
16308        let __s_b = self.gpu.stream();
16309        let mut b = __s_b.launch_builder(&f);
16310        b.arg(qb)
16311            .arg(kb)
16312            .arg(vref)
16313            .arg(o)
16314            .arg(&hd)
16315            .arg(&nh)
16316            .arg(&nhkv)
16317            .arg(&ti)
16318            .arg(&tkvi)
16319            .arg(&scale)
16320            .arg(&cz);
16321        unsafe {
16322            b.launch(cfg)?;
16323        }
16324        Ok(())
16325    }
16326
16327    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
16328    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
16329    #[allow(clippy::too_many_arguments)]
16330    pub fn fa_prefill_hd512_arm(
16331        &self,
16332        q: &CudaSlice<f32>,
16333        k: &CudaSlice<f32>,
16334        v: &CudaSlice<f32>,
16335        o: &mut CudaSlice<f32>,
16336        head_dim: usize,
16337        n_head: usize,
16338        n_head_kv: usize,
16339        t: usize,
16340        t_kv: usize,
16341        scale: f32,
16342        causal: bool,
16343        f32_stage: bool,
16344        sp: bool,
16345        f16pv: bool,
16346    ) -> Result<(), Box<dyn std::error::Error>> {
16347        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
16348        if sp && !f32_stage {
16349            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
16350            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
16351            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
16352            const SP_M: usize = 16;
16353            const BKS: usize = 32;
16354            let nw = if f16pv { fa512_wide_warps() } else { 2 };
16355            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
16356            let f = self.func(if hp {
16357                "fa_prefill_bf16_hd512_sp16h2"
16358            } else {
16359                match (f16pv, nw) {
16360                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
16361                    (true, _) => "fa_prefill_bf16_hd512_sp16",
16362                    _ => "fa_prefill_bf16_hd512_sp",
16363                }
16364            });
16365            let (nwarp, npart) = if hp {
16366                (4usize, 4usize)
16367            } else if nw > 2 {
16368                (nw, nw)
16369            } else {
16370                (2, 1)
16371            };
16372            let shmem = if hp {
16373                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
16374                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
16375            } else {
16376                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
16377                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
16378            };
16379            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16380            f.set_attribute(
16381                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16382                shmem as i32,
16383            )?;
16384            let grid_y = if hp {
16385                (n_head / 2) as u32
16386            } else {
16387                n_head as u32
16388            };
16389            let cfg = LaunchConfig {
16390                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
16391                block_dim: (32, nwarp as u32, 1),
16392                shared_mem_bytes: shmem,
16393            };
16394            let (hd, nh, nhkv, ti, tkvi, cz) = (
16395                head_dim as i32,
16396                n_head as i32,
16397                n_head_kv as i32,
16398                t as i32,
16399                t_kv as i32,
16400                causal as i32,
16401            );
16402            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16403            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16404            let vb = if f16pv {
16405                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
16406            } else {
16407                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
16408            };
16409            let __s_b = self.gpu.stream();
16410            let mut b = __s_b.launch_builder(&f);
16411            b.arg(&qb)
16412                .arg(&kb)
16413                .arg(&vb)
16414                .arg(o)
16415                .arg(&hd)
16416                .arg(&nh)
16417                .arg(&nhkv)
16418                .arg(&ti)
16419                .arg(&tkvi)
16420                .arg(&scale)
16421                .arg(&cz);
16422            unsafe {
16423                b.launch(cfg)?;
16424            }
16425            return Ok(());
16426        }
16427        const BLOCK_Q: usize = 32;
16428        const BK: usize = 32;
16429        const HALF: usize = 256;
16430        let f = self.func(if f32_stage {
16431            "fa_prefill_f32_hd512"
16432        } else {
16433            "fa_prefill_bf16_hd512"
16434        });
16435        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
16436        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
16437            + 4 * BLOCK_Q) as u32;
16438        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16439        f.set_attribute(
16440            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16441            shmem as i32,
16442        )?;
16443        let cfg = LaunchConfig {
16444            grid_dim: (
16445                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16446                n_head as u32,
16447                2,
16448            ),
16449            block_dim: (32, 2, 1),
16450            shared_mem_bytes: shmem,
16451        };
16452        let (hd, nh, nhkv, ti, tkvi, cz) = (
16453            head_dim as i32,
16454            n_head as i32,
16455            n_head_kv as i32,
16456            t as i32,
16457            t_kv as i32,
16458            causal as i32,
16459        );
16460        if f32_stage {
16461            let __s_b = self.gpu.stream();
16462            let mut b = __s_b.launch_builder(&f);
16463            b.arg(q)
16464                .arg(k)
16465                .arg(v)
16466                .arg(o)
16467                .arg(&hd)
16468                .arg(&nh)
16469                .arg(&nhkv)
16470                .arg(&ti)
16471                .arg(&tkvi)
16472                .arg(&scale)
16473                .arg(&cz);
16474            unsafe {
16475                b.launch(cfg)?;
16476            }
16477        } else {
16478            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
16479            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
16480            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
16481            let __s_b = self.gpu.stream();
16482            let mut b = __s_b.launch_builder(&f);
16483            b.arg(&qb)
16484                .arg(&kb)
16485                .arg(&vb)
16486                .arg(o)
16487                .arg(&hd)
16488                .arg(&nh)
16489                .arg(&nhkv)
16490                .arg(&ti)
16491                .arg(&tkvi)
16492                .arg(&scale)
16493                .arg(&cz);
16494            unsafe {
16495                b.launch(cfg)?;
16496            }
16497        }
16498        Ok(())
16499    }
16500
16501    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
16502    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
16503    /// separate f32_to_bf16 the FA entries would run).
16504    #[allow(clippy::too_many_arguments)]
16505    pub fn rope_neox2_bf16e(
16506        &self,
16507        q: &mut CudaSlice<f32>,
16508        k: &mut CudaSlice<f32>,
16509        qb: &mut CudaSlice<u8>,
16510        kb: &mut CudaSlice<u8>,
16511        pos: &CudaSlice<i32>,
16512        head_dim: usize,
16513        n_dims: usize,
16514        nh_q: usize,
16515        nh_k: usize,
16516        n_tokens: usize,
16517        base: f32,
16518        freq_scale: f32,
16519        ff: Option<&CudaSlice<f32>>,
16520    ) -> Result<(), Box<dyn std::error::Error>> {
16521        let f = self.func("rope_neox2_bf16e_f32");
16522        let rows = ((nh_q + nh_k) * n_tokens) as u32;
16523        let cfg = LaunchConfig {
16524            grid_dim: (rows, 1, 1),
16525            block_dim: ((head_dim / 2) as u32, 1, 1),
16526            shared_mem_bytes: 0,
16527        };
16528        let theta_scale = base.powf(-2.0 / n_dims as f32);
16529        let (hd, nd, nhq, nhk, nt) = (
16530            head_dim as i32,
16531            n_dims as i32,
16532            nh_q as i32,
16533            nh_k as i32,
16534            n_tokens as i32,
16535        );
16536        let __s_b = self.gpu.stream();
16537        let mut b = __s_b.launch_builder(&f);
16538        match ff {
16539            Some(t) => {
16540                b.arg(&mut *q)
16541                    .arg(&mut *k)
16542                    .arg(&mut *qb)
16543                    .arg(&mut *kb)
16544                    .arg(pos)
16545                    .arg(&hd)
16546                    .arg(&nd)
16547                    .arg(&nhq)
16548                    .arg(&nhk)
16549                    .arg(&nt)
16550                    .arg(&theta_scale)
16551                    .arg(&freq_scale)
16552                    .arg(t);
16553                unsafe {
16554                    b.launch(cfg)?;
16555                }
16556            }
16557            None => {
16558                let null: u64 = 0;
16559                b.arg(&mut *q)
16560                    .arg(&mut *k)
16561                    .arg(&mut *qb)
16562                    .arg(&mut *kb)
16563                    .arg(pos)
16564                    .arg(&hd)
16565                    .arg(&nd)
16566                    .arg(&nhq)
16567                    .arg(&nhk)
16568                    .arg(&nt)
16569                    .arg(&theta_scale)
16570                    .arg(&freq_scale)
16571                    .arg(&null);
16572                unsafe {
16573                    b.launch(cfg)?;
16574                }
16575            }
16576        }
16577        Ok(())
16578    }
16579
16580    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
16581    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
16582    pub fn f32_to_bf16(
16583        &self,
16584        x: &CudaSlice<f32>,
16585        n: usize,
16586    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16587        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
16588        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16589        let f = self.func("f32_to_bf16_flat");
16590        let n_i = n as i64;
16591        let cfg = LaunchConfig {
16592            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16593            block_dim: (256, 1, 1),
16594            shared_mem_bytes: 0,
16595        };
16596        let __s_b = self.gpu.stream();
16597        let mut b = __s_b.launch_builder(&f);
16598        b.arg(x).arg(&mut y).arg(&n_i);
16599        unsafe {
16600            b.launch(cfg)?;
16601        }
16602        Ok(y)
16603    }
16604
16605    pub fn f32_to_f16(
16606        &self,
16607        x: &CudaSlice<f32>,
16608        n: usize,
16609    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16610        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
16611        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16612        let f = self.func("f32_to_f16_flat");
16613        let n_i = n as i64;
16614        let cfg = LaunchConfig {
16615            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
16616            block_dim: (256, 1, 1),
16617            shared_mem_bytes: 0,
16618        };
16619        let __s_b = self.gpu.stream();
16620        let mut b = __s_b.launch_builder(&f);
16621        b.arg(x).arg(&mut y).arg(&n_i);
16622        unsafe {
16623            b.launch(cfg)?;
16624        }
16625        Ok(y)
16626    }
16627
16628    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
16629    pub fn bf16_to_f16(
16630        &self,
16631        xb: &CudaSlice<u8>,
16632        n: usize,
16633    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
16634        let mut y = self.alloc_uninit::<u8>(n * 2)?;
16635        self.bf16_to_f16_into(xb, n, &mut y)?;
16636        Ok(y)
16637    }
16638
16639    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
16640    pub fn bf16_to_f16_into(
16641        &self,
16642        xb: &CudaSlice<u8>,
16643        n: usize,
16644        y: &mut CudaSlice<u8>,
16645    ) -> Result<(), Box<dyn std::error::Error>> {
16646        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
16647        assert!(y.len() >= n * 2);
16648        let f = self.func("bf16_to_f16_flat");
16649        let n2 = (n / 2) as i64;
16650        let cfg = LaunchConfig {
16651            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
16652            block_dim: (256, 1, 1),
16653            shared_mem_bytes: 0,
16654        };
16655        let __s_b = self.gpu.stream();
16656        let mut b = __s_b.launch_builder(&f);
16657        b.arg(xb).arg(y).arg(&n2);
16658        unsafe {
16659            b.launch(cfg)?;
16660        }
16661        Ok(())
16662    }
16663
16664    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
16665    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
16666    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
16667    /// head_dim in {256, 128}, bf16kv lane on.
16668    #[allow(clippy::too_many_arguments)]
16669    pub fn fa_prefill_vl8(
16670        &self,
16671        seqs: &[FaSeqVl],
16672        head_dim: usize,
16673        n_head: usize,
16674        n_head_kv: usize,
16675        scale: f32,
16676    ) -> Result<(), Box<dyn std::error::Error>> {
16677        const BK: usize = 32;
16678        let b = seqs.len();
16679        assert!(b >= 1 && b <= 8);
16680        let mut packed = [FaSeqVl::default(); 8];
16681        packed[..b].copy_from_slice(seqs);
16682        let v = FaVl8(packed);
16683        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16684        let ept = (n_head_kv * head_dim) as i32;
16685        {
16686            let f = self.func("fa_mirror_vl");
16687            let max_n = (max_t as i64) * ept as i64;
16688            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
16689            for which in 0..2i32 {
16690                let cfg = LaunchConfig {
16691                    grid_dim: (blocks, 1, b as u32),
16692                    block_dim: (256, 1, 1),
16693                    shared_mem_bytes: 0,
16694                };
16695                let __s_lb = self.gpu.stream();
16696                let mut lb = __s_lb.launch_builder(&f);
16697                lb.arg(&v).arg(&ept).arg(&which);
16698                unsafe {
16699                    lb.launch(cfg)?;
16700                }
16701            }
16702        }
16703        let hd_sfx = fa_hd_suffix(head_dim)?;
16704        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
16705        let block_q = 64usize;
16706        let kv_stages = 2usize;
16707        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
16708            + 4 * (block_q * BK + 2 * block_q)) as u32;
16709        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16710        f.set_attribute(
16711            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16712            shmem as i32,
16713        )?;
16714        let cfg = LaunchConfig {
16715            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
16716            block_dim: (32, 4, 1),
16717            shared_mem_bytes: shmem,
16718        };
16719        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16720        let __s_lb = self.gpu.stream();
16721        let mut lb = __s_lb.launch_builder(&f);
16722        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
16723        unsafe {
16724            lb.launch(cfg)?;
16725        }
16726        Ok(())
16727    }
16728
16729    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
16730    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
16731    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
16732    #[allow(clippy::too_many_arguments)]
16733    pub fn attn_pre_vl8(
16734        &self,
16735        seqs: &[AttnPreVl],
16736        wq: &CudaSlice<f32>,
16737        wk: &CudaSlice<f32>,
16738        head_dim: usize,
16739        rope_dims: usize,
16740        n_head: usize,
16741        n_head_kv: usize,
16742        eps: f32,
16743        freq_base: f32,
16744        freq_scale: f32,
16745        kv_dim_k: usize,
16746        kv_dim_v: usize,
16747        k_tok_bytes: usize,
16748        v_tok_bytes: usize,
16749    ) -> Result<(), Box<dyn std::error::Error>> {
16750        let b = seqs.len();
16751        assert!(b >= 1 && b <= 8);
16752        let mut packed = [AttnPreVl::default(); 8];
16753        packed[..b].copy_from_slice(seqs);
16754        let v = AttnPreVl8(packed);
16755        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
16756        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16757        {
16758            let f = self.func("q_gate_split_vl");
16759            let n = max_t * (n_head * head_dim) as u32;
16760            let cfg = LaunchConfig {
16761                grid_dim: (n.div_ceil(256), 1, b as u32),
16762                block_dim: (256, 1, 1),
16763                shared_mem_bytes: 0,
16764            };
16765            let __s_lb = self.gpu.stream();
16766            let mut lb = __s_lb.launch_builder(&f);
16767            lb.arg(&v).arg(&hd).arg(&nh);
16768            unsafe {
16769                lb.launch(cfg)?;
16770            }
16771        }
16772        {
16773            let f = self.func("attn_rms_vl");
16774            let cfg = LaunchConfig {
16775                grid_dim: (max_t * n_head as u32, 2, b as u32),
16776                block_dim: (rms_block(), 1, 1),
16777                shared_mem_bytes: 0,
16778            };
16779            let __s_lb = self.gpu.stream();
16780            let mut lb = __s_lb.launch_builder(&f);
16781            lb.arg(&v)
16782                .arg(wq)
16783                .arg(wk)
16784                .arg(&hd)
16785                .arg(&nh)
16786                .arg(&nhkv)
16787                .arg(&eps);
16788            unsafe {
16789                lb.launch(cfg)?;
16790            }
16791        }
16792        {
16793            let f = self.func("attn_rope_vl");
16794            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
16795            let nd = rope_dims as i32;
16796            let cfg = LaunchConfig {
16797                grid_dim: (max_t * n_head as u32, 2, b as u32),
16798                block_dim: ((head_dim / 2) as u32, 1, 1),
16799                shared_mem_bytes: 0,
16800            };
16801            let __s_lb = self.gpu.stream();
16802            let mut lb = __s_lb.launch_builder(&f);
16803            lb.arg(&v)
16804                .arg(&hd)
16805                .arg(&nd)
16806                .arg(&nh)
16807                .arg(&nhkv)
16808                .arg(&theta_scale)
16809                .arg(&freq_scale);
16810            unsafe {
16811                lb.launch(cfg)?;
16812            }
16813        }
16814        {
16815            let f = self.func("append_kv_vl");
16816            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
16817            let cfg = LaunchConfig {
16818                grid_dim: (nblk, max_t, b as u32),
16819                block_dim: (32, 1, 1),
16820                shared_mem_bytes: 0,
16821            };
16822            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16823            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16824            let __s_lb = self.gpu.stream();
16825            let mut lb = __s_lb.launch_builder(&f);
16826            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
16827            unsafe {
16828                lb.launch(cfg)?;
16829            }
16830        }
16831        Ok(())
16832    }
16833
16834    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
16835    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
16836    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
16837    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
16838    pub fn fa_prefill_view(
16839        &self,
16840        q: &CudaSlice<f32>,
16841        k: &cudarc::driver::CudaView<u8>,
16842        v: &cudarc::driver::CudaView<u8>,
16843        o: &mut CudaSlice<f32>,
16844        head_dim: usize,
16845        n_head: usize,
16846        n_head_kv: usize,
16847        t: usize,
16848        t_kv: usize,
16849        scale: f32,
16850        causal: bool,
16851        k_tok_bytes: usize,
16852        v_tok_bytes: usize,
16853        g: bool,
16854    ) -> Result<(), Box<dyn std::error::Error>> {
16855        if portable_mma_gated() {
16856            return self.sdpa_naive_quantized_view(
16857                q,
16858                k,
16859                v,
16860                o,
16861                head_dim,
16862                n_head,
16863                n_head_kv,
16864                t,
16865                t_kv,
16866                scale,
16867                causal,
16868                k_tok_bytes,
16869                v_tok_bytes,
16870            );
16871        }
16872        const BLOCK_Q: usize = 64;
16873        const BK: usize = 32;
16874        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
16875        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
16876        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
16877        let f = if g {
16878            self.func_g(&name)
16879        } else {
16880            self.func(&name)
16881        };
16882        let shmem =
16883            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16884        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16885        f.set_attribute(
16886            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16887            shmem as i32,
16888        )?;
16889        let cfg = LaunchConfig {
16890            grid_dim: (
16891                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16892                n_head as u32,
16893                1,
16894            ),
16895            block_dim: (32, 4, 1),
16896            shared_mem_bytes: shmem,
16897        };
16898        let (hd, nh, nhkv, ti, tkvi, cz) = (
16899            head_dim as i32,
16900            n_head as i32,
16901            n_head_kv as i32,
16902            t as i32,
16903            t_kv as i32,
16904            causal as i32,
16905        );
16906        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16907        let __s_b = self.gpu.stream();
16908        let mut b = __s_b.launch_builder(&f);
16909        b.arg(q)
16910            .arg(k)
16911            .arg(v)
16912            .arg(o)
16913            .arg(&hd)
16914            .arg(&nh)
16915            .arg(&nhkv)
16916            .arg(&ti)
16917            .arg(&tkvi)
16918            .arg(&scale)
16919            .arg(&cz)
16920            .arg(&ktb)
16921            .arg(&vtb);
16922        unsafe {
16923            b.launch(cfg)?;
16924        }
16925        Ok(())
16926    }
16927
16928    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
16929    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
16930    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
16931    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
16932    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
16933    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
16934    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
16935    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
16936    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
16937    #[allow(clippy::too_many_arguments)]
16938    pub fn fa_prefill_view_ws(
16939        &self,
16940        q: &CudaSlice<f32>,
16941        k: &cudarc::driver::CudaView<u8>,
16942        v: &cudarc::driver::CudaView<u8>,
16943        o: &mut CudaSlice<f32>,
16944        head_dim: usize,
16945        n_head: usize,
16946        n_head_kv: usize,
16947        t: usize,
16948        t_kv: usize,
16949        scale: f32,
16950        causal: bool,
16951        k_tok_bytes: usize,
16952        v_tok_bytes: usize,
16953        g: bool,
16954    ) -> Result<(), Box<dyn std::error::Error>> {
16955        if portable_mma_gated() {
16956            return self.sdpa_naive_quantized_view(
16957                q,
16958                k,
16959                v,
16960                o,
16961                head_dim,
16962                n_head,
16963                n_head_kv,
16964                t,
16965                t_kv,
16966                scale,
16967                causal,
16968                k_tok_bytes,
16969                v_tok_bytes,
16970            );
16971        }
16972        const BLOCK_Q: usize = 64;
16973        const BK: usize = 32;
16974        let kv_dim_k = n_head_kv * head_dim;
16975        let kv_dim_v = n_head_kv * head_dim;
16976        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16977        let v_ws_bytes = t_kv * kv_dim_v * 2;
16978        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
16979        let mut guard = self.prime_deqw_ws.lock().unwrap();
16980        let need_grow = match guard.as_ref() {
16981            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16982            None => true,
16983        };
16984        if need_grow {
16985            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16986            let (ck, cv) = guard
16987                .as_ref()
16988                .map(|(a, b)| (a.len(), b.len()))
16989                .unwrap_or((0, 0));
16990            *guard = Some((
16991                self.alloc_u8(grow(ck, k_ws_bytes))?,
16992                self.alloc_u8(grow(cv, v_ws_bytes))?,
16993            ));
16994        }
16995        let (kw, vw) = guard.as_mut().unwrap();
16996        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
16997        {
16998            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
16999            let f = if g {
17000                self.func_g("fa_dequant_kv_ws_bf16")
17001            } else {
17002                self.func("fa_dequant_kv_ws_bf16")
17003            };
17004            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17005            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17006            let cfg = LaunchConfig {
17007                grid_dim: (nblk.max(1), 1, 1),
17008                block_dim: (256, 1, 1),
17009                shared_mem_bytes: 0,
17010            };
17011            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17012            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17013            let __s_b = self.gpu.stream();
17014            let mut b = __s_b.launch_builder(&f);
17015            b.arg(k)
17016                .arg(v)
17017                .arg(&mut *kw)
17018                .arg(&mut *vw)
17019                .arg(&kdk)
17020                .arg(&kdv)
17021                .arg(&tkvi)
17022                .arg(&ktb)
17023                .arg(&vtb);
17024            unsafe {
17025                b.launch(cfg)?;
17026            }
17027        }
17028        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
17029        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
17030        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
17031        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
17032        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
17033        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
17034        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
17035        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17036            .map(|v| v != "0")
17037            .unwrap_or(true);
17038        {
17039            let hd_sfx = fa_hd_suffix(head_dim)?;
17040            let f = self.func(&format!(
17041                "fa_prefill_qw{}{hd_sfx}",
17042                if db { "_db" } else { "" }
17043            ));
17044            let shmem = if db {
17045                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
17046                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17047            } else {
17048                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17049            };
17050            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17051            f.set_attribute(
17052                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17053                shmem as i32,
17054            )?;
17055            let cfg = LaunchConfig {
17056                grid_dim: (
17057                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17058                    n_head as u32,
17059                    1,
17060                ),
17061                block_dim: (32, 4, 1),
17062                shared_mem_bytes: shmem,
17063            };
17064            let (hd, nh, nhkv, ti, tkvi, cz) = (
17065                head_dim as i32,
17066                n_head as i32,
17067                n_head_kv as i32,
17068                t as i32,
17069                t_kv as i32,
17070                causal as i32,
17071            );
17072            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17073            let __s_b = self.gpu.stream();
17074            let mut b = __s_b.launch_builder(&f);
17075            b.arg(q)
17076                .arg(&*kw)
17077                .arg(&*vw)
17078                .arg(o)
17079                .arg(&hd)
17080                .arg(&nh)
17081                .arg(&nhkv)
17082                .arg(&ti)
17083                .arg(&tkvi)
17084                .arg(&scale)
17085                .arg(&cz)
17086                .arg(&kdk)
17087                .arg(&kdv);
17088            unsafe {
17089                b.launch(cfg)?;
17090            }
17091        }
17092        Ok(())
17093    }
17094
17095    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
17096    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
17097    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
17098    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
17099    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
17100    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
17101    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
17102    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
17103    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
17104    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
17105    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
17106    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
17107    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
17108    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
17109    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
17110    #[allow(clippy::too_many_arguments)]
17111    pub fn fa_prefill_view_ws_w_hd128(
17112        &self,
17113        q: &CudaSlice<f32>,
17114        k: &cudarc::driver::CudaView<u8>,
17115        v: &cudarc::driver::CudaView<u8>,
17116        o: &mut CudaSlice<f32>,
17117        head_dim: usize,
17118        n_head: usize,
17119        n_head_kv: usize,
17120        t: usize,
17121        t_kv: usize,
17122        scale: f32,
17123        causal: bool,
17124        window: usize,
17125        k_tok_bytes: usize,
17126        v_tok_bytes: usize,
17127    ) -> Result<(), Box<dyn std::error::Error>> {
17128        assert_eq!(
17129            head_dim, 128,
17130            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
17131        );
17132        if portable_mma_gated() {
17133            return self.sdpa_naive_w_quantized_view(
17134                q,
17135                k,
17136                v,
17137                o,
17138                head_dim,
17139                n_head,
17140                n_head_kv,
17141                t,
17142                t_kv,
17143                scale,
17144                causal,
17145                window,
17146                k_tok_bytes,
17147                v_tok_bytes,
17148            );
17149        }
17150        const BLOCK_Q: usize = 64;
17151        const BK: usize = 32;
17152        let kv_dim_k = n_head_kv * head_dim;
17153        let kv_dim_v = n_head_kv * head_dim;
17154        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
17155        let v_ws_bytes = t_kv * kv_dim_v * 2;
17156        let mut guard = self.prime_deqw_ws.lock().unwrap();
17157        let need_grow = match guard.as_ref() {
17158            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
17159            None => true,
17160        };
17161        if need_grow {
17162            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
17163            let (ck, cv) = guard
17164                .as_ref()
17165                .map(|(a, b)| (a.len(), b.len()))
17166                .unwrap_or((0, 0));
17167            *guard = Some((
17168                self.alloc_u8(grow(ck, k_ws_bytes))?,
17169                self.alloc_u8(grow(cv, v_ws_bytes))?,
17170            ));
17171        }
17172        let (kw, vw) = guard.as_mut().unwrap();
17173        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
17174        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
17175        {
17176            let f = self.func("fa_dequant_kv_ws_bf16");
17177            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
17178            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
17179            let cfg = LaunchConfig {
17180                grid_dim: (nblk.max(1), 1, 1),
17181                block_dim: (256, 1, 1),
17182                shared_mem_bytes: 0,
17183            };
17184            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
17185            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17186            let __s_b = self.gpu.stream();
17187            let mut b = __s_b.launch_builder(&f);
17188            b.arg(k)
17189                .arg(v)
17190                .arg(&mut *kw)
17191                .arg(&mut *vw)
17192                .arg(&kdk)
17193                .arg(&kdv)
17194                .arg(&tkvi)
17195                .arg(&ktb)
17196                .arg(&vtb);
17197            unsafe {
17198                b.launch(cfg)?;
17199            }
17200        }
17201        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
17202        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
17203            .map(|v| v != "0")
17204            .unwrap_or(true);
17205        {
17206            let f = self.func(if db {
17207                "fa_prefill_qw_db_w_hd128"
17208            } else {
17209                "fa_prefill_qw_w_hd128"
17210            });
17211            let shmem = if db {
17212                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
17213            } else {
17214                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
17215            };
17216            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17217            f.set_attribute(
17218                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17219                shmem as i32,
17220            )?;
17221            let cfg = LaunchConfig {
17222                grid_dim: (
17223                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
17224                    n_head as u32,
17225                    1,
17226                ),
17227                block_dim: (32, 4, 1),
17228                shared_mem_bytes: shmem,
17229            };
17230            let (hd, nh, nhkv, ti, tkvi, cz) = (
17231                head_dim as i32,
17232                n_head as i32,
17233                n_head_kv as i32,
17234                t as i32,
17235                t_kv as i32,
17236                causal as i32,
17237            );
17238            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
17239            let __s_b = self.gpu.stream();
17240            let mut b = __s_b.launch_builder(&f);
17241            b.arg(q)
17242                .arg(&*kw)
17243                .arg(&*vw)
17244                .arg(o)
17245                .arg(&hd)
17246                .arg(&nh)
17247                .arg(&nhkv)
17248                .arg(&ti)
17249                .arg(&tkvi)
17250                .arg(&scale)
17251                .arg(&cz)
17252                .arg(&kdk)
17253                .arg(&kdv)
17254                .arg(&wnd);
17255            unsafe {
17256                b.launch(cfg)?;
17257            }
17258        }
17259        Ok(())
17260    }
17261
17262    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
17263    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
17264    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
17265    pub fn fa_decode(
17266        &self,
17267        q: &CudaSlice<f32>,
17268        k: &cudarc::driver::CudaView<u8>,
17269        v: &cudarc::driver::CudaView<u8>,
17270        o: &mut CudaSlice<f32>,
17271        head_dim: usize,
17272        n_head: usize,
17273        n_head_kv: usize,
17274        t_kv: usize,
17275        scale: f32,
17276        k_tok_bytes: usize,
17277        v_tok_bytes: usize,
17278    ) -> Result<(), Box<dyn std::error::Error>> {
17279        self.fa_decode_kvmod(
17280            q,
17281            k,
17282            v,
17283            o,
17284            head_dim,
17285            n_head,
17286            n_head_kv,
17287            t_kv,
17288            scale,
17289            k_tok_bytes,
17290            v_tok_bytes,
17291            false,
17292        )
17293    }
17294
17295    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
17296    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
17297    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
17298    #[allow(clippy::too_many_arguments)]
17299    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
17300    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
17301    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
17302    #[allow(clippy::too_many_arguments)]
17303    #[allow(clippy::too_many_arguments)]
17304    fn fa_decode_scalar_unified(
17305        &self,
17306        q: &cudarc::driver::CudaView<f32>,
17307        k: &cudarc::driver::CudaView<u8>,
17308        v: &cudarc::driver::CudaView<u8>,
17309        o: &mut cudarc::driver::CudaViewMut<f32>,
17310        head_dim: usize,
17311        n_head: usize,
17312        n_head_kv: usize,
17313        t_kv_host: usize,
17314        t_kv_dev: Option<&CudaSlice<i32>>,
17315        scale: f32,
17316        n_splits: usize,
17317        split_keys: usize,
17318        k_tok_bytes: usize,
17319        v_tok_bytes: usize,
17320        g: bool,
17321        part_o: &mut CudaSlice<f32>,
17322        part_m: &mut CudaSlice<f32>,
17323        part_l: &mut CudaSlice<f32>,
17324        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17325    ) -> Result<(), Box<dyn std::error::Error>> {
17326        let f = if g {
17327            self.func_g("fa_decode_f32")
17328        } else {
17329            self.fa_func("fa_decode_f32", head_dim)
17330        };
17331        let cfg = LaunchConfig {
17332            grid_dim: (n_head as u32, n_splits as u32, 1),
17333            block_dim: (head_dim as u32, 1, 1),
17334            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
17335        };
17336        let (hd, nh, nhkv, nsp) = (
17337            head_dim as i32,
17338            n_head as i32,
17339            n_head_kv as i32,
17340            n_splits as i32,
17341        );
17342        let (ktb, vtb, tkvi, ski) = (
17343            k_tok_bytes as i64,
17344            v_tok_bytes as i64,
17345            t_kv_host as i32,
17346            split_keys as i32,
17347        );
17348        let __s_b = self.gpu.stream();
17349        let mut b = __s_b.launch_builder(&f);
17350        match t_kv_dev {
17351            Some(d) => {
17352                b.arg(q)
17353                    .arg(k)
17354                    .arg(v)
17355                    .arg(&mut *part_o)
17356                    .arg(&mut *part_m)
17357                    .arg(&mut *part_l)
17358                    .arg(&hd)
17359                    .arg(&nh)
17360                    .arg(&nhkv)
17361                    .arg(&tkvi)
17362                    .arg(d)
17363                    .arg(&scale)
17364                    .arg(&nsp)
17365                    .arg(&ski)
17366                    .arg(&ktb)
17367                    .arg(&vtb);
17368                unsafe {
17369                    b.launch(cfg)?;
17370                }
17371            }
17372            None => {
17373                let null: u64 = 0;
17374                b.arg(q)
17375                    .arg(k)
17376                    .arg(v)
17377                    .arg(&mut *part_o)
17378                    .arg(&mut *part_m)
17379                    .arg(&mut *part_l)
17380                    .arg(&hd)
17381                    .arg(&nh)
17382                    .arg(&nhkv)
17383                    .arg(&tkvi)
17384                    .arg(&null)
17385                    .arg(&scale)
17386                    .arg(&nsp)
17387                    .arg(&ski)
17388                    .arg(&ktb)
17389                    .arg(&vtb);
17390                unsafe {
17391                    b.launch(cfg)?;
17392                }
17393            }
17394        }
17395        let cfg2 = LaunchConfig {
17396            grid_dim: (n_head as u32, 1, 1),
17397            block_dim: (head_dim as u32, 1, 1),
17398            shared_mem_bytes: 0,
17399        };
17400        if let Some((oq, od)) = q8_out {
17401            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
17402            let fc = if g {
17403                self.func_g("fa_decode_combine_q8_1")
17404            } else {
17405                self.fa_func("fa_decode_combine_q8_1", head_dim)
17406            };
17407            let __s_b2 = self.gpu.stream();
17408            let mut b2 = __s_b2.launch_builder(&fc);
17409            b2.arg(&*part_o)
17410                .arg(&*part_m)
17411                .arg(&*part_l)
17412                .arg(oq)
17413                .arg(od)
17414                .arg(&hd)
17415                .arg(&nh)
17416                .arg(&nsp);
17417            unsafe {
17418                b2.launch(cfg2)?;
17419            }
17420            return Ok(());
17421        }
17422        let fc = if g {
17423            self.func_g("fa_decode_combine_f32")
17424        } else {
17425            self.fa_func("fa_decode_combine_f32", head_dim)
17426        };
17427        let __s_b2 = self.gpu.stream();
17428        let mut b2 = __s_b2.launch_builder(&fc);
17429        b2.arg(&*part_o)
17430            .arg(&*part_m)
17431            .arg(&*part_l)
17432            .arg(o)
17433            .arg(&hd)
17434            .arg(&nh)
17435            .arg(&nsp);
17436        unsafe {
17437            b2.launch(cfg2)?;
17438        }
17439        Ok(())
17440    }
17441
17442    pub fn fa_decode_kvmod(
17443        &self,
17444        q: &CudaSlice<f32>,
17445        k: &cudarc::driver::CudaView<u8>,
17446        v: &cudarc::driver::CudaView<u8>,
17447        o: &mut CudaSlice<f32>,
17448        head_dim: usize,
17449        n_head: usize,
17450        n_head_kv: usize,
17451        t_kv: usize,
17452        scale: f32,
17453        k_tok_bytes: usize,
17454        v_tok_bytes: usize,
17455        g: bool,
17456    ) -> Result<(), Box<dyn std::error::Error>> {
17457        let q_view = q.as_view();
17458        let mut o_view = o.as_view_mut();
17459        self.fa_decode_kvmod_view(
17460            &q_view,
17461            k,
17462            v,
17463            &mut o_view,
17464            head_dim,
17465            n_head,
17466            n_head_kv,
17467            t_kv,
17468            scale,
17469            k_tok_bytes,
17470            v_tok_bytes,
17471            g,
17472        )
17473    }
17474
17475    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
17476    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
17477    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
17478    /// per-session KV view and FA launch.
17479    #[allow(clippy::too_many_arguments)]
17480    pub fn fa_decode_kvmod_view(
17481        &self,
17482        q: &cudarc::driver::CudaView<f32>,
17483        k: &cudarc::driver::CudaView<u8>,
17484        v: &cudarc::driver::CudaView<u8>,
17485        o: &mut cudarc::driver::CudaViewMut<f32>,
17486        head_dim: usize,
17487        n_head: usize,
17488        n_head_kv: usize,
17489        t_kv: usize,
17490        scale: f32,
17491        k_tok_bytes: usize,
17492        v_tok_bytes: usize,
17493        g: bool,
17494    ) -> Result<(), Box<dyn std::error::Error>> {
17495        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
17496        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
17497        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
17498        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
17499        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
17500        //
17501        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
17502        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
17503        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
17504        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
17505        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
17506        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
17507        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
17508        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
17509        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
17510        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
17511        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
17512        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
17513        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
17514        // fall to the exact scalar there instead of the broken register arm.
17515        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
17516        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
17517        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
17518        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
17519        if g && head_dim == 256 && !fa_v4_at(t_kv) {
17520            fa_vec = false;
17521        }
17522        let sp = fa_split_keys(t_kv, n_head_kv);
17523        let n_splits = if fa_vec {
17524            ((t_kv + sp - 1) / sp).max(1)
17525        } else {
17526            ((t_kv + 255) / 256).max(1)
17527        };
17528        let o_len = n_head * n_splits * head_dim;
17529        let ml_len = n_head * n_splits;
17530        let mut part_guard = self.fa_part_pool.lock().unwrap();
17531        if part_guard
17532            .as_ref()
17533            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17534            .unwrap_or(true)
17535        {
17536            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17537            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17538            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17539            // later live allocations land at those addresses, and the next graph REPLAY writes
17540            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17541            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17542            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17543            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17544            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17545            // (total retired < final size).
17546            let old = part_guard.take();
17547            let (co, cm) = old
17548                .as_ref()
17549                .map(|pp| (pp.0.len(), pp.1.len()))
17550                .unwrap_or((0, 0));
17551            if let Some(old) = old {
17552                self.fa_part_retired.lock().unwrap().push(old);
17553            }
17554            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17555                eprintln!(
17556                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17557                    co, o_len, cm, ml_len
17558                );
17559            }
17560            *part_guard = Some((
17561                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17562                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17563                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17564            ));
17565        }
17566        let pg = part_guard.as_mut().unwrap();
17567        self.gpu
17568            .stream()
17569            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17570        self.gpu
17571            .stream()
17572            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17573        self.gpu
17574            .stream()
17575            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17576        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17577        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17578        let (hd, nh, nhkv, tkvi, nsp) = (
17579            head_dim as i32,
17580            n_head as i32,
17581            n_head_kv as i32,
17582            t_kv as i32,
17583            n_splits as i32,
17584        );
17585        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17586        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
17587        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
17588        // silently truncating the accumulator.
17589        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
17590        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
17591        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
17592        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
17593        // 178.4 -> 173.7 when 512 rode vec unconditionally).
17594        let fa512_min = fa512_min_tkv();
17595        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
17596        // g-module keeps the v4 pick (its class is not the depth-decay class).
17597        let deep = fa_vec
17598            && head_dim == 256
17599            && fa_v4_at(t_kv)
17600            && !g
17601            && fa_deep_at(t_kv)
17602            && !matches!(fa_v4_mode(), "noB3" | "stage");
17603        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
17604            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
17605            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
17606            let gqa = (n_head / n_head_kv).max(1) as u32;
17607            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
17608            (
17609                fv,
17610                LaunchConfig {
17611                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17612                    block_dim: (32, gqa, 1),
17613                    shared_mem_bytes: 0,
17614                },
17615            )
17616        } else if fa_vec && head_dim <= 256 {
17617            let gqa = (n_head / n_head_kv).max(1) as u32;
17618            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
17619            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
17620            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
17621            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
17622            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
17623            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
17624            // dequant each tile ONCE per block.
17625            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
17626            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
17627            // there by 12x — latency, not bandwidth, rules small KV).
17628            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17629            let smem_tkv = *SMEM_TKV.get_or_init(|| {
17630                std::env::var("MEMRA_FA_SMEM_TKV")
17631                    .ok()
17632                    .and_then(|v| v.parse().ok())
17633                    .unwrap_or_else(|| {
17634                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17635                    })
17636            });
17637            if fa_v4_at(t_kv) && head_dim == 256 {
17638                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
17639                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
17640                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
17641                let v4name = match fa_v4_mode() {
17642                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
17643                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
17644                    _ if deep => "fa_decode_vec_q_v4_deep",
17645                    _ => "fa_decode_vec_q_v4",
17646                };
17647                let fv = if g {
17648                    self.func_g(v4name)
17649                } else {
17650                    self.func(v4name)
17651                };
17652                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
17653                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
17654                let shmem = (if deep { 12160 } else { 11520 }
17655                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
17656                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17657                fv.set_attribute(
17658                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17659                    shmem as i32,
17660                )?;
17661                (
17662                    fv,
17663                    LaunchConfig {
17664                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17665                        block_dim: (32, gqa, 1),
17666                        shared_mem_bytes: shmem,
17667                    },
17668                )
17669            } else if fa_v3_active(head_dim) {
17670                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
17671                // smem = sV only (half of v2's).
17672                let fv = if g {
17673                    self.func_g("fa_decode_vec_q_v3")
17674                } else {
17675                    self.func("fa_decode_vec_q_v3")
17676                };
17677                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
17678                (
17679                    fv,
17680                    LaunchConfig {
17681                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17682                        block_dim: (32, gqa, 1),
17683                        shared_mem_bytes: shmem,
17684                    },
17685                )
17686            } else if fa_v2_on() {
17687                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
17688                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
17689                // partials; same 32KB sK+sV tile as the smem twin.
17690                let fv = if g {
17691                    self.func_g("fa_decode_vec_q_v2")
17692                } else {
17693                    self.func("fa_decode_vec_q_v2")
17694                };
17695                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17696                (
17697                    fv,
17698                    LaunchConfig {
17699                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17700                        block_dim: (32, gqa, 1),
17701                        shared_mem_bytes: shmem,
17702                    },
17703                )
17704            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
17705            {
17706                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
17707                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
17708                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
17709                let fv = if g {
17710                    self.func_g("fa_decode_vec_q_smem")
17711                } else {
17712                    self.func("fa_decode_vec_q_smem")
17713                };
17714                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
17715                use cudarc::driver::sys::CUfunction_attribute_enum as A;
17716                fv.set_attribute(
17717                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17718                    shmem as i32,
17719                )?;
17720                (
17721                    fv,
17722                    LaunchConfig {
17723                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17724                        block_dim: (32, gqa, 1),
17725                        shared_mem_bytes: shmem,
17726                    },
17727                )
17728            } else {
17729                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
17730                // dequant, zero dynamic shared memory.
17731                let fv = if g {
17732                    self.func_g("fa_decode_vec_q")
17733                } else {
17734                    self.func("fa_decode_vec_q")
17735                };
17736                (
17737                    fv,
17738                    LaunchConfig {
17739                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
17740                        block_dim: (32, gqa, 1),
17741                        shared_mem_bytes: 0,
17742                    },
17743                )
17744            }
17745        } else {
17746            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
17747            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
17748            return self.fa_decode_scalar_unified(
17749                q,
17750                k,
17751                v,
17752                o,
17753                head_dim,
17754                n_head,
17755                n_head_kv,
17756                t_kv,
17757                None,
17758                scale,
17759                n_splits,
17760                if fa_vec { sp } else { 256 },
17761                k_tok_bytes,
17762                v_tok_bytes,
17763                g,
17764                part_o,
17765                part_m,
17766                part_l,
17767                None,
17768            );
17769        };
17770        let __s_b = self.gpu.stream();
17771        let mut b = __s_b.launch_builder(&f);
17772        b.arg(q)
17773            .arg(k)
17774            .arg(v)
17775            .arg(&mut *part_o)
17776            .arg(&mut *part_m)
17777            .arg(&mut *part_l)
17778            .arg(&hd)
17779            .arg(&nh)
17780            .arg(&nhkv)
17781            .arg(&tkvi)
17782            .arg(&scale)
17783            .arg(&nsp)
17784            .arg(&ktb)
17785            .arg(&vtb);
17786        unsafe {
17787            b.launch(cfg)?;
17788        }
17789        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
17790        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
17791        let (fc, cfg2) = (
17792            if g {
17793                self.func_g("fa_decode_combine_f32")
17794            } else {
17795                self.fa_func("fa_decode_combine_f32", head_dim)
17796            },
17797            LaunchConfig {
17798                grid_dim: (n_head as u32, 1, 1),
17799                block_dim: (head_dim as u32, 1, 1),
17800                shared_mem_bytes: 0,
17801            },
17802        );
17803        let __s_b2 = self.gpu.stream();
17804        let mut b2 = __s_b2.launch_builder(&fc);
17805        b2.arg(&*part_o)
17806            .arg(&*part_m)
17807            .arg(&*part_l)
17808            .arg(o)
17809            .arg(&hd)
17810            .arg(&nh)
17811            .arg(&nsp);
17812        unsafe {
17813            b2.launch(cfg2)?;
17814        }
17815        Ok(())
17816    }
17817
17818    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
17819    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
17820    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
17821    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
17822    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
17823    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
17824    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
17825    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
17826    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
17827    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
17828    #[allow(clippy::too_many_arguments)]
17829    pub fn fa_decode_batch_seqs_v4(
17830        &self,
17831        q: &CudaSlice<f32>,
17832        kv_ptrs: &cudarc::driver::CudaView<u64>,
17833        pos_seq: &CudaSlice<i32>,
17834        o: &mut CudaSlice<f32>,
17835        head_dim: usize,
17836        n_head: usize,
17837        n_head_kv: usize,
17838        b_n: usize,
17839        t_kv_max: usize,
17840        scale: f32,
17841        split_keys: usize,
17842        k_tok_bytes: usize,
17843        v_tok_bytes: usize,
17844    ) -> Result<(), Box<dyn std::error::Error>> {
17845        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
17846        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
17847        let o_len = b_n * n_head * n_splits_max * head_dim;
17848        let ml_len = b_n * n_head * n_splits_max;
17849        let mut part_guard = self.fa_part_pool.lock().unwrap();
17850        if part_guard
17851            .as_ref()
17852            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17853            .unwrap_or(true)
17854        {
17855            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17856            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17857            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17858            // later live allocations land at those addresses, and the next graph REPLAY writes
17859            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17860            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17861            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17862            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17863            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17864            // (total retired < final size).
17865            let old = part_guard.take();
17866            let (co, cm) = old
17867                .as_ref()
17868                .map(|pp| (pp.0.len(), pp.1.len()))
17869                .unwrap_or((0, 0));
17870            if let Some(old) = old {
17871                self.fa_part_retired.lock().unwrap().push(old);
17872            }
17873            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17874                eprintln!(
17875                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17876                    co, o_len, cm, ml_len
17877                );
17878            }
17879            *part_guard = Some((
17880                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17881                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17882                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17883            ));
17884        }
17885        let pg = part_guard.as_mut().unwrap();
17886        self.gpu
17887            .stream()
17888            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17889        self.gpu
17890            .stream()
17891            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17892        self.gpu
17893            .stream()
17894            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17895        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17896        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17897        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
17898        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17899        let gqa = (n_head / n_head_kv).max(1) as u32;
17900        let f = self.func("fa_decode_vec_q_seqs_v4");
17901        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
17902        let shmem = (11520 + 32 * head_dim * 2) as u32;
17903        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17904        f.set_attribute(
17905            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17906            shmem as i32,
17907        )?;
17908        let cfg = LaunchConfig {
17909            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
17910            block_dim: (32, gqa, 1),
17911            shared_mem_bytes: shmem,
17912        };
17913        {
17914            let __s_b = self.gpu.stream();
17915            let mut b = __s_b.launch_builder(&f);
17916            b.arg(q)
17917                .arg(kv_ptrs)
17918                .arg(pos_seq)
17919                .arg(&mut *part_o)
17920                .arg(&mut *part_m)
17921                .arg(&mut *part_l)
17922                .arg(&hd)
17923                .arg(&nh)
17924                .arg(&nhkv)
17925                .arg(&scale)
17926                .arg(&nspm)
17927                .arg(&spk)
17928                .arg(&ktb)
17929                .arg(&vtb);
17930            unsafe {
17931                b.launch(cfg)?;
17932            }
17933        }
17934        let fc = self.func("fa_decode_combine_seqs");
17935        let cfg2 = LaunchConfig {
17936            grid_dim: (n_head as u32, b_n as u32, 1),
17937            block_dim: (head_dim as u32, 1, 1),
17938            shared_mem_bytes: 0,
17939        };
17940        let __s_b2 = self.gpu.stream();
17941        let mut b2 = __s_b2.launch_builder(&fc);
17942        b2.arg(&*part_o)
17943            .arg(&*part_m)
17944            .arg(&*part_l)
17945            .arg(o)
17946            .arg(&hd)
17947            .arg(&nh)
17948            .arg(pos_seq)
17949            .arg(&nspm)
17950            .arg(&spk);
17951        unsafe {
17952            b2.launch(cfg2)?;
17953        }
17954        Ok(())
17955    }
17956
17957    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
17958    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
17959    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
17960    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
17961    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
17962    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
17963    #[allow(clippy::too_many_arguments)]
17964    pub fn append_kv_quantized_seqs(
17965        &self,
17966        k_rows: &CudaSlice<f32>,
17967        v_rows: &CudaSlice<f32>,
17968        kv_ptrs: &cudarc::driver::CudaView<u64>,
17969        pos_seq: &CudaSlice<i32>,
17970        b_n: usize,
17971        kv_dim_k: usize,
17972        kv_dim_v: usize,
17973        k_tok_bytes: usize,
17974        v_tok_bytes: usize,
17975    ) -> Result<(), Box<dyn std::error::Error>> {
17976        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
17977        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
17978        let cfg = LaunchConfig {
17979            grid_dim: (nblk, b_n as u32, 1),
17980            block_dim: (32, 1, 1),
17981            shared_mem_bytes: 0,
17982        };
17983        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17984        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17985        let __s_b = self.gpu.stream();
17986        let mut b = __s_b.launch_builder(&f);
17987        b.arg(k_rows)
17988            .arg(v_rows)
17989            .arg(kv_ptrs)
17990            .arg(pos_seq)
17991            .arg(&kdk)
17992            .arg(&kdv)
17993            .arg(&ktb)
17994            .arg(&vtb);
17995        unsafe {
17996            b.launch(cfg)?;
17997        }
17998        Ok(())
17999    }
18000
18001    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
18002    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
18003    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
18004    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
18005    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
18006    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
18007        std::env::var("MEMRA_NO_FA_VEC").is_err()
18008            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
18009            && base_len + 1 >= fa_vec_min_tkv()
18010            && head_dim <= 256
18011            && head_dim % 32 == 0
18012    }
18013
18014    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
18015    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
18016    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
18017    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
18018    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
18019    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
18020    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
18021    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
18022    #[allow(clippy::too_many_arguments)]
18023    pub fn fa_decode_rows(
18024        &self,
18025        q: &CudaSlice<f32>,
18026        k: &cudarc::driver::CudaView<u8>,
18027        v: &cudarc::driver::CudaView<u8>,
18028        o: &mut CudaSlice<f32>,
18029        head_dim: usize,
18030        n_head: usize,
18031        n_head_kv: usize,
18032        base_len: usize,
18033        t: usize,
18034        scale: f32,
18035        k_tok_bytes: usize,
18036        v_tok_bytes: usize,
18037        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
18038        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
18039        // keep the host arg. None is a bug for hd512 (asserted below).
18040        base_dev: Option<(&CudaSlice<i32>, i32)>,
18041        // K and V planes hold the same values (gemma globals, wv:=wk): pick
18042        // the _kv twin — V plane never read, value rides the q8_0 key dq.
18043        kv_shared: bool,
18044        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
18045        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
18046        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
18047        g: bool,
18048        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
18049        // (hd512 path) — the standalone quantize launch folds away.
18050        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18051    ) -> Result<(), Box<dyn std::error::Error>> {
18052        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
18053        let t_kv_max = base_len + t; // LAST row's key bound
18054        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
18055        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
18056        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
18057        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
18058        // (parity law), so the partition is freely tunable — verify and decode move together.
18059        if head_dim == 512 {
18060            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18061            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
18062            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
18063            let v = *SP512.get_or_init(|| {
18064                std::env::var("MEMRA_FA_SP512")
18065                    .ok()
18066                    .and_then(|x| x.parse().ok())
18067                    .unwrap_or(0)
18068            });
18069            sp = if v >= 8 {
18070                v
18071            } else {
18072                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18073            };
18074        }
18075        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18076        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18077        let gqa = (n_head / n_head_kv).max(1) as u32;
18078        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
18079        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
18080        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
18081        // the different partition changes the combine's FP order (greedy tie flips at depth;
18082        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
18083        // consecutive rows by their OWN ladder value and launch once per group — each row then
18084        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
18085        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
18086        // sp override is t_kv-independent by construction).
18087        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
18088        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
18089            groups.push((0, t, sp));
18090        } else {
18091            let mut r0 = 0usize;
18092            while r0 < t {
18093                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
18094                let mut r1 = r0 + 1;
18095                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
18096                    r1 += 1;
18097                }
18098                groups.push((r0, r1 - r0, sp_g));
18099                r0 = r1;
18100            }
18101        }
18102        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
18103        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
18104        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
18105        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18106        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
18107            std::env::var("MEMRA_FA_SMEM_TKV")
18108                .ok()
18109                .and_then(|v| v.parse().ok())
18110                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18111        });
18112        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
18113        let v3 = fa_v3_active(head_dim);
18114        let smem_rows =
18115            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
18116        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
18117        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
18118        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
18119        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
18120        let _ = kv_shared;
18121        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
18122        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
18123        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
18124        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
18125        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
18126        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
18127        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
18128        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
18129        // (kv_head, split) stages its tile once and loops the rows over it — kills the
18130        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
18131        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
18132        // shared by every hd512 caller through this wrapper (decode+verify flip together;
18133        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
18134        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
18135        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
18136        // not unpack-bound; jsonl 2026-07-14.
18137        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18138        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
18139        let tb512 = head_dim == 512
18140            && sp <= 32
18141            && n_head / n_head_kv.max(1) <= 16
18142            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
18143        let fname = if tb512 {
18144            "fa_decode_vec_q_rows_v4_512_tb"
18145        } else if i2 {
18146            "fa_decode_vec_q_rows_dpl16_i2"
18147        } else if head_dim == 512 {
18148            "fa_decode_vec_q_rows_dpl16"
18149        }
18150        // gemma globals (parity law)
18151        else if v4 {
18152            "fa_decode_vec_q_rows_v4"
18153        } else if v3 {
18154            "fa_decode_vec_q_rows_v3"
18155        } else if fa_v2_on() {
18156            "fa_decode_vec_q_rows_v2"
18157        } else if smem_rows {
18158            "fa_decode_vec_q_rows_smem"
18159        } else {
18160            "fa_decode_vec_q_rows"
18161        };
18162        let f = if head_dim == 512 {
18163            self.fa_func(fname, head_dim)
18164        } else if g {
18165            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
18166            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
18167            // g-module rows against decode's g-module v4 — different programs, short-VG
18168            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
18169            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
18170            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
18171            // dq macros are format-aware.
18172            self.func_g(if smem_rows {
18173                "fa_decode_vec_q_rows"
18174            } else {
18175                fname
18176            })
18177        } else {
18178            self.func(fname)
18179        };
18180        let shmem = if tb512 {
18181            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
18182            let gk = Self::gkv_on();
18183            let sh =
18184                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
18185            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18186            f.set_attribute(
18187                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18188                sh as i32,
18189            )?;
18190            sh
18191        } else if v4 || v3 || smem_rows || fa_v2_on() {
18192            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
18193            let sh = (if v4 {
18194                11520 + 32 * head_dim * if g { 1 } else { 2 }
18195            } else if v3 {
18196                32 * head_dim * 2
18197            } else {
18198                2 * 32 * head_dim * 2
18199            }) as u32;
18200            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18201            f.set_attribute(
18202                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18203                sh as i32,
18204            )?;
18205            sh
18206        } else {
18207            0
18208        };
18209        // Per-GROUP launches (single group in the common case — identical to the pre-fix
18210        // single launch there): each group gets its own partials (the rows kernel indexes
18211        // partials by its LOCAL grid.z row) and q/o row-offset views.
18212        for &(r0, t_g, sp_g) in &groups {
18213            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
18214            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
18215            let base_i = (base_len + r0) as i32;
18216            let o_len = t_g * n_head * n_splits_g * head_dim;
18217            let ml_len = t_g * n_head * n_splits_g;
18218            let mut part_guard = self.fa_part_pool.lock().unwrap();
18219            if part_guard
18220                .as_ref()
18221                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18222                .unwrap_or(true)
18223            {
18224                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18225                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18226                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18227                // later live allocations land at those addresses, and the next graph REPLAY writes
18228                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18229                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18230                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18231                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18232                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18233                // (total retired < final size).
18234                let old = part_guard.take();
18235                let (co, cm) = old
18236                    .as_ref()
18237                    .map(|pp| (pp.0.len(), pp.1.len()))
18238                    .unwrap_or((0, 0));
18239                if let Some(old) = old {
18240                    self.fa_part_retired.lock().unwrap().push(old);
18241                }
18242                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18243                    eprintln!(
18244                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18245                        co, o_len, cm, ml_len
18246                    );
18247                }
18248                *part_guard = Some((
18249                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18250                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18251                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18252                ));
18253            }
18254            let pg = part_guard.as_mut().unwrap();
18255            self.gpu
18256                .stream()
18257                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18258            self.gpu
18259                .stream()
18260                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18261            self.gpu
18262                .stream()
18263                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18264            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18265            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
18266            let qv = self.view(q, t * n_head * head_dim);
18267            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18268            let cfg = LaunchConfig {
18269                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
18270                block_dim: (32, gqa, 1),
18271                shared_mem_bytes: shmem,
18272            };
18273            {
18274                let __s_b = self.gpu.stream();
18275                let mut b = __s_b.launch_builder(&f);
18276                if tb512 {
18277                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
18278                    let (bd, plus) =
18279                        base_dev.expect("hd512 rows twin requires a device base counter");
18280                    let plus_g = plus + r0 as i32;
18281                    let nr = t_g as i32;
18282                    if Self::pdl_on() && Self::pdl_wb_on() {
18283                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
18284                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18285                        let s = &self.gpu.stream();
18286                        let (pq, _b0) = q_g.device_ptr(s);
18287                        let (pk, _b1) = k.device_ptr(s);
18288                        let (pv, _b2) = v.device_ptr(s);
18289                        let (po, _b3) = part_o.device_ptr_mut(s);
18290                        let (pm, _b4) = part_m.device_ptr_mut(s);
18291                        let (pl, _b5) = part_l.device_ptr_mut(s);
18292                        let (pb, _b6) = bd.device_ptr(s);
18293                        let mut ps = [
18294                            &pq as *const _ as *mut std::ffi::c_void,
18295                            &pk as *const _ as *mut _,
18296                            &pv as *const _ as *mut _,
18297                            &po as *const _ as *mut _,
18298                            &pm as *const _ as *mut _,
18299                            &pl as *const _ as *mut _,
18300                            &hd as *const _ as *mut _,
18301                            &nh as *const _ as *mut _,
18302                            &nhkv as *const _ as *mut _,
18303                            &pb as *const _ as *mut _,
18304                            &plus_g as *const _ as *mut _,
18305                            &scale as *const _ as *mut _,
18306                            &nspm as *const _ as *mut _,
18307                            &spk as *const _ as *mut _,
18308                            &ktb as *const _ as *mut _,
18309                            &vtb as *const _ as *mut _,
18310                            &nr as *const _ as *mut _,
18311                        ];
18312                        unsafe {
18313                            self.launch_pdl_flash(
18314                                Self::gkv_on(),
18315                                "fa_decode_vec_q_rows_v4_512_tb",
18316                                (n_head_kv as u32, n_splits_g as u32, 1),
18317                                (32, gqa, 1),
18318                                shmem,
18319                                &mut ps,
18320                            )?;
18321                        }
18322                    } else {
18323                        let cfg_tb = LaunchConfig {
18324                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
18325                            block_dim: (32, gqa, 1),
18326                            shared_mem_bytes: shmem,
18327                        };
18328                        b.arg(&q_g)
18329                            .arg(k)
18330                            .arg(v)
18331                            .arg(&mut *part_o)
18332                            .arg(&mut *part_m)
18333                            .arg(&mut *part_l)
18334                            .arg(&hd)
18335                            .arg(&nh)
18336                            .arg(&nhkv)
18337                            .arg(bd)
18338                            .arg(&plus_g)
18339                            .arg(&scale)
18340                            .arg(&nspm)
18341                            .arg(&spk)
18342                            .arg(&ktb)
18343                            .arg(&vtb)
18344                            .arg(&nr);
18345                        unsafe {
18346                            b.launch(cfg_tb)?;
18347                        }
18348                    }
18349                } else if head_dim == 512 {
18350                    let (bd, plus) =
18351                        base_dev.expect("hd512 rows twin requires a device base counter");
18352                    let plus_g = plus + r0 as i32;
18353                    b.arg(&q_g)
18354                        .arg(k)
18355                        .arg(v)
18356                        .arg(&mut *part_o)
18357                        .arg(&mut *part_m)
18358                        .arg(&mut *part_l)
18359                        .arg(&hd)
18360                        .arg(&nh)
18361                        .arg(&nhkv)
18362                        .arg(bd)
18363                        .arg(&plus_g)
18364                        .arg(&scale)
18365                        .arg(&nspm)
18366                        .arg(&spk)
18367                        .arg(&ktb)
18368                        .arg(&vtb);
18369                    unsafe {
18370                        b.launch(cfg)?;
18371                    }
18372                } else {
18373                    b.arg(&q_g)
18374                        .arg(k)
18375                        .arg(v)
18376                        .arg(&mut *part_o)
18377                        .arg(&mut *part_m)
18378                        .arg(&mut *part_l)
18379                        .arg(&hd)
18380                        .arg(&nh)
18381                        .arg(&nhkv)
18382                        .arg(&base_i)
18383                        .arg(&scale)
18384                        .arg(&nspm)
18385                        .arg(&spk)
18386                        .arg(&ktb)
18387                        .arg(&vtb);
18388                    unsafe {
18389                        b.launch(cfg)?;
18390                    }
18391                }
18392            }
18393            let cfg2 = LaunchConfig {
18394                grid_dim: (n_head as u32, t_g as u32, 1),
18395                block_dim: (head_dim as u32, 1, 1),
18396                shared_mem_bytes: 0,
18397            };
18398            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
18399            if head_dim == 512 {
18400                // device-len combine (shared by verify/eager/graph — parity by symbol): the
18401                // per-row n_splits derives from the SAME counter the rows kernel read.
18402                let (bd, plus) = base_dev.unwrap();
18403                let plus_g = plus + r0 as i32;
18404                if let Some((oq, od)) = q8_out.as_mut() {
18405                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
18406                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
18407                    if Self::pdl_on() && Self::pdl_wb_on() {
18408                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
18409                        use cudarc::driver::{DevicePtr, DevicePtrMut};
18410                        let s = &self.gpu.stream();
18411                        let (po, _g0) = part_o.device_ptr(s);
18412                        let (pm, _g1) = part_m.device_ptr(s);
18413                        let (pl, _g2) = part_l.device_ptr(s);
18414                        let (pq, _g3) = oq.device_ptr_mut(s);
18415                        let (pd, _g4) = od.device_ptr_mut(s);
18416                        let (pb, _g5) = bd.device_ptr(s);
18417                        let mut ps = [
18418                            &po as *const _ as *mut std::ffi::c_void,
18419                            &pm as *const _ as *mut _,
18420                            &pl as *const _ as *mut _,
18421                            &pq as *const _ as *mut _,
18422                            &pd as *const _ as *mut _,
18423                            &hd as *const _ as *mut _,
18424                            &nh as *const _ as *mut _,
18425                            &pb as *const _ as *mut _,
18426                            &plus_g as *const _ as *mut _,
18427                            &nspm as *const _ as *mut _,
18428                            &spk as *const _ as *mut _,
18429                        ];
18430                        unsafe {
18431                            self.launch_pdl_flash(
18432                                Self::gkv_on(),
18433                                "fa_decode_combine_rows_dc_q8_1",
18434                                cfg2.grid_dim,
18435                                cfg2.block_dim,
18436                                0,
18437                                &mut ps,
18438                            )?;
18439                        }
18440                        continue;
18441                    }
18442                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
18443                    let __s_b2 = self.gpu.stream();
18444                    let mut b2 = __s_b2.launch_builder(&fc);
18445                    b2.arg(&*part_o)
18446                        .arg(&*part_m)
18447                        .arg(&*part_l)
18448                        .arg(&mut **oq)
18449                        .arg(&mut **od)
18450                        .arg(&hd)
18451                        .arg(&nh)
18452                        .arg(bd)
18453                        .arg(&plus_g)
18454                        .arg(&nspm)
18455                        .arg(&spk);
18456                    unsafe {
18457                        b2.launch(cfg2)?;
18458                    }
18459                    continue;
18460                }
18461                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
18462                let __s_b2 = self.gpu.stream();
18463                let mut b2 = __s_b2.launch_builder(&fc);
18464                b2.arg(&*part_o)
18465                    .arg(&*part_m)
18466                    .arg(&*part_l)
18467                    .arg(&mut o_g)
18468                    .arg(&hd)
18469                    .arg(&nh)
18470                    .arg(bd)
18471                    .arg(&plus_g)
18472                    .arg(&nspm)
18473                    .arg(&spk);
18474                unsafe {
18475                    b2.launch(cfg2)?;
18476                }
18477            } else {
18478                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
18479                // leave the caller's pair unwritten (consumer would read garbage).
18480                assert!(
18481                    q8_out.is_none(),
18482                    "rows q8 emit requires the hd512 dc combine"
18483                );
18484                let fc = self.func("fa_decode_combine_rows");
18485                let __s_b2 = self.gpu.stream();
18486                let mut b2 = __s_b2.launch_builder(&fc);
18487                b2.arg(&*part_o)
18488                    .arg(&*part_m)
18489                    .arg(&*part_l)
18490                    .arg(&mut o_g)
18491                    .arg(&hd)
18492                    .arg(&nh)
18493                    .arg(&base_i)
18494                    .arg(&nspm)
18495                    .arg(&spk);
18496                unsafe {
18497                    b2.launch(cfg2)?;
18498                }
18499            }
18500        }
18501        Ok(())
18502    }
18503
18504    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
18505    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
18506    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
18507    #[allow(clippy::too_many_arguments)]
18508    pub fn fa_decode_rows_w(
18509        &self,
18510        q: &CudaSlice<f32>,
18511        k: &cudarc::driver::CudaView<u8>,
18512        v: &cudarc::driver::CudaView<u8>,
18513        o: &mut CudaSlice<f32>,
18514        head_dim: usize,
18515        n_head: usize,
18516        n_head_kv: usize,
18517        base_dev: &CudaSlice<i32>,
18518        base_plus: i32,
18519        t: usize,
18520        scale: f32,
18521        window: usize,
18522        k_tok_bytes: usize,
18523        v_tok_bytes: usize,
18524        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18525    ) -> Result<(), Box<dyn std::error::Error>> {
18526        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
18527        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
18528        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
18529        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
18530        debug_assert!(head_dim == 256);
18531        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
18532        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
18533        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
18534        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
18535        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
18536        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
18537        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
18538        let sp = {
18539            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18540            let v = *SPW.get_or_init(|| {
18541                std::env::var("MEMRA_FA_SPW")
18542                    .ok()
18543                    .and_then(|x| x.parse().ok())
18544                    .unwrap_or(0)
18545            });
18546            if v >= 8 {
18547                v
18548            } else {
18549                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
18550            }
18551        };
18552        let n_splits_max = (window + sp - 1) / sp;
18553        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18554        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
18555        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18556        let gqa = (n_head / n_head_kv).max(1) as u32;
18557        let o_len = t * n_head * n_splits_max * head_dim;
18558        let ml_len = t * n_head * n_splits_max;
18559        let mut part_guard = self.fa_part_pool.lock().unwrap();
18560        if part_guard
18561            .as_ref()
18562            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18563            .unwrap_or(true)
18564        {
18565            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18566            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18567            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18568            // later live allocations land at those addresses, and the next graph REPLAY writes
18569            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18570            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18571            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18572            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18573            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18574            // (total retired < final size).
18575            let old = part_guard.take();
18576            let (co, cm) = old
18577                .as_ref()
18578                .map(|pp| (pp.0.len(), pp.1.len()))
18579                .unwrap_or((0, 0));
18580            if let Some(old) = old {
18581                self.fa_part_retired.lock().unwrap().push(old);
18582            }
18583            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18584                eprintln!(
18585                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18586                    co, o_len, cm, ml_len
18587                );
18588            }
18589            *part_guard = Some((
18590                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18591                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18592                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18593            ));
18594        }
18595        let pg = part_guard.as_mut().unwrap();
18596        self.gpu
18597            .stream()
18598            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18599        self.gpu
18600            .stream()
18601            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18602        self.gpu
18603            .stream()
18604            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18605        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18606        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
18607        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
18608        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
18609        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
18610        // floor (deep-ctx broadcast win); register twin between.
18611        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18612        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
18613            std::env::var("MEMRA_FA_SMEM_TKV")
18614                .ok()
18615                .and_then(|v| v.parse().ok())
18616                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
18617        });
18618        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
18619        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
18620        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
18621        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
18622        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
18623        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18624        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
18625        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
18626        // per (lane, format-module) keeps parity structural; the old register-i2 detour
18627        // (-33%) is retired.
18628        let wg = Self::wkv_on();
18629        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
18630        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
18631        let sp2 =
18632            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
18633        if sp2 {
18634            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18635            if Self::pdl_on() && Self::pdl_wb_on() {
18636                // wave-B2b: flavor mirrors wg.
18637                use cudarc::driver::{DevicePtr, DevicePtrMut};
18638                let s = &self.gpu.stream();
18639                let (pq, _b0) = q.device_ptr(s);
18640                let (pk, _b1) = k.device_ptr(s);
18641                let (pv, _b2) = v.device_ptr(s);
18642                let (po, _b3) = part_o.device_ptr_mut(s);
18643                let (pm, _b4) = part_m.device_ptr_mut(s);
18644                let (pl, _b5) = part_l.device_ptr_mut(s);
18645                let (pb, _b6) = base_dev.device_ptr(s);
18646                let mut ps = [
18647                    &pq as *const _ as *mut std::ffi::c_void,
18648                    &pk as *const _ as *mut _,
18649                    &pv as *const _ as *mut _,
18650                    &po as *const _ as *mut _,
18651                    &pm as *const _ as *mut _,
18652                    &pl as *const _ as *mut _,
18653                    &hd as *const _ as *mut _,
18654                    &nh as *const _ as *mut _,
18655                    &nhkv as *const _ as *mut _,
18656                    &pb as *const _ as *mut _,
18657                    &base_plus as *const _ as *mut _,
18658                    &scale as *const _ as *mut _,
18659                    &nspm as *const _ as *mut _,
18660                    &spk as *const _ as *mut _,
18661                    &ktb as *const _ as *mut _,
18662                    &vtb as *const _ as *mut _,
18663                    &wini as *const _ as *mut _,
18664                ];
18665                unsafe {
18666                    self.launch_pdl_flash(
18667                        wg,
18668                        "fa_decode_vec_q_rows_v4_w_sp",
18669                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18670                        (32, gqa + 1, 1),
18671                        sh,
18672                        &mut ps,
18673                    )?;
18674                }
18675            } else {
18676                let f = if wg {
18677                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
18678                } else {
18679                    self.func("fa_decode_vec_q_rows_v4_w_sp")
18680                };
18681                f.set_attribute(
18682                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18683                    sh as i32,
18684                )?;
18685                let cfg = LaunchConfig {
18686                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18687                    block_dim: (32, gqa + 1, 1),
18688                    shared_mem_bytes: sh,
18689                };
18690                let __s_b = self.gpu.stream();
18691                let mut b = __s_b.launch_builder(&f);
18692                b.arg(q)
18693                    .arg(k)
18694                    .arg(v)
18695                    .arg(&mut *part_o)
18696                    .arg(&mut *part_m)
18697                    .arg(&mut *part_l)
18698                    .arg(&hd)
18699                    .arg(&nh)
18700                    .arg(&nhkv)
18701                    .arg(base_dev)
18702                    .arg(&base_plus)
18703                    .arg(&scale)
18704                    .arg(&nspm)
18705                    .arg(&spk)
18706                    .arg(&ktb)
18707                    .arg(&vtb)
18708                    .arg(&wini);
18709                unsafe {
18710                    b.launch(cfg)?;
18711                }
18712            }
18713        } else {
18714            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
18715                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
18716                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
18717                use cudarc::driver::{DevicePtr, DevicePtrMut};
18718                let s = &self.gpu.stream();
18719                let (pq, _b0) = q.device_ptr(s);
18720                let (pk, _b1) = k.device_ptr(s);
18721                let (pv, _b2) = v.device_ptr(s);
18722                let (po, _b3) = part_o.device_ptr_mut(s);
18723                let (pm, _b4) = part_m.device_ptr_mut(s);
18724                let (pl, _b5) = part_l.device_ptr_mut(s);
18725                let (pb, _b6) = base_dev.device_ptr(s);
18726                let mut ps = [
18727                    &pq as *const _ as *mut std::ffi::c_void,
18728                    &pk as *const _ as *mut _,
18729                    &pv as *const _ as *mut _,
18730                    &po as *const _ as *mut _,
18731                    &pm as *const _ as *mut _,
18732                    &pl as *const _ as *mut _,
18733                    &hd as *const _ as *mut _,
18734                    &nh as *const _ as *mut _,
18735                    &nhkv as *const _ as *mut _,
18736                    &pb as *const _ as *mut _,
18737                    &base_plus as *const _ as *mut _,
18738                    &scale as *const _ as *mut _,
18739                    &nspm as *const _ as *mut _,
18740                    &spk as *const _ as *mut _,
18741                    &ktb as *const _ as *mut _,
18742                    &vtb as *const _ as *mut _,
18743                    &wini as *const _ as *mut _,
18744                ];
18745                unsafe {
18746                    self.launch_pdl_flash(
18747                        wg,
18748                        "fa_decode_vec_q_rows_v4_w",
18749                        (n_head_kv as u32, n_splits_max as u32, t as u32),
18750                        (32, gqa, 1),
18751                        sh,
18752                        &mut ps,
18753                    )?;
18754                }
18755            } else {
18756                let pick = |name: &str| {
18757                    if wg {
18758                        self.func_g(name)
18759                    } else {
18760                        self.func(name)
18761                    }
18762                };
18763                let (f, sh) = if fa_v4_at(window) {
18764                    let f = pick("fa_decode_vec_q_rows_v4_w");
18765                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
18766                } else if smem_tkv > 0 && window >= smem_tkv {
18767                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
18768                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
18769                    (
18770                        pick("fa_decode_vec_q_rows_smem_w"),
18771                        (2 * 32 * head_dim * 2) as u32,
18772                    )
18773                } else {
18774                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
18775                };
18776                f.set_attribute(
18777                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18778                    sh as i32,
18779                )?;
18780                let cfg = LaunchConfig {
18781                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18782                    block_dim: (32, gqa, 1),
18783                    shared_mem_bytes: sh,
18784                };
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(base_dev)
18797                    .arg(&base_plus)
18798                    .arg(&scale)
18799                    .arg(&nspm)
18800                    .arg(&spk)
18801                    .arg(&ktb)
18802                    .arg(&vtb)
18803                    .arg(&wini);
18804                unsafe {
18805                    b.launch(cfg)?;
18806                }
18807            }
18808        }
18809        let cfg2 = LaunchConfig {
18810            grid_dim: (n_head as u32, t as u32, 1),
18811            block_dim: (head_dim as u32, 1, 1),
18812            shared_mem_bytes: 0,
18813        };
18814        if let Some((oq, od)) = q8_out {
18815            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
18816            // consumes the pair directly; the standalone quantize launch folds away.
18817            if Self::pdl_on() && Self::pdl_wb_on() {
18818                // wave-B2: flavor mirrors the builder's wg choice.
18819                use cudarc::driver::{DevicePtr, DevicePtrMut};
18820                let s = &self.gpu.stream();
18821                let (po, _g0) = part_o.device_ptr(s);
18822                let (pm, _g1) = part_m.device_ptr(s);
18823                let (pl, _g2) = part_l.device_ptr(s);
18824                let (pq, _g3) = oq.device_ptr_mut(s);
18825                let (pd, _g4) = od.device_ptr_mut(s);
18826                let mut ps = [
18827                    &po as *const _ as *mut std::ffi::c_void,
18828                    &pm as *const _ as *mut _,
18829                    &pl as *const _ as *mut _,
18830                    &pq as *const _ as *mut _,
18831                    &pd as *const _ as *mut _,
18832                    &hd as *const _ as *mut _,
18833                    &nh as *const _ as *mut _,
18834                    &nspm as *const _ as *mut _,
18835                    &spk as *const _ as *mut _,
18836                    &wini as *const _ as *mut _,
18837                ];
18838                unsafe {
18839                    self.launch_pdl_flash(
18840                        wg,
18841                        "fa_decode_combine_rows_w_q8_1",
18842                        cfg2.grid_dim,
18843                        cfg2.block_dim,
18844                        0,
18845                        &mut ps,
18846                    )?;
18847                }
18848                return Ok(());
18849            }
18850            let fc = if wg {
18851                self.func_g("fa_decode_combine_rows_w_q8_1")
18852            } else {
18853                self.func("fa_decode_combine_rows_w_q8_1")
18854            };
18855            let __s_b2 = self.gpu.stream();
18856            let mut b2 = __s_b2.launch_builder(&fc);
18857            b2.arg(&*part_o)
18858                .arg(&*part_m)
18859                .arg(&*part_l)
18860                .arg(oq)
18861                .arg(od)
18862                .arg(&hd)
18863                .arg(&nh)
18864                .arg(&nspm)
18865                .arg(&spk)
18866                .arg(&wini);
18867            unsafe {
18868                b2.launch(cfg2)?;
18869            }
18870            return Ok(());
18871        }
18872        let fc = if wg {
18873            self.func_g("fa_decode_combine_rows_w")
18874        } else {
18875            self.func("fa_decode_combine_rows_w")
18876        };
18877        let __s_b2 = self.gpu.stream();
18878        let mut b2 = __s_b2.launch_builder(&fc);
18879        b2.arg(&*part_o)
18880            .arg(&*part_m)
18881            .arg(&*part_l)
18882            .arg(o)
18883            .arg(&hd)
18884            .arg(&nh)
18885            .arg(&nspm)
18886            .arg(&spk)
18887            .arg(&wini);
18888        unsafe {
18889            b2.launch(cfg2)?;
18890        }
18891        Ok(())
18892    }
18893
18894    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
18895    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
18896    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
18897    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
18898    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
18899    #[allow(clippy::too_many_arguments)]
18900    pub fn fa_decode_rows_dc(
18901        &self,
18902        q: &CudaSlice<f32>,
18903        k: &cudarc::driver::CudaView<u8>,
18904        v: &cudarc::driver::CudaView<u8>,
18905        o: &mut CudaSlice<f32>,
18906        head_dim: usize,
18907        n_head: usize,
18908        n_head_kv: usize,
18909        base_dev: &CudaSlice<i32>,
18910        t_kv_upper: usize,
18911        t: usize,
18912        scale: f32,
18913        k_tok_bytes: usize,
18914        v_tok_bytes: usize,
18915        base_plus: i32,
18916        g: bool,
18917    ) -> Result<(), Box<dyn std::error::Error>> {
18918        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
18919        assert!(
18920            v4 || fa_v3_active(head_dim),
18921            "stream fa rows requires the v3 or v4 lane"
18922        );
18923        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
18924        if v4 {
18925            let sp = fa_split_keys(t_kv_upper, n_head_kv);
18926            let n_splits_max = (t_kv_upper + sp - 1) / sp;
18927            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18928            let (nspm, spk) = (n_splits_max as i32, sp as i32);
18929            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18930            let gqa = (n_head / n_head_kv).max(1) as u32;
18931            let o_len = t * n_head * n_splits_max * head_dim;
18932            let ml_len = t * n_head * n_splits_max;
18933            let mut part_guard = self.fa_part_pool.lock().unwrap();
18934            if part_guard
18935                .as_ref()
18936                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18937                .unwrap_or(true)
18938            {
18939                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18940                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18941                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18942                // later live allocations land at those addresses, and the next graph REPLAY writes
18943                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18944                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18945                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18946                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18947                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18948                // (total retired < final size).
18949                let old = part_guard.take();
18950                let (co, cm) = old
18951                    .as_ref()
18952                    .map(|pp| (pp.0.len(), pp.1.len()))
18953                    .unwrap_or((0, 0));
18954                if let Some(old) = old {
18955                    self.fa_part_retired.lock().unwrap().push(old);
18956                }
18957                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18958                    eprintln!(
18959                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18960                        co, o_len, cm, ml_len
18961                    );
18962                }
18963                *part_guard = Some((
18964                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18965                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18966                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18967                ));
18968            }
18969            let pg = part_guard.as_mut().unwrap();
18970            self.gpu
18971                .stream()
18972                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18973            self.gpu
18974                .stream()
18975                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18976            self.gpu
18977                .stream()
18978                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18979            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18980            let f = if g {
18981                self.func_g("fa_decode_vec_q_rows_v4_dc")
18982            } else {
18983                self.func("fa_decode_vec_q_rows_v4_dc")
18984            };
18985            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18986            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18987            f.set_attribute(
18988                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18989                sh as i32,
18990            )?;
18991            let cfg = LaunchConfig {
18992                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18993                block_dim: (32, gqa, 1),
18994                shared_mem_bytes: sh,
18995            };
18996            let __s_b = self.gpu.stream();
18997            let mut b = __s_b.launch_builder(&f);
18998            b.arg(q)
18999                .arg(k)
19000                .arg(v)
19001                .arg(&mut *part_o)
19002                .arg(&mut *part_m)
19003                .arg(&mut *part_l)
19004                .arg(&hd)
19005                .arg(&nh)
19006                .arg(&nhkv)
19007                .arg(base_dev)
19008                .arg(&base_plus)
19009                .arg(&scale)
19010                .arg(&nspm)
19011                .arg(&spk)
19012                .arg(&ktb)
19013                .arg(&vtb);
19014            unsafe {
19015                b.launch(cfg)?;
19016            }
19017            let fc = self.func("fa_decode_combine_rows_dc");
19018            let cfg2 = LaunchConfig {
19019                grid_dim: (n_head as u32, t as u32, 1),
19020                block_dim: (head_dim as u32, 1, 1),
19021                shared_mem_bytes: 0,
19022            };
19023            let __s_b2 = self.gpu.stream();
19024            let mut b2 = __s_b2.launch_builder(&fc);
19025            b2.arg(&*part_o)
19026                .arg(&*part_m)
19027                .arg(&*part_l)
19028                .arg(o)
19029                .arg(&hd)
19030                .arg(&nh)
19031                .arg(base_dev)
19032                .arg(&base_plus)
19033                .arg(&nspm)
19034                .arg(&spk);
19035            unsafe {
19036                b2.launch(cfg2)?;
19037            }
19038            return Ok(());
19039        }
19040        let sp = fa_split_keys(t_kv_upper, n_head_kv);
19041        let n_splits_max = (t_kv_upper + sp - 1) / sp;
19042        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
19043        let (nspm, spk) = (n_splits_max as i32, sp as i32);
19044        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19045        let gqa = (n_head / n_head_kv).max(1) as u32;
19046        let o_len = t * n_head * n_splits_max * head_dim;
19047        let ml_len = t * n_head * n_splits_max;
19048        let mut part_guard = self.fa_part_pool.lock().unwrap();
19049        if part_guard
19050            .as_ref()
19051            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19052            .unwrap_or(true)
19053        {
19054            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19055            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19056            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19057            // later live allocations land at those addresses, and the next graph REPLAY writes
19058            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19059            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19060            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19061            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19062            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19063            // (total retired < final size).
19064            let old = part_guard.take();
19065            let (co, cm) = old
19066                .as_ref()
19067                .map(|pp| (pp.0.len(), pp.1.len()))
19068                .unwrap_or((0, 0));
19069            if let Some(old) = old {
19070                self.fa_part_retired.lock().unwrap().push(old);
19071            }
19072            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19073                eprintln!(
19074                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19075                    co, o_len, cm, ml_len
19076                );
19077            }
19078            *part_guard = Some((
19079                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19080                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19081                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19082            ));
19083        }
19084        let pg = part_guard.as_mut().unwrap();
19085        self.gpu
19086            .stream()
19087            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19088        self.gpu
19089            .stream()
19090            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19091        self.gpu
19092            .stream()
19093            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19094        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19095        let f = self.func("fa_decode_vec_q_rows_v3_dc");
19096        let sh = (32 * head_dim * 2) as u32;
19097        use cudarc::driver::sys::CUfunction_attribute_enum as A;
19098        f.set_attribute(
19099            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19100            sh as i32,
19101        )?;
19102        let cfg = LaunchConfig {
19103            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
19104            block_dim: (32, gqa, 1),
19105            shared_mem_bytes: sh,
19106        };
19107        let __s_b = self.gpu.stream();
19108        let mut b = __s_b.launch_builder(&f);
19109        b.arg(q)
19110            .arg(k)
19111            .arg(v)
19112            .arg(&mut *part_o)
19113            .arg(&mut *part_m)
19114            .arg(&mut *part_l)
19115            .arg(&hd)
19116            .arg(&nh)
19117            .arg(&nhkv)
19118            .arg(base_dev)
19119            .arg(&scale)
19120            .arg(&nspm)
19121            .arg(&spk)
19122            .arg(&ktb)
19123            .arg(&vtb);
19124        unsafe {
19125            b.launch(cfg)?;
19126        }
19127        let fc = self.func("fa_decode_combine_rows_dc");
19128        let cfg2 = LaunchConfig {
19129            grid_dim: (n_head as u32, t as u32, 1),
19130            block_dim: (head_dim as u32, 1, 1),
19131            shared_mem_bytes: 0,
19132        };
19133        let plus0 = 0i32;
19134        let __s_b2 = self.gpu.stream();
19135        let mut b2 = __s_b2.launch_builder(&fc);
19136        b2.arg(&*part_o)
19137            .arg(&*part_m)
19138            .arg(&*part_l)
19139            .arg(o)
19140            .arg(&hd)
19141            .arg(&nh)
19142            .arg(base_dev)
19143            .arg(&plus0)
19144            .arg(&nspm)
19145            .arg(&spk);
19146        unsafe {
19147            b2.launch(cfg2)?;
19148        }
19149        Ok(())
19150    }
19151
19152    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
19153    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
19154    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
19155    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
19156    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
19157    ///
19158    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
19159    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
19160    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
19161    /// grouping (different but mathematically-equal log-sum-exp merge).
19162    pub fn fa_decode_dc(
19163        &self,
19164        q: &CudaSlice<f32>,
19165        k: &cudarc::driver::CudaView<u8>,
19166        v: &cudarc::driver::CudaView<u8>,
19167        o: &mut CudaSlice<f32>,
19168        head_dim: usize,
19169        n_head: usize,
19170        n_head_kv: usize,
19171        t_kv_dev: &CudaSlice<i32>,
19172        bucket_max: usize,
19173        scale: f32,
19174        k_tok_bytes: usize,
19175        v_tok_bytes: usize,
19176        g: bool,
19177    ) -> Result<(), Box<dyn std::error::Error>> {
19178        self.fa_decode_dc_q8(
19179            q,
19180            k,
19181            v,
19182            o,
19183            head_dim,
19184            n_head,
19185            n_head_kv,
19186            t_kv_dev,
19187            bucket_max,
19188            scale,
19189            k_tok_bytes,
19190            v_tok_bytes,
19191            g,
19192            None,
19193        )
19194    }
19195
19196    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
19197    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
19198    #[allow(clippy::too_many_arguments)]
19199    pub fn fa_decode_dc_q8(
19200        &self,
19201        q: &CudaSlice<f32>,
19202        k: &cudarc::driver::CudaView<u8>,
19203        v: &cudarc::driver::CudaView<u8>,
19204        o: &mut CudaSlice<f32>,
19205        head_dim: usize,
19206        n_head: usize,
19207        n_head_kv: usize,
19208        t_kv_dev: &CudaSlice<i32>,
19209        bucket_max: usize,
19210        scale: f32,
19211        k_tok_bytes: usize,
19212        v_tok_bytes: usize,
19213        g: bool,
19214        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
19215    ) -> Result<(), Box<dyn std::error::Error>> {
19216        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
19217        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
19218        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
19219        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
19220        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
19221        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
19222        // 2026-07-12).
19223        let mut fa_vec =
19224            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
19225        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
19226            fa_vec = false;
19227        } // mirror kvmod/geom
19228        let sp = fa_split_keys(bucket_max, n_head_kv);
19229        let n_splits = if fa_vec {
19230            ((bucket_max + sp - 1) / sp).max(1)
19231        } else {
19232            ((bucket_max + 255) / 256).max(1)
19233        };
19234        let o_len = n_head * n_splits * head_dim;
19235        let ml_len = n_head * n_splits;
19236        let mut part_guard = self.fa_part_pool.lock().unwrap();
19237        if part_guard
19238            .as_ref()
19239            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
19240            .unwrap_or(true)
19241        {
19242            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
19243            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
19244            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
19245            // later live allocations land at those addresses, and the next graph REPLAY writes
19246            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
19247            // output corruption began the burst after the trunk's t_kv growth first realloc'd
19248            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
19249            // the baked addresses alive (single-stream: eager writes the new buffers, replays
19250            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
19251            // (total retired < final size).
19252            let old = part_guard.take();
19253            let (co, cm) = old
19254                .as_ref()
19255                .map(|pp| (pp.0.len(), pp.1.len()))
19256                .unwrap_or((0, 0));
19257            if let Some(old) = old {
19258                self.fa_part_retired.lock().unwrap().push(old);
19259            }
19260            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
19261                eprintln!(
19262                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
19263                    co, o_len, cm, ml_len
19264                );
19265            }
19266            *part_guard = Some((
19267                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
19268                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19269                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
19270            ));
19271        }
19272        let pg = part_guard.as_mut().unwrap();
19273        self.gpu
19274            .stream()
19275            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
19276        self.gpu
19277            .stream()
19278            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
19279        self.gpu
19280            .stream()
19281            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
19282        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
19283        let (hd, nh, nhkv, nsp) = (
19284            head_dim as i32,
19285            n_head as i32,
19286            n_head_kv as i32,
19287            n_splits as i32,
19288        );
19289        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
19290        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
19291        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
19292        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
19293        let deep = fa_vec
19294            && head_dim == 256
19295            && fa_v4_at(bucket_max)
19296            && !g
19297            && fa_deep_at(bucket_max)
19298            && !matches!(fa_v4_mode(), "noB3" | "stage");
19299        let (f, cfg) = if fa_vec
19300            && head_dim == 512
19301            && bucket_max >= {
19302                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19303                *FA512_MIN_DC.get_or_init(|| {
19304                    std::env::var("MEMRA_FA512_MIN")
19305                        .ok()
19306                        .and_then(|v| v.parse().ok())
19307                        .unwrap_or(512)
19308                })
19309            } {
19310            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
19311            let gqa = (n_head / n_head_kv).max(1) as u32;
19312            (
19313                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
19314                LaunchConfig {
19315                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19316                    block_dim: (32, gqa, 1),
19317                    shared_mem_bytes: 0,
19318                },
19319            )
19320        } else if fa_vec && head_dim == 512 {
19321            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
19322            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
19323            let q_view = q.as_view();
19324            let mut o_view = o.as_view_mut();
19325            return self.fa_decode_scalar_unified(
19326                &q_view,
19327                k,
19328                v,
19329                &mut o_view,
19330                head_dim,
19331                n_head,
19332                n_head_kv,
19333                0,
19334                Some(t_kv_dev),
19335                scale,
19336                n_splits,
19337                sp,
19338                k_tok_bytes,
19339                v_tok_bytes,
19340                g,
19341                &mut *part_o,
19342                &mut *part_m,
19343                &mut *part_l,
19344                q8_out,
19345            );
19346        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
19347            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
19348            // incl the g-module route + raw-e4m3 sV sizing.
19349            let gqa = (n_head / n_head_kv).max(1) as u32;
19350            let fv = if g {
19351                self.func_g("fa_decode_vec_q_v4_dc")
19352            } else if deep {
19353                self.func("fa_decode_vec_q_v4_deep_dc")
19354            } else {
19355                self.func("fa_decode_vec_q_v4_dc")
19356            };
19357            let shmem =
19358                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
19359            use cudarc::driver::sys::CUfunction_attribute_enum as A;
19360            fv.set_attribute(
19361                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
19362                shmem as i32,
19363            )?;
19364            (
19365                fv,
19366                LaunchConfig {
19367                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19368                    block_dim: (32, gqa, 1),
19369                    shared_mem_bytes: shmem,
19370                },
19371            )
19372        } else if fa_vec && fa_v3_active(head_dim) {
19373            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
19374            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
19375            let gqa = (n_head / n_head_kv).max(1) as u32;
19376            let fv = if g {
19377                self.func_g("fa_decode_vec_q_v3_dc")
19378            } else {
19379                self.func("fa_decode_vec_q_v3_dc")
19380            };
19381            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
19382            (
19383                fv,
19384                LaunchConfig {
19385                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19386                    block_dim: (32, gqa, 1),
19387                    shared_mem_bytes: shmem,
19388                },
19389            )
19390        } else if fa_vec && fa_v2_on() {
19391            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
19392            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
19393            // a numeric config; eager, rows-verify and graph all switch together).
19394            let gqa = (n_head / n_head_kv).max(1) as u32;
19395            let fv = if g {
19396                self.func_g("fa_decode_vec_q_v2_dc")
19397            } else {
19398                self.func("fa_decode_vec_q_v2_dc")
19399            };
19400            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
19401            (
19402                fv,
19403                LaunchConfig {
19404                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19405                    block_dim: (32, gqa, 1),
19406                    shared_mem_bytes: shmem,
19407                },
19408            )
19409        } else if fa_vec {
19410            let gqa = (n_head / n_head_kv).max(1) as u32;
19411            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
19412            let fv = if g {
19413                self.func_g("fa_decode_vec_q_dc")
19414            } else {
19415                self.func("fa_decode_vec_q_dc")
19416            };
19417            (
19418                fv,
19419                LaunchConfig {
19420                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
19421                    block_dim: (32, gqa, 1),
19422                    shared_mem_bytes: 0,
19423                },
19424            )
19425        } else {
19426            let q_view = q.as_view();
19427            let mut o_view = o.as_view_mut();
19428            return self.fa_decode_scalar_unified(
19429                &q_view,
19430                k,
19431                v,
19432                &mut o_view,
19433                head_dim,
19434                n_head,
19435                n_head_kv,
19436                0,
19437                Some(t_kv_dev),
19438                scale,
19439                n_splits,
19440                if fa_vec { sp } else { 256 },
19441                k_tok_bytes,
19442                v_tok_bytes,
19443                g,
19444                &mut *part_o,
19445                &mut *part_m,
19446                &mut *part_l,
19447                q8_out,
19448            );
19449        };
19450        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
19451        let __s_b = self.gpu.stream();
19452        let mut b = __s_b.launch_builder(&f);
19453        b.arg(q)
19454            .arg(k)
19455            .arg(v)
19456            .arg(&mut *part_o)
19457            .arg(&mut *part_m)
19458            .arg(&mut *part_l)
19459            .arg(&hd)
19460            .arg(&nh)
19461            .arg(&nhkv)
19462            .arg(t_kv_dev)
19463            .arg(&scale)
19464            .arg(&nsp)
19465            .arg(&ski)
19466            .arg(&ktb)
19467            .arg(&vtb);
19468        unsafe {
19469            b.launch(cfg)?;
19470        }
19471        let cfg2 = LaunchConfig {
19472            grid_dim: (n_head as u32, 1, 1),
19473            block_dim: (head_dim as u32, 1, 1),
19474            shared_mem_bytes: 0,
19475        };
19476        if let Some((oq, od)) = q8_out {
19477            let fc = if g {
19478                self.func_g("fa_decode_combine_q8_1")
19479            } else {
19480                self.fa_func("fa_decode_combine_q8_1", head_dim)
19481            };
19482            let __s_b2 = self.gpu.stream();
19483            let mut b2 = __s_b2.launch_builder(&fc);
19484            b2.arg(&*part_o)
19485                .arg(&*part_m)
19486                .arg(&*part_l)
19487                .arg(oq)
19488                .arg(od)
19489                .arg(&hd)
19490                .arg(&nh)
19491                .arg(&nsp);
19492            unsafe {
19493                b2.launch(cfg2)?;
19494            }
19495            return Ok(());
19496        }
19497        let fc = if g {
19498            self.func_g("fa_decode_combine_f32")
19499        } else {
19500            self.fa_func("fa_decode_combine_f32", head_dim)
19501        };
19502        let __s_b2 = self.gpu.stream();
19503        let mut b2 = __s_b2.launch_builder(&fc);
19504        b2.arg(&*part_o)
19505            .arg(&*part_m)
19506            .arg(&*part_l)
19507            .arg(o)
19508            .arg(&hd)
19509            .arg(&nh)
19510            .arg(&nsp);
19511        unsafe {
19512            b2.launch(cfg2)?;
19513        }
19514        Ok(())
19515    }
19516
19517    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
19518    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
19519    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
19520    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
19521    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
19522    pub fn fa_geom_eager(
19523        &self,
19524        t_kv: usize,
19525        head_dim: usize,
19526        n_head_kv: usize,
19527        g: bool,
19528    ) -> (bool, usize) {
19529        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
19530        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
19531        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
19532        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
19533        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
19534        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
19535        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
19536        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
19537        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
19538        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
19539        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
19540        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
19541        // family; everything else falls to the g-module scalar.
19542        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
19543        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
19544        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
19545        if g && head_dim == 256 && !fa_v4_at(t_kv) {
19546            fa_vec = false;
19547        }
19548        let sp = fa_split_keys(t_kv, n_head_kv);
19549        let n_splits = if fa_vec {
19550            ((t_kv + sp - 1) / sp).max(1)
19551        } else {
19552            ((t_kv + 255) / 256).max(1)
19553        };
19554        (fa_vec, n_splits)
19555    }
19556
19557    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
19558    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
19559    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
19560    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
19561    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
19562    pub fn fa_bucket_key(
19563        &self,
19564        t_kv: usize,
19565        head_dim: usize,
19566        n_head_kv: usize,
19567        g: bool,
19568    ) -> (bool, usize) {
19569        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
19570    }
19571
19572    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
19573    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
19574    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
19575    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
19576    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
19577    /// device data) — every per-step varying scalar must come from a device counter. Returns the
19578    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
19579    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
19580    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
19581    /// replays (transients returning to the pool get reused by unrelated work and corrupt
19582    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
19583    pub fn capture_graph_retained<F>(
19584        &self,
19585        step: F,
19586    ) -> Result<
19587        (
19588            cudarc::driver::CudaGraph,
19589            Vec<Box<dyn std::any::Any + Send>>,
19590        ),
19591        Box<dyn std::error::Error>,
19592    >
19593    where
19594        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19595    {
19596        use cudarc::driver::sys::CUgraphInstantiate_flags;
19597        self.capture_graph_retained_flags(
19598            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19599            step,
19600        )
19601    }
19602
19603    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
19604    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
19605    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
19606    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
19607    pub fn capture_graph_retained_flags<F>(
19608        &self,
19609        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
19610        mut step: F,
19611    ) -> Result<
19612        (
19613            cudarc::driver::CudaGraph,
19614            Vec<Box<dyn std::any::Any + Send>>,
19615        ),
19616        Box<dyn std::error::Error>,
19617    >
19618    where
19619        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19620    {
19621        use cudarc::driver::sys::CUstreamCaptureMode;
19622        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
19623        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
19624        // while the capture region is open become dead copy NODES replayed every launch
19625        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
19626        // warmup runs allocate the same transient sequence at the same pool addresses, so
19627        // retaining the warmup clones preserves the draft-graph fix without polluting the
19628        // captured graph.
19629        self.capture_keep.lock().unwrap().clear();
19630        let was_tracking = self.gpu.ctx.is_event_tracking();
19631        if was_tracking {
19632            unsafe {
19633                self.gpu.ctx.disable_event_tracking();
19634            }
19635        }
19636        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19637            self.capture_keep_on
19638                .store(true, std::sync::atomic::Ordering::Relaxed);
19639            let w = (|| {
19640                step(self)?;
19641                step(self)
19642            })();
19643            self.capture_keep_on
19644                .store(false, std::sync::atomic::Ordering::Relaxed);
19645            w?;
19646            self.gpu.stream().synchronize()?;
19647            self.gpu
19648                .stream()
19649                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19650            let r = step(self);
19651            let g = self.gpu.stream().end_capture(flags);
19652            r?;
19653            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19654            graph.upload()?;
19655            Ok(graph)
19656        };
19657        let result = run();
19658        self.capture_keep_on
19659            .store(false, std::sync::atomic::Ordering::Relaxed);
19660        if was_tracking {
19661            unsafe {
19662                self.gpu.ctx.enable_event_tracking();
19663            }
19664        }
19665        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
19666        Ok((result?, keeper))
19667    }
19668
19669    pub fn capture_graph<F>(
19670        &self,
19671        mut step: F,
19672    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
19673    where
19674        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
19675    {
19676        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
19677        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
19678        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
19679        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
19680        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
19681        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
19682        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
19683        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
19684        let was_tracking = self.gpu.ctx.is_event_tracking();
19685        if was_tracking {
19686            unsafe {
19687                self.gpu.ctx.disable_event_tracking();
19688            }
19689        }
19690        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
19691        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
19692        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
19693        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
19694        // measure that scan's real cost on the generic path. Diagnostic door only; the
19695        // default stays AUTO_FREE until a measured A/B justifies moving it.
19696        let iflag = {
19697            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
19698            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
19699                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
19700                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
19701                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
19702                Ok("priority") => {
19703                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
19704                }
19705                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
19706            })
19707        };
19708        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
19709        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
19710        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
19711        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
19712        // eager step executions and are node-count-invariant. Printing the split bounds the
19713        // refactor's ceiling instead of assuming it.
19714        let ct = {
19715            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19716            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
19717        };
19718        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
19719        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
19720        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
19721        // chased, and node-count-invariant, so no capture-body refactor could touch it.
19722        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
19723        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
19724        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
19725        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
19726        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
19727        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
19728        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
19729        // grow and never frees, resident counters/scratch, cache set in place), and the
19730        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
19731        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
19732        // settling and pool mapping. Arbitrated adversarially, not by taste:
19733        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
19734        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
19735        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
19736        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
19737        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
19738        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
19739        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
19740        let warmups = {
19741            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19742            *W.get_or_init(|| {
19743                std::env::var("MEMRA_GRAPH_WARMUPS")
19744                    .ok()
19745                    .and_then(|v| v.parse().ok())
19746                    .filter(|n| *n >= 1)
19747                    .unwrap_or(1)
19748            })
19749        };
19750        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
19751            let t_w = std::time::Instant::now();
19752            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
19753            for _ in 0..warmups {
19754                step(self)?;
19755            }
19756            self.gpu.stream().synchronize()?;
19757            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
19758            // capture the third run.
19759            let t_c = std::time::Instant::now();
19760            self.gpu
19761                .stream()
19762                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
19763            // If the body errors mid-capture, end the capture before propagating so the stream isn't
19764            // left in a capturing state.
19765            let r = step(self);
19766            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
19767            let t_i = std::time::Instant::now();
19768            let g = self.gpu.stream().end_capture(iflag);
19769            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
19770            r?;
19771            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
19772            let t_u = std::time::Instant::now();
19773            graph.upload()?;
19774            if ct {
19775                println!(
19776                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
19777                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
19778                    t_u.elapsed().as_secs_f64() * 1e3
19779                );
19780            }
19781            Ok(graph)
19782        };
19783        let result = run();
19784        if was_tracking {
19785            unsafe {
19786                self.gpu.ctx.enable_event_tracking();
19787            }
19788        }
19789        result
19790    }
19791
19792    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
19793    pub fn gdn_scan_s128_view(
19794        &self,
19795        q: &CudaSlice<f32>,
19796        k: &CudaSlice<f32>,
19797        v: &CudaSlice<f32>,
19798        g: &CudaSlice<f32>,
19799        beta: &CudaSlice<f32>,
19800        state_in: &cudarc::driver::CudaView<f32>,
19801        state_out: &mut cudarc::driver::CudaViewMut<f32>,
19802        o: &mut CudaSlice<f32>,
19803        n_head: usize,
19804        t: usize,
19805        scale: f32,
19806    ) -> Result<(), Box<dyn std::error::Error>> {
19807        let f = self.func("gdn_scan_s128");
19808        const S_V: u32 = 128;
19809        const WARP: u32 = 32;
19810        const COLS: u32 = 4;
19811        let cfg = LaunchConfig {
19812            grid_dim: (n_head as u32, 1, S_V / COLS),
19813            block_dim: (WARP, COLS, 1),
19814            shared_mem_bytes: 0,
19815        };
19816        let (h, ti) = (n_head as i32, t as i32);
19817        let __s_b = self.gpu.stream();
19818        let mut b = __s_b.launch_builder(&f);
19819        b.arg(q)
19820            .arg(k)
19821            .arg(v)
19822            .arg(g)
19823            .arg(beta)
19824            .arg(state_in)
19825            .arg(state_out)
19826            .arg(o)
19827            .arg(&h)
19828            .arg(&ti)
19829            .arg(&scale);
19830        unsafe {
19831            b.launch(cfg)?;
19832        }
19833        Ok(())
19834    }
19835
19836    /// conv1d where the input is a CudaView (resident conv state assembled in place).
19837    pub fn ssm_conv1d_view(
19838        &self,
19839        x: &cudarc::driver::CudaView<f32>,
19840        w: &CudaSlice<f32>,
19841        y: &mut CudaSlice<f32>,
19842        conv_dim: usize,
19843        t: usize,
19844        d_conv: usize,
19845        silu: bool,
19846    ) -> Result<(), Box<dyn std::error::Error>> {
19847        let f = self.func("ssm_conv1d_silu_f32");
19848        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
19849        let cfg = LaunchConfig {
19850            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19851            block_dim: (256, 1, 1),
19852            shared_mem_bytes: 0,
19853        };
19854        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19855        let __s_b = self.gpu.stream();
19856        let mut b = __s_b.launch_builder(&f);
19857        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19858        unsafe {
19859            b.launch(cfg)?;
19860        }
19861        Ok(())
19862    }
19863
19864    /// Depthwise causal conv1d + optional SiLU.
19865    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
19866    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
19867    /// FUSED prefill conv (token-major input, zero left-state): replaces
19868    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
19869    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
19870    pub fn ssm_conv1d_tm(
19871        &self,
19872        qkv_tm: &CudaSlice<f32>,
19873        w: &CudaSlice<f32>,
19874        y: &mut CudaSlice<f32>,
19875        conv_dim: usize,
19876        t: usize,
19877        d_conv: usize,
19878    ) -> Result<(), Box<dyn std::error::Error>> {
19879        let f = self.func("ssm_conv1d_tm_f32");
19880        let cfg = LaunchConfig {
19881            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19882            block_dim: (256, 1, 1),
19883            shared_mem_bytes: 0,
19884        };
19885        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19886        let __s_b = self.gpu.stream();
19887        let mut b = __s_b.launch_builder(&f);
19888        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
19889        unsafe {
19890            b.launch(cfg)?;
19891        }
19892        Ok(())
19893    }
19894
19895    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
19896    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
19897    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
19898    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
19899    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
19900    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
19901    /// columns; the final ring == what T sequential decode ring rolls leave).
19902    pub fn ssm_conv1d_tm_state(
19903        &self,
19904        qkv_tm: &CudaSlice<f32>,
19905        conv_state: &mut CudaSlice<f32>,
19906        w: &CudaSlice<f32>,
19907        y: &mut CudaSlice<f32>,
19908        conv_dim: usize,
19909        t: usize,
19910        d_conv: usize,
19911    ) -> Result<(), Box<dyn std::error::Error>> {
19912        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
19913    }
19914
19915    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
19916    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
19917    #[allow(clippy::too_many_arguments)]
19918    pub fn ssm_conv1d_tm_state_pad(
19919        &self,
19920        qkv_tm: &CudaSlice<f32>,
19921        conv_state: &mut CudaSlice<f32>,
19922        w: &CudaSlice<f32>,
19923        y: &mut CudaSlice<f32>,
19924        conv_dim: usize,
19925        t: usize,
19926        d_conv: usize,
19927        pad_len: Option<&CudaSlice<i32>>,
19928    ) -> Result<(), Box<dyn std::error::Error>> {
19929        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19930        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19931        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19932        // cloning first keeps the ordering trivially correct under any future stream split.
19933        let ring_old = if t < d_conv - 1 {
19934            Some(self.clone_dtod(conv_state)?)
19935        } else {
19936            None
19937        };
19938        {
19939            let f = self.func("ssm_conv1d_tm_state_f32");
19940            let cfg = LaunchConfig {
19941                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19942                block_dim: (256, 1, 1),
19943                shared_mem_bytes: 0,
19944            };
19945            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19946            let __s_b = self.gpu.stream();
19947            let mut b = __s_b.launch_builder(&f);
19948            b.arg(qkv_tm)
19949                .arg(&*conv_state)
19950                .arg(w)
19951                .arg(y)
19952                .arg(&cd)
19953                .arg(&ti)
19954                .arg(&dc);
19955            unsafe {
19956                b.launch(cfg)?;
19957            }
19958        }
19959        match (ring_old, pad_len) {
19960            (None, Some(len_d)) => {
19961                let f = self.func("ssm_conv_ring_update_dev_f32");
19962                let n = conv_dim * (d_conv - 1);
19963                let cfg = LaunchConfig::for_num_elems(n as u32);
19964                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19965                let __s_b = self.gpu.stream();
19966                let mut b = __s_b.launch_builder(&f);
19967                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19968                unsafe {
19969                    b.launch(cfg)?;
19970                }
19971            }
19972            (None, None) => {
19973                let f = self.func("ssm_conv_ring_update_f32");
19974                let n = conv_dim * (d_conv - 1);
19975                let cfg = LaunchConfig::for_num_elems(n as u32);
19976                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19977                let __s_b = self.gpu.stream();
19978                let mut b = __s_b.launch_builder(&f);
19979                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19980                unsafe {
19981                    b.launch(cfg)?;
19982                }
19983            }
19984            (Some(old), _) => {
19985                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
19986            }
19987        }
19988        Ok(())
19989    }
19990
19991    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
19992    pub fn ssm_conv1d_tm_state_pad_v(
19993        &self,
19994        qkv_tm: &cudarc::driver::CudaView<f32>,
19995        conv_state: &mut CudaSlice<f32>,
19996        w: &CudaSlice<f32>,
19997        y: &mut CudaSlice<f32>,
19998        conv_dim: usize,
19999        t: usize,
20000        d_conv: usize,
20001        pad_len: Option<&CudaSlice<i32>>,
20002    ) -> Result<(), Box<dyn std::error::Error>> {
20003        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
20004        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
20005        // the window kernel both read the pre-roll ring; the roll launches after both) — but
20006        // cloning first keeps the ordering trivially correct under any future stream split.
20007        let ring_old = if t < d_conv - 1 {
20008            Some(self.clone_dtod(conv_state)?)
20009        } else {
20010            None
20011        };
20012        {
20013            let f = self.func("ssm_conv1d_tm_state_f32");
20014            let cfg = LaunchConfig {
20015                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20016                block_dim: (256, 1, 1),
20017                shared_mem_bytes: 0,
20018            };
20019            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20020            let __s_b = self.gpu.stream();
20021            let mut b = __s_b.launch_builder(&f);
20022            b.arg(qkv_tm)
20023                .arg(&*conv_state)
20024                .arg(w)
20025                .arg(y)
20026                .arg(&cd)
20027                .arg(&ti)
20028                .arg(&dc);
20029            unsafe {
20030                b.launch(cfg)?;
20031            }
20032        }
20033        match (ring_old, pad_len) {
20034            (None, Some(len_d)) => {
20035                let f = self.func("ssm_conv_ring_update_dev_f32");
20036                let n = conv_dim * (d_conv - 1);
20037                let cfg = LaunchConfig::for_num_elems(n as u32);
20038                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20039                let __s_b = self.gpu.stream();
20040                let mut b = __s_b.launch_builder(&f);
20041                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20042                unsafe {
20043                    b.launch(cfg)?;
20044                }
20045            }
20046            (None, None) => {
20047                let f = self.func("ssm_conv_ring_update_f32");
20048                let n = conv_dim * (d_conv - 1);
20049                let cfg = LaunchConfig::for_num_elems(n as u32);
20050                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20051                let __s_b = self.gpu.stream();
20052                let mut b = __s_b.launch_builder(&f);
20053                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20054                unsafe {
20055                    b.launch(cfg)?;
20056                }
20057            }
20058            (Some(_), _) => unreachable!(
20059                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
20060            ),
20061        }
20062        Ok(())
20063    }
20064
20065    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
20066    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
20067    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
20068    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
20069    pub fn ssm_conv_ring_rebuild(
20070        &self,
20071        qkv_tm: &CudaSlice<f32>,
20072        ring_old: &CudaSlice<f32>,
20073        conv_state: &mut CudaSlice<f32>,
20074        conv_dim: usize,
20075        tc: usize,
20076        d_conv: usize,
20077    ) -> Result<(), Box<dyn std::error::Error>> {
20078        let f = self.func("ssm_conv_ring_rebuild_f32");
20079        let n = conv_dim * (d_conv - 1);
20080        let cfg = LaunchConfig::for_num_elems(n as u32);
20081        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
20082        let __s_b = self.gpu.stream();
20083        let mut b = __s_b.launch_builder(&f);
20084        b.arg(qkv_tm)
20085            .arg(ring_old)
20086            .arg(conv_state)
20087            .arg(&cd)
20088            .arg(&ti)
20089            .arg(&dc);
20090        unsafe {
20091            b.launch(cfg)?;
20092        }
20093        Ok(())
20094    }
20095
20096    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
20097    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
20098    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
20099    /// the argmax + run-spec gates are the authority.
20100    #[allow(clippy::too_many_arguments)]
20101    pub fn gdn_prep_decode(
20102        &self,
20103        conv_out: &CudaSlice<f32>,
20104        beta_raw: &CudaSlice<f32>,
20105        alpha: &CudaSlice<f32>,
20106        dt_bias: &CudaSlice<f32>,
20107        a: &CudaSlice<f32>,
20108        q_l2: &mut CudaSlice<f32>,
20109        k_l2: &mut CudaSlice<f32>,
20110        v_g: &mut CudaSlice<f32>,
20111        beta: &mut CudaSlice<f32>,
20112        g_log: &mut CudaSlice<f32>,
20113        d_state: usize,
20114        num_v: usize,
20115        num_k: usize,
20116        key_dim: usize,
20117        eps: f32,
20118    ) -> Result<(), Box<dyn std::error::Error>> {
20119        let f = self.func("gdn_prep_decode_f32");
20120        let cfg = LaunchConfig {
20121            grid_dim: (num_v as u32, 1, 1),
20122            block_dim: (32, 4, 1),
20123            shared_mem_bytes: 0,
20124        };
20125        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20126        let __s_b = self.gpu.stream();
20127        let mut b = __s_b.launch_builder(&f);
20128        b.arg(conv_out)
20129            .arg(beta_raw)
20130            .arg(alpha)
20131            .arg(dt_bias)
20132            .arg(a)
20133            .arg(q_l2)
20134            .arg(k_l2)
20135            .arg(v_g)
20136            .arg(beta)
20137            .arg(g_log)
20138            .arg(&ds)
20139            .arg(&nv)
20140            .arg(&nk)
20141            .arg(&kd)
20142            .arg(&eps);
20143        unsafe {
20144            b.launch(cfg)?;
20145        }
20146        Ok(())
20147    }
20148
20149    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
20150    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
20151    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
20152    #[allow(clippy::too_many_arguments)]
20153    pub fn ssm_conv1d_gdn(
20154        &self,
20155        qkv_tm: &CudaSlice<f32>,
20156        w: &CudaSlice<f32>,
20157        q_g: &mut CudaSlice<f32>,
20158        k_g: &mut CudaSlice<f32>,
20159        v_g: &mut CudaSlice<f32>,
20160        conv_dim: usize,
20161        t: usize,
20162        d_conv: usize,
20163        d_state: usize,
20164        num_v: usize,
20165        num_k: usize,
20166        key_dim: usize,
20167    ) -> Result<(), Box<dyn std::error::Error>> {
20168        let f = self.func("ssm_conv1d_gdn_f32");
20169        let cfg = LaunchConfig {
20170            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20171            block_dim: (256, 1, 1),
20172            shared_mem_bytes: 0,
20173        };
20174        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20175        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20176        let __s_b = self.gpu.stream();
20177        let mut b = __s_b.launch_builder(&f);
20178        b.arg(qkv_tm)
20179            .arg(w)
20180            .arg(q_g)
20181            .arg(k_g)
20182            .arg(v_g)
20183            .arg(&cd)
20184            .arg(&ti)
20185            .arg(&dc)
20186            .arg(&ds)
20187            .arg(&nv)
20188            .arg(&nk)
20189            .arg(&kd);
20190        unsafe {
20191            b.launch(cfg)?;
20192        }
20193        Ok(())
20194    }
20195
20196    pub fn ssm_conv1d(
20197        &self,
20198        x: &CudaSlice<f32>,
20199        w: &CudaSlice<f32>,
20200        y: &mut CudaSlice<f32>,
20201        conv_dim: usize,
20202        t: usize,
20203        d_conv: usize,
20204        silu: bool,
20205    ) -> Result<(), Box<dyn std::error::Error>> {
20206        let f = self.func("ssm_conv1d_silu_f32");
20207        let cfg = LaunchConfig {
20208            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
20209            block_dim: (256, 1, 1),
20210            shared_mem_bytes: 0,
20211        };
20212        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
20213        let __s_b = self.gpu.stream();
20214        let mut b = __s_b.launch_builder(&f);
20215        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
20216        unsafe {
20217            b.launch(cfg)?;
20218        }
20219        Ok(())
20220    }
20221
20222    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
20223    /// o:[128,H,T]. Single sequence.
20224    pub fn gdn_scan_s128(
20225        &self,
20226        q: &CudaSlice<f32>,
20227        k: &CudaSlice<f32>,
20228        v: &CudaSlice<f32>,
20229        g: &CudaSlice<f32>,
20230        beta: &CudaSlice<f32>,
20231        state_in: &CudaSlice<f32>,
20232        state_out: &mut CudaSlice<f32>,
20233        o: &mut CudaSlice<f32>,
20234        n_head: usize,
20235        t: usize,
20236        scale: f32,
20237    ) -> Result<(), Box<dyn std::error::Error>> {
20238        let f = self.func("gdn_scan_s128");
20239        const S_V: u32 = 128;
20240        const WARP: u32 = 32;
20241        const COLS_PER_BLOCK: u32 = 4;
20242        let cfg = LaunchConfig {
20243            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
20244            block_dim: (WARP, COLS_PER_BLOCK, 1),
20245            shared_mem_bytes: 0,
20246        };
20247        let (h, ti) = (n_head as i32, t as i32);
20248        let __s_b = self.gpu.stream();
20249        let mut b = __s_b.launch_builder(&f);
20250        b.arg(q)
20251            .arg(k)
20252            .arg(v)
20253            .arg(g)
20254            .arg(beta)
20255            .arg(state_in)
20256            .arg(state_out)
20257            .arg(o)
20258            .arg(&h)
20259            .arg(&ti)
20260            .arg(&scale);
20261        unsafe {
20262            b.launch(cfg)?;
20263        }
20264        Ok(())
20265    }
20266
20267    // ==== B2' batched decode state ops (decode_batch.rs) ====
20268    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
20269    // Bodies are the single-seq kernels per sequence — bit-identical per row.
20270
20271    #[allow(clippy::too_many_arguments)]
20272    pub fn ssm_conv1d_fused_decode_b(
20273        &self,
20274        qkv_cols: &CudaSlice<f32>,
20275        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20276        w: &CudaSlice<f32>,
20277        conv_outs: &mut CudaSlice<f32>,
20278        conv_dim: usize,
20279        d_conv: usize,
20280        b_n: usize,
20281    ) -> Result<(), Box<dyn std::error::Error>> {
20282        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20283        let cfg = LaunchConfig {
20284            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20285            block_dim: (256, 1, 1),
20286            shared_mem_bytes: 0,
20287        };
20288        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20289        let __s_b = self.gpu.stream();
20290        let mut b = __s_b.launch_builder(&f);
20291        b.arg(qkv_cols)
20292            .arg(conv_state_ptrs)
20293            .arg(w)
20294            .arg(conv_outs)
20295            .arg(&cd)
20296            .arg(&dc);
20297        unsafe {
20298            b.launch(cfg)?;
20299        }
20300        Ok(())
20301    }
20302
20303    #[allow(clippy::too_many_arguments)]
20304    pub fn gdn_prep_decode_b(
20305        &self,
20306        conv_outs: &CudaSlice<f32>,
20307        beta_raws: &CudaSlice<f32>,
20308        alphas: &CudaSlice<f32>,
20309        dt_bias: &CudaSlice<f32>,
20310        a: &CudaSlice<f32>,
20311        q_l2: &mut CudaSlice<f32>,
20312        k_l2: &mut CudaSlice<f32>,
20313        v_g: &mut CudaSlice<f32>,
20314        beta: &mut CudaSlice<f32>,
20315        g_log: &mut CudaSlice<f32>,
20316        d_state: usize,
20317        num_v: usize,
20318        num_k: usize,
20319        key_dim: usize,
20320        eps: f32,
20321        conv_dim: usize,
20322        b_n: usize,
20323    ) -> Result<(), Box<dyn std::error::Error>> {
20324        let f = self.func("gdn_prep_decode_b_f32");
20325        let cfg = LaunchConfig {
20326            grid_dim: (num_v as u32, 1, b_n as u32),
20327            block_dim: (32, 4, 1),
20328            shared_mem_bytes: 0,
20329        };
20330        let (ds, nv, nk, kd, cd) = (
20331            d_state as i32,
20332            num_v as i32,
20333            num_k as i32,
20334            key_dim as i32,
20335            conv_dim as i32,
20336        );
20337        let __s_b = self.gpu.stream();
20338        let mut b = __s_b.launch_builder(&f);
20339        b.arg(conv_outs)
20340            .arg(beta_raws)
20341            .arg(alphas)
20342            .arg(dt_bias)
20343            .arg(a)
20344            .arg(q_l2)
20345            .arg(k_l2)
20346            .arg(v_g)
20347            .arg(beta)
20348            .arg(g_log)
20349            .arg(&ds)
20350            .arg(&nv)
20351            .arg(&nk)
20352            .arg(&kd)
20353            .arg(&eps)
20354            .arg(&cd);
20355        unsafe {
20356            b.launch(cfg)?;
20357        }
20358        Ok(())
20359    }
20360
20361    #[allow(clippy::too_many_arguments)]
20362    pub fn gdn_scan_s128_batched(
20363        &self,
20364        q: &CudaSlice<f32>,
20365        k: &CudaSlice<f32>,
20366        v: &CudaSlice<f32>,
20367        g: &CudaSlice<f32>,
20368        beta: &CudaSlice<f32>,
20369        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20370        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20371        o: &mut CudaSlice<f32>,
20372        n_head: usize,
20373        b_n: usize,
20374        scale: f32,
20375    ) -> Result<(), Box<dyn std::error::Error>> {
20376        let f = self.func("gdn_scan_s128_b");
20377        const S_V: u32 = 128;
20378        const WARP: u32 = 32;
20379        const COLS_PER_BLOCK: u32 = 4;
20380        let cfg = LaunchConfig {
20381            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20382            block_dim: (WARP, COLS_PER_BLOCK, 1),
20383            shared_mem_bytes: 0,
20384        };
20385        let h = n_head as i32;
20386        let __s_b = self.gpu.stream();
20387        let mut b = __s_b.launch_builder(&f);
20388        b.arg(q)
20389            .arg(k)
20390            .arg(v)
20391            .arg(g)
20392            .arg(beta)
20393            .arg(state_in_ptrs)
20394            .arg(state_out_ptrs)
20395            .arg(o)
20396            .arg(&h)
20397            .arg(&scale);
20398        unsafe {
20399            b.launch(cfg)?;
20400        }
20401        Ok(())
20402    }
20403
20404    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
20405    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
20406    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
20407    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
20408    /// numeric class; only the pointer arithmetic moved host-side.
20409    #[allow(clippy::too_many_arguments)]
20410    pub fn ssm_conv1d_fused_decode_b_view(
20411        &self,
20412        qkv_cols: &cudarc::driver::CudaView<f32>,
20413        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
20414        w: &CudaSlice<f32>,
20415        conv_outs: &mut CudaSlice<f32>,
20416        conv_dim: usize,
20417        d_conv: usize,
20418        b_n: usize,
20419    ) -> Result<(), Box<dyn std::error::Error>> {
20420        let f = self.func("ssm_conv1d_fused_decode_b_f32");
20421        let cfg = LaunchConfig {
20422            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
20423            block_dim: (256, 1, 1),
20424            shared_mem_bytes: 0,
20425        };
20426        let (cd, dc) = (conv_dim as i32, d_conv as i32);
20427        let __s_b = self.gpu.stream();
20428        let mut b = __s_b.launch_builder(&f);
20429        b.arg(qkv_cols)
20430            .arg(conv_state_ptrs)
20431            .arg(w)
20432            .arg(conv_outs)
20433            .arg(&cd)
20434            .arg(&dc);
20435        unsafe {
20436            b.launch(cfg)?;
20437        }
20438        Ok(())
20439    }
20440
20441    #[allow(clippy::too_many_arguments)]
20442    pub fn gdn_prep_decode_b_view(
20443        &self,
20444        conv_outs: &CudaSlice<f32>,
20445        beta_raws: &cudarc::driver::CudaView<f32>,
20446        alphas: &cudarc::driver::CudaView<f32>,
20447        dt_bias: &CudaSlice<f32>,
20448        a: &CudaSlice<f32>,
20449        q_l2: &mut CudaSlice<f32>,
20450        k_l2: &mut CudaSlice<f32>,
20451        v_g: &mut CudaSlice<f32>,
20452        beta: &mut CudaSlice<f32>,
20453        g_log: &mut CudaSlice<f32>,
20454        d_state: usize,
20455        num_v: usize,
20456        num_k: usize,
20457        key_dim: usize,
20458        eps: f32,
20459        conv_dim: usize,
20460        b_n: usize,
20461    ) -> Result<(), Box<dyn std::error::Error>> {
20462        let f = self.func("gdn_prep_decode_b_f32");
20463        let cfg = LaunchConfig {
20464            grid_dim: (num_v as u32, 1, b_n as u32),
20465            block_dim: (32, 4, 1),
20466            shared_mem_bytes: 0,
20467        };
20468        let (ds, nv, nk, kd, cd) = (
20469            d_state as i32,
20470            num_v as i32,
20471            num_k as i32,
20472            key_dim as i32,
20473            conv_dim as i32,
20474        );
20475        let __s_b = self.gpu.stream();
20476        let mut b = __s_b.launch_builder(&f);
20477        b.arg(conv_outs)
20478            .arg(beta_raws)
20479            .arg(alphas)
20480            .arg(dt_bias)
20481            .arg(a)
20482            .arg(q_l2)
20483            .arg(k_l2)
20484            .arg(v_g)
20485            .arg(beta)
20486            .arg(g_log)
20487            .arg(&ds)
20488            .arg(&nv)
20489            .arg(&nk)
20490            .arg(&kd)
20491            .arg(&eps)
20492            .arg(&cd);
20493        unsafe {
20494            b.launch(cfg)?;
20495        }
20496        Ok(())
20497    }
20498
20499    #[allow(clippy::too_many_arguments)]
20500    pub fn gdn_scan_s128_batched_view(
20501        &self,
20502        q: &CudaSlice<f32>,
20503        k: &CudaSlice<f32>,
20504        v: &CudaSlice<f32>,
20505        g: &CudaSlice<f32>,
20506        beta: &CudaSlice<f32>,
20507        state_in_ptrs: &cudarc::driver::CudaView<u64>,
20508        state_out_ptrs: &cudarc::driver::CudaView<u64>,
20509        o: &mut cudarc::driver::CudaViewMut<f32>,
20510        n_head: usize,
20511        b_n: usize,
20512        scale: f32,
20513    ) -> Result<(), Box<dyn std::error::Error>> {
20514        let f = self.func("gdn_scan_s128_b");
20515        const S_V: u32 = 128;
20516        const WARP: u32 = 32;
20517        const COLS_PER_BLOCK: u32 = 4;
20518        let cfg = LaunchConfig {
20519            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
20520            block_dim: (WARP, COLS_PER_BLOCK, 1),
20521            shared_mem_bytes: 0,
20522        };
20523        let h = n_head as i32;
20524        let __s_b = self.gpu.stream();
20525        let mut b = __s_b.launch_builder(&f);
20526        b.arg(q)
20527            .arg(k)
20528            .arg(v)
20529            .arg(g)
20530            .arg(beta)
20531            .arg(state_in_ptrs)
20532            .arg(state_out_ptrs)
20533            .arg(o)
20534            .arg(&h)
20535            .arg(&scale);
20536        unsafe {
20537            b.launch(cfg)?;
20538        }
20539        Ok(())
20540    }
20541
20542    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
20543    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
20544    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
20545    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
20546    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
20547    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
20548    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
20549    /// identity law); prime_cache/forward/forward_last are the only callers.
20550    pub fn gdn_chunked_enabled() -> bool {
20551        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
20552        *E.get_or_init(|| {
20553            std::env::var("MEMRA_GDN_CHUNKED")
20554                .map(|v| v != "0")
20555                .unwrap_or(true)
20556        })
20557    }
20558
20559    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
20560    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
20561    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
20562    /// of 32 in [32, 128] (kernel row mappings require it).
20563    pub fn gdn_chunk_size() -> usize {
20564        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
20565        *C.get_or_init(|| {
20566            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
20567                .ok()
20568                .and_then(|v| v.parse().ok())
20569                .unwrap_or(32);
20570            c.clamp(32, 128) / 32 * 32
20571        })
20572    }
20573
20574    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
20575    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
20576    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
20577    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
20578    #[allow(clippy::too_many_arguments)]
20579    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
20580    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
20581    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
20582    #[allow(clippy::too_many_arguments)]
20583    pub fn gdn_chunk_k123(
20584        &self,
20585        q: &CudaSlice<f32>,
20586        k: &CudaSlice<f32>,
20587        v: &CudaSlice<f32>,
20588        g: &CudaSlice<f32>,
20589        beta: &CudaSlice<f32>,
20590        wb16: Option<&mut CudaSlice<u8>>,
20591        n_head: usize,
20592        t: usize,
20593        c: usize,
20594        hk: usize,
20595        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
20596    ) -> Result<
20597        (
20598            CudaSlice<f32>,
20599            CudaSlice<f32>,
20600            CudaSlice<f32>,
20601            CudaSlice<f32>,
20602        ),
20603        Box<dyn std::error::Error>,
20604    > {
20605        const D: usize = 128;
20606        let h = n_head;
20607        let nc = (t + c - 1) / c;
20608        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20609        let mut gcum = self.uninit(t * h)?;
20610        let mut a = self.uninit(nc * h * c * c)?;
20611        let mut p = self.uninit(nc * h * c * c)?;
20612        let mut u = self.uninit(nc * h * c * D)?;
20613        let mut w = self.uninit(nc * h * c * D)?;
20614        {
20615            // K1
20616            let f = self.func("gdn_chunk_cumgate_f32");
20617            let cfg = LaunchConfig {
20618                grid_dim: (nc as u32, h as u32, 1),
20619                block_dim: (32, 1, 1),
20620                shared_mem_bytes: 0,
20621            };
20622            let __s_b = self.gpu.stream();
20623            let mut b = __s_b.launch_builder(&f);
20624            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
20625            unsafe {
20626                b.launch(cfg)?;
20627            }
20628        }
20629        if let Some((qb, kb, pb)) = k2w {
20630            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
20631            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
20632            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
20633            let f = self.func("gdn_k2_wgmma");
20634            let cfg = LaunchConfig {
20635                grid_dim: (nc as u32, h as u32, 1),
20636                block_dim: (128, 1, 1),
20637                shared_mem_bytes: 0,
20638            };
20639            let hki = hk as i32;
20640            let __s_b = self.gpu.stream();
20641            let mut b = __s_b.launch_builder(&f);
20642            b.arg(qb)
20643                .arg(kb)
20644                .arg(&gcum)
20645                .arg(beta)
20646                .arg(&mut a)
20647                .arg(&mut *pb)
20648                .arg(&hi)
20649                .arg(&ti)
20650                .arg(&ci)
20651                .arg(&hki);
20652            unsafe {
20653                b.launch(cfg)?;
20654            }
20655        } else if c <= 64 && !portable_mma_gated() {
20656            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
20657            let f = self.func("gdn_chunk_attn_f32");
20658            let jt = ((c + 31) / 32) as u32;
20659            let cfg = LaunchConfig {
20660                grid_dim: (nc as u32, h as u32, jt),
20661                block_dim: (256, 1, 1),
20662                shared_mem_bytes: 0,
20663            };
20664            let hki = hk as i32;
20665            let __s_b = self.gpu.stream();
20666            let mut b = __s_b.launch_builder(&f);
20667            b.arg(q)
20668                .arg(k)
20669                .arg(&gcum)
20670                .arg(beta)
20671                .arg(&mut a)
20672                .arg(&mut p)
20673                .arg(&hi)
20674                .arg(&ti)
20675                .arg(&ci)
20676                .arg(&hki);
20677            unsafe {
20678                b.launch(cfg)?;
20679            }
20680        } else {
20681            // K2 generic (C = 128, or the portable target's low-smem fallback)
20682            assert!(
20683                hk == h,
20684                "generic K2 is broadcast-only (de-broadcast rides C==32)"
20685            );
20686            let f = self.func("gdn_chunk_attn_g_f32");
20687            let cfg = LaunchConfig {
20688                grid_dim: (nc as u32, h as u32, 1),
20689                block_dim: (32, 8, 1),
20690                shared_mem_bytes: 0,
20691            };
20692            let __s_b = self.gpu.stream();
20693            let mut b = __s_b.launch_builder(&f);
20694            b.arg(q)
20695                .arg(k)
20696                .arg(&gcum)
20697                .arg(beta)
20698                .arg(&mut a)
20699                .arg(&mut p)
20700                .arg(&hi)
20701                .arg(&ti)
20702                .arg(&ci);
20703            unsafe {
20704                b.launch(cfg)?;
20705            }
20706        }
20707        {
20708            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
20709            let cfg = LaunchConfig {
20710                grid_dim: (nc as u32, h as u32, 1),
20711                block_dim: (256, 1, 1),
20712                shared_mem_bytes: 0,
20713            };
20714            match c {
20715                32 | 64 => {
20716                    let f = self.func(if c == 32 {
20717                        "gdn_chunk_solve32_f32"
20718                    } else {
20719                        "gdn_chunk_solve64_f32"
20720                    });
20721                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
20722                    let wb: u64 = match wb16 {
20723                        Some(d) => self.addr_u8(d),
20724                        None => 0,
20725                    };
20726                    let hki = hk as i32;
20727                    let __s_b = self.gpu.stream();
20728                    let mut b = __s_b.launch_builder(&f);
20729                    b.arg(v)
20730                        .arg(k)
20731                        .arg(&a)
20732                        .arg(&gcum)
20733                        .arg(&mut u)
20734                        .arg(&mut w)
20735                        .arg(&wb)
20736                        .arg(&hi)
20737                        .arg(&ti)
20738                        .arg(&hki);
20739                    unsafe {
20740                        b.launch(cfg)?;
20741                    }
20742                }
20743                _ => {
20744                    assert!(hk == h, "generic K3 is broadcast-only");
20745                    let f = self.func("gdn_chunk_solve_f32");
20746                    let __s_b = self.gpu.stream();
20747                    let mut b = __s_b.launch_builder(&f);
20748                    b.arg(v)
20749                        .arg(k)
20750                        .arg(&a)
20751                        .arg(&gcum)
20752                        .arg(&mut u)
20753                        .arg(&mut w)
20754                        .arg(&hi)
20755                        .arg(&ti)
20756                        .arg(&ci);
20757                    unsafe {
20758                        b.launch(cfg)?;
20759                    }
20760                }
20761            }
20762        }
20763        Ok((gcum, p, u, w))
20764    }
20765
20766    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
20767    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
20768    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
20769    pub fn gdn_db_on() -> bool {
20770        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
20771    }
20772
20773    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
20774    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
20775    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
20776        !portable_mma_gated()
20777            && c == 32
20778            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20779                Ok("1") => true,
20780                Ok("0") => false,
20781                _ => cfg!(memra_hopper_mma),
20782            }
20783    }
20784
20785    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
20786    /// mma config; same per-call env read discipline).
20787    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
20788        self.gdn_mma_enabled(c)
20789            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20790                Ok("0") => false,
20791                Ok("1") => true,
20792                _ => cfg!(memra_hopper_mma),
20793            }
20794    }
20795
20796    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
20797    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
20798    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
20799    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
20800    #[allow(clippy::too_many_arguments)]
20801    pub fn ssm_conv1d_gdn_state_pad(
20802        &self,
20803        qkv_tm: &cudarc::driver::CudaView<f32>,
20804        conv_state: &mut CudaSlice<f32>,
20805        w: &CudaSlice<f32>,
20806        q_g: &mut CudaSlice<f32>,
20807        k_g: &mut CudaSlice<f32>,
20808        v_g: &mut CudaSlice<f32>,
20809        conv_dim: usize,
20810        t: usize,
20811        d_conv: usize,
20812        d_state: usize,
20813        num_v: usize,
20814        num_k: usize,
20815        key_dim: usize,
20816        hk: usize,
20817        pad_len: Option<&CudaSlice<i32>>,
20818    ) -> Result<(), Box<dyn std::error::Error>> {
20819        assert!(
20820            t >= d_conv - 1,
20821            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
20822        );
20823        {
20824            let f = self.func("ssm_conv1d_gdn_state_f32");
20825            let cfg = LaunchConfig {
20826                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
20827                block_dim: (256, 1, 1),
20828                shared_mem_bytes: 0,
20829            };
20830            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20831            let (ds, nv, nk, kd, hki) = (
20832                d_state as i32,
20833                num_v as i32,
20834                num_k as i32,
20835                key_dim as i32,
20836                hk as i32,
20837            );
20838            let __s_b = self.gpu.stream();
20839            let mut b = __s_b.launch_builder(&f);
20840            b.arg(qkv_tm)
20841                .arg(&*conv_state)
20842                .arg(w)
20843                .arg(q_g)
20844                .arg(k_g)
20845                .arg(v_g)
20846                .arg(&cd)
20847                .arg(&ti)
20848                .arg(&dc)
20849                .arg(&ds)
20850                .arg(&nv)
20851                .arg(&nk)
20852                .arg(&kd)
20853                .arg(&hki);
20854            unsafe {
20855                b.launch(cfg)?;
20856            }
20857        }
20858        match pad_len {
20859            Some(len_d) => {
20860                let f = self.func("ssm_conv_ring_update_dev_f32");
20861                let n = conv_dim * (d_conv - 1);
20862                let cfg = LaunchConfig::for_num_elems(n as u32);
20863                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20864                let __s_b = self.gpu.stream();
20865                let mut b = __s_b.launch_builder(&f);
20866                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20867                unsafe {
20868                    b.launch(cfg)?;
20869                }
20870            }
20871            None => {
20872                let f = self.func("ssm_conv_ring_update_f32");
20873                let n = conv_dim * (d_conv - 1);
20874                let cfg = LaunchConfig::for_num_elems(n as u32);
20875                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20876                let __s_b = self.gpu.stream();
20877                let mut b = __s_b.launch_builder(&f);
20878                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20879                unsafe {
20880                    b.launch(cfg)?;
20881                }
20882            }
20883        }
20884        Ok(())
20885    }
20886
20887    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
20888    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
20889    /// K2/K3 can write them.
20890    pub fn gdn_chunk_alloc(
20891        &self,
20892        n_head: usize,
20893        t: usize,
20894        c: usize,
20895        hk: usize,
20896    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
20897        const D: usize = 128;
20898        assert!(
20899            c == 32,
20900            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
20901        );
20902        let h = n_head;
20903        let nc = (t + c - 1) / c;
20904        Ok(GdnChunkBufs {
20905            gcum: self.uninit(t * h)?,
20906            a: self.uninit(nc * h * c * c)?,
20907            p: self.uninit(nc * h * c * c)?,
20908            u: self.uninit(nc * h * c * D)?,
20909            w: self.uninit(nc * h * c * D)?,
20910            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20911            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20912            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20913            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
20914            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20915            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
20916            o: self.uninit(D * h * t)?,
20917            t,
20918            nc,
20919        })
20920    }
20921
20922    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
20923    pub fn f32_to_bf16_v(
20924        &self,
20925        x: &cudarc::driver::CudaView<f32>,
20926        dst: &mut CudaSlice<u8>,
20927        n: usize,
20928    ) -> Result<(), Box<dyn std::error::Error>> {
20929        let f = self.func("f32_to_bf16_bulk");
20930        let ni = n as i64;
20931        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20932        let __s_b = self.gpu.stream();
20933        let mut b = __s_b.launch_builder(&f);
20934        b.arg(x).arg(dst).arg(&ni);
20935        unsafe {
20936            b.launch(cfg)?;
20937        }
20938        Ok(())
20939    }
20940
20941    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
20942    pub fn f32_to_bf16_into(
20943        &self,
20944        x: &CudaSlice<f32>,
20945        dst: &mut CudaSlice<u8>,
20946        n: usize,
20947    ) -> Result<(), Box<dyn std::error::Error>> {
20948        let f = self.func("f32_to_bf16_bulk");
20949        let ni = n as i64;
20950        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20951        let __s_b = self.gpu.stream();
20952        let mut b = __s_b.launch_builder(&f);
20953        b.arg(x).arg(dst).arg(&ni);
20954        unsafe {
20955            b.launch(cfg)?;
20956        }
20957        Ok(())
20958    }
20959
20960    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
20961    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
20962    pub fn gdn_chunk_k123_vl8(
20963        &self,
20964        seqs: &[GdnSeqVl],
20965        n_head: usize,
20966        hk: usize,
20967        wq: Option<&GdnWVl8>,
20968    ) -> Result<(), Box<dyn std::error::Error>> {
20969        let b = seqs.len();
20970        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
20971        let mut packed = [GdnSeqVl::default(); 8];
20972        packed[..b].copy_from_slice(seqs);
20973        let v = GdnVl8(packed);
20974        let (hi, ci) = (n_head as i32, 32i32);
20975        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20976        {
20977            let f = self.func("gdn_chunk_cumgate_vl");
20978            let cfg = LaunchConfig {
20979                grid_dim: (max_nc, n_head as u32, b as u32),
20980                block_dim: (32, 1, 1),
20981                shared_mem_bytes: 0,
20982            };
20983            let __s_lb = self.gpu.stream();
20984            let mut lb = __s_lb.launch_builder(&f);
20985            lb.arg(&v).arg(&hi).arg(&ci);
20986            unsafe {
20987                lb.launch(cfg)?;
20988            }
20989        }
20990        let hki = hk as i32;
20991        if let Some(w) = wq {
20992            // K2-wgmma vl twin (writes A + pre-masked Pb16)
20993            let f = self.func("gdn_k2_wgmma_vl");
20994            let cfg = LaunchConfig {
20995                grid_dim: (max_nc, n_head as u32, b as u32),
20996                block_dim: (128, 1, 1),
20997                shared_mem_bytes: 0,
20998            };
20999            let __s_lb = self.gpu.stream();
21000            let mut lb = __s_lb.launch_builder(&f);
21001            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
21002            unsafe {
21003                lb.launch(cfg)?;
21004            }
21005        } else {
21006            let f = self.func("gdn_chunk_attn_vl");
21007            let cfg = LaunchConfig {
21008                grid_dim: (max_nc, n_head as u32, b as u32),
21009                block_dim: (256, 1, 1),
21010                shared_mem_bytes: 0,
21011            };
21012            let __s_lb = self.gpu.stream();
21013            let mut lb = __s_lb.launch_builder(&f);
21014            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21015            unsafe {
21016                lb.launch(cfg)?;
21017            }
21018        }
21019        {
21020            let f = self.func("gdn_chunk_solve32_vl");
21021            let cfg = LaunchConfig {
21022                grid_dim: (max_nc, n_head as u32, b as u32),
21023                block_dim: (256, 1, 1),
21024                shared_mem_bytes: 0,
21025            };
21026            let __s_lb = self.gpu.stream();
21027            let mut lb = __s_lb.launch_builder(&f);
21028            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21029            unsafe {
21030                lb.launch(cfg)?;
21031            }
21032        }
21033        Ok(())
21034    }
21035
21036    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
21037    /// fused gate-prep, 5 launches for every sequence (per-element math identical
21038    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
21039    #[allow(clippy::too_many_arguments)]
21040    pub fn gdn_prep_vl8(
21041        &self,
21042        seqs: &[GdnPrepVl],
21043        conv_w: &CudaSlice<f32>,
21044        dt_bias: &CudaSlice<f32>,
21045        a: &CudaSlice<f32>,
21046        conv_dim: usize,
21047        d_conv: usize,
21048        d_state: usize,
21049        num_v: usize,
21050        num_k: usize,
21051        key_dim: usize,
21052        hk: usize,
21053        eps: f32,
21054    ) -> Result<(), Box<dyn std::error::Error>> {
21055        let b = seqs.len();
21056        assert!(b >= 1 && b <= 8);
21057        let mut packed = [GdnPrepVl::default(); 8];
21058        packed[..b].copy_from_slice(seqs);
21059        let v = GdnPrepVl8(packed);
21060        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21061        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
21062        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
21063        assert!(
21064            conv_fuse || hk == num_v,
21065            "de-broadcast requires the fused conv"
21066        );
21067        if conv_fuse {
21068            let f = self.func("ssm_conv1d_gdn_state_vl");
21069            let cfg = LaunchConfig {
21070                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21071                block_dim: (256, 1, 1),
21072                shared_mem_bytes: 0,
21073            };
21074            let (dsi, nvi, nki, kdi, hki) = (
21075                d_state as i32,
21076                num_v as i32,
21077                num_k as i32,
21078                key_dim as i32,
21079                hk as i32,
21080            );
21081            let __s_lb = self.gpu.stream();
21082            let mut lb = __s_lb.launch_builder(&f);
21083            lb.arg(&v)
21084                .arg(conv_w)
21085                .arg(&cdi)
21086                .arg(&dci)
21087                .arg(&dsi)
21088                .arg(&nvi)
21089                .arg(&nki)
21090                .arg(&kdi)
21091                .arg(&hki);
21092            unsafe {
21093                lb.launch(cfg)?;
21094            }
21095        } else {
21096            let f = self.func("ssm_conv1d_tm_state_vl");
21097            let cfg = LaunchConfig {
21098                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
21099                block_dim: (256, 1, 1),
21100                shared_mem_bytes: 0,
21101            };
21102            let __s_lb = self.gpu.stream();
21103            let mut lb = __s_lb.launch_builder(&f);
21104            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
21105            unsafe {
21106                lb.launch(cfg)?;
21107            }
21108        }
21109        {
21110            let f = self.func("ssm_conv_ring_update_vl");
21111            let n = (conv_dim * (d_conv - 1)) as u32;
21112            let cfg = LaunchConfig {
21113                grid_dim: (n.div_ceil(256), 1, b as u32),
21114                block_dim: (256, 1, 1),
21115                shared_mem_bytes: 0,
21116            };
21117            let __s_lb = self.gpu.stream();
21118            let mut lb = __s_lb.launch_builder(&f);
21119            lb.arg(&v).arg(&cdi).arg(&dci);
21120            unsafe {
21121                lb.launch(cfg)?;
21122            }
21123        }
21124        if !conv_fuse {
21125            let f = self.func("qkv_to_gdn_repack_vl");
21126            let n = max_t * (num_v * d_state) as u32;
21127            let cfg = LaunchConfig {
21128                grid_dim: (n.div_ceil(256), 1, b as u32),
21129                block_dim: (256, 1, 1),
21130                shared_mem_bytes: 0,
21131            };
21132            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
21133            let __s_lb = self.gpu.stream();
21134            let mut lb = __s_lb.launch_builder(&f);
21135            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
21136            unsafe {
21137                lb.launch(cfg)?;
21138            }
21139        }
21140        if Self::l2_v2_on(d_state) {
21141            let f = self.func("gdn_l2_v2_vl");
21142            let cfg = LaunchConfig {
21143                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
21144                block_dim: (256, 1, 1),
21145                shared_mem_bytes: 0,
21146            };
21147            let (dsi, nvi) = (d_state as i32, hk as i32);
21148            let __s_lb = self.gpu.stream();
21149            let mut lb = __s_lb.launch_builder(&f);
21150            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21151            unsafe {
21152                lb.launch(cfg)?;
21153            }
21154        } else {
21155            let f = self.func("gdn_l2_vl");
21156            let cfg = LaunchConfig {
21157                grid_dim: (max_t * hk as u32, 2, b as u32),
21158                block_dim: (256, 1, 1),
21159                shared_mem_bytes: 0,
21160            };
21161            let (dsi, nvi) = (d_state as i32, hk as i32);
21162            let __s_lb = self.gpu.stream();
21163            let mut lb = __s_lb.launch_builder(&f);
21164            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
21165            unsafe {
21166                lb.launch(cfg)?;
21167            }
21168        }
21169        {
21170            let f = self.func("gdn_gate_prep_vl");
21171            let n = max_t * num_v as u32;
21172            let cfg = LaunchConfig {
21173                grid_dim: (n.div_ceil(256), 1, b as u32),
21174                block_dim: (256, 1, 1),
21175                shared_mem_bytes: 0,
21176            };
21177            let nvi = num_v as i32;
21178            let __s_lb = self.gpu.stream();
21179            let mut lb = __s_lb.launch_builder(&f);
21180            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
21181            unsafe {
21182                lb.launch(cfg)?;
21183            }
21184        }
21185        Ok(())
21186    }
21187
21188    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
21189    pub fn gdn_mirror_vl8(
21190        &self,
21191        seqs: &[GdnSeqVl],
21192        n_head: usize,
21193        which: i32,
21194        hk: usize,
21195    ) -> Result<(), Box<dyn std::error::Error>> {
21196        let b = seqs.len();
21197        assert!(b >= 1 && b <= 8);
21198        let mut packed = [GdnSeqVl::default(); 8];
21199        packed[..b].copy_from_slice(seqs);
21200        let v = GdnVl8(packed);
21201        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
21202        let max_n = seqs
21203            .iter()
21204            .map(|s| {
21205                if which == 0 {
21206                    s.t as i64 * ept as i64
21207                } else {
21208                    s.nc as i64 * ept as i64 * 32
21209                }
21210            })
21211            .max()
21212            .unwrap();
21213        let f = self.func("gdn_mirror_vl");
21214        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
21215        let cfg = LaunchConfig {
21216            grid_dim: (blocks, 1, b as u32),
21217            block_dim: (256, 1, 1),
21218            shared_mem_bytes: 0,
21219        };
21220        let __s_lb = self.gpu.stream();
21221        let mut lb = __s_lb.launch_builder(&f);
21222        lb.arg(&v).arg(&ept).arg(&which);
21223        unsafe {
21224            lb.launch(cfg)?;
21225        }
21226        Ok(())
21227    }
21228
21229    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
21230    pub fn gdn_tail_vl8(
21231        &self,
21232        seqs: &[GdnPrepVl],
21233        norm_w: &CudaSlice<f32>,
21234        d_state: usize,
21235        num_v: usize,
21236        eps: f32,
21237    ) -> Result<(), Box<dyn std::error::Error>> {
21238        let b = seqs.len();
21239        assert!(b >= 1 && b <= 8);
21240        let mut packed = [GdnPrepVl::default(); 8];
21241        packed[..b].copy_from_slice(seqs);
21242        let v = GdnPrepVl8(packed);
21243        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
21244        let f = self.func("gated_rmsnorm_f16out_vl");
21245        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21246        let cfg = LaunchConfig {
21247            grid_dim: (max_t * num_v as u32, 1, b as u32),
21248            block_dim: (128, 1, 1),
21249            shared_mem_bytes: 0,
21250        };
21251        let (dsi, nvi) = (d_state as i32, num_v as i32);
21252        let __s_lb = self.gpu.stream();
21253        let mut lb = __s_lb.launch_builder(&f);
21254        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
21255        unsafe {
21256            lb.launch(cfg)?;
21257        }
21258        Ok(())
21259    }
21260
21261    /// Raw device address helpers for the varlen by-value arg struct (single-stream
21262    /// launches; every buffer outlives the call — the f16 FFI discipline).
21263    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
21264        use cudarc::driver::DevicePtr;
21265        let s = self.gpu.stream();
21266        let (p, _g) = x.device_ptr(&s);
21267        p as u64
21268    }
21269    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
21270        use cudarc::driver::DevicePtrMut;
21271        let s = self.gpu.stream();
21272        let (p, _g) = x.device_ptr_mut(&s);
21273        p as u64
21274    }
21275    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
21276        use cudarc::driver::DevicePtr;
21277        let s = self.gpu.stream();
21278        let (p, _g) = x.device_ptr(&s);
21279        p as u64
21280    }
21281    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
21282        use cudarc::driver::DevicePtr;
21283        let s = self.gpu.stream();
21284        let (p, _g) = x.device_ptr(&s);
21285        p as u64
21286    }
21287
21288    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
21289    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
21290    /// launches, so this is strictly bit-gateable against them).
21291    pub fn gdn_chunk_vl8(
21292        &self,
21293        seqs: &[GdnSeqVl],
21294        n_head: usize,
21295        scale: f32,
21296        hk: usize,
21297        wq: Option<&GdnWVl8>,
21298    ) -> Result<(), Box<dyn std::error::Error>> {
21299        const NSPLIT: u32 = 4;
21300        let b = seqs.len();
21301        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
21302        let mut packed = [GdnSeqVl::default(); 8];
21303        packed[..b].copy_from_slice(seqs);
21304        let v = GdnVl8(packed);
21305        let (hi, ci) = (n_head as i32, 32i32);
21306        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
21307        let hki = hk as i32;
21308        if let Some(w) = wq {
21309            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
21310            let f = self.func("gdn_k45_wgmma_vl");
21311            let cfg = LaunchConfig {
21312                grid_dim: (n_head as u32, NSPLIT, b as u32),
21313                block_dim: (256, 1, 1),
21314                shared_mem_bytes: 0,
21315            };
21316            let __s_lb = self.gpu.stream();
21317            let mut lb = __s_lb.launch_builder(&f);
21318            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
21319            unsafe {
21320                lb.launch(cfg)?;
21321            }
21322            let _ = max_nc;
21323            return Ok(());
21324        }
21325        {
21326            let f = self.func("gdn_chunk_state_mma_vl");
21327            let cfg = LaunchConfig {
21328                grid_dim: (n_head as u32, NSPLIT, b as u32),
21329                block_dim: (256, 1, 1),
21330                shared_mem_bytes: 0,
21331            };
21332            let __s_lb = self.gpu.stream();
21333            let mut lb = __s_lb.launch_builder(&f);
21334            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
21335            unsafe {
21336                lb.launch(cfg)?;
21337            }
21338        }
21339        {
21340            let f = self.func("gdn_chunk_output_mma_vl");
21341            let cfg = LaunchConfig {
21342                grid_dim: (max_nc, n_head as u32, b as u32),
21343                block_dim: (256, 1, 1),
21344                shared_mem_bytes: 0,
21345            };
21346            let __s_lb = self.gpu.stream();
21347            let mut lb = __s_lb.launch_builder(&f);
21348            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
21349            unsafe {
21350                lb.launch(cfg)?;
21351            }
21352        }
21353        Ok(())
21354    }
21355    pub fn gdn_scan_chunked(
21356        &self,
21357        q: &CudaSlice<f32>,
21358        k: &CudaSlice<f32>,
21359        v: &CudaSlice<f32>,
21360        g: &CudaSlice<f32>,
21361        beta: &CudaSlice<f32>,
21362        kb16_pre: Option<&CudaSlice<u8>>,
21363        qb16_pre: Option<&CudaSlice<u8>>,
21364        state_in: &CudaSlice<f32>,
21365        state_out: &mut CudaSlice<f32>,
21366        o: &mut CudaSlice<f32>,
21367        n_head: usize,
21368        t: usize,
21369        scale: f32,
21370        c: usize,
21371        hk: usize,
21372    ) -> Result<(), Box<dyn std::error::Error>> {
21373        const D: usize = 128;
21374        const NSPLIT: u32 = 4;
21375        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
21376        let h = n_head;
21377        let nc = (t + c - 1) / c;
21378        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
21379        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
21380        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
21381        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
21382        let gdn_mma_pre = !portable_mma_gated()
21383            && c == 32
21384            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21385                Ok("1") => true,
21386                Ok("0") => false,
21387                _ => cfg!(memra_hopper_mma),
21388            };
21389        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
21390            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
21391        } else {
21392            None
21393        };
21394        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
21395        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
21396        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
21397        let gdn_wgmma_pre = gdn_mma_pre
21398            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
21399                Ok("0") => false,
21400                Ok("1") => true,
21401                _ => cfg!(memra_hopper_mma),
21402            };
21403        let nk = t * hk * D;
21404        let mut kb16_local: Option<CudaSlice<u8>> = None;
21405        if gdn_mma_pre && kb16_pre.is_none() {
21406            let mut kb = self.alloc_u8_uninit(nk * 2)?;
21407            let f = self.func("f32_to_bf16_bulk");
21408            let n2 = nk as i64;
21409            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21410            let __s_b = self.gpu.stream();
21411            let mut b = __s_b.launch_builder(&f);
21412            b.arg(k).arg(&mut kb).arg(&n2);
21413            unsafe {
21414                b.launch(cfg2)?;
21415            }
21416            kb16_local = Some(kb);
21417        }
21418        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
21419        if let Some(kb) = kb16_pre {
21420            assert!(kb.len() >= nk * 2, "kb16_pre too small");
21421        }
21422        let mut qb16: Option<CudaSlice<u8>> = None;
21423        let mut pb16: Option<CudaSlice<u8>> = None;
21424        if gdn_wgmma_pre {
21425            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
21426            // the standalone bulk cvt only serves callers without the prep mirror.
21427            if qb16_pre.is_none() {
21428                let mut qb = self.alloc_u8_uninit(nk * 2)?;
21429                let f = self.func("f32_to_bf16_bulk");
21430                let n2 = nk as i64;
21431                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
21432                let __s_b = self.gpu.stream();
21433                let mut b = __s_b.launch_builder(&f);
21434                b.arg(q).arg(&mut qb).arg(&n2);
21435                unsafe {
21436                    b.launch(cfg2)?;
21437                }
21438                qb16 = Some(qb);
21439            } else if let Some(qb) = qb16_pre {
21440                assert!(qb.len() >= nk * 2, "qb16_pre too small");
21441            }
21442            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
21443        }
21444        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
21445        let k2w = if gdn_wgmma_pre {
21446            Some((
21447                *qb16_ref0.as_ref().unwrap(),
21448                *kb16_ref0.as_ref().unwrap(),
21449                pb16.as_mut().unwrap(),
21450            ))
21451        } else {
21452            None
21453        };
21454        let (gcum, p, u, w) =
21455            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
21456        let _ = &w;
21457        let mut y = self.uninit(nc * h * c * D)?;
21458        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
21459        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
21460        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
21461        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
21462        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
21463        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
21464        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
21465        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
21466        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
21467        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
21468        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
21469        let gdn_mma = !portable_mma_gated()
21470            && c == 32
21471            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
21472                Ok("1") => true,
21473                Ok("0") => false,
21474                _ => cfg!(memra_hopper_mma),
21475            };
21476        if gdn_mma {
21477            let wb16 = wb16_pre
21478                .take()
21479                .expect("mma path pre-allocates wb16 (K3 store fold)");
21480            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
21481            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
21482            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
21483            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
21484            // pass runs inside the persistent-M kernel; Y and Ssnap are never
21485            // materialized. New numeric class (gk folds into k^T instead of ys) —
21486            // explicit opt-in until the state-carry battery promotes it. Env read per
21487            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
21488            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
21489            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
21490            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
21491            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
21492            if gdn_wgmma_pre {
21493                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
21494                let qb16 = qb16_ref0.unwrap();
21495                let pb16 = pb16.as_ref().unwrap();
21496                {
21497                    let f = self.func("gdn_k45_wgmma");
21498                    let cfg = LaunchConfig {
21499                        grid_dim: (h as u32, 4, 1),
21500                        block_dim: (256, 1, 1),
21501                        shared_mem_bytes: 0,
21502                    };
21503                    let hki = hk as i32;
21504                    let __s_b = self.gpu.stream();
21505                    let mut b = __s_b.launch_builder(&f);
21506                    b.arg(kb16_ref)
21507                        .arg(&gcum)
21508                        .arg(beta)
21509                        .arg(&u)
21510                        .arg(&wb16)
21511                        .arg(qb16)
21512                        .arg(pb16)
21513                        .arg(o)
21514                        .arg(&scale)
21515                        .arg(state_in)
21516                        .arg(&mut *state_out)
21517                        .arg(&hi)
21518                        .arg(&ti)
21519                        .arg(&ci)
21520                        .arg(&hki);
21521                    unsafe {
21522                        b.launch(cfg)?;
21523                    }
21524                }
21525                return Ok(());
21526            }
21527            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
21528            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
21529            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
21530            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
21531            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
21532            {
21533                let f = self.func("gdn_chunk_state_mma");
21534                let cfg = LaunchConfig {
21535                    grid_dim: (h as u32, NSPLIT, 1),
21536                    block_dim: (256, 1, 1),
21537                    shared_mem_bytes: 0,
21538                };
21539                let hki = hk as i32;
21540                let __s_b = self.gpu.stream();
21541                let mut b = __s_b.launch_builder(&f);
21542                b.arg(kb16_ref)
21543                    .arg(&gcum)
21544                    .arg(beta)
21545                    .arg(&u)
21546                    .arg(&wb16)
21547                    .arg(&mut y16)
21548                    .arg(&mut ssnap16)
21549                    .arg(state_in)
21550                    .arg(&mut *state_out)
21551                    .arg(&hi)
21552                    .arg(&ti)
21553                    .arg(&ci)
21554                    .arg(&hki);
21555                unsafe {
21556                    b.launch(cfg)?;
21557                }
21558            }
21559            {
21560                // K5-mma (bf16 St/Y consumers)
21561                let f = self.func("gdn_chunk_output_mma");
21562                let jt = ((c + 31) / 32) as u32;
21563                let cfg = LaunchConfig {
21564                    grid_dim: (nc as u32, h as u32, jt),
21565                    block_dim: (256, 1, 1),
21566                    shared_mem_bytes: 0,
21567                };
21568                let hki = hk as i32;
21569                let __s_b = self.gpu.stream();
21570                let mut b = __s_b.launch_builder(&f);
21571                b.arg(q)
21572                    .arg(&gcum)
21573                    .arg(&p)
21574                    .arg(&y16)
21575                    .arg(&ssnap16)
21576                    .arg(o)
21577                    .arg(&hi)
21578                    .arg(&ti)
21579                    .arg(&ci)
21580                    .arg(&scale)
21581                    .arg(&hki);
21582                unsafe {
21583                    b.launch(cfg)?;
21584                }
21585            }
21586            return Ok(());
21587        }
21588        {
21589            // K4 (sequential over chunks inside; blocks col-partition the state)
21590            let f = self.func("gdn_chunk_state_f32");
21591            let cfg = LaunchConfig {
21592                grid_dim: (h as u32, NSPLIT, 1),
21593                block_dim: (256, 1, 1),
21594                shared_mem_bytes: 0,
21595            };
21596            let __s_b = self.gpu.stream();
21597            let mut b = __s_b.launch_builder(&f);
21598            b.arg(k)
21599                .arg(&gcum)
21600                .arg(beta)
21601                .arg(&u)
21602                .arg(&w)
21603                .arg(&mut y)
21604                .arg(&mut ssnap)
21605                .arg(state_in)
21606                .arg(&mut *state_out)
21607                .arg(&hi)
21608                .arg(&ti)
21609                .arg(&ci);
21610            unsafe {
21611                b.launch(cfg)?;
21612            }
21613        }
21614        {
21615            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
21616            let f = self.func("gdn_chunk_output_f32");
21617            let jt = ((c + 31) / 32) as u32;
21618            let cfg = LaunchConfig {
21619                grid_dim: (nc as u32, h as u32, jt),
21620                block_dim: (256, 1, 1),
21621                shared_mem_bytes: 0,
21622            };
21623            let __s_b = self.gpu.stream();
21624            let mut b = __s_b.launch_builder(&f);
21625            b.arg(q)
21626                .arg(&gcum)
21627                .arg(&p)
21628                .arg(&y)
21629                .arg(&ssnap)
21630                .arg(o)
21631                .arg(&hi)
21632                .arg(&ti)
21633                .arg(&ci)
21634                .arg(&scale);
21635            unsafe {
21636                b.launch(cfg)?;
21637            }
21638        }
21639        Ok(())
21640    }
21641
21642    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
21643    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
21644    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
21645    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
21646    ///
21647    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
21648    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
21649    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
21650    #[allow(clippy::too_many_arguments)]
21651    #[allow(clippy::too_many_arguments)]
21652    pub fn gdn_scan_prefill(
21653        &self,
21654        q: &CudaSlice<f32>,
21655        k: &CudaSlice<f32>,
21656        v: &CudaSlice<f32>,
21657        g: &CudaSlice<f32>,
21658        beta: &CudaSlice<f32>,
21659        kb16_pre: Option<&CudaSlice<u8>>,
21660        qb16_pre: Option<&CudaSlice<u8>>,
21661        state_in: &CudaSlice<f32>,
21662        state_out: &mut CudaSlice<f32>,
21663        o: &mut CudaSlice<f32>,
21664        n_head: usize,
21665        t: usize,
21666        scale: f32,
21667        hk: usize,
21668    ) -> Result<(), Box<dyn std::error::Error>> {
21669        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
21670            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
21671            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
21672        }
21673        if Self::gdn_chunked_enabled() && t >= 16 {
21674            self.gdn_scan_chunked(
21675                q,
21676                k,
21677                v,
21678                g,
21679                beta,
21680                kb16_pre,
21681                qb16_pre,
21682                state_in,
21683                state_out,
21684                o,
21685                n_head,
21686                t,
21687                scale,
21688                Self::gdn_chunk_size(),
21689                hk,
21690            )
21691        } else {
21692            assert!(
21693                hk == n_head,
21694                "s128 scan is broadcast-only (prep guarantees by predicate)"
21695            );
21696            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
21697        }
21698    }
21699
21700    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
21701    #[allow(clippy::too_many_arguments)]
21702    fn gdn_scan_diff(
21703        &self,
21704        q: &CudaSlice<f32>,
21705        k: &CudaSlice<f32>,
21706        v: &CudaSlice<f32>,
21707        g: &CudaSlice<f32>,
21708        beta: &CudaSlice<f32>,
21709        state_in: &CudaSlice<f32>,
21710        state_out: &mut CudaSlice<f32>,
21711        o: &mut CudaSlice<f32>,
21712        n_head: usize,
21713        t: usize,
21714        scale: f32,
21715    ) -> Result<(), Box<dyn std::error::Error>> {
21716        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
21717        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
21718        let mut o_c = self.uninit(o.len())?;
21719        let mut st_c = self.uninit(state_out.len())?;
21720        self.gdn_scan_chunked(
21721            q,
21722            k,
21723            v,
21724            g,
21725            beta,
21726            None,
21727            None,
21728            state_in,
21729            &mut st_c,
21730            &mut o_c,
21731            n_head,
21732            t,
21733            scale,
21734            Self::gdn_chunk_size(),
21735            n_head,
21736        )?;
21737        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
21738        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
21739        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
21740        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
21741            let mut max_abs = 0f32;
21742            let mut max_rel = 0f32;
21743            let mut sum_rel = 0f64;
21744            for (x, y) in a.iter().zip(b) {
21745                let ad = (x - y).abs();
21746                let rel = ad / x.abs().max(y.abs()).max(1e-3);
21747                if ad > max_abs {
21748                    max_abs = ad;
21749                }
21750                if rel > max_rel {
21751                    max_rel = rel;
21752                }
21753                sum_rel += rel as f64;
21754            }
21755            (max_abs, max_rel, sum_rel / a.len() as f64)
21756        };
21757        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
21758        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
21759        println!(
21760            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
21761                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
21762            Self::gdn_chunk_size()
21763        );
21764        Ok(())
21765    }
21766
21767    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
21768    pub fn gdn_glog(
21769        &self,
21770        alpha: &CudaSlice<f32>,
21771        dt_bias: &CudaSlice<f32>,
21772        a: &CudaSlice<f32>,
21773        g_log: &mut CudaSlice<f32>,
21774        n_head: usize,
21775        t: usize,
21776    ) -> Result<(), Box<dyn std::error::Error>> {
21777        let f = self.func("gdn_glog_f32");
21778        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21779        let (h, ti) = (n_head as i32, t as i32);
21780        let __s_b = self.gpu.stream();
21781        let mut b = __s_b.launch_builder(&f);
21782        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21783        unsafe {
21784            b.launch(cfg)?;
21785        }
21786        Ok(())
21787    }
21788
21789    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
21790    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
21791    pub fn sigmoid_v(
21792        &self,
21793        x: &cudarc::driver::CudaView<f32>,
21794        y: &mut CudaSlice<f32>,
21795        n: usize,
21796    ) -> Result<(), Box<dyn std::error::Error>> {
21797        let f = self.func("sigmoid_f32");
21798        let cfg = LaunchConfig::for_num_elems(n as u32);
21799        let ni = n as i32;
21800        let __s_b = self.gpu.stream();
21801        let mut b = __s_b.launch_builder(&f);
21802        b.arg(x).arg(y).arg(&ni);
21803        unsafe {
21804            b.launch(cfg)?;
21805        }
21806        Ok(())
21807    }
21808
21809    pub fn gdn_glog_v(
21810        &self,
21811        alpha: &cudarc::driver::CudaView<f32>,
21812        dt_bias: &CudaSlice<f32>,
21813        a: &CudaSlice<f32>,
21814        g_log: &mut CudaSlice<f32>,
21815        n_head: usize,
21816        t: usize,
21817    ) -> Result<(), Box<dyn std::error::Error>> {
21818        let f = self.func("gdn_glog_f32");
21819        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
21820        let (h, ti) = (n_head as i32, t as i32);
21821        let __s_b = self.gpu.stream();
21822        let mut b = __s_b.launch_builder(&f);
21823        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
21824        unsafe {
21825            b.launch(cfg)?;
21826        }
21827        Ok(())
21828    }
21829
21830    pub fn sigmoid(
21831        &self,
21832        x: &CudaSlice<f32>,
21833        y: &mut CudaSlice<f32>,
21834        n: usize,
21835    ) -> Result<(), Box<dyn std::error::Error>> {
21836        let f = self.func("sigmoid_f32");
21837        let cfg = LaunchConfig::for_num_elems(n as u32);
21838        let ni = n as i32;
21839        let __s_b = self.gpu.stream();
21840        let mut b = __s_b.launch_builder(&f);
21841        b.arg(x).arg(y).arg(&ni);
21842        unsafe {
21843            b.launch(cfg)?;
21844        }
21845        Ok(())
21846    }
21847
21848    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
21849    /// (replaces sigmoid + mul + convert). Bit-identical class.
21850    pub fn sig_mul_f16out(
21851        &self,
21852        a: &CudaSlice<f32>,
21853        g: &CudaSlice<f32>,
21854        dst: &mut CudaSlice<f32>,
21855        dst16: &mut CudaSlice<u8>,
21856        n: usize,
21857    ) -> Result<(), Box<dyn std::error::Error>> {
21858        let f = self.func("sig_mul_f16out_f32");
21859        let cfg = LaunchConfig::for_num_elems(n as u32);
21860        let ni = n as i32;
21861        let __s_b = self.gpu.stream();
21862        let mut b = __s_b.launch_builder(&f);
21863        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
21864        unsafe {
21865            b.launch(cfg)?;
21866        }
21867        Ok(())
21868    }
21869
21870    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
21871    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
21872    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
21873    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
21874    ///
21875    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
21876    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
21877    /// applies the wrong number of distinct gate values.
21878    #[allow(clippy::too_many_arguments)]
21879    pub fn attn_head_gate(
21880        &self,
21881        a: &CudaSlice<f32>,
21882        g: &CudaSlice<f32>,
21883        dst: &mut CudaSlice<f32>,
21884        dst16: Option<&mut CudaSlice<u8>>,
21885        head_dim: usize,
21886        n_head: usize,
21887        t: usize,
21888    ) -> Result<(), Box<dyn std::error::Error>> {
21889        let f = self.func("attn_head_gate_f32");
21890        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21891        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21892        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
21893        let d16: u64 = match dst16 {
21894            Some(d) => self.addr_u8(d),
21895            None => 0,
21896        };
21897        let __s_b = self.gpu.stream();
21898        let mut b = __s_b.launch_builder(&f);
21899        b.arg(a)
21900            .arg(g)
21901            .arg(dst)
21902            .arg(&d16)
21903            .arg(&hd)
21904            .arg(&nh)
21905            .arg(&ti);
21906        unsafe {
21907            b.launch(cfg)?;
21908        }
21909        Ok(())
21910    }
21911
21912    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
21913    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
21914    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
21915    ///
21916    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
21917    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
21918    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
21919    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
21920    #[allow(clippy::too_many_arguments)]
21921    pub fn swiglu_clamped_mul_scaled(
21922        &self,
21923        gate: &CudaSlice<f32>,
21924        up: &CudaSlice<f32>,
21925        gs: f32,
21926        us: f32,
21927        limit: f32,
21928        dst: &mut CudaSlice<f32>,
21929        n: usize,
21930    ) -> Result<(), Box<dyn std::error::Error>> {
21931        debug_assert!(
21932            limit > 1e-6,
21933            "swiglu_clamped needs a live limit; use silu_mul_scaled"
21934        );
21935        let f = self.func("swiglu_clamped_mul_scaled_f32");
21936        let cfg = LaunchConfig::for_num_elems(n as u32);
21937        let ni = n as i32;
21938        let __s_b = self.gpu.stream();
21939        let mut b = __s_b.launch_builder(&f);
21940        b.arg(gate)
21941            .arg(up)
21942            .arg(&gs)
21943            .arg(&us)
21944            .arg(&limit)
21945            .arg(dst)
21946            .arg(&ni);
21947        unsafe {
21948            b.launch(cfg)?;
21949        }
21950        Ok(())
21951    }
21952
21953    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
21954    pub fn gated_rmsnorm(
21955        &self,
21956        o: &CudaSlice<f32>,
21957        w: &CudaSlice<f32>,
21958        z: &CudaSlice<f32>,
21959        dst: &mut CudaSlice<f32>,
21960        ncols: usize,
21961        nrows: usize,
21962        eps: f32,
21963    ) -> Result<(), Box<dyn std::error::Error>> {
21964        let f = self.func("gated_rmsnorm_f32");
21965        let cfg = LaunchConfig {
21966            grid_dim: (nrows as u32, 1, 1),
21967            block_dim: (128, 1, 1),
21968            shared_mem_bytes: 0,
21969        };
21970        let (nc, e) = (ncols as i32, eps);
21971        let __s_b = self.gpu.stream();
21972        let mut b = __s_b.launch_builder(&f);
21973        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
21974        unsafe {
21975            b.launch(cfg)?;
21976        }
21977        Ok(())
21978    }
21979
21980    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
21981    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
21982    pub fn gated_rmsnorm_f16out(
21983        &self,
21984        o: &CudaSlice<f32>,
21985        w: &CudaSlice<f32>,
21986        z: &CudaSlice<f32>,
21987        dst: &mut CudaSlice<f32>,
21988        dst16: &mut CudaSlice<u8>,
21989        ncols: usize,
21990        nrows: usize,
21991        eps: f32,
21992    ) -> Result<(), Box<dyn std::error::Error>> {
21993        let f = self.func("gated_rmsnorm_f16out_f32");
21994        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21995        let cfg = LaunchConfig {
21996            grid_dim: (nrows as u32, 1, 1),
21997            block_dim: (128, 1, 1),
21998            shared_mem_bytes: 0,
21999        };
22000        let (nc, e) = (ncols as i32, eps);
22001        let __s_b = self.gpu.stream();
22002        let mut b = __s_b.launch_builder(&f);
22003        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22004        unsafe {
22005            b.launch(cfg)?;
22006        }
22007        Ok(())
22008    }
22009
22010    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
22011    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
22012    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
22013    #[allow(clippy::too_many_arguments)]
22014    pub fn add_rms_norm_zq8(
22015        &self,
22016        a: &CudaSlice<f32>,
22017        b_in: &CudaSlice<f32>,
22018        w: &CudaSlice<f32>,
22019        res: &mut CudaSlice<f32>,
22020        z: &mut CudaSlice<f32>,
22021        ncols: usize,
22022        nrows: usize,
22023        eps: f32,
22024    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22025        assert!(ncols % 32 == 0);
22026        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
22027        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22028        let f = self.func("add_rms_norm_zq8");
22029        let cfg = LaunchConfig {
22030            grid_dim: (nrows as u32, 1, 1),
22031            block_dim: (1024, 1, 1),
22032            shared_mem_bytes: 0,
22033        };
22034        let (nc, ep) = (ncols as i32, eps);
22035        let __s_b = self.gpu.stream();
22036        let mut b = __s_b.launch_builder(&f);
22037        b.arg(a)
22038            .arg(b_in)
22039            .arg(w)
22040            .arg(res)
22041            .arg(z)
22042            .arg(&mut q)
22043            .arg(&mut d)
22044            .arg(&nc)
22045            .arg(&ep);
22046        unsafe {
22047            b.launch(cfg)?;
22048        }
22049        Ok((q, d))
22050    }
22051
22052    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
22053    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
22054    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
22055    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
22056    pub fn gated_rmsnorm_zv(
22057        &self,
22058        o: &CudaSlice<f32>,
22059        w: &CudaSlice<f32>,
22060        z: &cudarc::driver::CudaView<f32>,
22061        dst: &mut CudaSlice<f32>,
22062        ncols: usize,
22063        nrows: usize,
22064        eps: f32,
22065    ) -> Result<(), Box<dyn std::error::Error>> {
22066        let f = self.func("gated_rmsnorm_f32");
22067        let cfg = LaunchConfig {
22068            grid_dim: (nrows as u32, 1, 1),
22069            block_dim: (128, 1, 1),
22070            shared_mem_bytes: 0,
22071        };
22072        let (nc, e) = (ncols as i32, eps);
22073        let __s_b = self.gpu.stream();
22074        let mut b = __s_b.launch_builder(&f);
22075        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
22076        unsafe {
22077            b.launch(cfg)?;
22078        }
22079        Ok(())
22080    }
22081
22082    pub fn gated_rmsnorm_f16out_zv(
22083        &self,
22084        o: &CudaSlice<f32>,
22085        w: &CudaSlice<f32>,
22086        z: &cudarc::driver::CudaView<f32>,
22087        dst: &mut CudaSlice<f32>,
22088        dst16: &mut CudaSlice<u8>,
22089        ncols: usize,
22090        nrows: usize,
22091        eps: f32,
22092    ) -> Result<(), Box<dyn std::error::Error>> {
22093        let f = self.func("gated_rmsnorm_f16out_f32");
22094        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
22095        let cfg = LaunchConfig {
22096            grid_dim: (nrows as u32, 1, 1),
22097            block_dim: (128, 1, 1),
22098            shared_mem_bytes: 0,
22099        };
22100        let (nc, e) = (ncols as i32, eps);
22101        let __s_b = self.gpu.stream();
22102        let mut b = __s_b.launch_builder(&f);
22103        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
22104        unsafe {
22105            b.launch(cfg)?;
22106        }
22107        Ok(())
22108    }
22109
22110    pub fn gated_rmsnorm_q8_1(
22111        &self,
22112        o: &CudaSlice<f32>,
22113        w: &CudaSlice<f32>,
22114        z: &CudaSlice<f32>,
22115        ncols: usize,
22116        nrows: usize,
22117        eps: f32,
22118    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
22119        assert!(ncols % 32 == 0);
22120        let f = self.func("gated_rmsnorm_q8_1");
22121        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
22122        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
22123        let cfg = LaunchConfig {
22124            grid_dim: (nrows as u32, 1, 1),
22125            block_dim: (128, 1, 1),
22126            shared_mem_bytes: 0,
22127        };
22128        let (nc, ep) = (ncols as i32, eps);
22129        let __s_b = self.gpu.stream();
22130        let mut b = __s_b.launch_builder(&f);
22131        b.arg(o)
22132            .arg(w)
22133            .arg(z)
22134            .arg(&mut out_q)
22135            .arg(&mut out_d)
22136            .arg(&nc)
22137            .arg(&ep);
22138        unsafe {
22139            b.launch(cfg)?;
22140        }
22141        Ok((out_q, out_d))
22142    }
22143
22144    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
22145    pub fn transpose(
22146        &self,
22147        inp: &CudaSlice<f32>,
22148        rows: usize,
22149        cols: usize,
22150    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22151        let f = self.func("transpose_f32");
22152        let mut out = self.zeros(rows * cols)?;
22153        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
22154        let (r, c) = (rows as i32, cols as i32);
22155        let __s_b = self.gpu.stream();
22156        let mut b = __s_b.launch_builder(&f);
22157        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
22158        unsafe {
22159            b.launch(cfg)?;
22160        }
22161        Ok(out)
22162    }
22163
22164    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
22165    pub fn repeat_heads(
22166        &self,
22167        inp: &CudaSlice<f32>,
22168        out: &mut CudaSlice<f32>,
22169        head_dim: usize,
22170        n_in: usize,
22171        n_out: usize,
22172        t: usize,
22173    ) -> Result<(), Box<dyn std::error::Error>> {
22174        let f = self.func("repeat_heads_f32");
22175        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
22176        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
22177        let __s_b = self.gpu.stream();
22178        let mut b = __s_b.launch_builder(&f);
22179        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
22180        unsafe {
22181            b.launch(cfg)?;
22182        }
22183        Ok(())
22184    }
22185
22186    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
22187    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
22188    ///
22189    /// Bounds-checked against `qf` before the launch: the kernel reads `2*head_dim*n_head*t`
22190    /// floats, and running it on a `wq` output that carries no fused gate reads 2x off the end
22191    /// (silently, on the device). A layout mismatch is a typed `FusedQGateExtent` here instead.
22192    pub fn q_gate_split(
22193        &self,
22194        qf: &CudaSlice<f32>,
22195        q_out: &mut CudaSlice<f32>,
22196        gate_out: &mut CudaSlice<f32>,
22197        head_dim: usize,
22198        n_head: usize,
22199        t: usize,
22200    ) -> Result<(), Box<dyn std::error::Error>> {
22201        memra_gguf::config::check_fused_q_gate_extent(qf.len(), head_dim, n_head, t)?;
22202        let out_need = head_dim * n_head * t;
22203        if q_out.len() < out_need || gate_out.len() < out_need {
22204            return Err(format!(
22205                "q_gate_split destinations too small: need {out_need} each, have q={} gate={}",
22206                q_out.len(),
22207                gate_out.len()
22208            )
22209            .into());
22210        }
22211        let f = self.func("q_gate_split_f32");
22212        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
22213        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
22214        let __s_b = self.gpu.stream();
22215        let mut b = __s_b.launch_builder(&f);
22216        b.arg(qf)
22217            .arg(q_out)
22218            .arg(gate_out)
22219            .arg(&hd)
22220            .arg(&nh)
22221            .arg(&ti);
22222        unsafe {
22223            b.launch(cfg)?;
22224        }
22225        Ok(())
22226    }
22227
22228    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
22229    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
22230    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
22231    pub fn qkv_to_gdn_repack(
22232        &self,
22233        conv_out: &CudaSlice<f32>,
22234        q_g: &mut CudaSlice<f32>,
22235        k_g: &mut CudaSlice<f32>,
22236        v_g: &mut CudaSlice<f32>,
22237        d_state: usize,
22238        num_v: usize,
22239        num_k: usize,
22240        key_dim: usize,
22241        t: usize,
22242    ) -> Result<(), Box<dyn std::error::Error>> {
22243        let f = self.func("qkv_to_gdn_repack_f32");
22244        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
22245        let (ds, nv, nk, kd, ti) = (
22246            d_state as i32,
22247            num_v as i32,
22248            num_k as i32,
22249            key_dim as i32,
22250            t as i32,
22251        );
22252        let __s_b = self.gpu.stream();
22253        let mut b = __s_b.launch_builder(&f);
22254        b.arg(conv_out)
22255            .arg(q_g)
22256            .arg(k_g)
22257            .arg(v_g)
22258            .arg(&ds)
22259            .arg(&nv)
22260            .arg(&nk)
22261            .arg(&kd)
22262            .arg(&ti);
22263        unsafe {
22264            b.launch(cfg)?;
22265        }
22266        Ok(())
22267    }
22268
22269    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
22270    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
22271    pub fn conv_left_pad(
22272        &self,
22273        src: &CudaSlice<f32>,
22274        dst: &mut CudaSlice<f32>,
22275        conv_dim: usize,
22276        t: usize,
22277        pad: usize,
22278    ) -> Result<(), Box<dyn std::error::Error>> {
22279        let f = self.func("conv_left_pad_f32");
22280        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
22281        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
22282        let __s_b = self.gpu.stream();
22283        let mut b = __s_b.launch_builder(&f);
22284        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
22285        unsafe {
22286            b.launch(cfg)?;
22287        }
22288        Ok(())
22289    }
22290
22291    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
22292    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
22293    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
22294    pub fn conv_assemble_and_roll(
22295        &self,
22296        qkv_col: &CudaSlice<f32>,
22297        conv_state: &mut CudaSlice<f32>,
22298        conv_in: &mut CudaSlice<f32>,
22299        conv_dim: usize,
22300        pad: usize,
22301    ) -> Result<(), Box<dyn std::error::Error>> {
22302        let f = self.func("conv_assemble_and_roll_f32");
22303        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22304        let (cd, p) = (conv_dim as i32, pad as i32);
22305        let __s_b = self.gpu.stream();
22306        let mut b = __s_b.launch_builder(&f);
22307        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
22308        unsafe {
22309            b.launch(cfg)?;
22310        }
22311        Ok(())
22312    }
22313
22314    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
22315    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
22316    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
22317    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
22318    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
22319    pub fn ssm_conv1d_fused_decode(
22320        &self,
22321        qkv_col: &CudaSlice<f32>,
22322        conv_state: &mut CudaSlice<f32>,
22323        w: &CudaSlice<f32>,
22324        conv_out: &mut CudaSlice<f32>,
22325        conv_dim: usize,
22326        d_conv: usize,
22327    ) -> Result<(), Box<dyn std::error::Error>> {
22328        let f = self.func("ssm_conv1d_fused_decode_f32");
22329        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
22330        let (cd, dc) = (conv_dim as i32, d_conv as i32);
22331        let __s_b = self.gpu.stream();
22332        let mut b = __s_b.launch_builder(&f);
22333        b.arg(qkv_col)
22334            .arg(conv_state)
22335            .arg(w)
22336            .arg(conv_out)
22337            .arg(&cd)
22338            .arg(&dc);
22339        unsafe {
22340            b.launch(cfg)?;
22341        }
22342        Ok(())
22343    }
22344
22345    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
22346    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
22347    pub fn slice_range(
22348        &self,
22349        src: &CudaSlice<f32>,
22350        start: usize,
22351        len: usize,
22352    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22353        let host = self.gpu.stream().clone_dtoh(src)?;
22354        self.gpu.stream().synchronize()?;
22355        Ok(self.htod(&host[start..start + len])?)
22356    }
22357}
22358
22359#[cfg(test)]
22360mod target_dispatch_tests {
22361    use super::legacy_quant_gemm_allowed;
22362
22363    #[test]
22364    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
22365        // sm_120a native lane
22366        assert!(legacy_quant_gemm_allowed(false, false, false));
22367        assert!(!legacy_quant_gemm_allowed(false, false, true));
22368        // pure portable lane (sm_89): gated
22369        assert!(!legacy_quant_gemm_allowed(true, false, false));
22370        assert!(!legacy_quant_gemm_allowed(true, false, true));
22371        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
22372        assert!(legacy_quant_gemm_allowed(true, true, false));
22373        assert!(!legacy_quant_gemm_allowed(true, true, true));
22374    }
22375
22376    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
22377    #[test]
22378    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
22379        assert!(!legacy_quant_gemm_allowed(
22380            cfg!(memra_portable_cuda),
22381            cfg!(memra_hopper_mma),
22382            false
22383        ));
22384    }
22385
22386    #[cfg(memra_hopper_mma)]
22387    #[test]
22388    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
22389        assert!(legacy_quant_gemm_allowed(
22390            cfg!(memra_portable_cuda),
22391            cfg!(memra_hopper_mma),
22392            false
22393        ));
22394        assert!(super::portable_mma_gated() == false);
22395    }
22396}
22397
22398/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
22399/// inherent methods (inherent methods win name resolution, so no recursion).
22400impl memra_kv::KvDev for Engine {
22401    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22402        Engine::zeros(self, n)
22403    }
22404    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22405        Engine::uninit(self, n)
22406    }
22407    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
22408        Engine::alloc_u8(self, n)
22409    }
22410    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
22411        Engine::htod_i32(self, v)
22412    }
22413    fn clone_dtod(
22414        &self,
22415        src: &CudaSlice<f32>,
22416    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
22417        Engine::clone_dtod(self, src)
22418    }
22419    fn copy_into(
22420        &self,
22421        dst: &mut CudaSlice<f32>,
22422        off: usize,
22423        src: &CudaSlice<f32>,
22424        len: usize,
22425    ) -> Result<(), Box<dyn std::error::Error>> {
22426        Engine::copy_into(self, dst, off, src, len)
22427    }
22428    fn set_i32_one(
22429        &self,
22430        d: &mut CudaSlice<i32>,
22431        v: i32,
22432    ) -> Result<(), Box<dyn std::error::Error>> {
22433        Engine::set_i32_one(self, d, v)
22434    }
22435}
22436
22437#[cfg(test)]
22438mod fused_gate_bounds_tests {
22439    use super::*;
22440
22441    /// The fused `[q|gate]` split's read-site guard, on the device.
22442    ///
22443    /// `q_gate_split_f32` reads `2*head_dim*n_head*T` floats out of `qf`. A checkpoint whose gate
22444    /// is a SEPARATE tensor produces a `wq` output of exactly half that, so before 2026-08-19 the
22445    /// kernel launched and read 2x past the end of the allocation — an out-of-bounds DEVICE read:
22446    /// no panic, no error, just whatever memory follows. The guard turns it into a typed
22447    /// `FusedQGateExtent` before the launch.
22448    ///
22449    /// Catch demonstration for this test (guard temporarily removed, then restored):
22450    /// `compute-sanitizer --tool memcheck` on the half-width case reported invalid `__global__`
22451    /// reads of size 4 in `q_gate_split_f32`; with the guard in place the same run is clean and
22452    /// the call returns `Err`. Receipt in the lane report.
22453    #[test]
22454    #[ignore = "requires a CUDA GPU"]
22455    fn q_gate_split_refuses_a_separate_gate_wq_instead_of_reading_past_it() {
22456        let e = Engine::new(0).unwrap();
22457        let (head_dim, n_head, t) = (8usize, 4usize, 2usize);
22458        let fused = 2 * head_dim * n_head * t;
22459        let out_n = head_dim * n_head * t;
22460
22461        // half-width `qf` = the separate-gate / ungated layout. MUST be refused.
22462        let narrow = e.htod(&vec![1.0f32; out_n]).unwrap();
22463        let mut q = e.uninit(out_n).unwrap();
22464        let mut gate = e.uninit(out_n).unwrap();
22465        let err = e
22466            .q_gate_split(&narrow, &mut q, &mut gate, head_dim, n_head, t)
22467            .expect_err("half-width wq must be refused, not read past")
22468            .to_string();
22469        assert!(err.contains("NO fused gate"), "{err}");
22470        assert!(err.contains(&format!("{fused}")), "{err}");
22471
22472        // full-width `qf` = a real qwen3.5 fused layout. MUST still run, and split correctly:
22473        // per head hh the block is [q(head_dim) | gate(head_dim)] at stride 2*head_dim.
22474        let host: Vec<f32> = (0..fused).map(|i| i as f32).collect();
22475        let wide = e.htod(&host).unwrap();
22476        e.q_gate_split(&wide, &mut q, &mut gate, head_dim, n_head, t)
22477            .expect("full-width wq splits");
22478        let (qh, gh) = (e.dtoh(&q).unwrap(), e.dtoh(&gate).unwrap());
22479        for tok in 0..t {
22480            for hh in 0..n_head {
22481                for d in 0..head_dim {
22482                    let base = tok * (n_head * 2 * head_dim) + hh * (2 * head_dim);
22483                    let idx = tok * (n_head * head_dim) + hh * head_dim + d;
22484                    assert_eq!(qh[idx], host[base + d], "q t{tok} h{hh} d{d}");
22485                    assert_eq!(gh[idx], host[base + head_dim + d], "gate t{tok} h{hh} d{d}");
22486                }
22487            }
22488        }
22489
22490        // undersized destinations are refused too (the other half of the extent contract)
22491        let mut small = e.uninit(out_n - 1).unwrap();
22492        assert!(
22493            e.q_gate_split(&wide, &mut small, &mut gate, head_dim, n_head, t)
22494                .is_err()
22495        );
22496    }
22497}
22498
22499/// FULL-WIDTH-ROPE CONTRACT on the fused rms_norm+qkv+rope kernels
22500/// (lane/graph-s-key-exactness-20260819, probe O-10 follow-up). CPU-only: the guard runs before
22501/// any launch, so the refusal is testable without a device.
22502#[cfg(test)]
22503mod fused_rope_width_tests {
22504    use super::Engine;
22505
22506    /// gemma-4: rotary width == head width on both classes (GGUF 256/256 and 512/512, and the
22507    /// safetensors route derives the same), which is why the fusion is legal there today.
22508    #[test]
22509    fn full_width_is_accepted() {
22510        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 256).is_ok());
22511        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_cat", 512, 512).is_ok());
22512        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append", 128, 128).is_ok());
22513    }
22514
22515    /// The widths the gemma-4 31B OFFICIAL artifact declares, read from its own GGUF header
22516    /// (`gemma-4-31B-it-official-Q8_0-MTP.gguf`, box3, 2026-08-19):
22517    ///
22518    /// ```text
22519    /// attention.key_length     512   rope.dimension_count     512   (global class)
22520    /// attention.key_length_swa 256   rope.dimension_count_swa 256   (SWA class)
22521    /// ```
22522    ///
22523    /// Both classes satisfy `n_rot == head_dim`, which is why the fusion is legal for gemma and
22524    /// why `HybridModel::gemma4_rope_dims` can feed this guard without refusing what we serve.
22525    /// An artifact that ever declares otherwise gets a loud refusal at the first fused launch
22526    /// instead of a silently over-rotated head.
22527    #[test]
22528    fn gemma4_official_artifact_widths_pass() {
22529        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 512, 512).is_ok());
22530        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 256, 256).is_ok());
22531    }
22532
22533    /// The bug this guard exists to make impossible: a partial-rotary arch fused onto a kernel
22534    /// with no `n_dims`, silently rotating the pass-through band.
22535    #[test]
22536    fn partial_rotary_is_refused_with_the_geometry_named() {
22537        // qwen3.5: n_rot 64 of head_dim 256 (the shape probe O-10 pinned in the split path).
22538        let err = Engine::full_width_rope_only("rms_norm_qkv_rope", 64, 256)
22539            .expect_err("partial rotary must refuse");
22540        let msg = err.to_string();
22541        assert!(msg.contains("PARTIAL ROTARY REFUSED"), "{msg}");
22542        assert!(msg.contains("n_rot 64"), "{msg}");
22543        assert!(msg.contains("head_dim 256"), "{msg}");
22544        assert!(
22545            msg.contains("64..256"),
22546            "names the band it would corrupt: {msg}"
22547        );
22548        // step35 full-attn: 64 of 128 (upstream halves n_rot_full).
22549        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope_append_dc", 64, 128).is_err());
22550        // and the reverse mismatch (a wider rope than the head) is not "close enough" either.
22551        assert!(Engine::full_width_rope_only("rms_norm_qkv_rope", 256, 128).is_err());
22552    }
22553}