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;
31/// The dual cache lives in the shared `memra-kv` crate (Phase D extraction); this
32/// re-export keeps every `crate::cache::` / `memra_engine::cache::` path unchanged.
33pub mod cache {
34    pub use memra_kv::*;
35}
36pub mod decode;
37pub mod decode_batch;
38pub mod dflash;
39pub mod eagle;
40pub mod gemma_spec;
41pub mod graph_update;
42/// MLA (multi-head latent attention) CPU f32 reference — GLM-5.2 bring-up lane increment 1.
43/// Naive vs absorbed decode forms + NORM/NEOX rope permutation, unit-tested; the permanent
44/// oracle for the MLA kernel family (`research/mla-bringup-20260801/DESIGN.md`). No CUDA deps.
45pub mod mla;
46pub mod moesd;
47pub mod pp;
48pub mod round_stream;
49pub mod spec;
50pub use memra_sampling as sampler;
51
52/// In-house MoE router GEMV on the spec-verify small-t path (DEFAULT ON since 2026-07-10:
53/// battery green on 35B p2/p3 K=1..8, acceptance bit-identical, +2-4% spec e2e — replaces
54/// ~240 per-column cuBLAS gemv launches/round). MEMRA_ROUTER_KERNEL=0 is the rollback seam.
55/// MoE grouped f16 GEMM door (experimental until gated), f16-mirror numeric class:
56/// per-layer expert dequant to f16 + one grouped f16 GEMM over the CSR groups.
57///   MEMRA_MOE_F16G=1  cublasGemmGroupedBatchedEx (round 46 arc 2). The grouped API issues
58///                     through cublas-internal streams NOT ordered with ours — v1 pays a full
59///                     stream sync per projection (round-47 ledgered defect).
60///   MEMRA_MOE_F16G=2  single-kernel grouped GEMM on the engine stream (round 49): ordered by
61///                     construction, zero syncs, f32 C with the act row-scale folded in.
62/// DEFAULT (2026-08-01, round 49 promotion): mode 1 on the Hopper lane — with the 41/41
63/// dequant coverage fix the q35 board-2048 prime measured 5490 (MMQ) / 8380 (mode 1,
64/// +53%) / 7990 (mode 2) x3 interleaved on the H100, argmax MATCH — the last board loss
65/// flips. The 5090 measured FLAT (858GB/s makes the dequant-workspace traffic cancel the
66/// GEMM win) — but that verdict is for expert banks the int8-MMA MMQ arm can take
67/// (IQ3_S/IQ4_XS/Q4_0). MEMRA_MOE_F16G=0 kills anywhere.
68///
69/// HOPPER RE-VERDICT (2026-08-02, lane/h100-flip-full): mode 2 with full direct coverage
70/// (Q4_K/Q6_K/IQ4_XS/IQ3_S tile loaders, lane/iq-direct-loaders) + the deep tail
71/// (lane/sk-tail-form) FLIPS past cublas mode 1 on the H100 — q35 board-2048 prime
72/// 13163.6 (mode 2, cross=32) vs 8626.5 (mode 1) vs 8073.4 (round-51 sk form), +52.6%,
73/// interleaved x5 zero overlap, argmax MATCH 30/30. The round-54 NO-FLIP (8547 vs 8112)
74/// was coverage-priced at 5.2% direct; ~100% coverage kills the workspace pass and the
75/// verdict inverts. Hopper naked default -> mode 2 (this arm); the gemma (gelu) site
76/// stays env-explicit-only via moe_f16g_gemma_on (Err => closed, unaffected by this arm).
77///
78/// MODE-2 DEFAULT (sm_120a naked, 2026-08-02, lane/f16g-default-rearb): with the direct
79/// tile loaders covering Q4_K/Q6_K/IQ4_XS/IQ3_S, the sk visitor beats the int8-MMA MMQ
80/// tiles on the IQ-bank models too (q35 board-2048 +33.9%, KAT pp512 +46.7% / pp2048
81/// +30.6% — research/iq-direct-loaders-20260802 §3-5, confirmed + full battery in
82/// research/f16g-default-rearb-20260802/), so every f16g-admitted expert layer rides
83/// mode 2 naked. Decode/verify stay on dp4a (t >= 16 floor). f16-mirror numeric class
84/// for naked q35/KAT prefill+prime — new token-sha anchors stamped in the rearb lane.
85///
86/// AUTO-KQUANT (mode 3, 2026-08-02, lane/q4k-expert-prefill): the previous sm_120a
87/// default, kept reachable via MEMRA_MOE_F16G=3. The mode-2 sk form is admitted ONLY for
88/// layers the MMA MMQ arm rejects (k-quant expert projections — Q3_K/Q4_K/Q6_K), i.e.
89/// exactly where the baseline is the per-pair moe_pairs_matvec_q8_em fallback with zero
90/// token reuse (Ornith-35B Q4_K_M board-2048 1098.2 -> 3453.7, 3.14x,
91/// research/q4k-expert-prefill-20260802/). Its "IQ banks keep their measured-faster MMQ
92/// tiles" ruling was priced BEFORE the IQ direct loaders and is refuted on the 5090 —
93/// the k-quant-only admission survives as the rollback seam, not the default.
94/// The gemma (gelu) site stays env-explicit-only (moe_f16g_gemma_on).
95pub fn moe_f16g_mode() -> u8 {
96    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
97    *M.get_or_init(|| match std::env::var("MEMRA_MOE_F16G").as_deref() {
98        Ok("0") => 0,
99        Ok("2") => 2,
100        Ok("3") => 3,
101        Ok(_) => 1,
102        // Both arches independently re-arbitrated to mode 2 on 2026-08-02
103        // (5090: lane/f16g-default-rearb; H100: lane/h100-flip-full) — unset = 2 everywhere.
104        Err(_) => 2,
105    })
106}
107/// Mode-2 sk kernel form policy (round 51, lane/sk-bm128): the single-kernel grouped GEMM runs
108/// as a persistent problem-visitor over the real CSR tiles with two tile forms. Returns
109/// (shape_sel, cross) for the FFI:
110///   MEMRA_F16G_SK=0    -> (-1, _): the round-49 grid-scan kernel (rollback seam).
111///   MEMRA_F16G_SK=32   -> all groups on the 32x64x32 2-stage form (cross = i32::MAX).
112///   MEMRA_F16G_SK=128  -> all groups on the 128x64x64 3-stage form (cross = 1; groups fall
113///                         back to 32x64 in-launcher when the device/in_f can't take it).
114///   unset              -> hybrid split: groups with m_e >= MEMRA_F16G_SK_CROSS ride the 128
115///                         form. Default cross = 64 (5090 sweep 2026-08-01, receipts
116///                         research/sk-bm128-20260801/; H100 re-swept on the direct+tail
117///                         form 2026-08-02, lane/h100-flip-full: {16,32,64} ->
118///                         12868/13192/13225 — 64 wins there too, the pre-direct 32
119///                         verdict was stale).
120pub fn moe_f16g_sk_params() -> (i32, i32) {
121    static P: std::sync::OnceLock<(i32, i32)> = std::sync::OnceLock::new();
122    *P.get_or_init(|| match std::env::var("MEMRA_F16G_SK").as_deref() {
123        Ok("0") => (-1, 0),
124        Ok("32") => (0, i32::MAX),
125        Ok("128") => (0, 1),
126        _ => {
127            let cross = std::env::var("MEMRA_F16G_SK_CROSS")
128                .ok()
129                .and_then(|v| v.parse().ok())
130                .unwrap_or(64);
131            (0, cross)
132        }
133    })
134}
135/// DIRECT-FROM-QUANT sk tile loaders (lane/kquant-tile-loaders, 2026-08-02; IQ classes added
136/// by lane/iq-direct-loaders): Q4_K/Q6_K/IQ4_XS/IQ3_S expert projections on the mode-2/3 sk
137/// visitor forms dequant their weight tiles in-register from the quant superblocks instead of
138/// running the per-(layer,projection) dequant pass into an f16 workspace (41.8% of Ornith-35B
139/// t=512 kernel time — the pp512 wall, research/q4k-expert-prefill-20260802 §5; the IQ classes
140/// are 94.8% of q35's bank bytes — the h100-sk-direct coverage pricing). Bit-identical to the
141/// workspace path by construction (kernel-check "f16g-kq-direct" gates it bitwise) — a
142/// data-movement change, not a numeric-class change. Default ON; MEMRA_F16G_DIRECT=0 reverts
143/// to the workspace path everywhere; MEMRA_F16G_DIRECT=kq keeps the k-quant loaders and
144/// reverts only the IQ classes (the iq-direct-loaders A/B seam — the pre-lane shipped config).
145pub fn moe_f16g_direct_on(qtype: i32) -> bool {
146    static M: std::sync::OnceLock<u8> = std::sync::OnceLock::new();
147    let m = *M.get_or_init(|| match std::env::var("MEMRA_F16G_DIRECT").as_deref() {
148        Ok("0") => 0,
149        Ok("kq") => 1,
150        _ => 2,
151    });
152    match m {
153        0 => false,
154        1 => qtype == QT_Q4_K || qtype == QT_Q6_K,
155        _ => true,
156    }
157}
158/// DEEP-TAIL sk form (lane/sk-tail-form, 2026-08-02): groups below the visitor crossover ride
159/// a 32x64x64 3-STAGE cp.async tile instead of the round-51 32x64x32 2-stage — the same 32-row
160/// tile (zero extra padding), 2 k-blocks in flight instead of 1 and half the syncs per k. The
161/// H100 ncu pricing (research/sk-bm128-20260801) put the 2-stage tail at 31% of the sk GEMM
162/// stage under q35's routing skew. Bit-identical to every other sk form by construction
163/// (kernel-check "f16g-sk" gates all tail arms maxdiff==0); exists in both the workspace-f16
164/// and direct-from-quant variants. Default ON; MEMRA_F16G_TAIL=0 = rollback to the 2-stage
165/// tail. in_f % 64 != 0 falls back in-launcher.
166pub fn moe_f16g_tail_on() -> bool {
167    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
168    *ON.get_or_init(|| std::env::var("MEMRA_F16G_TAIL").as_deref() != Ok("0"))
169}
170
171/// Per-model door for the gemma-MoE (gelu) grouped path: round 49's Hopper default
172/// REGRESSED g26 board-2048 prefill -8.3% interleaved x5 on-box (def median 10380,
173/// wild 8.9k-11.7k spread; off 11317, ±0.13%) — the +6-15% probe verdict didn't
174/// survive the board workload (stale-verdict law, round 50). The silu/qwen class
175/// keeps the round-49 default (q35 +53% board-2048). Explicit MEMRA_MOE_F16G=1/2
176/// still opens this door for A/B.
177pub fn moe_f16g_gemma_on() -> bool {
178    static M: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
179    *M.get_or_init(|| !matches!(std::env::var("MEMRA_MOE_F16G").as_deref(), Ok("0") | Err(_)))
180}
181
182/// Fused act-epilogue (silu/gelu-mul + q8_1_mmq quantize in one launch) for the MoE prefill
183/// MMA arms. Byte-identical to the two-pass path (kernel-check gated) — default ON.
184/// MEMRA_MOE_FUSE_ACTQ=0 is the rollback/A-B seam.
185pub fn moe_fuse_actq_on() -> bool {
186    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
187    *ON.get_or_init(|| std::env::var("MEMRA_MOE_FUSE_ACTQ").as_deref() != Ok("0"))
188}
189
190/// PREFILL router m-invariance (lane/concat-prime-exact, 2026-08-02). The batched cuBLASLt
191/// router GEMM changes a row's logits when OTHER rows join the call (probed: first change at
192/// m=65 on the Ornith-35B router, 3.9e-3 — while the MMQ/f16 trunk GEMMs are bit-identical
193/// across m). Feeding a top-k discontinuity, that made a served request's expert selection a
194/// function of its CO-ARRIVALS under cross-request prime batching. The in-house router GEMV
195/// is m-invariant, so prefill uses it too and routing depends on a session's own tokens only.
196/// DEFAULT ON: it is the serving isolation contract, and it is the same kernel decode and spec
197/// verify already use (dispatch parity, one router kernel for every t).
198/// MEMRA_ROUTER_PREFILL_EXACT=0 reverts to the batched GEMM.
199pub fn router_prefill_exact_on() -> bool {
200    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
201    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_PREFILL_EXACT").as_deref() != Ok("0"))
202}
203
204pub fn router_kernel_on() -> bool {
205    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
206    *ON.get_or_init(|| {
207        let on = std::env::var("MEMRA_ROUTER_KERNEL").as_deref() != Ok("0");
208        if !on {
209            eprintln!("[memra] router kernel OFF (rollback: per-column cuBLAS gemv)");
210        }
211        on
212    })
213}
214
215/// FAST-ROUTER batch twin (lane/fast-router, 2026-08-02). The concat-prime exactness fix
216/// (router_prefill_exact_on) routes prefill through router_gemv — m-invariant, but a
217/// per-(expert,token) GEMV program with zero operand reuse, so q35 board-2048 prefill paid
218/// -10% on the 5090. router_gemv_f32_w8_batch register-tiles (8x8 expert-x-token) the same
219/// per-row FP chains (BIT-IDENTICAL per row — kernel-check sweeps m=1..2048 on real router
220/// weights), so the t crossover below is pure perf, not a numeric config. Swept on-box
221/// (research/fast-router-20260802/crossover-router*.jsonl): plain wins t<=4, batch +7-9%
222/// at t=8, 1.9x at t=16 rising to 3.45x at t=2048 — MIN_T=8. Decode t=1 and spec verify
223/// t<8 keep the plain w8 form. MEMRA_ROUTER_BATCH=0 forces plain at every t (rollback
224/// seam, perf-only: bits are equal by the kernel-check gate).
225/// Killed arms (same sweep, JSONL is the record): the 8x16 tile lost to 8x8 at every t
226/// (128-accumulator register pressure beats the halved w-traffic), and the same-shape
227/// sigmoid_dot_rows twin (out_f=1) measured 0.62-0.89x at every prefill t
228/// (launch-latency-bound, ~7us/layer at m=2048) — both bit-identity-PASSED before dying.
229pub const ROUTER_BATCH_MIN_T: usize = 8;
230pub fn router_batch_on() -> bool {
231    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
232    *ON.get_or_init(|| std::env::var("MEMRA_ROUTER_BATCH").as_deref() != Ok("0"))
233}
234mod cpu_experts;
235#[cfg(memra_cutlass)]
236pub mod cutlass_ffi;
237pub mod f16_ffi;
238pub mod fp8_ffi;
239pub mod mmq_ffi;
240pub mod moe_cache;
241pub mod prime_graph;
242pub mod spill;
243mod spill_pread;
244
245// Fatbins are EMBEDDED (crates-release lane, 2026-08-04): build.rs still writes them to
246// OUT_DIR, but the bytes ship inside the binary via include_bytes! and load through
247// cuModuleLoadData. Distribution contract: a prebuilt or cargo-installed binary must be
248// self-contained — the old baked OUT_DIR *paths* pointed at the builder's temp dir and
249// broke every machine that wasn't the build machine. Same bytes, same module image;
250// the runtime MEMRA_GEMM_FATBIN tune-seam override below is preserved.
251const FATBIN: &[u8] = include_bytes!(env!("MEMRA_ENGINE_FATBIN"));
252const HYBRID_FATBIN: &[u8] = include_bytes!(env!("MEMRA_HYBRID_FATBIN"));
253const QMATVEC_FATBIN: &[u8] = include_bytes!(env!("MEMRA_QMATVEC_FATBIN"));
254const FLASH_FATBIN: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN"));
255const GEMM_FATBIN: &[u8] = include_bytes!(env!("MEMRA_GEMM_FATBIN"));
256const ROUTER_FATBIN: &[u8] = include_bytes!(env!("MEMRA_ROUTER_FATBIN"));
257/// spec_sample.cu: sampled-spec primitives (Philox Gumbel-max / softmax gather / residual sampler).
258const SAMPLE_FATBIN: &[u8] = include_bytes!(env!("MEMRA_SAMPLE_FATBIN"));
259
260/// TUNE SEAM (tools/sweep): a RUNTIME `MEMRA_GEMM_FATBIN=<path>` overrides the baked-in
261/// qmatvec_gemm.cu fatbin path (build.rs bakes the same name at COMPILE time via
262/// cargo:rustc-env — that constant is the default). Lets the sweep harness swap in a
263/// `-D`-tuned fatbin per process with NO rust rebuild. Unset at runtime => the
264/// compile-time default (zero behavior change).
265fn gemm_fatbin_bytes() -> std::borrow::Cow<'static, [u8]> {
266    assert!(
267        !(portable_mma_gated() && std::env::var_os("MEMRA_GEMM_FATBIN").is_some()),
268        "MEMRA_GEMM_FATBIN overrides are not allowed in the portable CUDA lane"
269    );
270    match std::env::var("MEMRA_GEMM_FATBIN") {
271        Ok(path) => std::borrow::Cow::Owned(
272            std::fs::read(&path).unwrap_or_else(|e| panic!("MEMRA_GEMM_FATBIN read {path}: {e}")),
273        ),
274        Err(_) => std::borrow::Cow::Borrowed(GEMM_FATBIN),
275    }
276}
277
278/// Phase A (ARCHITECTURE-H100.md): sm_90a re-enables the portable-PTX tensor-core paths
279/// (int8 mma.m16n8k32/k16.s8, bf16 m16n8k16, ldmatrix, cp.async — all sm_80-class, native
280/// on Hopper) that the portable boot lane gates off. Dispatch guards that used to test
281/// `cfg!(memra_portable_cuda)` test this instead; sm_89 keeps the pure-portable behavior.
282/// The sm_120a/sm_100a-only MMA kinds (mxf4nvf4, kind::f8f6f4) are NOT covered — their
283/// launchers stay fail-closed stubs on 90a and their dispatch arms stay arch-gated.
284pub(crate) const fn portable_mma_gated() -> bool {
285    cfg!(memra_portable_cuda) && !cfg!(memra_hopper_mma)
286}
287
288/// The legacy quantized prefill GEMMs are tuned and validated for sm_120a; sm_90a re-admits
289/// them through the Hopper-MMA lane (int8 m16n8k32.s8 is sm_80-class PTX).  Keep the policy
290/// in a pure helper so the dispatch guard can be regression-tested without constructing an
291/// Engine or allocating a GPU tensor.
292const fn legacy_quant_gemm_allowed(portable_cuda: bool, hopper_mma: bool, no_gemm: bool) -> bool {
293    (!portable_cuda || hopper_mma) && !no_gemm
294}
295
296// ---- KV-cache format selection (kvbytes lane, 2026-07-08; default OFF = daily config) ----
297// `MEMRA_KV_K` = q8_0 (default, 34 B/32elem) | fp8 (raw e4m3, 32 B — the -6% K-bytes arm)
298// `MEMRA_KV_V` = q5_1 (default, 24 B/32elem) | q4_0 (18 B, -25% V bytes) | fp8 (32 B, +33%)
299// A non-default format is a NEW NUMERIC CONFIG: its own run-gen argmax baseline is legal,
300// but the gate battery (kernel-check, run-spec self-consistency) must pass WITHIN it and
301// the choice is explicit env, never silent. flash_attn.cu is compiled once per format pair
302// (build.rs); the kernels keep their names — Engine::new just loads the matching fatbin.
303const FLASH_FATBIN_VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VQ4"));
304const FLASH_FATBIN_VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_VF8"));
305const FLASH_FATBIN_KF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8"));
306const FLASH_FATBIN_KF8VQ4: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VQ4"));
307const FLASH_FATBIN_KF8VF8: &[u8] = include_bytes!(env!("MEMRA_FLASH_FATBIN_KF8VF8"));
308
309/// KV format policy moved to the shared `memra-kv` crate (Phase D); re-exported so the
310/// fatbin router below and every existing `crate::kv_blk_bytes()` call site is unchanged.
311pub use memra_kv::{kv_blk_bytes, kv_cache_formats};
312
313/// The flash_attn fatbin matching the selected KV formats.
314fn flash_fatbin_bytes() -> &'static [u8] {
315    match kv_cache_formats() {
316        ("q8_0", "q5_1") => FLASH_FATBIN,
317        ("q8_0", "q4_0") => FLASH_FATBIN_VQ4,
318        ("q8_0", "fp8") => FLASH_FATBIN_VF8,
319        ("fp8", "q5_1") => FLASH_FATBIN_KF8,
320        ("fp8", "q4_0") => FLASH_FATBIN_KF8VQ4,
321        ("fp8", "fp8") => FLASH_FATBIN_KF8VF8,
322        other => unreachable!("kv_cache_formats returned {other:?}"),
323    }
324}
325
326/// TUNE SEAM (tools/sweep): kernel1 (Q8_0/Q4_K/Q5_K) launch-tile override,
327/// `MEMRA_GEMM_K1_LAUNCH="BM,BN,NWARP"`. MUST match the `-D K1_BM/K1_BN/NWARP` the swept
328/// fatbin was compiled with (the .cu tile and the host launch grid/block have to agree —
329/// the hardcoded (128,128,8) in qmatvec_gemm/qmatvec_gemm_raw is the shipped default).
330/// Kernel2 (Q6_K/NVFP4) launch is untouched. Unset or malformed => None => shipped
331/// defaults (zero behavior change).
332fn k1_launch_override() -> Option<(u32, u32, u32)> {
333    static K1: std::sync::OnceLock<Option<(u32, u32, u32)>> = std::sync::OnceLock::new();
334    *K1.get_or_init(|| {
335        let v = std::env::var("MEMRA_GEMM_K1_LAUNCH").ok()?;
336        let p: Vec<u32> = v.split(',').filter_map(|s| s.trim().parse().ok()).collect();
337        match p.as_slice() {
338            [bm, bn, w] => Some((*bm, *bn, *w)),
339            _ => None,
340        }
341    })
342}
343
344/// H100 wgmma prefill-GEMM seam (task 8, ARCHITECTURE-H100.md): OPT-IN (MEMRA_WGMMA=1).
345/// v0 verdict (2026-07-26, N=5 pp512 9B-Q8_0): wgmma 3845 tok/s vs MMQ 8692 — the
346/// standalone harness's "688us MMQ ref" was a pp2048-shape figure, so v0 (unpipelined,
347/// 64x64 tile, wait_group<0> every 32-K step) is ~3x SLOWER per launch at m=512 model
348/// shapes. Default stays MMQ until the pipelined version beats it N=5 (repo law).
349/// Correctness stays pinned regardless: kernel-check's wgmma case is cfg-gated, not env-gated.
350pub(crate) fn wgmma_gemm_enabled() -> bool {
351    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
352    *V.get_or_init(|| std::env::var("MEMRA_WGMMA").as_deref() == Ok("1"))
353}
354
355/// TUNE SEAM: keys per FA-decode split (`MEMRA_FA_SPLIT` forces a fixed size; default 64). Smaller
356/// splits raise grid.y so grid = n_head_kv * n_splits fills the 82 SMs at short/mid ctx (vec path
357/// launches only n_head_kv=8 CTAs per split). Swept clock-locked 2026-07-03 (graph tg128): 32 beat
358/// 64 at ctx 128/512 (+0.5/+1.2%) and lost at 2048 (-3%) — BUT the adaptive 32/64 default BROKE the
359/// MTP spec-decode exact-match gate (run-spec K=1/2 self-consistency FAIL with 32; PASS with 64):
360/// the split count changes the combine's FP summation order, and the spec verify's batched forward
361/// only argmax-matches single-step decode under the 64-split order on real prompts. Spec exactness
362/// (the bigger lever) outranks a <=1.2% decode win -> default stays FIXED 64; sweeps use the env.
363/// Takes t_kv so eager, _dc capture, and fa_geom_eager stay signature-compatible for future
364/// adaptive retries (any retry MUST pass run-spec self-consistency first).
365/// Minimum t_kv for the warp-per-token vec FA path (below it the scalar path's 4x-more-blocks
366/// hides latency better — measured crossover, see `fa_decode`). Shared by fa_decode / fa_decode_dc /
367/// fa_geom_eager / fa_decode_rows-eligibility (spec verify) so the kernel pick NEVER diverges
368/// between eager decode and the verify (the spec-exactness law).
369pub const FA_VEC_MIN_TKV: usize = 96;
370/// Env-overridable crossover (MEMRA_FA_VEC_MIN, default FA_VEC_MIN_TKV). The 96 floor was
371/// measured on the qwen geometry (nkv=2); gemma4 SWA layers run nkv=8 = 4x the vec grid,
372/// which moves the crossover — sweep per model, adopt per the battery.
373pub fn fa_vec_min_tkv() -> usize {
374    static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
375    *V.get_or_init(|| {
376        std::env::var("MEMRA_FA_VEC_MIN")
377            .ok()
378            .and_then(|v| v.parse().ok())
379            .unwrap_or_else(|| FA_VEC_MIN_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
380    })
381}
382
383/// f16-P/V class (DEFAULT since 2026-07-23 stamp v4; MEMRA_FA_F16PV=0 = f32-class rollback):
384/// llama-fa=1-style f16 P + f16 P@V accumulation on the hd512/SWA prefill stamps
385/// (KQ/softmax/normalize stay f32). Laptop stamp: 12B 1.045x, 31B 0.979x vs llama.
386///
387/// SPEC-SERVING FLIP (2026-07-26, the wkv acceptance-law pattern): with MEMRA_DRAFT set the
388/// default is OFF. f16 P/V shifts the PRIME's hidden states/KV in the sub-argmax logit
389/// space the drafter feeds on — argmax gates stay MATCH while depth acceptance falls off a
390/// cliff (26B d1736 0.883 -> 0.405, -40% e2e; f16pv-off alone restores 0.846/314 tok/s —
391/// the perf-ci acceptance battery is the only gate that sees this class). Explicit
392/// MEMRA_FA_F16PV always wins; plain serving keeps the f16 prefill win.
393pub fn fa_f16pv_on() -> bool {
394    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
395    *ON.get_or_init(|| {
396        std::env::var("MEMRA_FA_F16PV")
397            .map(|v| v != "0")
398            .unwrap_or_else(|_| std::env::var("MEMRA_DRAFT").is_err())
399    })
400}
401
402/// hd512 head-pair arm (DEFAULT since stamp v4; MEMRA_FA512_HP=0 reverts to sp16): GQA
403/// ncols2=2 — 2 heads per CTA share each staged K/V tile, Q register-resident. Engages
404/// when n_head is even and the GQA group (n_head/n_head_kv) is even.
405pub fn fa512_hp_on() -> bool {
406    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
407    *ON.get_or_init(|| std::env::var("MEMRA_FA512_HP").as_deref() != Ok("0"))
408}
409
410/// SWA head-pair arm (DEFAULT since stamp v4; MEMRA_FAW_HP=0 reverts to p1): llama-class
411/// windowed geometry — 32 q-rows x 2 heads per CTA sharing staged K/V, f16 P@V
412/// accumulation. Even n_head and even GQA group required (guarded per call).
413pub fn faw_hp_on() -> bool {
414    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
415    *ON.get_or_init(|| std::env::var("MEMRA_FAW_HP").as_deref() != Ok("0"))
416}
417
418/// 4-warp sp16 experiment arm (MEMRA_FA512_W4=1, requires the f16pv door): GEMM0 split-K
419/// 4-way + GEMM1 4x128 O-dims. Own partial-sum order — oracle-band gated. Returns warp
420/// count (2 = base sp16). 8-warp arm measured NEGATIVE 2026-07-23 (jsonl) and removed.
421pub fn fa512_wide_warps() -> usize {
422    static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
423    *N.get_or_init(|| match std::env::var("MEMRA_FA512_W4").as_deref() {
424        Ok("1") => 4,
425        _ => 2,
426    })
427}
428
429/// hd-512 vec crossover floor (MEMRA_FA512_MIN, default 512) — shared by fa_decode dispatch
430/// and the gemma global-layer rows/parity call sites.
431pub fn fa512_min_tkv() -> usize {
432    static FA512_MIN: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
433    *FA512_MIN.get_or_init(|| {
434        std::env::var("MEMRA_FA512_MIN")
435            .ok()
436            .and_then(|v| v.parse().ok())
437            .unwrap_or(512)
438    })
439}
440/// Per-model crossover default, set at model load BEFORE the first decode (per-model
441/// numeric-config adoption law). qwen keeps the measured 96; gemma4 (nkv=8 SWA) measured
442/// vec-always fastest: 119.9 (96) / 130.0 (48) / 133.2 (1) tok/s tg128-regime, 2026-07-10.
443pub static FA_VEC_MIN_DEFAULT: std::sync::atomic::AtomicUsize =
444    std::sync::atomic::AtomicUsize::new(FA_VEC_MIN_TKV);
445/// Per-model windowed-split default (MEMRA_FA_SPW overrides): gemma MoE (26B, nkv=8) measured
446/// 32 (grid-limited t=1 under the raw-e4m3 sV ceiling, 2026-07-12); dense gemma (31B)
447/// measured 64 (37.13/37.12 vs 36.87/36.86 at 1.7k, N=2 — different attention geometry).
448pub static FA_SPW_DEFAULT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(32);
449/// Per-model hd512 (gemma globals) split default (MEMRA_FA_SP512 overrides): 26B measured 16
450/// (2026-07-11 N=2), dense 31B measured 32 (36.86/36.93 vs 36.73/36.73 at 1.7k, 2026-07-12).
451/// fused t=1 q4_0 pair/triple row mapping: true = mr1 (one row/warp). Per-model default
452/// (dense gemma wins +1.1% short / +0.6% depth on the 31B; MoE 26B REGRESSES −1.2% —
453/// its shared-expert fused2 shapes lose to the finer grid). MEMRA_Q40_MR env still wins.
454pub static FUSED_MR1_DEFAULT: std::sync::atomic::AtomicBool =
455    std::sync::atomic::AtomicBool::new(false);
456/// Per-model router-GEMV form (2026-07-31): the 8-warp twin is +8.8% on the H100 q35
457/// decode step (router was 14.8% of it) with argmax + spec self-consistency green on
458/// qwen-class MoE both rigs. The gemma-4 26B knife-edge block (2026-07-31, single
459/// synthetic prompt) was RE-ARBITRATED 2026-08-01 on 6 real prompts — gate outcomes
460/// identical to the lone-warp arm, +13% g26 decode — so gemma4 rides the default too
461/// (research/g26-decode-20260801/). MEMRA_ROUTER_V2 env overrides either way.
462pub static ROUTER_W8_DEFAULT: std::sync::atomic::AtomicBool =
463    std::sync::atomic::AtomicBool::new(true);
464pub static FA_SP512_DEFAULT: std::sync::atomic::AtomicUsize =
465    std::sync::atomic::AtomicUsize::new(16);
466/// Per-model rms_norm block size (per-model numeric-config law: the per-thread partial-sum
467/// split changes with blockDim -> different FP order -> battery-arbitrated per model).
468/// qwen keeps the shipped 256; gemma4 adopts 1024 (single-row 2816-col norms are one-block
469/// latency-bound at 256 threads — 7us/launch measured).
470pub static RMS_BLOCK_DEFAULT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(256);
471/// gemma4 fa split ladder switch (set at model load; see fa_split_keys).
472pub static FA_SP_GEMMA: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
473/// Per-model stream-k entry override for SPEC serving (-1 = unset → env/default;
474/// 0 = force tiling; 1 = admit the deterministic form selector). The former timing
475/// selector made identical boots choose different fold orders; `MEMRA_MMQ_SK_FORM` is the
476/// explicit numerical-form seam. mmq_ffi reads this before the env.
477pub static MMQ_SK_FORCE: std::sync::atomic::AtomicI8 = std::sync::atomic::AtomicI8::new(-1);
478/// Per-model FP8-KV door — lives in memra-kv next to the format policy it drives
479/// (re-export keeps `crate::KV_FP8_FORCE` setters in model.rs/hybrid.rs working).
480pub use memra_kv::KV_FP8_FORCE;
481pub(crate) fn rms_block() -> u32 {
482    static V: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
483    *V.get_or_init(|| {
484        std::env::var("MEMRA_RMS_BLOCK")
485            .ok()
486            .and_then(|v| v.parse().ok())
487            .unwrap_or_else(|| RMS_BLOCK_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
488    })
489}
490
491pub(crate) fn fa_split_keys(t_kv: usize, n_head_kv: usize) -> usize {
492    static S: std::sync::OnceLock<Option<usize>> = std::sync::OnceLock::new();
493    if let Some(forced) = *S.get_or_init(|| {
494        std::env::var("MEMRA_FA_SPLIT")
495            .ok()
496            .and_then(|v| v.parse().ok())
497            .filter(|&s: &usize| s >= 8 && s % 8 == 0)
498    }) {
499        return forced;
500    }
501    // CTX-ADAPTIVE default (2026-07-05 40k sweep: sp32 24.5 vs sp128 26.0 tok/s = +5.8% — at
502    // deep ctx the n_splits count explodes (40k/32 = 1265 splits x 8 kv-heads) and the combine
503    // + partial-buffer cost dominates; at short ctx small splits fill the SMs). Exactness: split
504    // size only changes the PARTITION of keys; the rows/combine order per split is fixed and the
505    // gate battery (kernel-check + run-spec K=1..8) arbitrates every default change.
506    //
507    // SM-AWARE SHORT-CTX RUNG (2026-07-06 g7e): the 32-key rung was tuned on the 82-SM 5090.
508    // On 188 SMs the vec grid (n_head_kv x n_splits CTAs) starves at short ctx — the 35B has
509    // n_head_kv=2, so ctx128/split32 = 8 CTAs on 188 SMs. Measured on g7e (N=1 sweep + N=3
510    // interleaved confirm): 35B ctx128 sp16 179 vs sp32 161 (+11%), ctx512 178 vs 158, ctx2048
511    // flat, ctx>=4096 sp64 edges sp16 by ~3%; 27B ctx128 70.9 vs 66.3 (+7%); 9B 177 vs 163
512    // (+9%). Rigs <=100 SMs keep the validated 5090 ladder EXACTLY (default unchanged there —
513    // rig-divergence law: this branch is measured on 188 SMs only).
514    // gemma4 all-16 ladder probe REVERTED (2026-07-10): +1.3 plain at d1736 (157.5 vs 156.2)
515    // but depth VERIFY collapsed (spec 203.5 -> 169 — the windowed rows' per-row combine over
516    // 64 splits). The mixed default (swa nkv=8 -> 32, globals nkv=2 -> 8-ladder) stays; a
517    // caller-split policy would break row-vs-decode split parity. FA_SP_GEMMA kept as a seam.
518    if FA_SP_GEMMA.load(std::sync::atomic::Ordering::Relaxed)
519        && std::env::var("MEMRA_FA_SP16").as_deref() == Ok("1")
520    {
521        return if t_kv <= 8192 {
522            16
523        } else if t_kv <= 16384 {
524            64
525        } else {
526            128
527        };
528    }
529    let big_rig = fa_sm_count() >= 128;
530    if big_rig {
531        let _ = n_head_kv;
532        if t_kv <= 2048 {
533            16
534        } else if t_kv <= 16384 {
535            64
536        } else {
537            128
538        }
539    } else if n_head_kv <= 4 {
540        // KV-HEAD-AWARE RUNG (2026-07-08, 5090): the 8192->32 rung was validated on kv=8 models
541        // (27B/9B: 8 heads x n_splits fills 82 SMs). The 35B has n_head_kv=2 — at ctx512/sp32
542        // the vec grid is 2 x 20 = 40 CTAs on 82 SMs (half idle). Measured (35B, run-gen 128tok
543        // N=1 sweep + N=3 confirm): sp8 162.1 / sp16 161.3 / sp32 159.4 at short ctx.
544        // DEPTH TAPER (same day, the deep-ctx lesson re-learned on this rung): sp8 at d6257 =
545        // 782 splits -> combine + partial-buffer cost dominates (141.2 tok/s); the d6257 sweep
546        // says sp64 = 153.0 (sp16/32 147, sp96 147.6, sp128 141). Few-kv-head models need the
547        // taper EARLIER than kv=8 (per-split grid 4x thinner, same per-split combine cost).
548        // Crossover hunt: sp8 vs sp64 = 156.7/155.9 at d3072, 151.7/155.6 at d4096 -> boundary 3072.
549        // RUNG RE-SWEPT UNDER THE DEEP KERNEL (2026-08-02, lane/ladder-3072 — the stale-verdict
550        // law: the 3072 boundary was calibrated on the conflicted v4 core; the deep rewrite cut
551        // vec cost ~1.2-1.4x while combine scales with n_splits, so sp8's combine bill
552        // dominates far earlier). Kernel receipts (quiet-rig nsys, deep vec + combine us):
553        // d1024 sp8 17.1 vs sp64 10.6; d2048 31.0 vs 12.2; d3072 44.0 vs 18.3. e2e run-gen
554        // tg128 N=3 interleaved (KAT + q35, research/ladder-3072-20260802/): sp8 loses at
555        // EVERY depth >= 1024 (KAT d2048 182.6 vs 188.0 = -2.9%, d3072 175.9 vs 186.4 =
556        // -5.6%; q35 d4096 169.2 vs 182.6 = -7.4%); d512 flat (+-0.2%, inside noise). sp32
557        // ties sp64 within noise in the mid band and loses at d4096 -> no extra rung.
558        // Boundary 3072 -> 512: sp8 keeps only the short-ctx band it was validated on
559        // (ctx128-512); sp64 takes over where the deep kernel made combine the bill.
560        if t_kv <= 512 {
561            8
562        } else if t_kv <= 16384 {
563            64
564        } else {
565            128
566        }
567    } else {
568        if t_kv <= 8192 {
569            32
570        } else if t_kv <= 16384 {
571            64
572        } else {
573            128
574        }
575    }
576}
577
578/// SM count of device 0, cached (used by fa_split_keys' rig-size rung; primary-context query,
579/// same attribute Engine::batched_variant reads).
580fn fa_sm_count() -> i32 {
581    static N: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
582    *N.get_or_init(|| {
583        cudarc::driver::result::init().ok();
584        cudarc::driver::result::device::get(0)
585            .and_then(|d| unsafe { cudarc::driver::result::device::get_attribute(
586                d, cudarc::driver::sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) })
587            .unwrap_or(82)
588    })
589}
590
591/// FA-prefill kernel-name suffix for a head_dim (the template-stamped twins in flash_attn.cu):
592/// 256 = the original names (qwen35 class, dispatch unchanged), 128 = `_hd128` (MiniMax-M3).
593/// Any other dim errors — callers gate to sdpa_naive before dispatching FA.
594fn fa_hd_suffix(head_dim: usize) -> Result<&'static str, Box<dyn std::error::Error>> {
595    match head_dim {
596        256 => Ok(""),
597        128 => Ok("_hd128"),
598        d => Err(format!(
599            "fa_prefill: no kernel stamped for head_dim={d} (only 256/128); \
600                          callers must gate to sdpa_naive"
601        )
602        .into()),
603    }
604}
605
606/// Quant type codes matching qmatvec.cu QType enum.
607pub const QT_Q8_0: i32 = 0;
608pub const QT_Q4_K: i32 = 1;
609pub const QT_Q6_K: i32 = 2;
610pub const QT_Q5_K: i32 = 3;
611pub const QT_Q3_K: i32 = 4;
612pub const QT_IQ4_XS: i32 = 5;
613pub const QT_IQ3_S: i32 = 6;
614pub const QT_NVFP4: i32 = 7;
615/// Checkpoint-native FP8-E4M3 (MEMRA_ST_E4M3, lane e4m3dec): raw safetensors e4m3 weight bytes
616/// [out_f, in_f] row-major (row_bytes == in_f), per-tensor f32 weight_scale in GpuTensor `scale`
617/// (fused at the mmvq write / post-matmul scale_inplace). Decode = qmatvec_e4m3_mmvq (+ _b2/_b4/_b8
618/// batched twins); prefill (m>=16) = the cuBLASLt FP8 GEMM on the SAME resident bytes (fp8_ffi.rs)
619/// — ONE weight copy total, no Q8_0 re-encode duplicate.
620pub const QT_F8_E4M3: i32 = 10;
621/// Device-side tag for the A6 SPLIT-PLANE repacked NVFP4 layout (Stage-A generic kernel only;
622/// GpuTensor keeps qtype=QT_NVFP4 + an `rp` flag — this tag never lives in a GpuTensor).
623pub const QT_NVFP4_RP: i32 = 9;
624/// Unquantized f32 weight (safetensors MoE Path A: experts dequantized to f32 host-resident).
625pub const QT_F32: i32 = 8;
626pub const QT_BF16: i32 = 11;
627pub const QT_Q4_0: i32 = 12; // gemma-4 QAT GGUF weight format (18B/32: fp16 d + nibbles)
628/// GGUF Q2_K. Appended after the existing Q4_0 code so kernel ABI values do not move.
629/// Mixed-expert artifacts use the generic f32-dequant staged kernel until a target-rig-gated
630/// dp4a/MMQ implementation exists.
631pub const QT_Q2_K: i32 = 13;
632/// Checkpoint-native FP8-E4M3 with a BLOCK-128 weight-scale GRID (lane/fp8-blk128-decode,
633/// 2026-08-05) — the Qwen-official FP8 / DeepSeek-V3 scale class. Same raw e4m3 bytes as
634/// `QT_F8_E4M3` ([out_f, in_f] row-major, row_bytes == in_f), but the dequant scale is
635/// `GpuTensor::Quant.blk` (`Fp8BlockScales`, [ceil(out_f/128), ceil(in_f/128)] f32) and the
636/// scalar `scale` field is 1.0 by the layout contract.
637///
638/// WHY A DISTINCT CODE rather than `QT_F8_E4M3` + a `blk` flag: every existing QT_F8_E4M3
639/// consumer (qmatvec_e4m3_mmvq and its batched/fused twins, e4m3_fused_params,
640/// matmul_pre_dual_noscale's F8 arm, try_fp8_gemm) threads exactly ONE scalar weight scale. Under
641/// a shared code, any consumer that was not taught the grid would still MATCH and would dequant
642/// every tile at scale 1.0 — a silent numeric corruption. Under a distinct code every untaught
643/// consumer refuses loudly instead (`mmvq_supports`/`gemm_supports`/`mmq_supports` return false;
644/// the mmvq name match panics), so a missed dispatch site is a crash or a refusal receipt, never
645/// wrong numbers. Decode = `qmatvec_e4m3_blk_mmvq`; prefill (m>=16) = the per-block FP8 MMQ tile
646/// on the SAME resident bytes+grid (fp8_ffi::try_fp8_blk_mmq) — ONE weight copy total.
647pub const QT_F8_E4M3_BLK: i32 = 14;
648
649/// Engine device context: CUDA context, stream, loaded kernel modules, cuBLASLt (via runtime::Gpu).
650pub struct Engine {
651    pub gpu: memra_runtime::Gpu,
652    module: Arc<CudaModule>,
653    hybrid: Arc<CudaModule>,
654    qmatvec: Arc<CudaModule>,
655    flash: Arc<CudaModule>,
656    /// FP8-GLOBALS module (2026-07-11): the kf8vf8 fatbin loaded ALONGSIDE the default —
657    /// gemma GLOBAL layers (hd512) append + attend in e4m3 (dequant-latency arc, HANDOVER).
658    /// Lazy: loaded on first global-format use; None until then.
659    flash_g: std::sync::OnceLock<Arc<CudaModule>>,
660    gemm: Arc<CudaModule>,
661    router: Arc<CudaModule>,
662    /// Sampled-spec kernels (research/sampled-spec-impl-map.md piece A).
663    sample: Arc<CudaModule>,
664    /// EDGE-1 §B: one shared SLRU expert-residency cache, lazily built on first MoE dispatch under
665    /// MEMRA_MOE_CACHE. `Mutex` makes it multi-agent safe (§E.2); the lock covers only lookup/admit/
666    /// memcpy-issue (µs), NOT the GEMM, so streams still overlap. `None` => cache disabled.
667    moe_cache: Mutex<Option<crate::moe_cache::MoeSlotCache>>,
668    /// Exact retained expert-block lengths collected after model load. Mixed-layout models use
669    /// this inventory to preallocate fixed-address size classes instead of sizing every slot to
670    /// the single largest block. The cache still owns every address for its full lifetime.
671    moe_cache_layout: Mutex<Option<Vec<usize>>>,
672    /// CAPTURE-RETAIN mode (graph arc, 2026-07-12): while a graph capture (and its allocator
673    /// warmups) runs, every Engine allocation is ALSO kept alive here — a captured graph's
674    /// transient buffers must never return to the pool, or later allocations (e.g. the spec
675    /// verify between replays) reuse their addresses and the replay reads/writes live memory
676    /// (the draft-graph corruption root cause). Fast-path cost when off: one relaxed atomic.
677    capture_keep_on: std::sync::atomic::AtomicBool,
678    /// VERIFY-EXACT scope (dflash lane, 2026-07-13): when set, matmul/matmul_pre skip the
679    /// m>=16 prefill-GEMM branches so a t>=16 batched VERIFY rides the decode-exact b-tier
680    /// class (the parity law). The t=16 dflash verify tripped the GEMM threshold — 770us/
681    /// matmul (54% of the round) AND a different FP order than decode (issue-10 landmine).
682    verify_exact: std::sync::atomic::AtomicBool,
683    capture_keep: Mutex<Vec<Box<dyn std::any::Any + Send>>>,
684    /// EDGE-1 §C.2: dedicated H2D copy stream for async prefetch (event-synced to the compute stream).
685    pub copy_stream: Arc<CudaStream>,
686    /// Resident CUTLASS NVFP4 prefill scratch (workspace + a_packed + sfa_linear + sfa_sw + y + alpha),
687    /// allocated ONCE and grown to the largest prefill GEMM shape, then reused per-call. Removes the
688    /// 6 fresh allocations + alpha htod that `cutlass_fp4_gemm` did every prefill matmul (~200/prefill).
689    /// Safe as a single shared buffer because all GPU compute serializes on the one `gpu.stream` worker
690    /// thread (the server runs one GPU worker; no concurrent CUTLASS GEMMs share this scratch). `None`
691    /// until the first CUTLASS FP4 GEMM. Mutex guards lazy build/grow only (matches `moe_cache`).
692    #[cfg(memra_cutlass)]
693    cutlass_scratch: Mutex<Option<crate::cutlass_ffi::CutlassScratch>>,
694    /// FP8-ACT PREFILL scratch (MEMRA_PP_FP8): quantized-activation buffer + scale block + cuBLASLt
695    /// workspace, allocated once and grown to the largest prefill m*k (see fp8_ffi.rs). `None`
696    /// until the first FP8 prefill GEMM; Mutex guards lazy build/grow only (matches cutlass_scratch).
697    fp8_scratch: Mutex<Option<crate::fp8_ffi::Fp8Scratch>>,
698    /// f16-P/V door: pooled V re-encode buffer (bf16->f16) for the hd512 _pre path. Lazy-grow;
699    /// per-call cudaMalloc was a laptop-regression suspect (VRAM pressure, 31B nkv=4 = 4x bytes).
700    fa_vf16_scratch: Mutex<Option<CudaSlice<u8>>>,
701    /// Pooled fa-decode split partials (part_o, part_m, part_l): per-call zeros() was 3
702    /// alloc+memset pairs per fa launch (~144 mem nodes per decode token — the graph door's
703    /// residual launch tax) — lazy-grow, memset-prefix per use, stream-ordered reuse.
704    fa_part_pool: Mutex<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
705    /// Retired fa-part pool generations (#68): old buffers whose addresses captured graphs may
706    /// have baked — kept alive for the Engine's lifetime instead of returning to the async pool
707    /// (see the RETIRE-ON-GROW comment at the realloc sites). Doubling growth bounds the total.
708    fa_part_retired: Mutex<Vec<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>>,
709    /// name -> resolved CudaFunction (capture-safe lookups; see `func`).
710    fn_cache: Mutex<std::collections::HashMap<String, CudaFunction>>,
711    f16_scratch: Mutex<Option<crate::f16_ffi::F16Scratch>>,
712    /// RANK1 LEVER (parallel argmax): resident pass-1 partials scratch (part_v[NB] f32, part_i[NB] i32),
713    /// allocated ONCE on first parallel-argmax call and reused. Stable pointers so the 2-pass argmax
714    /// is CUDA-graph-capturable (the buffer is referenced by both captured passes; lazy-allocated
715    /// before capture under the generate_graph tracking-off window so it carries no events).
716    argmax_partials: Mutex<Option<(CudaSlice<f32>, CudaSlice<i32>)>>,
717    /// ARC B (chunk-prime dequant-once): resident bf16 K/V workspace for `fa_prefill_view_ws`
718    /// ((K bytes, V bytes) u8 buffers holding [t_kv, kv_dim] bf16). Grown lazily to the largest
719    /// (t_kv, kv_dim) seen, REUSED across layers/chunks/calls (contents rewritten per launch —
720    /// safe because all compute serializes on the one gpu.stream). ~82MB at 40k ctx on the 27B.
721    prime_deqw_ws: Mutex<Option<(CudaSlice<u8>, CudaSlice<u8>)>>,
722    /// LAUNCH-STRUCTURE STAGE 1: persistent PINNED (cacheable, flags=0) host staging buffer for the
723    /// fused-router sel/w readback — one async DtoH pair + ONE sync instead of two synced dtohs.
724    /// Grown lazily; reused every MoE layer (single-threaded decode serializes on the sync).
725    router_stage: Mutex<Option<PinnedStage>>,
726}
727
728/// FAVENDOR lane env gate (2026-07-08): MEMRA_FA_V2=1 dispatches the llama-fattn-vec-mechanism
729/// decode kernels (fa_decode_vec_q_v2 / fa_decode_vec_q_rows_v2 / fa_decode_vec_q_v2_dc):
730/// tile-batched online softmax (one alpha rescale per 32-key tile instead of per key) + wide-load
731/// block dequant in the staging phase. NOTE rev2: llama's register streaming (no smem) was ALSO
732/// tried and measured 2x WORSE at depth in our gqa-warps frame — the smem KV-tile broadcast stays
733/// (see the kernel comment). NEW NUMERIC CONFIG (tile-level softmax regrouping changes FP order vs
734/// the per-key twins) — own argmax baseline; eager decode, the spec-verify rows path AND the
735/// graph _dc path switch TOGETHER (the spec-exactness law). Default OFF. Read per call (not
736/// OnceLock) so the gate battery can A/B within one process, matching the MEMRA_NO_FA_VEC pattern.
737fn fa_v2_on() -> bool {
738    // DEFAULT ON since 2026-07-08 (MEMRA_FA_V2=0 reverts): tile-batched online softmax, e2e
739    // measured across every model x depth — 35B 168.7->173.4 (d512) / 153.1->158.5 (d6257),
740    // 9B 131.2->132.7 / 108.4->124.5 (+15% — the engine-wide depth-slope fix), 27B 47.2->47.7 /
741    // 42.2->44.9. One-time numeric-config change; kernel-check + argmax + spec self-consistency
742    // + graph bit-identity green on all three models.
743    std::env::var("MEMRA_FA_V2")
744        .map(|v| v != "0")
745        .unwrap_or(true)
746}
747
748/// FA v3 gate (default ON since 2026-07-09; MEMRA_FA_V3=0 reverts to v2 — research/fa/fa_v3_design.md):
749/// HYBRID decode twins (fa_decode_vec_q_v3 / _rows_v3 / _v3_dc): llama's int8-dp4a K.Q with
750/// register-quantized Q (no K dequant, no K smem) + OUR CTA-shared staged bf16 V tile + OUR
751/// split partition/combine. NEW NUMERIC CONFIG (int8 Q quantization changes the K.Q accumulation
752/// vs the bf16-roundtrip FMA chain) — own argmax baseline; eager decode, the spec-verify rows
753/// path AND the graph _dc path switch TOGETHER (the spec-exactness law). Read per call so the
754/// gate battery can A/B within one process (the MEMRA_FA_V2 pattern).
755fn fa_v3_on() -> bool {
756    // DEFAULT ON since 2026-07-09 (MEMRA_FA_V3=0 reverts to v2): dp4a-K hybrid FA decode —
757    // fa kernel -21-23% at depth (micro), 35B spec p3 +5% (190->200, the last spec cell),
758    // d6257 +1.7%. Own numeric config; full battery green on 35B+9B incl graph bit-identity.
759    std::env::var("MEMRA_FA_V3")
760        .map(|v| v != "0")
761        .unwrap_or(true)
762}
763
764/// The v3 dp4a K path reads RAW q8_0 bytes (34B blocks) and stages q5_1 V verbatim — it is only
765/// correct on the DEFAULT KV formats — and needs dpl % 4 == 0 consecutive quants per lane
766/// (head_dim % 128 == 0; both daily models are hd256). All three dispatch sites share this
767/// predicate so the twins can never diverge.
768fn fa_v4_mode() -> &'static str {
769    static M: std::sync::OnceLock<String> = std::sync::OnceLock::new();
770    M.get_or_init(|| std::env::var("MEMRA_FA_V4").unwrap_or_default())
771}
772fn fa_v4_on() -> bool {
773    fa_v4_mode() != "0"
774} // DEFAULT ON 2026-07-10 (MEMRA_FA_V4=0 rollback)
775/// t_kv-conditional v4 pick (gemma depth lesson 2026-07-10: v4's key-per-lane pipeline starves
776/// at the 1024-window with short splits — MEMRA_FA_V4=0 measured depth plain 158.0 vs 156.7).
777/// Threshold MEMRA_FA_V4_MAX (default usize::MAX = unchanged behavior; gemma sets 1024 at load
778/// via FA_V4_MAX_DEFAULT). Applied at EVERY dispatch site (eager, rows, rows_w, dc) so verify
779/// stays kernel-family-identical to decode at the same t_kv.
780/// Per-model deep-ctx smem floor default (MEMRA_FA_SMEM_TKV env overrides): gemma pushes it
781/// above the 1024 window so the windowed decode + verify rows share the REGISTER family.
782pub static FA_SMEM_TKV_DEFAULT: std::sync::atomic::AtomicUsize =
783    std::sync::atomic::AtomicUsize::new(1024);
784pub static FA_V4_MAX_DEFAULT: std::sync::atomic::AtomicUsize =
785    std::sync::atomic::AtomicUsize::new(usize::MAX);
786pub fn fa_v4_at_pub(t_kv: usize) -> bool {
787    fa_v4_at(t_kv)
788}
789fn fa_v4_at(t_kv: usize) -> bool {
790    static M: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
791    let mx = *M.get_or_init(|| {
792        std::env::var("MEMRA_FA_V4_MAX")
793            .ok()
794            .and_then(|v| v.parse().ok())
795            .unwrap_or_else(|| FA_V4_MAX_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
796    });
797    fa_v4_on() && t_kv < mx
798}
799/// FA-DEEP gate (2026-08-02, lane fa-decode-deep): deep-ctx v4 twins
800/// (fa_decode_vec_q_v4_deep / _deep_dc) — the depth-decode lane's priced fix. Unlike
801/// v2/v3/v4 this is NOT a numeric config: the deep twins run the v4 program VERBATIM
802/// (same split partition, same softmax/accumulation order, same partials/combine) and only
803/// move the smem physical layout (bank de-conflict row pads) + the load schedule (next-tile
804/// L2 prefetch) — kernel-check pins bitdiff==0 vs the v4 twins across depths, so eager /
805/// rows-verify / graph / seqs stay mutually bit-identical wherever the threshold falls.
806/// Engages at t_kv >= MEMRA_FA_DEEP_MIN. The swept floor is 0 = ALWAYS ON where v4 ran
807/// (fa-deep-bench fine grid 96..6144, 2026-08-02: deep flat-or-better at EVERY depth,
808/// 1.01-1.26x, no losing cell — so there is no engagement boundary and no new
809/// capture-recapture edge; the env stays as a sweep/diagnostic seam only).
810/// MEMRA_FA_DEEP=0 is the rollback seam. Read per call so the battery + bench can A/B
811/// within one process (the v2/v3 pattern).
812pub const FA_DEEP_MIN_DEFAULT: usize = 0;
813fn fa_deep_at(t_kv: usize) -> bool {
814    if std::env::var("MEMRA_FA_DEEP").as_deref() == Ok("0") {
815        return false;
816    }
817    let min = std::env::var("MEMRA_FA_DEEP_MIN")
818        .ok()
819        .and_then(|v| v.parse().ok())
820        .unwrap_or(FA_DEEP_MIN_DEFAULT);
821    t_kv >= min
822}
823/// Public twin (kernel-check builds the deep-vs-v4 bit pin; bench sweeps the floor).
824pub fn fa_deep_at_pub(t_kv: usize) -> bool {
825    fa_deep_at(t_kv)
826}
827
828fn fa_v3_active(head_dim: usize) -> bool {
829    // v3's dp4a-K walk reads raw q8_0 K bytes — no e4m3 arm; the fp8-KV arm (MEMRA_KV_FP8)
830    // must fall back like any non-default KV format (the rows_dc stream path asserts on it).
831    fa_v3_on()
832        && head_dim % 128 == 0
833        && kv_cache_formats() == ("q8_0", "q5_1")
834        && !Engine::kv_fp8_on()
835}
836
837/// BATCHED-TICK increment 2 (2026-08-01): true iff a row at this t_kv would take the v4
838/// eager arm in `fa_decode_kvmod`'s dispatch — the exact precondition for the z-batched
839/// `fa_decode_vec_q_seqs_v4` twin to reproduce its per-seq program bit-identically.
840/// Mirrors the kvmod predicates: vec on + above the vec floor + hd256 + inside the v4
841/// window + the PRODUCTION v4 body (the noB3/stage phase probes are wrong-output) + the
842/// default flash module (no fp8-KV g-module). Callers must ALSO group rows on one
843/// `fa_split_keys` rung (the rows-twins' straddle law) before batching.
844pub fn fa_seqs_eligible(t_kv: usize, head_dim: usize) -> bool {
845    std::env::var("MEMRA_NO_FA_VEC").is_err()
846        && t_kv >= fa_vec_min_tkv()
847        && head_dim == 256
848        && fa_v4_at(t_kv)
849        && !matches!(fa_v4_mode(), "noB3" | "stage")
850        && !Engine::kv_fp8_on()
851}
852/// Public twin of the crate-private split ladder (kernel-check builds the seqs-vs-loop pin).
853pub fn fa_split_keys_pub(t_kv: usize, n_head_kv: usize) -> usize {
854    fa_split_keys(t_kv, n_head_kv)
855}
856
857/// A raw pinned (page-locked, CACHEABLE — flags=0, not write-combined) host allocation for
858/// DtoH staging. cudarc's `alloc_pinned` uses CU_MEMHOSTALLOC_WRITECOMBINED, which is right for
859/// HtoD streams but pathologically slow for host READS — the router readback is host-read-heavy,
860/// so we allocate through `result::malloc_host` with flags=0 directly.
861struct PinnedStage {
862    ptr: *mut u8,
863    cap: usize,
864}
865unsafe impl Send for PinnedStage {}
866impl PinnedStage {
867    fn new(cap: usize) -> Result<Self, Box<dyn std::error::Error>> {
868        let ptr = unsafe { cudarc::driver::result::malloc_host(cap, 0)? } as *mut u8;
869        Ok(PinnedStage { ptr, cap })
870    }
871}
872impl Drop for PinnedStage {
873    fn drop(&mut self) {
874        let _ = unsafe { cudarc::driver::result::free_host(self.ptr as _) };
875    }
876}
877
878/// Number of pass-1 blocks for the parallel argmax (fan-out across SMs to saturate HBM). 256 blocks
879/// x 256 threads = 65536 threads covering the 248K-vocab scan in ~4 strided loads/thread.
880pub const ARGMAX_NB: usize = 256;
881
882/// crate-visible alias for the batched FA3 shim entry (hybrid_forward's batch arm).
883pub(crate) use memra_fa3_vl as fa3_vl_raw;
884
885unsafe extern "C" {
886    /// FA3 v10 shim (cu/fa3_prefill.cu): TMA-swizzled wgmma FA, fresh causal hd256.
887    fn memra_fa3_prefill(
888        q16: *const core::ffi::c_void,
889        k16: *const core::ffi::c_void,
890        v16: *const core::ffi::c_void,
891        o: *mut f32,
892        t: i32,
893        h: i32,
894        hkv: i32,
895        d: i32,
896        scale: f32,
897        stream: *mut core::ffi::c_void,
898    ) -> i32;
899    /// batched varlen twin: host arrays of device pointers per seq (B <= 8).
900    pub(crate) fn memra_fa3_vl(
901        q16s: *const *const core::ffi::c_void,
902        k16s: *const *const core::ffi::c_void,
903        v16s: *const *const core::ffi::c_void,
904        os: *const *mut f32,
905        ts: *const i32,
906        b: i32,
907        h: i32,
908        hkv: i32,
909        d: i32,
910        scale: f32,
911        stream: *mut core::ffi::c_void,
912    ) -> i32;
913}
914
915/// STAGE-2 GROUPED DECODE: 8 expert weight-block device pointers passed BY VALUE as one kernel
916/// param (matches the CUDA `wptr8_t` struct: 8x 64-bit pointers, `#[repr(C)]` => identical
917/// layout). The pointers are SLRU cache-slot base addresses — fixed for the engine's lifetime
918/// (slots are never re-allocated), so passing raw values is stable across the launch.
919#[repr(C)]
920#[derive(Clone, Copy)]
921pub struct WPtr8(pub [u64; 8]);
922unsafe impl cudarc::driver::DeviceRepr for WPtr8 {}
923
924/// task #18 varlen GDN: per-seq args for gdn_chunk_{state,output}_mma_vl — one launch
925/// runs all B<=8 sequences' K4/K5 (CUDA `gdnseq_t`/`gdnvl_t`, layout-identical repr(C)).
926/// Raw addresses are valid for the launch: every referenced buffer outlives the call and
927/// all work is on the single compute stream (same discipline as the f16 GEMM FFI).
928#[repr(C)]
929#[derive(Clone, Copy, Default)]
930pub struct GdnSeqVl {
931    pub kb16: u64,
932    pub gcum: u64,
933    pub beta: u64,
934    pub u: u64,
935    pub wb16: u64,
936    pub y: u64,
937    pub ssnap: u64,
938    pub state_in: u64,
939    pub state_out: u64,
940    pub q: u64,
941    pub p: u64,
942    pub o: u64,
943    pub k: u64,
944    pub v: u64,
945    pub g: u64,
946    pub a: u64,
947    pub w: u64,
948    pub t: i32,
949    pub nc: i32,
950}
951unsafe impl cudarc::driver::DeviceRepr for GdnSeqVl {}
952#[repr(C)]
953#[derive(Clone, Copy)]
954pub struct GdnVl8(pub [GdnSeqVl; 8]);
955unsafe impl cudarc::driver::DeviceRepr for GdnVl8 {}
956
957/// task #22: per-seq wgmma-fused extras (CUDA `gdnw_t`/`gdnwvl_t`) — qb16 mirror +
958/// pre-masked Pb16, riding NEXT TO GdnSeqVl so the base struct stays untouched.
959#[repr(C)]
960#[derive(Clone, Copy, Default)]
961pub struct GdnWVl {
962    pub qb16: u64,
963    pub pb16: u64,
964}
965unsafe impl cudarc::driver::DeviceRepr for GdnWVl {}
966#[repr(C)]
967#[derive(Clone, Copy)]
968pub struct GdnWVl8(pub [GdnWVl; 8]);
969unsafe impl cudarc::driver::DeviceRepr for GdnWVl8 {}
970
971/// task #18 increment 3: per-seq PREP/TAIL args (CUDA `gdnprep_t`/`gdnprepvl_t`).
972#[repr(C)]
973#[derive(Clone, Copy, Default)]
974pub struct GdnPrepVl {
975    pub qkv: u64,
976    pub conv_state: u64,
977    pub conv_out: u64,
978    pub q_g: u64,
979    pub k_g: u64,
980    pub v_g: u64,
981    pub q_l2: u64,
982    pub k_l2: u64,
983    pub beta_raw: u64,
984    pub alpha: u64,
985    pub beta: u64,
986    pub g_log: u64,
987    pub o: u64,
988    pub z: u64,
989    pub gn: u64,
990    pub gn16: u64,
991    pub kb16: u64,
992    pub qb16: u64,
993    pub t: i32,
994    pub pad: i32,
995}
996unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl {}
997#[repr(C)]
998#[derive(Clone, Copy)]
999pub struct GdnPrepVl8(pub [GdnPrepVl; 8]);
1000unsafe impl cudarc::driver::DeviceRepr for GdnPrepVl8 {}
1001
1002/// task #18 (attn side): per-seq varlen FA args (CUDA `faseq_t`/`favl_t`).
1003#[repr(C)]
1004#[derive(Clone, Copy, Default)]
1005pub struct FaSeqVl {
1006    pub q: u64,
1007    pub k16: u64,
1008    pub v16: u64,
1009    pub o: u64,
1010    pub kf: u64,
1011    pub vf: u64,
1012    pub t: i32,
1013    pub pad: i32,
1014}
1015unsafe impl cudarc::driver::DeviceRepr for FaSeqVl {}
1016#[repr(C)]
1017#[derive(Clone, Copy)]
1018pub struct FaVl8(pub [FaSeqVl; 8]);
1019unsafe impl cudarc::driver::DeviceRepr for FaVl8 {}
1020
1021/// task #18 (attn pre-FA): per-seq split/norm/rope/append args (CUDA `attnpre_t`).
1022#[repr(C)]
1023#[derive(Clone, Copy, Default)]
1024pub struct AttnPreVl {
1025    pub qf: u64,
1026    pub kf: u64,
1027    pub vf: u64,
1028    pub q: u64,
1029    pub gate: u64,
1030    pub qn: u64,
1031    pub kn: u64,
1032    pub kc: u64,
1033    pub vc: u64,
1034    pub t: i32,
1035    pub pad: i32,
1036}
1037unsafe impl cudarc::driver::DeviceRepr for AttnPreVl {}
1038#[repr(C)]
1039#[derive(Clone, Copy)]
1040pub struct AttnPreVl8(pub [AttnPreVl; 8]);
1041unsafe impl cudarc::driver::DeviceRepr for AttnPreVl8 {}
1042
1043/// task #18 increment 2: one sequence's FULL chunk-buffer set (alloc-only; the
1044/// varlen K1-K5 chain fills them).
1045pub struct GdnChunkBufs {
1046    pub gcum: CudaSlice<f32>,
1047    pub a: CudaSlice<f32>,
1048    pub p: CudaSlice<f32>,
1049    pub u: CudaSlice<f32>,
1050    pub w: CudaSlice<f32>,
1051    pub kb16: CudaSlice<u8>,
1052    pub wb16: CudaSlice<u8>,
1053    pub y16: CudaSlice<u8>,
1054    pub ssnap16: CudaSlice<u8>,
1055    pub qb16: CudaSlice<u8>,
1056    pub pb16: CudaSlice<u8>,
1057    pub o: CudaSlice<f32>,
1058    pub t: usize,
1059    pub nc: usize,
1060}
1061
1062/// STAGE-2 GROUPED DECODE: the 8 routed-expert weights by value (CUDA `f32x8_t`).
1063#[repr(C)]
1064#[derive(Clone, Copy)]
1065pub struct F32x8(pub [f32; 8]);
1066unsafe impl cudarc::driver::DeviceRepr for F32x8 {}
1067
1068/// Harness timing contract: wall nanos of the LAST generate/generate_spec prompt prime on this
1069/// process. Bench binaries read it right after the call to print gen-only throughput without the
1070/// prime-subtraction hack (which amplifies prime jitter into the gen number at long prompts).
1071pub static PRIME_NANOS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1072
1073impl Engine {
1074    pub fn new(ordinal: usize) -> Result<Self, Box<dyn std::error::Error>> {
1075        let gpu = memra_runtime::Gpu::new(ordinal)?;
1076        // ARCH GUARD (unified dual-arch engine): the fatbins carry single-arch SASS, so a
1077        // binary/device mismatch otherwise dies at first module load with an opaque CUDA
1078        // error. Fail early with the rebuild hint instead. MEMRA_ARCH_CHECK=0 skips.
1079        if std::env::var("MEMRA_ARCH_CHECK").as_deref() != Ok("0") {
1080            use cudarc::driver::sys::CUdevice_attribute_enum as A;
1081            let (maj, min) = cudarc::driver::result::device::get(ordinal as i32)
1082                .and_then(|d| unsafe {
1083                    Ok((
1084                        cudarc::driver::result::device::get_attribute(
1085                            d,
1086                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
1087                        )?,
1088                        cudarc::driver::result::device::get_attribute(
1089                            d,
1090                            A::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
1091                        )?,
1092                    ))
1093                })
1094                .unwrap_or((0, 0));
1095            let built = env!("MEMRA_BUILT_CUDA_ARCH");
1096            let ok = matches!(
1097                (built, maj, min),
1098                ("120a", 12, 0) | ("120a", 12, 1) | ("100a", 10, 0) | ("90a", 9, 0) | ("89", 8, 9)
1099            );
1100            if !ok {
1101                return Err(format!(
1102                    "memra was built for sm_{built} but device {ordinal} reports compute \
1103                     capability {maj}.{min}. Rebuild on this machine (MEMRA_CUDA_ARCH \
1104                     auto-detects the GPU) or set MEMRA_ARCH_CHECK=0 to bypass."
1105                )
1106                .into());
1107            }
1108        }
1109        // Default async-pool RELEASE_THRESHOLD is 0: freed blocks return to the OS at every
1110        // sync, so cuMemAllocAsync NODES inside captured graphs re-map memory on EVERY
1111        // cuGraphLaunch (measured 226us/launch on the gemma graph door, 2026-07-23 osrt).
1112        // Pinning the threshold keeps the pool cached -> alloc nodes become pointer bumps.
1113        unsafe {
1114            use cudarc::driver::sys;
1115            let dev: sys::CUdevice = ordinal as sys::CUdevice;
1116            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1117            if sys::cuDeviceGetDefaultMemPool(&mut pool, dev) == sys::CUresult::CUDA_SUCCESS {
1118                let mut thresh: u64 = u64::MAX;
1119                let _ = sys::cuMemPoolSetAttribute(
1120                    pool,
1121                    sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
1122                    &mut thresh as *mut u64 as *mut core::ffi::c_void,
1123                );
1124            }
1125        }
1126        let module = gpu.ctx.load_module(Ptx::from_binary(FATBIN.to_vec()))?;
1127        let hybrid = gpu
1128            .ctx
1129            .load_module(Ptx::from_binary(HYBRID_FATBIN.to_vec()))?;
1130        let qmatvec = gpu
1131            .ctx
1132            .load_module(Ptx::from_binary(QMATVEC_FATBIN.to_vec()))?;
1133        let flash = gpu
1134            .ctx
1135            .load_module(Ptx::from_binary(flash_fatbin_bytes().to_vec()))?;
1136        let gemm = gpu
1137            .ctx
1138            .load_module(Ptx::from_binary(gemm_fatbin_bytes().into_owned()))?;
1139        let router = gpu
1140            .ctx
1141            .load_module(Ptx::from_binary(ROUTER_FATBIN.to_vec()))?;
1142        let sample = gpu
1143            .ctx
1144            .load_module(Ptx::from_binary(SAMPLE_FATBIN.to_vec()))?;
1145        let copy_stream = gpu.ctx.new_stream()?;
1146        // DECODE EVENT-TRACKING ELISION — DEFAULT ON (2026-07-05; MEMRA_EVT=1 = escape hatch).
1147        // cudarc is in multi-stream mode (main stream +
1148        // copy_stream are both created streams), so with tracking on EVERY launch arg records a
1149        // read/write CudaEvent and inserts cuStreamWaitEvent on prior events. On the 35B MoE decode
1150        // that is ~19k cuStreamWaitEvent + ~9k cuEventRecord + ~6k event create/destroy per token
1151        // (~7 ms/tok host time, measured nsys 2026-07-04 g7e), and +4.6% measured on 27B decode —
1152        // protecting NOTHING: every hot-path kernel/memcpy runs on the ONE gpu.stream.
1153        // CROSS-STREAM HAZARD AUDIT: MoeSlotCache in-memory prefetch uses copy_stream. Every
1154        // overwrite explicitly records the prior compute point and makes copy_stream wait; every
1155        // consumer explicitly waits for the copy completion event. The opt-in positioned-read
1156        // proof stays on gpu.stream and retains an explicit event solely to guard pinned-source
1157        // reuse. Graph-capture sites use only gpu.stream, so these handoffs never rely on cudarc's
1158        // implicit event tracking.
1159        // SAFETY: single-stream ordering is total; the runtime mem-pool is configured with
1160        // internal-dependency reuse (memra-runtime), so alloc reuse is stream-ordered too.
1161        if std::env::var("MEMRA_EVT")
1162            .map(|v| v == "1")
1163            .unwrap_or(false)
1164        {
1165            // escape hatch: keep cudarc's implicit cross-stream event tracking.
1166        } else {
1167            unsafe {
1168                gpu.ctx.disable_event_tracking();
1169            }
1170        }
1171        Ok(Self {
1172            gpu,
1173            module,
1174            hybrid,
1175            qmatvec,
1176            flash,
1177            flash_g: std::sync::OnceLock::new(),
1178            gemm,
1179            router,
1180            sample,
1181            moe_cache: Mutex::new(None),
1182            moe_cache_layout: Mutex::new(None),
1183            copy_stream,
1184            capture_keep_on: std::sync::atomic::AtomicBool::new(false),
1185            verify_exact: std::sync::atomic::AtomicBool::new(false),
1186            capture_keep: Mutex::new(Vec::new()),
1187            argmax_partials: Mutex::new(None),
1188            prime_deqw_ws: Mutex::new(None),
1189            router_stage: Mutex::new(None),
1190            fp8_scratch: Mutex::new(None),
1191            fa_vf16_scratch: Mutex::new(None),
1192            fa_part_pool: Mutex::new(None),
1193            fa_part_retired: Mutex::new(Vec::new()),
1194            fn_cache: Mutex::new(Default::default()),
1195            f16_scratch: Mutex::new(None),
1196            #[cfg(memra_cutlass)]
1197            cutlass_scratch: Mutex::new(None),
1198        })
1199    }
1200
1201    pub fn ctx(&self) -> &Arc<CudaContext> {
1202        &self.gpu.ctx
1203    }
1204
1205    /// Bytes the async pool holds MAPPED but NOT LIVE (reserved - used), i.e. freed blocks
1206    /// parked in the pool because `Engine::new` pins RELEASE_THRESHOLD to u64::MAX above.
1207    ///
1208    /// Why this is a public engine surface: `mem_get_info`'s `free` DOES NOT SEE these bytes —
1209    /// they are mapped to this process, so `free` counts them as gone, yet the very next
1210    /// `alloc_u8` is satisfied from them without touching `free` at all. Any admission or
1211    /// budget decision that reads `free` alone therefore under-counts real headroom by exactly
1212    /// this amount. Effective allocatable headroom is `free + pool_cached_bytes()`.
1213    ///
1214    /// MEASURED SIZE (c=64 serve burst, 9B NVFP4 + draft, 24GB card, 2026-08-06): 34-89 MB
1215    /// during the burst — SMALL. The admission gate adds it because a term that can only ever
1216    /// under-count headroom does not belong in a gate that queues real work, but the honest
1217    /// reading of this number is that pool caching is NOT where a long-running server's VRAM
1218    /// hides on this path: reserved ~= used throughout, so the memory the driver reports as
1219    /// gone is genuinely LIVE (see `pool_reserved_used` for the diagnostic pair).
1220    ///
1221    /// Returns 0 if the pool cannot be queried (never a false-positive headroom claim).
1222    pub fn pool_cached_bytes(&self) -> usize {
1223        let (reserved, used) = self.pool_reserved_used();
1224        reserved.saturating_sub(used)
1225    }
1226
1227    /// Raw async-pool occupancy: (RESERVED_MEM_CURRENT, USED_MEM_CURRENT) in bytes. Reserved is
1228    /// what the pool has mapped from the driver; used is what is live inside it. Exposed for
1229    /// admission/VRAM diagnostics — the pair distinguishes "memory is parked in the pool and
1230    /// `free` cannot see it" (reserved >> used) from "memory is genuinely held live by some
1231    /// owner" (reserved ~= used), which are opposite bugs with opposite fixes.
1232    /// (0, 0) if the pool cannot be queried.
1233    pub fn pool_reserved_used(&self) -> (usize, usize) {
1234        use cudarc::driver::sys;
1235        unsafe {
1236            let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
1237            if sys::cuDeviceGetDefaultMemPool(&mut pool, self.gpu.ctx.ordinal() as sys::CUdevice)
1238                != sys::CUresult::CUDA_SUCCESS
1239            {
1240                return (0, 0);
1241            }
1242            let (mut reserved, mut used) = (0u64, 0u64);
1243            if sys::cuMemPoolGetAttribute(
1244                pool,
1245                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
1246                &mut reserved as *mut u64 as *mut core::ffi::c_void,
1247            ) != sys::CUresult::CUDA_SUCCESS
1248            {
1249                return (0, 0);
1250            }
1251            if sys::cuMemPoolGetAttribute(
1252                pool,
1253                sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
1254                &mut used as *mut u64 as *mut core::ffi::c_void,
1255            ) != sys::CUresult::CUDA_SUCCESS
1256            {
1257                return (0, 0);
1258            }
1259            (reserved as usize, used as usize)
1260        }
1261    }
1262
1263    /// Ambient stream (by value since M1-PP2 increment 2): the thread's pp2 stage stream
1264    /// when a stage scope is active, else the main compute stream — see `Gpu::stream`.
1265    pub fn stream(&self) -> Arc<CudaStream> {
1266        self.gpu.stream()
1267    }
1268    /// FP8-GLOBALS switch (MEMRA_GEMMA_GKV, default ON): gemma global (hd512) layers keep
1269    /// their KV in e4m3 — the dequant-latency arc (HANDOVER). Windowed layers stay q8_0/q5_1.
1270    pub fn gkv_on() -> bool {
1271        memra_kv::gkv_on()
1272    }
1273
1274    /// FP8-WINDOWED switch (MEMRA_GEMMA_WKV — measured 2026-07-12 in a validity-gated
1275    /// window: 1.7k 174.1-174.4 vs 168.6-169.4 default (+3%), 4.9k 158.7-160.4; vs llama
1276    /// same-window 159.5-160.2 / 140.6 = 1.09x / 1.13x): gemma windowed (hd256 SWA)
1277    /// layers hold e4m3 KV and ride the format-aware v4 lane from the kf8vf8 module.
1278    /// SERVING-MODE DEFAULT (2026-07-12, the 31B spec unlock): fp8-windowed KV GUTS the
1279    /// MTP drafter's acceptance — its single swa attention reads the windowed cache and
1280    /// e4m3 noise flips its argmaxes (31B short accept .758 -> 1.000 with q8/q5, spec 88
1281    /// -> 122.7 vs llama-mtp 112; depth .59 -> .78; 26B depth .57 -> .89). So the default
1282    /// keys on serving intent: SPEC serving (MEMRA_DRAFT set) -> OFF, plain -> ON (its
1283    /// depth-plain +3% stands). Explicit MEMRA_GEMMA_WKV always wins. GKV (globals) stays
1284    /// ON for both — no acceptance cost measured.
1285    pub fn wkv_on() -> bool {
1286        memra_kv::wkv_on()
1287    }
1288
1289    /// QWEN FP8-KV switch (MEMRA_KV_FP8 explicit; else the per-model KV_FP8_FORCE door set
1290    /// at model load; else OFF). Non-gemma full-attn layers hold e4m3 K/V via the kf8vf8
1291    /// module. Per-model verdict 2026-07-12: 9B +0.7-4% scaling with depth, 27B flat,
1292    /// 35B −2% (fp8 format-gates its v3 dp4a lane) — so the 9B class defaults ON
1293    /// (adopted 2026-07-28 with the deferred acceptance battery), others stay OFF.
1294    pub fn kv_fp8_on() -> bool {
1295        memra_kv::kv_fp8_on()
1296    }
1297
1298    /// fa kernel routed by head_dim: hd512 (gemma globals) resolves from the kf8vf8 module
1299    /// when the fp8-globals arm is on; everything else from the default flash module.
1300    fn fa_func(&self, name: &str, head_dim: usize) -> CudaFunction {
1301        if head_dim == 512 && Self::gkv_on() {
1302            self.func_g(name)
1303        } else {
1304            self.func(name)
1305        }
1306    }
1307
1308    /// Kernel from the FP8-GLOBALS (kf8vf8) flash module — gemma global-layer arm only.
1309    /// Format-AGNOSTIC kernels (e.g. fa_decode_combine_f32) are not compiled into the
1310    /// per-format fatbins; fall back to the base modules for those.
1311    fn func_g(&self, name: &str) -> CudaFunction {
1312        let m = self.flash_g.get_or_init(|| {
1313            self.gpu
1314                .ctx
1315                .load_module(cudarc::nvrtc::Ptx::from_binary(
1316                    FLASH_FATBIN_KF8VF8.to_vec(),
1317                ))
1318                .expect("load kf8vf8 flash fatbin (fp8-globals arm)")
1319        });
1320        let key = format!("g:{name}");
1321        if let Some(f) = self.fn_cache.lock().unwrap().get(&key) {
1322            return f.clone();
1323        }
1324        let f = match m.load_function(name) {
1325            Ok(f) => f,
1326            Err(_) => self.func(name),
1327        };
1328        self.fn_cache.lock().unwrap().insert(key, f.clone());
1329        f
1330    }
1331
1332    fn func(&self, name: &str) -> CudaFunction {
1333        // Resolution cache: cuModuleGetFunction fails inside a CUDA-graph capture region,
1334        // so capture-time lookups MUST be host-memory hits (warmups populate the cache).
1335        if let Some(f) = self.fn_cache.lock().unwrap().get(name) {
1336            return f.clone();
1337        }
1338        let f = self
1339            .module
1340            .load_function(name)
1341            .or_else(|_| self.hybrid.load_function(name))
1342            .or_else(|_| self.qmatvec.load_function(name))
1343            .or_else(|_| self.flash.load_function(name))
1344            .or_else(|_| self.gemm.load_function(name))
1345            .or_else(|_| self.router.load_function(name))
1346            .or_else(|_| self.sample.load_function(name))
1347            .unwrap_or_else(|_| panic!("kernel {name} not in any fatbin"));
1348        self.fn_cache
1349            .lock()
1350            .unwrap()
1351            .insert(name.to_string(), f.clone());
1352        f
1353    }
1354
1355    /// Scatter trimmed draft logits into full-vocab space: dst = -inf everywhere, then
1356    /// dst[d2t[i]] = src[i]. Two launches (fill, scatter) — no grid-wide sync needed.
1357    pub fn scatter_trim_logits(
1358        &self,
1359        src: &CudaSlice<f32>,
1360        d2t: &CudaSlice<u32>,
1361        dst: &mut CudaSlice<f32>,
1362        d_vocab: usize,
1363        n_vocab: usize,
1364    ) -> Result<(), Box<dyn std::error::Error>> {
1365        let f1 = self.func("scatter_trim_logits_f32");
1366        let f2 = self.func("scatter_trim_logits_pass2_f32");
1367        let (dv, nv) = (d_vocab as i32, n_vocab as i32);
1368        let cfg1 = LaunchConfig {
1369            grid_dim: (256, 1, 1),
1370            block_dim: (256, 1, 1),
1371            shared_mem_bytes: 0,
1372        };
1373        let __s_b1 = self.gpu.stream();
1374        let mut b1 = __s_b1.launch_builder(&f1);
1375        b1.arg(src).arg(d2t).arg(&mut *dst).arg(&dv).arg(&nv);
1376        unsafe {
1377            b1.launch(cfg1)?;
1378        }
1379        let cfg2 = LaunchConfig {
1380            grid_dim: (d_vocab.div_ceil(256) as u32, 1, 1),
1381            block_dim: (256, 1, 1),
1382            shared_mem_bytes: 0,
1383        };
1384        let __s_b2 = self.gpu.stream();
1385        let mut b2 = __s_b2.launch_builder(&f2);
1386        b2.arg(src).arg(d2t).arg(&mut *dst).arg(&dv);
1387        unsafe {
1388            b2.launch(cfg2)?;
1389        }
1390        Ok(())
1391    }
1392
1393    // ---- FILTERED-SPEC (feat/filtered-spec): top-k/p/min-p transforms applied symmetrically
1394    // to p and q — rejection sampling stays distribution-exact for the filtered target. ----
1395
1396    /// Per-row filtered-softmax stats: out[r] = (threshold_e, renorm_mass_e, row_max) for the
1397    /// filter (top_k, top_p, min_p) at `temp`. Rows index into x with row_stride f32s.
1398    #[allow(clippy::too_many_arguments)]
1399    pub fn filter_stats(
1400        &self,
1401        x: &CudaSlice<f32>,
1402        row_stride: usize,
1403        rows: &CudaSlice<i32>,
1404        out_th: &mut CudaSlice<f32>,
1405        out_z: &mut CudaSlice<f32>,
1406        out_max: &mut CudaSlice<f32>,
1407        n: usize,
1408        nrow: usize,
1409        temp: f32,
1410        top_k: i32,
1411        top_p: f32,
1412        min_p: f32,
1413    ) -> Result<(), Box<dyn std::error::Error>> {
1414        let f = self.func("filter_stats_f32");
1415        let (ni, nr, rs) = (n as i32, nrow as i32, row_stride as i64);
1416        let cfg = LaunchConfig {
1417            grid_dim: (nrow as u32, 1, 1),
1418            block_dim: (1024, 1, 1),
1419            shared_mem_bytes: 0,
1420        };
1421        let __s_b = self.gpu.stream();
1422        let mut b = __s_b.launch_builder(&f);
1423        b.arg(x)
1424            .arg(&rs)
1425            .arg(rows)
1426            .arg(&mut *out_th)
1427            .arg(&mut *out_z)
1428            .arg(&mut *out_max)
1429            .arg(&ni)
1430            .arg(&nr)
1431            .arg(&temp)
1432            .arg(&top_k)
1433            .arg(&top_p)
1434            .arg(&min_p);
1435        unsafe {
1436            b.launch(cfg)?;
1437        }
1438        Ok(())
1439    }
1440
1441    /// out[pair] = filtered-softmax prob of ids[pair] in row rows[pair] (th/z per PAIR).
1442    #[allow(clippy::too_many_arguments)]
1443    pub fn softmax_gather_filtered(
1444        &self,
1445        x: &CudaSlice<f32>,
1446        row_stride: usize,
1447        ids: &CudaSlice<u32>,
1448        rows: &CudaSlice<i32>,
1449        th: &CudaSlice<f32>,
1450        z: &CudaSlice<f32>,
1451        out: &mut CudaSlice<f32>,
1452        n: usize,
1453        npair: usize,
1454        temp: f32,
1455    ) -> Result<(), Box<dyn std::error::Error>> {
1456        let f = self.func("softmax_gather_filtered_f32");
1457        let (ni, np, rs) = (n as i32, npair as i32, row_stride as i64);
1458        let cfg = LaunchConfig {
1459            grid_dim: (npair as u32, 1, 1),
1460            block_dim: (256, 1, 1),
1461            shared_mem_bytes: 0,
1462        };
1463        let __s_b = self.gpu.stream();
1464        let mut b = __s_b.launch_builder(&f);
1465        b.arg(x)
1466            .arg(&rs)
1467            .arg(ids)
1468            .arg(rows)
1469            .arg(th)
1470            .arg(z)
1471            .arg(&mut *out)
1472            .arg(&ni)
1473            .arg(&np)
1474            .arg(&temp);
1475        unsafe {
1476            b.launch(cfg)?;
1477        }
1478        Ok(())
1479    }
1480
1481    /// Filtered residual sample: token ~ norm(max(0, fp - fq)) with fp/fq the filtered softmaxes.
1482    #[allow(clippy::too_many_arguments)]
1483    pub fn residual_sample_filtered(
1484        &self,
1485        p: &CudaSlice<f32>,
1486        q: Option<&CudaSlice<f32>>,
1487        n: usize,
1488        temp: f32,
1489        seed: u64,
1490        stream_pos: u32,
1491        p_stats: (f32, f32, f32),
1492        q_stats: (f32, f32, f32),
1493        out_tok: &mut CudaSlice<u32>,
1494    ) -> Result<(), Box<dyn std::error::Error>> {
1495        let f = self.func("residual_sample_filtered_f32");
1496        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1497        let has_q: i32 = q.is_some() as i32;
1498        let qbuf = q.unwrap_or(p);
1499        let (pm, pth, pz) = p_stats;
1500        let (qm, qth, qz) = q_stats;
1501        let cfg = LaunchConfig {
1502            grid_dim: (1, 1, 1),
1503            block_dim: (1024, 1, 1),
1504            shared_mem_bytes: 0,
1505        };
1506        let __s_b = self.gpu.stream();
1507        let mut b = __s_b.launch_builder(&f);
1508        b.arg(p)
1509            .arg(qbuf)
1510            .arg(&has_q)
1511            .arg(&ni)
1512            .arg(&temp)
1513            .arg(&slo)
1514            .arg(&shi)
1515            .arg(&stream_pos)
1516            .arg(&pm)
1517            .arg(&pth)
1518            .arg(&pz)
1519            .arg(&qm)
1520            .arg(&qth)
1521            .arg(&qz)
1522            .arg(&mut *out_tok);
1523        unsafe {
1524            b.launch(cfg)?;
1525        }
1526        Ok(())
1527    }
1528
1529    /// Gumbel-max draw from the FILTERED distribution (masked perturb; argmax after).
1530    #[allow(clippy::too_many_arguments)]
1531    pub fn gumbel_perturb_filtered(
1532        &self,
1533        x: &CudaSlice<f32>,
1534        y: &mut CudaSlice<f32>,
1535        n: usize,
1536        seed: u64,
1537        stream_pos: u32,
1538        temp: f32,
1539        row_max: f32,
1540        th: f32,
1541    ) -> Result<(), Box<dyn std::error::Error>> {
1542        let f = self.func("gumbel_perturb_filtered_f32");
1543        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
1544        let cfg = LaunchConfig {
1545            grid_dim: (n.div_ceil(256) as u32, 1, 1),
1546            block_dim: (256, 1, 1),
1547            shared_mem_bytes: 0,
1548        };
1549        let __s_b = self.gpu.stream();
1550        let mut b = __s_b.launch_builder(&f);
1551        b.arg(x)
1552            .arg(&mut *y)
1553            .arg(&ni)
1554            .arg(&slo)
1555            .arg(&shi)
1556            .arg(&stream_pos)
1557            .arg(&temp)
1558            .arg(&row_max)
1559            .arg(&th);
1560        unsafe {
1561            b.launch(cfg)?;
1562        }
1563        Ok(())
1564    }
1565
1566    /// Keskar penalties applied IN PLACE to a logits buffer: history token ids get
1567    /// rep-divided/multiplied + freq*count + presence subtracted. Symmetric p/q usage keeps
1568    /// filtered rejection sampling exact for the penalized target.
1569    #[allow(clippy::too_many_arguments)]
1570    pub fn penalize_logits(
1571        &self,
1572        x: &mut CudaSlice<f32>,
1573        hist: &CudaSlice<u32>,
1574        n_hist: usize,
1575        rep: f32,
1576        freq: f32,
1577        present: f32,
1578        n: usize,
1579    ) -> Result<(), Box<dyn std::error::Error>> {
1580        if n_hist == 0 {
1581            return Ok(());
1582        }
1583        let f = self.func("penalize_logits_f32");
1584        let (nh, ni) = (n_hist as i32, n as i32);
1585        let cfg = LaunchConfig {
1586            grid_dim: (n_hist.div_ceil(128) as u32, 1, 1),
1587            block_dim: (128, 1, 1),
1588            shared_mem_bytes: 0,
1589        };
1590        let __s_b = self.gpu.stream();
1591        let mut b = __s_b.launch_builder(&f);
1592        b.arg(&mut *x)
1593            .arg(hist)
1594            .arg(&nh)
1595            .arg(&rep)
1596            .arg(&freq)
1597            .arg(&present)
1598            .arg(&ni);
1599        unsafe {
1600            b.launch(cfg)?;
1601        }
1602        Ok(())
1603    }
1604
1605    /// Rows variant: penalize `nrow` contiguous rows of length n in one launch.
1606    #[allow(clippy::too_many_arguments)]
1607    pub fn penalize_logits_rows(
1608        &self,
1609        x: &mut CudaSlice<f32>,
1610        hist: &CudaSlice<u32>,
1611        n_hist: usize,
1612        rep: f32,
1613        freq: f32,
1614        present: f32,
1615        n: usize,
1616        nrow: usize,
1617    ) -> Result<(), Box<dyn std::error::Error>> {
1618        if n_hist == 0 || nrow == 0 {
1619            return Ok(());
1620        }
1621        let f = self.func("penalize_logits_rows_f32");
1622        let (nh, ni, nr) = (n_hist as i32, n as i32, nrow as i32);
1623        let cfg = LaunchConfig {
1624            grid_dim: (n_hist.div_ceil(128) as u32, nrow as u32, 1),
1625            block_dim: (128, 1, 1),
1626            shared_mem_bytes: 0,
1627        };
1628        let __s_b = self.gpu.stream();
1629        let mut b = __s_b.launch_builder(&f);
1630        b.arg(&mut *x)
1631            .arg(hist)
1632            .arg(&nh)
1633            .arg(&rep)
1634            .arg(&freq)
1635            .arg(&present)
1636            .arg(&ni)
1637            .arg(&nr);
1638        unsafe {
1639            b.launch(cfg)?;
1640        }
1641        Ok(())
1642    }
1643
1644    /// WEIGHT PREFETCH (SOTA item 3, 2026-07-13, DEFAULT ON): during a bandwidth-idle
1645    /// window (the fa launch reads KV, not weights) prefetch the NEXT matvec's
1646    /// decode-plane bytes into L2 so it reads L2-warm. Value-free scheduling op — same
1647    /// class as prefetch_l2 (numerics untouched by construction). Wired only where it
1648    /// measured positive: the E4B dc attn arm (+0.65%). 26B (flat — MoE ffn dominates),
1649    /// 31B (−0.2% — decode at the DRAM wall) and the ffn gate/up cascade (−1% — 29MB/layer
1650    /// floods the fill path) all probed and NOT wired. MEMRA_WPF=0 rollback seam.
1651    pub fn wpf_level() -> u32 {
1652        static ON: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
1653        *ON.get_or_init(|| {
1654            std::env::var("MEMRA_WPF")
1655                .ok()
1656                .and_then(|v| v.parse().ok())
1657                .unwrap_or(1)
1658        })
1659    }
1660
1661    /// PDL launch arm (SOTA item 2, 2026-07-13, DEFAULT ON): the six MEMRA_PDL_ENTRY glue
1662    /// kernels launch through cuLaunchKernelEx with PROGRAMMATIC_STREAM_SERIALIZATION — the
1663    /// grid launches while the predecessor drains (~120ns/kernel back, pdl_probe), the
1664    /// kernels' entry grid-dep sync restores read order (SASS-audited: ACQBULK precedes
1665    /// every LDG in all six). Valid windows: E4B +1.0-1.2% (128 AND 384-tok gens);
1666    /// 26B/31B/qwen flat no-harm. Battery: kernel-check GREEN, run-gen tokens IDENTICAL x3
1667    /// gemma, spec 64/64 E4B K=1/4/8 + 26B/31B K=4 + qwen PASS. Works eager AND under
1668    /// capture (capture encodes native programmatic edges — the post-capture edge-REWRITE
1669    /// arm died: engine graphs hold cuMemAllocAsync alloc nodes, edge edits on those return
1670    /// CUDA_ERROR_NOT_SUPPORTED). MEMRA_PDL=0 rollback seam.
1671    /// See the `verify_exact` field. Scoped by the dflash round around its t=16 verify.
1672    pub fn set_verify_exact(&self, on: bool) {
1673        self.verify_exact
1674            .store(on, std::sync::atomic::Ordering::Relaxed);
1675    }
1676    pub(crate) fn verify_exact_on(&self) -> bool {
1677        self.verify_exact.load(std::sync::atomic::Ordering::Relaxed)
1678    }
1679
1680    /// m=1 norm+rope+append fold seam (2026-07-23): MEMRA_QKV_APPEND=0 reverts to the
1681    /// fused-norm-rope + standalone-append pair (the exact-oracle bisect arm).
1682    pub fn qkv_append_on() -> bool {
1683        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1684        *ON.get_or_init(|| {
1685            std::env::var("MEMRA_QKV_APPEND")
1686                .map(|v| v != "0")
1687                .unwrap_or(true)
1688        })
1689    }
1690
1691    /// PDL wave-B1a seam: the four dense-glue kernels (rms_norm_f32, add_rms_norm_f32,
1692    /// add_scale_rms_norm_q8_1, quantize_q8_1). MEMRA_PDL_WB=0 reverts alone.
1693    pub fn pdl_wb_on() -> bool {
1694        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1695        *ON.get_or_init(|| {
1696            std::env::var("MEMRA_PDL_WB")
1697                .map(|v| v != "0")
1698                .unwrap_or(true)
1699        })
1700    }
1701
1702    /// PDL wave-A seam: the mmvq matvec PDL launches only (the six glue kernels keep
1703    /// their own MEMRA_PDL master seam). MEMRA_PDL_MMVQ=0 reverts wave-A alone — the
1704    /// per-model no-harm bisect knob.
1705    pub fn pdl_mmvq_on() -> bool {
1706        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1707        *ON.get_or_init(|| {
1708            std::env::var("MEMRA_PDL_MMVQ")
1709                .map(|v| v != "0")
1710                .unwrap_or(true)
1711        })
1712    }
1713
1714    pub fn pdl_on() -> bool {
1715        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1716        *ON.get_or_init(|| std::env::var("MEMRA_PDL").map(|v| v != "0").unwrap_or(true))
1717    }
1718
1719    /// Raw CUfunction for a PDL-attributed launch: the SAME kernels.fatbin loaded once more
1720    /// through the raw driver API (cudarc hides its CUfunction handles; a duplicate module
1721    /// of tiny glue kernels is free). Resolved lazily per name, cached process-wide.
1722    /// Fused t=1 q4_0 mr policy: env MEMRA_Q40_MR wins (1/2); else the per-model
1723    /// FUSED_MR1_DEFAULT (dense gemma = mr1, MoE = mr2 — see the static's doc).
1724    fn q40_mr1_on() -> bool {
1725        static Q40MR: std::sync::OnceLock<Option<u32>> = std::sync::OnceLock::new();
1726        match *Q40MR.get_or_init(|| {
1727            std::env::var("MEMRA_Q40_MR")
1728                .ok()
1729                .and_then(|v| v.parse().ok())
1730        }) {
1731            Some(v) => v == 1,
1732            None => crate::FUSED_MR1_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
1733        }
1734    }
1735
1736    /// PDL wave-B2: flash-module PDL functions. `g` selects the kf8vf8 flavor — the
1737    /// caller MUST pass the SAME flavor its builder launch would resolve (fa_func/func_g
1738    /// mirror); the flavors differ semantically (KV byte formats), a wrong-module launch
1739    /// writes wrong bytes silently.
1740    fn pdl_func_flash(
1741        &self,
1742        g: bool,
1743        name: &'static str,
1744    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1745        use cudarc::driver::sys as cu;
1746        // PER-CONTEXT caches (M1-PP2 cross-device fix, 8x box 2026-08-02): CUmodule and
1747        // CUfunction handles are CONTEXT-scoped, and a remote-stage Engine
1748        // (MEMRA_PP_DEVICES=a,b) lives in the other device's primary context. The old
1749        // process-wide OnceLock cache handed stage 1 the dev-a handles, so every stage-1
1750        // launch_pdl* died CUDA_ERROR_INVALID_HANDLE. Key module + function caches by
1751        // this engine's CUcontext; single-context runs behave exactly as before.
1752        static MODS: std::sync::Mutex<Option<std::collections::HashMap<(usize, bool), usize>>> =
1753            std::sync::Mutex::new(None);
1754        static FNS: std::sync::Mutex<
1755            Option<std::collections::HashMap<(usize, bool, &'static str), usize>>,
1756        > = std::sync::Mutex::new(None);
1757        let ctx_key = self.ctx().cu_ctx() as usize;
1758        if let Some(&f) = FNS
1759            .lock()
1760            .unwrap()
1761            .get_or_insert_with(Default::default)
1762            .get(&(ctx_key, g, name))
1763        {
1764            return Ok(f as cu::CUfunction);
1765        }
1766        let module = {
1767            let mut mods = MODS.lock().unwrap();
1768            let map = mods.get_or_insert_with(Default::default);
1769            match map.get(&(ctx_key, g)) {
1770                Some(&m) => m,
1771                None => {
1772                    let m = self.pdl_load_module_in_ctx(if g {
1773                        FLASH_FATBIN_KF8VF8
1774                    } else {
1775                        FLASH_FATBIN
1776                    })?;
1777                    map.insert((ctx_key, g), m);
1778                    m
1779                }
1780            }
1781        };
1782        let cname = std::ffi::CString::new(name)?;
1783        let mut f: cu::CUfunction = std::ptr::null_mut();
1784        let r = unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1785        if r != cu::CUresult::CUDA_SUCCESS {
1786            return Err(format!("pdl_func_flash {name} (g={g}): {r:?}").into());
1787        }
1788        FNS.lock()
1789            .unwrap()
1790            .get_or_insert_with(Default::default)
1791            .insert((ctx_key, g, name), f as usize);
1792        Ok(f)
1793    }
1794
1795    /// Load a fatbin as a raw CUmodule IN THIS ENGINE'S CONTEXT. `cuModuleLoadData` binds
1796    /// the module to the thread's CURRENT context — a remote-stage engine must not
1797    /// inherit the primary's (the INVALID_HANDLE class above). Restores the caller's
1798    /// current context before returning.
1799    fn pdl_load_module_in_ctx(&self, bytes: &[u8]) -> Result<usize, Box<dyn std::error::Error>> {
1800        use cudarc::driver::sys as cu;
1801        let mut prev: cu::CUcontext = std::ptr::null_mut();
1802        unsafe {
1803            cu::cuCtxGetCurrent(&mut prev).result()?;
1804        }
1805        self.ctx().bind_to_thread()?;
1806        let mut m: cu::CUmodule = std::ptr::null_mut();
1807        let r = unsafe { cu::cuModuleLoadData(&mut m, bytes.as_ptr() as *const std::ffi::c_void) };
1808        let restore = if prev.is_null() {
1809            cu::CUresult::CUDA_SUCCESS
1810        } else {
1811            unsafe { cu::cuCtxSetCurrent(prev) }
1812        };
1813        if r != cu::CUresult::CUDA_SUCCESS {
1814            return Err(format!("pdl module load: {r:?}").into());
1815        }
1816        if restore != cu::CUresult::CUDA_SUCCESS {
1817            return Err(format!("pdl module load: ctx restore {restore:?}").into());
1818        }
1819        Ok(m as usize)
1820    }
1821
1822    fn pdl_func(
1823        &self,
1824        name: &'static str,
1825    ) -> Result<cudarc::driver::sys::CUfunction, Box<dyn std::error::Error>> {
1826        use cudarc::driver::sys as cu;
1827        // PER-CONTEXT caches — same M1-PP2 cross-device fix as pdl_func_flash (handles
1828        // are context-scoped; key everything by this engine's CUcontext).
1829        static MODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1830            std::sync::Mutex::new(None);
1831        // PDL wave-A: the mmvq kernels live in the qmatvec fatbin, not kernels.cu — second
1832        // duplicate module, loaded lazily on the first kernels-module miss.
1833        static QMODULES: std::sync::Mutex<Option<std::collections::HashMap<usize, usize>>> =
1834            std::sync::Mutex::new(None);
1835        static FNS: std::sync::Mutex<
1836            Option<std::collections::HashMap<(usize, &'static str), usize>>,
1837        > = std::sync::Mutex::new(None);
1838        let ctx_key = self.ctx().cu_ctx() as usize;
1839        if let Some(&f) = FNS
1840            .lock()
1841            .unwrap()
1842            .get_or_insert_with(Default::default)
1843            .get(&(ctx_key, name))
1844        {
1845            return Ok(f as cu::CUfunction);
1846        }
1847        let module = {
1848            let mut mods = MODULES.lock().unwrap();
1849            let map = mods.get_or_insert_with(Default::default);
1850            match map.get(&ctx_key) {
1851                Some(&m) => m,
1852                None => {
1853                    let m = self.pdl_load_module_in_ctx(FATBIN)?;
1854                    map.insert(ctx_key, m);
1855                    m
1856                }
1857            }
1858        };
1859        let cname = std::ffi::CString::new(name)?;
1860        let mut f: cu::CUfunction = std::ptr::null_mut();
1861        let mut r =
1862            unsafe { cu::cuModuleGetFunction(&mut f, module as cu::CUmodule, cname.as_ptr()) };
1863        if r == cu::CUresult::CUDA_ERROR_NOT_FOUND {
1864            let qmodule = {
1865                let mut mods = QMODULES.lock().unwrap();
1866                let map = mods.get_or_insert_with(Default::default);
1867                match map.get(&ctx_key) {
1868                    Some(&m) => m,
1869                    None => {
1870                        let m = self.pdl_load_module_in_ctx(QMATVEC_FATBIN)?;
1871                        map.insert(ctx_key, m);
1872                        m
1873                    }
1874                }
1875            };
1876            r = unsafe { cu::cuModuleGetFunction(&mut f, qmodule as cu::CUmodule, cname.as_ptr()) };
1877        }
1878        if r != cu::CUresult::CUDA_SUCCESS {
1879            return Err(format!("pdl_func {name}: {r:?}").into());
1880        }
1881        FNS.lock()
1882            .unwrap()
1883            .get_or_insert_with(Default::default)
1884            .insert((ctx_key, name), f as usize);
1885        Ok(f)
1886    }
1887
1888    /// cuLaunchKernelEx with CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION on the
1889    /// compute stream. ONLY legal for kernels whose entry carries MEMRA_PDL_ENTRY.
1890    ///
1891    /// # Safety
1892    /// `params` must match the kernel's exact parameter list (order, types, count) —
1893    /// a mismatch corrupts the launch silently.
1894    /// Flash-module twin of `launch_pdl` — `g` picks the kf8vf8 flavor (must mirror the
1895    /// builder path's fa_func/func_g choice exactly).
1896    ///
1897    /// # Safety
1898    /// Same contract as `launch_pdl`.
1899    unsafe fn launch_pdl_flash(
1900        &self,
1901        g: bool,
1902        name: &'static str,
1903        grid: (u32, u32, u32),
1904        block: (u32, u32, u32),
1905        smem: u32,
1906        params: &mut [*mut std::ffi::c_void],
1907    ) -> Result<(), Box<dyn std::error::Error>> {
1908        use cudarc::driver::sys as cu;
1909        let f = self.pdl_func_flash(g, name)?;
1910        if smem > 0 {
1911            // mirror the builder path's opt-in ceiling (idempotent host-side set).
1912            let r =
1913                unsafe {
1914                    cu::cuFuncSetAttribute(f,
1915                cu::CUfunction_attribute_enum::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
1916                smem as i32)
1917                };
1918            if r != cu::CUresult::CUDA_SUCCESS {
1919                return Err(format!("pdl smem attr {name}: {r:?}").into());
1920            }
1921        }
1922        let mut attr = cu::CUlaunchAttribute {
1923            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1924            pad: [0; 4],
1925            value: cu::CUlaunchAttributeValue {
1926                programmaticStreamSerializationAllowed: 1,
1927            },
1928        };
1929        let cfg = cu::CUlaunchConfig {
1930            gridDimX: grid.0,
1931            gridDimY: grid.1,
1932            gridDimZ: grid.2,
1933            blockDimX: block.0,
1934            blockDimY: block.1,
1935            blockDimZ: block.2,
1936            sharedMemBytes: smem,
1937            hStream: self.gpu.stream().cu_stream(),
1938            attrs: &mut attr,
1939            numAttrs: 1,
1940        };
1941        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1942        if r != cu::CUresult::CUDA_SUCCESS {
1943            return Err(format!("launch_pdl_flash {name}: {r:?}").into());
1944        }
1945        Ok(())
1946    }
1947
1948    unsafe fn launch_pdl(
1949        &self,
1950        name: &'static str,
1951        grid: (u32, u32, u32),
1952        block: (u32, u32, u32),
1953        params: &mut [*mut std::ffi::c_void],
1954    ) -> Result<(), Box<dyn std::error::Error>> {
1955        use cudarc::driver::sys as cu;
1956        let f = self.pdl_func(name)?;
1957        let mut attr = cu::CUlaunchAttribute {
1958            id: cu::CUlaunchAttributeID::CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION,
1959            pad: [0; 4],
1960            value: cu::CUlaunchAttributeValue {
1961                programmaticStreamSerializationAllowed: 1,
1962            },
1963        };
1964        let cfg = cu::CUlaunchConfig {
1965            gridDimX: grid.0,
1966            gridDimY: grid.1,
1967            gridDimZ: grid.2,
1968            blockDimX: block.0,
1969            blockDimY: block.1,
1970            blockDimZ: block.2,
1971            sharedMemBytes: 0,
1972            hStream: self.gpu.stream().cu_stream(),
1973            attrs: &mut attr,
1974            numAttrs: 1,
1975        };
1976        let r = unsafe { cu::cuLaunchKernelEx(&cfg, f, params.as_mut_ptr(), std::ptr::null_mut()) };
1977        if r != cu::CUresult::CUDA_SUCCESS {
1978            return Err(format!("launch_pdl {name}: {r:?}").into());
1979        }
1980        Ok(())
1981    }
1982
1983    /// L2-prefetch a quant weight's DECODE plane (the rp4 split-plane mirror when present —
1984    /// that is what the m<=8 dispatch reads — else the raw block bytes). No-op on float arms.
1985    pub fn prefetch_weight_l2(
1986        &self,
1987        w: &crate::model::GpuTensor,
1988    ) -> Result<(), Box<dyn std::error::Error>> {
1989        if let crate::model::GpuTensor::Quant { bytes, rp4, .. } = w {
1990            let p = rp4.as_ref().unwrap_or(bytes);
1991            self.prefetch_l2(p, p.len())?;
1992        }
1993        Ok(())
1994    }
1995
1996    /// DSpark markov chain ops (dflash lane): gather one bf16 row of a [V, rank] table
1997    /// by the DEVICE token id at tok[idx] into f32.
1998    pub fn gather_row_bf16(
1999        &self,
2000        table: &CudaSlice<u8>,
2001        tok: &CudaSlice<u32>,
2002        idx: usize,
2003        dst: &mut CudaSlice<f32>,
2004        ncols: usize,
2005    ) -> Result<(), Box<dyn std::error::Error>> {
2006        let f = self.func("gather_row_bf16_f32");
2007        let cfg = LaunchConfig {
2008            grid_dim: (ncols.div_ceil(256) as u32, 1, 1),
2009            block_dim: (256, 1, 1),
2010            shared_mem_bytes: 0,
2011        };
2012        let (nc, ix) = (ncols as i32, idx as i32);
2013        let __s_b = self.gpu.stream();
2014        let mut b = __s_b.launch_builder(&f);
2015        b.arg(table).arg(tok).arg(&ix).arg(dst).arg(&nc);
2016        unsafe {
2017            b.launch(cfg)?;
2018        }
2019        Ok(())
2020    }
2021
2022    /// logits[row_off .. row_off+n] += bias[0..n] (in place, one row).
2023    pub fn add_row_inplace(
2024        &self,
2025        logits: &mut CudaSlice<f32>,
2026        bias: &CudaSlice<f32>,
2027        n: usize,
2028        row_off: usize,
2029    ) -> Result<(), Box<dyn std::error::Error>> {
2030        let f = self.func("add_row_inplace_f32");
2031        let cfg = LaunchConfig {
2032            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2033            block_dim: (256, 1, 1),
2034            shared_mem_bytes: 0,
2035        };
2036        let (ni, off) = (n as i32, row_off as i64);
2037        let __s_b = self.gpu.stream();
2038        let mut b = __s_b.launch_builder(&f);
2039        b.arg(logits).arg(bias).arg(&ni).arg(&off);
2040        unsafe {
2041            b.launch(cfg)?;
2042        }
2043        Ok(())
2044    }
2045
2046    /// L2 prefetch of a device byte range (latency-hiding arc; value-free scheduling op).
2047    pub fn prefetch_l2(
2048        &self,
2049        p: &CudaSlice<u8>,
2050        n: usize,
2051    ) -> Result<(), Box<dyn std::error::Error>> {
2052        let f = self.func("prefetch_l2_bytes");
2053        let lines = n.div_ceil(128);
2054        let ni = n as i64;
2055        let cfg = LaunchConfig {
2056            grid_dim: (lines.div_ceil(256) as u32, 1, 1),
2057            block_dim: (256, 1, 1),
2058            shared_mem_bytes: 0,
2059        };
2060        let __s_b = self.gpu.stream();
2061        let mut b = __s_b.launch_builder(&f);
2062        b.arg(p).arg(&ni);
2063        unsafe {
2064            b.launch(cfg)?;
2065        }
2066        Ok(())
2067    }
2068
2069    /// MoE router GEMV (MEMRA_ROUTER_KERNEL): deterministic warp-per-(expert,token) f32 dot.
2070    /// Different FP order than the cuBLAS path it replaces — battery-gated numeric config.
2071    pub fn router_gemv(
2072        &self,
2073        w: &CudaSlice<f32>,
2074        x: &CudaSlice<f32>,
2075        n_embd: usize,
2076        n_experts: usize,
2077        t: usize,
2078    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2079        // float4 v2 probed 2026-07-14: +0.25% but flips near-tie routing (new FP order,
2080        // stream differs) — too small to justify a numeric config change; deleted.
2081        // w8 twin (2026-07-31): on the 132-SM H100 the lone-warp form is 14.8% of the q35
2082        // decode step (latency-bound) — the calculus flipped. MEMRA_ROUTER_V2=0 reverts to
2083        // the warp form (rollback seam; new FP order, battery-arbitrated per model).
2084        let w8 = match std::env::var("MEMRA_ROUTER_V2").as_deref() {
2085            Ok("0") => false,
2086            Ok(_) => true,
2087            Err(_) => ROUTER_W8_DEFAULT.load(std::sync::atomic::Ordering::Relaxed),
2088        };
2089        // FAST-ROUTER batch twin (lane/fast-router, 2026-08-02): at prefill m the per-(e,tok)
2090        // w8 form re-streams both operand rows per output (GEMV program at GEMM shape — the
2091        // concat-prime exactness fix paid -10% q35 board-2048 prefill through it). The batch
2092        // twin (8x8 expert-x-token register tile) is BIT-IDENTICAL per row (same k order,
2093        // same tree, same fold — kernel-check sweeps m=1..2048 on real router weights), so
2094        // the crossover is pure perf, not a numeric config. MIN_T from the on-box sweep
2095        // (research/fast-router-20260802/crossover-router*.jsonl); decode t=1 and small-t
2096        // spec verify keep the plain w8 form. MEMRA_ROUTER_BATCH=0: rollback seam
2097        // (perf-only, bits equal).
2098        let batch = w8 && t >= ROUTER_BATCH_MIN_T && router_batch_on();
2099        self.router_gemv_form(w, x, n_embd, n_experts, t, w8, batch)
2100    }
2101
2102    /// Form-explicit router GEMV launch (kernel-check bit-identity gate + crossover bench
2103    /// force both forms; `batch` requires `w8`).
2104    pub fn router_gemv_form(
2105        &self,
2106        w: &CudaSlice<f32>,
2107        x: &CudaSlice<f32>,
2108        n_embd: usize,
2109        n_experts: usize,
2110        t: usize,
2111        w8: bool,
2112        batch: bool,
2113    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2114        debug_assert!(!batch || w8, "batch twin exists for the w8 form only");
2115        let mut y = self.alloc_uninit::<f32>(t * n_experts)?;
2116        let f = if batch {
2117            self.func("router_gemv_f32_w8_batch")
2118        } else if w8 {
2119            self.func("router_gemv_f32_w8")
2120        } else {
2121            self.func("router_gemv_f32")
2122        };
2123        let (ne, nx, ti) = (n_embd as i32, n_experts as i32, t as i32);
2124        let cfg = if batch {
2125            LaunchConfig {
2126                grid_dim: (n_experts.div_ceil(8) as u32, t.div_ceil(8) as u32, 1),
2127                block_dim: (32, 8, 1),
2128                shared_mem_bytes: 0,
2129            }
2130        } else {
2131            LaunchConfig {
2132                grid_dim: (n_experts as u32, t as u32, 1),
2133                block_dim: (32, if w8 { 8 } else { 1 }, 1),
2134                shared_mem_bytes: 0,
2135            }
2136        };
2137        let __s_b = self.gpu.stream();
2138        let mut b = __s_b.launch_builder(&f);
2139        b.arg(w).arg(x).arg(&mut y).arg(&ne).arg(&nx).arg(&ti);
2140        unsafe {
2141            b.launch(cfg)?;
2142        }
2143        Ok(y)
2144    }
2145
2146    /// f32 row permute: dst[idx[i], :] = src[i, :] (grouped-GEMM CSR -> pair-id reorder).
2147    pub fn rows_permute(
2148        &self,
2149        src: &CudaSlice<f32>,
2150        idx: &CudaSlice<i32>,
2151        nrows: usize,
2152        ncols: usize,
2153    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2154        let mut dst = self.alloc_uninit::<f32>(nrows * ncols)?;
2155        let f = self.func("rows_permute_f32");
2156        let (nc, nr) = (ncols as i32, nrows as i32);
2157        let cfg = LaunchConfig {
2158            grid_dim: (nrows as u32, 1, 1),
2159            block_dim: (256, 1, 1),
2160            shared_mem_bytes: 0,
2161        };
2162        let __s_b = self.gpu.stream();
2163        let mut b = __s_b.launch_builder(&f);
2164        b.arg(src).arg(idx).arg(&mut dst).arg(&nc).arg(&nr);
2165        unsafe {
2166            b.launch(cfg)?;
2167        }
2168        Ok(dst)
2169    }
2170
2171    /// shexp gate fused dot: g[tok] = sigmoid(dot(x[tok,:], w)) — replaces the per-layer
2172    /// cuBLASLt m=1 GEMM + separate sigmoid launch on the qwen35moe decode path (the
2173    /// splitKreduce x40/step dig, 2026-07-31). One fold order for every t, so the t=1
2174    /// decode chain and the small-t spec-verify chain match per row by construction.
2175    pub fn sigmoid_dot_rows(
2176        &self,
2177        x: &CudaSlice<f32>,
2178        w: &CudaSlice<f32>,
2179        n_embd: usize,
2180        t: usize,
2181    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
2182        // MEMRA_SHEXP_DOT=0: rollback seam to the cuBLASLt linear + sigmoid pair (numeric
2183        // config; same class as MEMRA_ROUTER_V2).
2184        static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2185        if *OFF.get_or_init(|| std::env::var("MEMRA_SHEXP_DOT").as_deref() == Ok("0")) {
2186            let gs = self.linear(x, w, t, n_embd, 1)?;
2187            let mut g = self.uninit(t)?;
2188            self.sigmoid(&gs, &mut g, t)?;
2189            return Ok(g);
2190        }
2191        // FAST-ROUTER lane note (2026-08-02): a register-tiled 8-token batch twin of this
2192        // kernel was built, proven bit-identical, and measured SLOWER at every prefill t on
2193        // the 5090 (0.62-0.89x — launch-latency-bound op, ~7us/layer at m=2048;
2194        // research/fast-router-20260802/crossover-router.jsonl). Dispatch arm killed per
2195        // flags doctrine; this per-token form serves every t.
2196        let mut g = self.alloc_uninit::<f32>(t)?;
2197        let f = self.func("sigmoid_dot_rows_f32");
2198        let (ne, ti) = (n_embd as i32, t as i32);
2199        let cfg = LaunchConfig {
2200            grid_dim: (t as u32, 1, 1),
2201            block_dim: (32, 8, 1),
2202            shared_mem_bytes: 0,
2203        };
2204        let __s_b = self.gpu.stream();
2205        let mut b = __s_b.launch_builder(&f);
2206        b.arg(x).arg(w).arg(&mut g).arg(&ne).arg(&ti);
2207        unsafe {
2208            b.launch(cfg)?;
2209        }
2210        Ok(g)
2211    }
2212
2213    /// ROUND-STREAM stream rollback: all counters <- pos_start + base + n_acc.
2214    pub fn spec_rollback_stream(
2215        &self,
2216        len_ptrs: &CudaSlice<u64>,
2217        pos_start: &CudaSlice<i32>,
2218        acc: &CudaSlice<u32>,
2219        base: usize,
2220        n_rows: usize,
2221    ) -> Result<(), Box<dyn std::error::Error>> {
2222        let f = self.func("spec_rollback_stream");
2223        let (b, nr) = (base as i32, n_rows as i32);
2224        let cfg = LaunchConfig {
2225            grid_dim: (n_rows.div_ceil(64) as u32, 1, 1),
2226            block_dim: (64, 1, 1),
2227            shared_mem_bytes: 0,
2228        };
2229        let __s_bl = self.gpu.stream();
2230        let mut bl = __s_bl.launch_builder(&f);
2231        bl.arg(len_ptrs).arg(pos_start).arg(acc).arg(&b).arg(&nr);
2232        unsafe {
2233            bl.launch(cfg)?;
2234        }
2235        Ok(())
2236    }
2237
2238    /// PLAIN-DECODE GRAPH ring store: ring[(pos_start - base) % cap] = vam[0].
2239    pub fn plain_tok_ring(
2240        &self,
2241        vam: &CudaSlice<u32>,
2242        pos_start: &CudaSlice<i32>,
2243        base: usize,
2244        ring: &mut CudaSlice<u32>,
2245    ) -> Result<(), Box<dyn std::error::Error>> {
2246        let f = self.func("plain_tok_ring");
2247        let (b, cap) = (base as i32, ring.len() as i32);
2248        let cfg = LaunchConfig {
2249            grid_dim: (1, 1, 1),
2250            block_dim: (32, 1, 1),
2251            shared_mem_bytes: 0,
2252        };
2253        let __s_bl = self.gpu.stream();
2254        let mut bl = __s_bl.launch_builder(&f);
2255        bl.arg(vam).arg(pos_start).arg(&b).arg(&mut *ring).arg(&cap);
2256        unsafe {
2257            bl.launch(cfg)?;
2258        }
2259        Ok(())
2260    }
2261
2262    /// ROUND-STREAM stage (c) 4 epilogue: ring commit + tiny counter copies.
2263    pub fn spec_ring_commit(
2264        &self,
2265        vtok: &CudaSlice<u32>,
2266        acc: &CudaSlice<u32>,
2267        brk: &CudaSlice<u32>,
2268        ring: &mut CudaSlice<u32>,
2269        pend: &mut CudaSlice<u32>,
2270    ) -> Result<(), Box<dyn std::error::Error>> {
2271        let f = self.func("spec_ring_commit");
2272        let cfg = LaunchConfig {
2273            grid_dim: (1, 1, 1),
2274            block_dim: (32, 1, 1),
2275            shared_mem_bytes: 0,
2276        };
2277        let __s_b = self.gpu.stream();
2278        let mut b = __s_b.launch_builder(&f);
2279        b.arg(vtok).arg(acc).arg(brk).arg(ring).arg(pend);
2280        unsafe {
2281            b.launch(cfg)?;
2282        }
2283        Ok(())
2284    }
2285    pub fn i32_copy_add(
2286        &self,
2287        src: &CudaSlice<i32>,
2288        dst: &mut CudaSlice<i32>,
2289        delta: i32,
2290    ) -> Result<(), Box<dyn std::error::Error>> {
2291        let f = self.func("i32_copy_add");
2292        let cfg = LaunchConfig {
2293            grid_dim: (1, 1, 1),
2294            block_dim: (32, 1, 1),
2295            shared_mem_bytes: 0,
2296        };
2297        let __s_b = self.gpu.stream();
2298        let mut b = __s_b.launch_builder(&f);
2299        b.arg(src).arg(dst).arg(&delta);
2300        unsafe {
2301            b.launch(cfg)?;
2302        }
2303        Ok(())
2304    }
2305    pub fn u32_copy(
2306        &self,
2307        src: &CudaSlice<u32>,
2308        dst: &mut CudaSlice<u32>,
2309    ) -> Result<(), Box<dyn std::error::Error>> {
2310        let f = self.func("u32_copy");
2311        let cfg = LaunchConfig {
2312            grid_dim: (1, 1, 1),
2313            block_dim: (32, 1, 1),
2314            shared_mem_bytes: 0,
2315        };
2316        let __s_b = self.gpu.stream();
2317        let mut b = __s_b.launch_builder(&f);
2318        b.arg(src).arg(dst);
2319        unsafe {
2320            b.launch(cfg)?;
2321        }
2322        Ok(())
2323    }
2324
2325    /// ROUND-GRAPH adaptive depth: brk[0] <- clamp(acc[0] + 1, floor, cap) — the host
2326    /// adaptive policy as a captured device op (policy-identical: the accept walk depth
2327    /// caps acceptance exactly like drafting fewer tokens).
2328    pub fn spec_adapt_k(
2329        &self,
2330        acc: &CudaSlice<u32>,
2331        brk: &mut CudaSlice<u32>,
2332        floor: usize,
2333        cap: usize,
2334    ) -> Result<(), Box<dyn std::error::Error>> {
2335        let f = self.func("spec_adapt_k");
2336        let (fl, cp) = (floor as i32, cap as i32);
2337        let cfg = LaunchConfig {
2338            grid_dim: (1, 1, 1),
2339            block_dim: (32, 1, 1),
2340            shared_mem_bytes: 0,
2341        };
2342        let __s_b = self.gpu.stream();
2343        let mut b = __s_b.launch_builder(&f);
2344        b.arg(acc).arg(brk).arg(&fl).arg(&cp);
2345        unsafe {
2346            b.launch(cfg)?;
2347        }
2348        Ok(())
2349    }
2350
2351    /// ROUND-STREAM stage (c) 3: accept walk fully device-driven (brk + assembled vtok).
2352    pub fn spec_accept_greedy_dc(
2353        &self,
2354        preds: &CudaSlice<u32>,
2355        vtok: &CudaSlice<u32>,
2356        last_pred: &CudaSlice<u32>,
2357        brk: &CudaSlice<u32>,
2358        out: &mut CudaSlice<u32>,
2359    ) -> Result<(), Box<dyn std::error::Error>> {
2360        let f = self.func("spec_accept_greedy_dc");
2361        let cfg = LaunchConfig {
2362            grid_dim: (1, 1, 1),
2363            block_dim: (32, 1, 1),
2364            shared_mem_bytes: 0,
2365        };
2366        let __s_b = self.gpu.stream();
2367        let mut b = __s_b.launch_builder(&f);
2368        b.arg(preds).arg(vtok).arg(last_pred).arg(brk).arg(out);
2369        unsafe {
2370            b.launch(cfg)?;
2371        }
2372        Ok(())
2373    }
2374
2375    /// ROUND-STREAM stage (c) 2: verify-chain device-pos entries.
2376    pub fn pos_iota(
2377        &self,
2378        pos0: &CudaSlice<i32>,
2379        out: &mut CudaSlice<i32>,
2380        t: usize,
2381    ) -> Result<(), Box<dyn std::error::Error>> {
2382        let f = self.func("pos_iota_i32");
2383        let ti = t as i32;
2384        let cfg = LaunchConfig {
2385            grid_dim: (1, 1, 1),
2386            block_dim: (t.max(1) as u32, 1, 1),
2387            shared_mem_bytes: 0,
2388        };
2389        let __s_b = self.gpu.stream();
2390        let mut b = __s_b.launch_builder(&f);
2391        b.arg(pos0).arg(out).arg(&ti);
2392        unsafe {
2393            b.launch(cfg)?;
2394        }
2395        Ok(())
2396    }
2397    #[allow(clippy::too_many_arguments)]
2398    pub fn append_kv_quantized_rows_dc(
2399        &self,
2400        k_rows: &CudaSlice<f32>,
2401        v_rows: &CudaSlice<f32>,
2402        kc: &mut CudaSlice<u8>,
2403        vc: &mut CudaSlice<u8>,
2404        t0_dev: &CudaSlice<i32>,
2405        t: usize,
2406        kv_dim_k: usize,
2407        kv_dim_v: usize,
2408        k_tok_bytes: usize,
2409        v_tok_bytes: usize,
2410        g: bool,
2411    ) -> Result<(), Box<dyn std::error::Error>> {
2412        let f = if g {
2413            self.func_g("append_quantize_kv_q8_0_q5_1_rows_dc")
2414        } else {
2415            self.func("append_quantize_kv_q8_0_q5_1_rows_dc")
2416        };
2417        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
2418        let cfg = LaunchConfig {
2419            grid_dim: (nblk, t as u32, 1),
2420            block_dim: (32, 1, 1),
2421            shared_mem_bytes: 0,
2422        };
2423        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2424        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2425        let __s_b = self.gpu.stream();
2426        let mut b = __s_b.launch_builder(&f);
2427        b.arg(k_rows)
2428            .arg(v_rows)
2429            .arg(kc)
2430            .arg(vc)
2431            .arg(t0_dev)
2432            .arg(&kdk)
2433            .arg(&kdv)
2434            .arg(&ktb)
2435            .arg(&vtb);
2436        unsafe {
2437            b.launch(cfg)?;
2438        }
2439        Ok(())
2440    }
2441
2442    /// t=1 dc append with a FUSED len_d increment (wave 5c) — one launch replaces
2443    /// append_rows_dc + inc_seqlen. Single block (read-before-inc ordering).
2444    #[allow(clippy::too_many_arguments)]
2445    pub fn append_kv_quantized_row_dc_inc(
2446        &self,
2447        k_row: &CudaSlice<f32>,
2448        v_row: &CudaSlice<f32>,
2449        kc: &mut CudaSlice<u8>,
2450        vc: &mut CudaSlice<u8>,
2451        t0_dev: &mut CudaSlice<i32>,
2452        kv_dim_k: usize,
2453        kv_dim_v: usize,
2454        k_tok_bytes: usize,
2455        v_tok_bytes: usize,
2456        g: bool,
2457    ) -> Result<(), Box<dyn std::error::Error>> {
2458        let f = if g {
2459            self.func_g("append_quantize_kv_q8_0_q5_1_dc_inc")
2460        } else {
2461            self.func("append_quantize_kv_q8_0_q5_1_dc_inc")
2462        };
2463        let nthreads = ((kv_dim_k.max(kv_dim_v) / 32) * 32).min(1024) as u32;
2464        let cfg = LaunchConfig {
2465            grid_dim: (1, 1, 1),
2466            block_dim: (nthreads, 1, 1),
2467            shared_mem_bytes: 0,
2468        };
2469        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
2470        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
2471        let __s_b = self.gpu.stream();
2472        let mut b = __s_b.launch_builder(&f);
2473        b.arg(k_row)
2474            .arg(v_row)
2475            .arg(kc)
2476            .arg(vc)
2477            .arg(t0_dev)
2478            .arg(&kdk)
2479            .arg(&kdv)
2480            .arg(&ktb)
2481            .arg(&vtb);
2482        unsafe {
2483            b.launch(cfg)?;
2484        }
2485        Ok(())
2486    }
2487
2488    /// ROUND-STREAM: draft-chain pack + in-graph d2t remap (see kernels.cu headers).
2489    pub fn pack_tok_p(
2490        &self,
2491        tok: &CudaSlice<u32>,
2492        p: &CudaSlice<f32>,
2493        out: &mut CudaSlice<u32>,
2494        slot: usize,
2495    ) -> Result<(), Box<dyn std::error::Error>> {
2496        let f = self.func("pack_tok_p");
2497        let sl = slot as i32;
2498        let cfg = LaunchConfig {
2499            grid_dim: (1, 1, 1),
2500            block_dim: (32, 1, 1),
2501            shared_mem_bytes: 0,
2502        };
2503        let __s_b = self.gpu.stream();
2504        let mut b = __s_b.launch_builder(&f);
2505        b.arg(tok).arg(p).arg(out).arg(&sl);
2506        unsafe {
2507            b.launch(cfg)?;
2508        }
2509        Ok(())
2510    }
2511    pub fn tok_map_u32(
2512        &self,
2513        tok: &mut CudaSlice<u32>,
2514        map: &CudaSlice<u32>,
2515    ) -> Result<(), Box<dyn std::error::Error>> {
2516        let f = self.func("tok_map_u32");
2517        let cfg = LaunchConfig {
2518            grid_dim: (1, 1, 1),
2519            block_dim: (32, 1, 1),
2520            shared_mem_bytes: 0,
2521        };
2522        let __s_b = self.gpu.stream();
2523        let mut b = __s_b.launch_builder(&f);
2524        b.arg(tok).arg(map);
2525        unsafe {
2526            b.launch(cfg)?;
2527        }
2528        Ok(())
2529    }
2530
2531    /// ROUND-STREAM stage (c) 1: device verify-token assembly + p-min break derivation.
2532    #[allow(clippy::too_many_arguments)]
2533    pub fn spec_assemble_verify(
2534        &self,
2535        tokp: &CudaSlice<u32>,
2536        pend: &CudaSlice<u32>,
2537        d2t: Option<&CudaSlice<u32>>,
2538        vtok: &mut CudaSlice<u32>,
2539        brk: &mut CudaSlice<u32>,
2540        p_min: f32,
2541        k: usize,
2542        pmin0: bool,
2543    ) -> Result<(), Box<dyn std::error::Error>> {
2544        let f = self.func("spec_assemble_verify");
2545        let (ki, pm) = (k as i32, if pmin0 { 1i32 } else { 0i32 });
2546        let cfg = LaunchConfig {
2547            grid_dim: (1, 1, 1),
2548            block_dim: (32, 1, 1),
2549            shared_mem_bytes: 0,
2550        };
2551        let __s_b = self.gpu.stream();
2552        let mut b = __s_b.launch_builder(&f);
2553        match d2t {
2554            Some(m) => {
2555                b.arg(tokp)
2556                    .arg(pend)
2557                    .arg(m)
2558                    .arg(vtok)
2559                    .arg(brk)
2560                    .arg(&p_min)
2561                    .arg(&ki)
2562                    .arg(&pm);
2563                unsafe {
2564                    b.launch(cfg)?;
2565                }
2566            }
2567            None => {
2568                let null: u64 = 0;
2569                b.arg(tokp)
2570                    .arg(pend)
2571                    .arg(&null)
2572                    .arg(vtok)
2573                    .arg(brk)
2574                    .arg(&p_min)
2575                    .arg(&ki)
2576                    .arg(&pm);
2577                unsafe {
2578                    b.launch(cfg)?;
2579                }
2580            }
2581        }
2582        Ok(())
2583    }
2584
2585    /// ROUND-STREAM stage (b) 3b: recur-restore twins with device-j (see hybrid.cu headers).
2586    #[allow(clippy::too_many_arguments)]
2587    pub fn ssm_conv_ring_rebuild_dc(
2588        &self,
2589        qkv_tm: &CudaSlice<f32>,
2590        ring_old: &CudaSlice<f32>,
2591        conv_state: &mut CudaSlice<f32>,
2592        conv_dim: usize,
2593        acc: &CudaSlice<u32>,
2594        base: usize,
2595        t_v: usize,
2596        d_conv: usize,
2597    ) -> Result<(), Box<dyn std::error::Error>> {
2598        let f = self.func("ssm_conv_ring_rebuild_f32_dc");
2599        let n = conv_dim * (d_conv - 1);
2600        let cfg = LaunchConfig::for_num_elems(n as u32);
2601        let (cd, b0, tv, dc) = (conv_dim as i32, base as i32, t_v as i32, d_conv as i32);
2602        let __s_b = self.gpu.stream();
2603        let mut b = __s_b.launch_builder(&f);
2604        b.arg(qkv_tm)
2605            .arg(ring_old)
2606            .arg(conv_state)
2607            .arg(&cd)
2608            .arg(acc)
2609            .arg(&b0)
2610            .arg(&tv)
2611            .arg(&dc);
2612        unsafe {
2613            b.launch(cfg)?;
2614        }
2615        Ok(())
2616    }
2617    #[allow(clippy::too_many_arguments)]
2618    pub fn gdn_scan_s128_dc(
2619        &self,
2620        q: &CudaSlice<f32>,
2621        k: &CudaSlice<f32>,
2622        v: &CudaSlice<f32>,
2623        g: &CudaSlice<f32>,
2624        beta: &CudaSlice<f32>,
2625        state_in: &CudaSlice<f32>,
2626        state_out: &mut CudaSlice<f32>,
2627        o: &mut CudaSlice<f32>,
2628        n_head: usize,
2629        acc: &CudaSlice<u32>,
2630        base: usize,
2631        t_v: usize,
2632        scale: f32,
2633    ) -> Result<(), Box<dyn std::error::Error>> {
2634        let f = self.func("gdn_scan_s128_dc");
2635        const S_V: u32 = 128;
2636        const WARP: u32 = 32;
2637        const COLS_PER_BLOCK: u32 = 4;
2638        let cfg = LaunchConfig {
2639            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
2640            block_dim: (WARP, COLS_PER_BLOCK, 1),
2641            shared_mem_bytes: 0,
2642        };
2643        let (h, b0, tv) = (n_head as i32, base as i32, t_v as i32);
2644        let __s_b = self.gpu.stream();
2645        let mut b = __s_b.launch_builder(&f);
2646        b.arg(q)
2647            .arg(k)
2648            .arg(v)
2649            .arg(g)
2650            .arg(beta)
2651            .arg(state_in)
2652            .arg(state_out)
2653            .arg(o)
2654            .arg(&h)
2655            .arg(acc)
2656            .arg(&b0)
2657            .arg(&tv)
2658            .arg(&scale);
2659        unsafe {
2660            b.launch(cfg)?;
2661        }
2662        Ok(())
2663    }
2664
2665    /// ROUND-STREAM stage (b) 3a: device per-layer KV-len rollback (see spec_rollback_kv).
2666    pub fn spec_rollback_kv(
2667        &self,
2668        len_ptrs: &CudaSlice<u64>,
2669        saved: &CudaSlice<i32>,
2670        acc: &CudaSlice<u32>,
2671        base: usize,
2672        n_layer: usize,
2673    ) -> Result<(), Box<dyn std::error::Error>> {
2674        let f = self.func("spec_rollback_kv");
2675        let (b, nl) = (base as i32, n_layer as i32);
2676        let cfg = LaunchConfig {
2677            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2678            block_dim: (64, 1, 1),
2679            shared_mem_bytes: 0,
2680        };
2681        let __s_bl = self.gpu.stream();
2682        let mut bl = __s_bl.launch_builder(&f);
2683        bl.arg(len_ptrs).arg(saved).arg(acc).arg(&b).arg(&nl);
2684        unsafe {
2685            bl.launch(cfg)?;
2686        }
2687        Ok(())
2688    }
2689
2690    /// OPTIPIPE increment 1: derive the K=1 successor-valid bit on device.
2691    pub fn spec_fork_valid(
2692        &self,
2693        acc: &CudaSlice<u32>,
2694        optimistic_pending: u32,
2695        valid: &mut CudaSlice<u32>,
2696    ) -> Result<(), Box<dyn std::error::Error>> {
2697        let f = self.func("spec_fork_valid");
2698        let cfg = LaunchConfig {
2699            grid_dim: (1, 1, 1),
2700            block_dim: (1, 1, 1),
2701            shared_mem_bytes: 0,
2702        };
2703        let __s_bl = self.gpu.stream();
2704        let mut bl = __s_bl.launch_builder(&f);
2705        bl.arg(acc).arg(&optimistic_pending).arg(valid);
2706        unsafe {
2707            bl.launch(cfg)?;
2708        }
2709        Ok(())
2710    }
2711
2712    /// OPTIPIPE increment 1: leave stage-local KV lengths on hit, restore them on miss.
2713    pub fn spec_fork_reconcile_kv(
2714        &self,
2715        len_ptrs: &CudaSlice<u64>,
2716        saved: &CudaSlice<i32>,
2717        acc: &CudaSlice<u32>,
2718        valid: &CudaSlice<u32>,
2719        base: usize,
2720        n_layer: usize,
2721    ) -> Result<(), Box<dyn std::error::Error>> {
2722        let f = self.func("spec_fork_reconcile_kv");
2723        let (b, nl) = (base as i32, n_layer as i32);
2724        let cfg = LaunchConfig {
2725            grid_dim: (n_layer.div_ceil(64) as u32, 1, 1),
2726            block_dim: (64, 1, 1),
2727            shared_mem_bytes: 0,
2728        };
2729        let __s_bl = self.gpu.stream();
2730        let mut bl = __s_bl.launch_builder(&f);
2731        bl.arg(len_ptrs)
2732            .arg(saved)
2733            .arg(acc)
2734            .arg(valid)
2735            .arg(&b)
2736            .arg(&nl);
2737        unsafe {
2738            bl.launch(cfg)?;
2739        }
2740        Ok(())
2741    }
2742
2743    /// OPTIPIPE increment 1: conditionally restore one stage-owned recurrent-state buffer.
2744    pub fn spec_fork_restore_f32(
2745        &self,
2746        snapshot: &CudaSlice<f32>,
2747        state: &mut CudaSlice<f32>,
2748        valid: &CudaSlice<u32>,
2749    ) -> Result<(), Box<dyn std::error::Error>> {
2750        assert_eq!(
2751            snapshot.len(),
2752            state.len(),
2753            "fork recurrent snapshot shape mismatch"
2754        );
2755        let f = self.func("spec_fork_restore_f32");
2756        let n = state.len() as i32;
2757        let blocks = state.len().div_ceil(256).min(65535).max(1) as u32;
2758        let cfg = LaunchConfig {
2759            grid_dim: (blocks, 1, 1),
2760            block_dim: (256, 1, 1),
2761            shared_mem_bytes: 0,
2762        };
2763        let __s_bl = self.gpu.stream();
2764        let mut bl = __s_bl.launch_builder(&f);
2765        bl.arg(snapshot).arg(state).arg(valid).arg(&n);
2766        unsafe {
2767            bl.launch(cfg)?;
2768        }
2769        Ok(())
2770    }
2771
2772    /// ROUND-STREAM stage (b): device next-round seed gather (see spec_seed_gather header).
2773    /// Caller D2Ds h_seed into fill_prev after (both slots carry the same value in every arm).
2774    pub fn spec_seed_gather(
2775        &self,
2776        vx: &CudaSlice<f32>,
2777        fill_prev: &CudaSlice<f32>,
2778        acc: &CudaSlice<u32>,
2779        h_seed: &mut CudaSlice<f32>,
2780        base: usize,
2781        n_embd: usize,
2782    ) -> Result<(), Box<dyn std::error::Error>> {
2783        let f = self.func("spec_seed_gather");
2784        let (b, ne) = (base as i32, n_embd as i32);
2785        let cfg = LaunchConfig {
2786            grid_dim: (n_embd.div_ceil(256) as u32, 1, 1),
2787            block_dim: (256, 1, 1),
2788            shared_mem_bytes: 0,
2789        };
2790        let __s_bl = self.gpu.stream();
2791        let mut bl = __s_bl.launch_builder(&f);
2792        bl.arg(vx)
2793            .arg(fill_prev)
2794            .arg(acc)
2795            .arg(h_seed)
2796            .arg(&b)
2797            .arg(&ne);
2798        unsafe {
2799            bl.launch(cfg)?;
2800        }
2801        Ok(())
2802    }
2803
2804    /// ROUND-STREAM stage (a): device greedy accept walk (see spec_accept_greedy header).
2805    pub fn spec_accept_greedy(
2806        &self,
2807        preds: &CudaSlice<u32>,
2808        draft: &CudaSlice<u32>,
2809        last_pred: u32,
2810        base: usize,
2811        k_round: usize,
2812        out: &mut CudaSlice<u32>,
2813    ) -> Result<(), Box<dyn std::error::Error>> {
2814        let f = self.func("spec_accept_greedy");
2815        let (b, k) = (base as i32, k_round as i32);
2816        let cfg = LaunchConfig {
2817            grid_dim: (1, 1, 1),
2818            block_dim: (32, 1, 1),
2819            shared_mem_bytes: 0,
2820        };
2821        let __s_bl = self.gpu.stream();
2822        let mut bl = __s_bl.launch_builder(&f);
2823        bl.arg(preds)
2824            .arg(draft)
2825            .arg(&last_pred)
2826            .arg(&b)
2827            .arg(&k)
2828            .arg(out);
2829        unsafe {
2830            bl.launch(cfg)?;
2831        }
2832        Ok(())
2833    }
2834
2835    // ================= SAMPLED-SPEC PRIMITIVES (spec_sample.cu, piece A) =================
2836    // Counter-based randomness: every call takes (seed, stream_pos) — the caller owns the
2837    // event counter (one per sampled token). temp <= 0 arms are exact greedy limits.
2838
2839    /// y = x/temp + Gumbel(Philox(seed, stream_pos)) over n logits (then run device argmax on y
2840    /// = one categorical sample at temperature `temp`). temp<=0: y = x (pure copy).
2841    pub fn gumbel_perturb(
2842        &self,
2843        x: &CudaSlice<f32>,
2844        y: &mut CudaSlice<f32>,
2845        n: usize,
2846        seed: u64,
2847        stream_pos: u32,
2848        temp: f32,
2849    ) -> Result<(), Box<dyn std::error::Error>> {
2850        let f = self.func("gumbel_perturb_f32");
2851        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2852        let cfg = LaunchConfig {
2853            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2854            block_dim: (256, 1, 1),
2855            shared_mem_bytes: 0,
2856        };
2857        let __s_b = self.gpu.stream();
2858        let mut b = __s_b.launch_builder(&f);
2859        b.arg(x)
2860            .arg(&mut *y)
2861            .arg(&ni)
2862            .arg(&slo)
2863            .arg(&shi)
2864            .arg(&stream_pos)
2865            .arg(&temp);
2866        unsafe {
2867            b.launch(cfg)?;
2868        }
2869        Ok(())
2870    }
2871
2872    /// GRAMMAR TOKEN MASK (constrained decoding, lane/constrained-full): ban every vocab id
2873    /// whose bit is unset in the packed llguidance bitset, IN PLACE on row `col` of a stacked
2874    /// [B, n_vocab] logits buffer. `mask` = the SimpleVob u32 words H2D'd verbatim
2875    /// (~n_vocab/8 bytes/step — trivial on PCIe); ids >= 32*mask_words (padded lm_head tail)
2876    /// are banned too, the device twin of constrained::apply_mask. Banned value -FLT_MAX ==
2877    /// the argmax/gumbel kernels' init sentinel, so a fully-banned tail can never win and
2878    /// ordering matches the host -inf mask bit-for-bit for every finite logit.
2879    pub fn mask_logits_col(
2880        &self,
2881        logits: &mut CudaSlice<f32>,
2882        mask: &CudaSlice<u32>,
2883        col: usize,
2884        n: usize,
2885        mask_words: usize,
2886    ) -> Result<(), Box<dyn std::error::Error>> {
2887        let f = self.func("mask_logits_f32");
2888        let (ci, ni, mw) = (col as i32, n as i32, mask_words as i32);
2889        let cfg = LaunchConfig {
2890            grid_dim: (n.div_ceil(256).min(1024) as u32, 1, 1),
2891            block_dim: (256, 1, 1),
2892            shared_mem_bytes: 0,
2893        };
2894        let __s_b = self.gpu.stream();
2895        let mut b = __s_b.launch_builder(&f);
2896        b.arg(&mut *logits).arg(mask).arg(&ci).arg(&ni).arg(&mw);
2897        unsafe {
2898            b.launch(cfg)?;
2899        }
2900        Ok(())
2901    }
2902
2903    /// Column-`col` twin of `gumbel_perturb` over stacked logits [B, n_vocab] (the batched
2904    /// serving tick's device sampler): y = x[col]/temp + gumbel(seed, stream_pos, lane).
2905    /// SAME kernel/Philox mapping as `gumbel_perturb` — bit-identical perturbation for the
2906    /// same (seed, stream_pos, temp) regardless of which batch column the row sits in
2907    /// (the lane index is the in-row position; `col` only moves the input pointer). That
2908    /// pointer-invariance IS the serving isolation contract for sampled rows.
2909    pub fn gumbel_perturb_col(
2910        &self,
2911        x: &CudaSlice<f32>,
2912        col: usize,
2913        y: &mut CudaSlice<f32>,
2914        n: usize,
2915        seed: u64,
2916        stream_pos: u32,
2917        temp: f32,
2918    ) -> Result<(), Box<dyn std::error::Error>> {
2919        let f = self.func("gumbel_perturb_f32");
2920        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2921        let col_view = x.slice(col * n..(col + 1) * n);
2922        let cfg = LaunchConfig {
2923            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2924            block_dim: (256, 1, 1),
2925            shared_mem_bytes: 0,
2926        };
2927        let __s_b = self.gpu.stream();
2928        let mut b = __s_b.launch_builder(&f);
2929        b.arg(&col_view)
2930            .arg(&mut *y)
2931            .arg(&ni)
2932            .arg(&slo)
2933            .arg(&shi)
2934            .arg(&stream_pos)
2935            .arg(&temp);
2936        unsafe {
2937            b.launch(cfg)?;
2938        }
2939        Ok(())
2940    }
2941
2942    /// Filtered twin of `gumbel_perturb_col`: the per-row (row_max, th) floor comes from
2943    /// DEVICE buffers (`filter_stats` output slots at `stat_idx`) — one filtered draw from
2944    /// the top-k/top-p/min-p-truncated softmax with no stat D2H and no row copy. Same
2945    /// Philox mapping as every gumbel kernel (pointer-invariant across batch columns —
2946    /// the serving isolation contract for sampled rows).
2947    #[allow(clippy::too_many_arguments)]
2948    pub fn gumbel_perturb_filtered_col(
2949        &self,
2950        x: &CudaSlice<f32>,
2951        col: usize,
2952        y: &mut CudaSlice<f32>,
2953        n: usize,
2954        seed: u64,
2955        stream_pos: u32,
2956        temp: f32,
2957        stat_max: &CudaSlice<f32>,
2958        stat_th: &CudaSlice<f32>,
2959        stat_idx: usize,
2960    ) -> Result<(), Box<dyn std::error::Error>> {
2961        let f = self.func("gumbel_perturb_filtered_col_f32");
2962        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2963        let (ci, si) = (col as i32, stat_idx as i32);
2964        let cfg = LaunchConfig {
2965            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2966            block_dim: (256, 1, 1),
2967            shared_mem_bytes: 0,
2968        };
2969        let __s_b = self.gpu.stream();
2970        let mut b = __s_b.launch_builder(&f);
2971        b.arg(x)
2972            .arg(&ci)
2973            .arg(&mut *y)
2974            .arg(&ni)
2975            .arg(&slo)
2976            .arg(&shi)
2977            .arg(&stream_pos)
2978            .arg(&temp)
2979            .arg(stat_max)
2980            .arg(stat_th)
2981            .arg(&si);
2982        unsafe {
2983            b.launch(cfg)?;
2984        }
2985        Ok(())
2986    }
2987
2988    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
2989    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
2990    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
2991    /// reads it (counter is data, not state — graph-replay-safe).
2992    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
2993        let f = self.func("memra_sctr_inc");
2994        let cfg = LaunchConfig {
2995            grid_dim: (1, 1, 1),
2996            block_dim: (1, 1, 1),
2997            shared_mem_bytes: 0,
2998        };
2999        let __s_b = self.gpu.stream();
3000        let mut b = __s_b.launch_builder(&f);
3001        b.arg(&mut *ctr);
3002        unsafe {
3003            b.launch(cfg)?;
3004        }
3005        Ok(())
3006    }
3007
3008    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
3009    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
3010    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
3011    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
3012    pub fn gumbel_perturb_ctr(
3013        &self,
3014        x: &CudaSlice<f32>,
3015        y: &mut CudaSlice<f32>,
3016        n: usize,
3017        seed: u64,
3018        ctr: &CudaSlice<u32>,
3019        temp: f32,
3020    ) -> Result<(), Box<dyn std::error::Error>> {
3021        let f = self.func("gumbel_perturb_ctr_f32");
3022        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3023        let cfg = LaunchConfig {
3024            grid_dim: (n.div_ceil(256) as u32, 1, 1),
3025            block_dim: (256, 1, 1),
3026            shared_mem_bytes: 0,
3027        };
3028        let __s_b = self.gpu.stream();
3029        let mut b = __s_b.launch_builder(&f);
3030        b.arg(x)
3031            .arg(&mut *y)
3032            .arg(&ni)
3033            .arg(&slo)
3034            .arg(&shi)
3035            .arg(ctr)
3036            .arg(&temp);
3037        unsafe {
3038            b.launch(cfg)?;
3039        }
3040        Ok(())
3041    }
3042
3043    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
3044    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
3045    /// (smallest-index tie-break — matches the argmax-gate contract).
3046    pub fn softmax_gather(
3047        &self,
3048        x: &CudaSlice<f32>,
3049        row_stride: usize,
3050        ids: &CudaSlice<u32>,
3051        rows: &CudaSlice<i32>,
3052        out: &mut CudaSlice<f32>,
3053        n: usize,
3054        npair: usize,
3055        temp: f32,
3056    ) -> Result<(), Box<dyn std::error::Error>> {
3057        let f = self.func("softmax_gather_f32");
3058        let (ni, rs) = (n as i32, row_stride as i64);
3059        let np = npair as i32;
3060        let cfg = LaunchConfig {
3061            grid_dim: (npair as u32, 1, 1),
3062            block_dim: (256, 1, 1),
3063            shared_mem_bytes: 0,
3064        };
3065        let __s_b = self.gpu.stream();
3066        let mut b = __s_b.launch_builder(&f);
3067        b.arg(x)
3068            .arg(&rs)
3069            .arg(ids)
3070            .arg(rows)
3071            .arg(&mut *out)
3072            .arg(&ni)
3073            .arg(&np)
3074            .arg(&temp);
3075        unsafe {
3076            b.launch(cfg)?;
3077        }
3078        Ok(())
3079    }
3080
3081    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3082    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3083    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3084    pub fn residual_sample(
3085        &self,
3086        p: &CudaSlice<f32>,
3087        q: Option<&CudaSlice<f32>>,
3088        n: usize,
3089        temp: f32,
3090        seed: u64,
3091        stream_pos: u32,
3092        out_tok: &mut CudaSlice<u32>,
3093    ) -> Result<(), Box<dyn std::error::Error>> {
3094        let f = self.func("residual_sample_f32");
3095        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3096        let nth = 1024u32;
3097        let cfg = LaunchConfig {
3098            grid_dim: (1, 1, 1),
3099            block_dim: (nth, 1, 1),
3100            shared_mem_bytes: 0,
3101        };
3102        let has_q: i32 = q.is_some() as i32;
3103        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3104        let __s_b = self.gpu.stream();
3105        let mut b = __s_b.launch_builder(&f);
3106        b.arg(p)
3107            .arg(qbuf)
3108            .arg(&has_q)
3109            .arg(&ni)
3110            .arg(&temp)
3111            .arg(&slo)
3112            .arg(&shi)
3113            .arg(&stream_pos)
3114            .arg(&mut *out_tok);
3115        unsafe {
3116            b.launch(cfg)?;
3117        }
3118        Ok(())
3119    }
3120
3121    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3122    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3123    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3124    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3125    pub fn with_moe_cache<R>(
3126        &self,
3127        max_block_bytes: usize,
3128        f: impl FnOnce(
3129            &mut crate::moe_cache::MoeSlotCache,
3130            &Engine,
3131        ) -> Result<R, Box<dyn std::error::Error>>,
3132    ) -> Result<R, Box<dyn std::error::Error>> {
3133        let mut guard = self.moe_cache.lock().unwrap();
3134        if guard.is_none() {
3135            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3136        }
3137        let cache = guard.as_mut().unwrap();
3138        f(cache, self)
3139    }
3140
3141    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3142    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3143    pub fn freeze_moe_cache(&self) {
3144        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3145            cache.freeze();
3146        }
3147    }
3148
3149    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3150    /// Never constructs a cache.
3151    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3152        self.moe_cache
3153            .lock()
3154            .unwrap()
3155            .as_ref()
3156            .map(crate::moe_cache::MoeSlotCache::export_residency)
3157    }
3158
3159    pub(crate) fn moe_cache_frozen(&self) -> bool {
3160        self.moe_cache
3161            .lock()
3162            .unwrap()
3163            .as_ref()
3164            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3165    }
3166
3167    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3168    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3169    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3170    /// while leaving the profiling warmup's established batched behavior untouched.
3171    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3172    /// tokenwise arm anyway.)
3173    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3174        crate::cpu_experts::configured()
3175            && self.moe_cache_frozen()
3176            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3177    }
3178
3179    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3180    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3181        assert!(
3182            self.moe_cache.lock().unwrap().is_none(),
3183            "MoE cache layout configured after cache construction"
3184        );
3185        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3186    }
3187
3188    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3189        self.moe_cache_layout.lock().unwrap().clone()
3190    }
3191
3192    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3193    pub fn moe_cache_enabled() -> bool {
3194        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3195    }
3196
3197    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3198    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3199    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3200        let guard = self.moe_cache.lock().unwrap();
3201        guard
3202            .as_ref()
3203            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3204    }
3205
3206    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3207    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3208    /// callers compare a before/after snapshot around a decode window.
3209    pub fn cpu_expert_stats(
3210        &self,
3211    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3212        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3213    }
3214
3215    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3216    /// the backend tail that resident-GPU expert work did not hide.
3217    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3218        crate::cpu_experts::predictor_stats()
3219    }
3220
3221    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3222        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3223    }
3224
3225    /// CPU-routed expert selections grouped by how many of their three projections were already
3226    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3227    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3228        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3229    }
3230
3231    /// Positioned-read proof-backend counters:
3232    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3233    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3234        let guard = self.moe_cache.lock().unwrap();
3235        guard
3236            .as_ref()
3237            .and_then(|cache| cache.pread_stats())
3238            .map(|stats| {
3239                (
3240                    stats.reads,
3241                    stats.bytes,
3242                    stats.read_errors,
3243                    stats.short_reads,
3244                    stats.fallbacks,
3245                    stats.buffer_waits,
3246                    stats.ring_full,
3247                )
3248            })
3249    }
3250
3251    /// Spill configuration values that warned and substituted their documented defaults.
3252    pub fn spill_config_fallbacks(&self) -> u64 {
3253        crate::spill_pread::config_fallbacks()
3254    }
3255
3256    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3257    pub fn moe_cache_reset_counters(&self) {
3258        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3259            c.reset_counters();
3260        }
3261    }
3262
3263    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3264        Ok(self.gpu.stream().clone_htod(v)?)
3265    }
3266
3267    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3268    /// past the final q4_0 block through their aligned window — the bytes never reach a
3269    /// result (funnelshift discards them) but must be mapped memory.
3270    pub fn htod_bytes_padded(
3271        &self,
3272        v: &[u8],
3273        pad: usize,
3274    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3275        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3276        {
3277            let mut view = d.slice_mut(0..v.len());
3278            self.gpu.stream().memcpy_htod(v, &mut view)?;
3279        }
3280        Ok(d)
3281    }
3282
3283    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3284    pub fn copy_into(
3285        &self,
3286        dst: &mut CudaSlice<f32>,
3287        off: usize,
3288        src: &CudaSlice<f32>,
3289        len: usize,
3290    ) -> Result<(), Box<dyn std::error::Error>> {
3291        let mut view = dst.slice_mut(off..off + len);
3292        self.gpu
3293            .stream()
3294            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3295        Ok(())
3296    }
3297
3298    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3299    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3300    pub fn copy_u8_into(
3301        &self,
3302        dst: &mut CudaSlice<u8>,
3303        off: usize,
3304        src: &CudaSlice<u8>,
3305        len: usize,
3306    ) -> Result<(), Box<dyn std::error::Error>> {
3307        let mut view = dst.slice_mut(off..off + len);
3308        self.gpu
3309            .stream()
3310            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3311        Ok(())
3312    }
3313
3314    /// D2D byte-range copy with explicit source and destination offsets.
3315    pub fn copy_u8_range_into(
3316        &self,
3317        dst: &mut CudaSlice<u8>,
3318        dst_off: usize,
3319        src: &CudaSlice<u8>,
3320        src_off: usize,
3321        len: usize,
3322    ) -> Result<(), Box<dyn std::error::Error>> {
3323        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3324        self.gpu
3325            .stream()
3326            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3327        Ok(())
3328    }
3329
3330    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3331    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3332    /// keeping the audited attention range contiguous without changing its absolute start.
3333    pub fn prepare_kv_append(
3334        &self,
3335        kv: &mut crate::cache::KvLayer,
3336        retain_from: usize,
3337        append_rows: usize,
3338    ) -> Result<usize, Box<dyn std::error::Error>> {
3339        let Some(plan) = kv
3340            .ring
3341            .as_ref()
3342            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3343            .transpose()?
3344        else {
3345            return Ok(kv.len);
3346        };
3347        match plan {
3348            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3349            crate::cache::KvRingAppend::Rebase {
3350                src_row,
3351                keep_rows,
3352                new_base,
3353                write_row,
3354            } => {
3355                if keep_rows > 0 {
3356                    let k_len = keep_rows * kv.k_tok_bytes;
3357                    let v_len = keep_rows * kv.v_tok_bytes;
3358                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3359                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3360                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3361                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3362                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3363                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3364                }
3365                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3366                Ok(write_row)
3367            }
3368        }
3369    }
3370
3371    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3372    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3373    pub fn htod_u8_into(
3374        &self,
3375        dst: &mut CudaSlice<u8>,
3376        off: usize,
3377        src: &[u8],
3378    ) -> Result<(), Box<dyn std::error::Error>> {
3379        let mut view = dst.slice_mut(off..off + src.len());
3380        self.gpu.stream().memcpy_htod(src, &mut view)?;
3381        Ok(())
3382    }
3383
3384    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3385        b.slice(0..len)
3386    }
3387
3388    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3389    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3390    pub fn view_u8_range<'a>(
3391        &self,
3392        b: &'a CudaSlice<u8>,
3393        start: usize,
3394        end: usize,
3395    ) -> cudarc::driver::CudaView<'a, u8> {
3396        b.slice(start..end)
3397    }
3398    pub fn view_u8<'a>(
3399        &self,
3400        b: &'a CudaSlice<u8>,
3401        len: usize,
3402    ) -> cudarc::driver::CudaView<'a, u8> {
3403        b.slice(0..len)
3404    }
3405
3406    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3407    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3408    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3409    pub fn append_kv_quantized(
3410        &self,
3411        k_row: &CudaSlice<f32>,
3412        v_row: &CudaSlice<f32>,
3413        kc: &mut CudaSlice<u8>,
3414        vc: &mut CudaSlice<u8>,
3415        t: usize,
3416        kv_dim_k: usize,
3417        kv_dim_v: usize,
3418        k_tok_bytes: usize,
3419        v_tok_bytes: usize,
3420        g: bool,
3421    ) -> Result<(), Box<dyn std::error::Error>> {
3422        let f = if g {
3423            self.func_g("append_quantize_kv_q8_0_q5_1")
3424        } else {
3425            self.func("append_quantize_kv_q8_0_q5_1")
3426        };
3427        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3428        let cfg = LaunchConfig {
3429            grid_dim: (nblk, 1, 1),
3430            block_dim: (32, 1, 1),
3431            shared_mem_bytes: 0,
3432        };
3433        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3434        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3435        let __s_b = self.gpu.stream();
3436        let mut b = __s_b.launch_builder(&f);
3437        b.arg(k_row)
3438            .arg(v_row)
3439            .arg(kc)
3440            .arg(vc)
3441            .arg(&ti)
3442            .arg(&kdk)
3443            .arg(&kdv)
3444            .arg(&ktb)
3445            .arg(&vtb);
3446        unsafe {
3447            b.launch(cfg)?;
3448        }
3449        Ok(())
3450    }
3451
3452    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3453    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3454    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3455    pub fn append_kv_quantized_dc(
3456        &self,
3457        k_row: &CudaSlice<f32>,
3458        v_row: &CudaSlice<f32>,
3459        kc: &mut CudaSlice<u8>,
3460        vc: &mut CudaSlice<u8>,
3461        t_dev: &CudaSlice<i32>,
3462        kv_dim_k: usize,
3463        kv_dim_v: usize,
3464        k_tok_bytes: usize,
3465        v_tok_bytes: usize,
3466        g: bool,
3467    ) -> Result<(), Box<dyn std::error::Error>> {
3468        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3469        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3470        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3471        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3472        if Self::pdl_on() && Self::pdl_wb_on() {
3473            use cudarc::driver::{DevicePtr, DevicePtrMut};
3474            let s = &self.gpu.stream();
3475            let (pk, _g0) = k_row.device_ptr(s);
3476            let (pv, _g1) = v_row.device_ptr(s);
3477            let (pkc, _g2) = kc.device_ptr_mut(s);
3478            let (pvc, _g3) = vc.device_ptr_mut(s);
3479            let (pt, _g4) = t_dev.device_ptr(s);
3480            let mut ps = [
3481                &pk as *const _ as *mut std::ffi::c_void,
3482                &pv as *const _ as *mut _,
3483                &pkc as *const _ as *mut _,
3484                &pvc as *const _ as *mut _,
3485                &pt as *const _ as *mut _,
3486                &kdk as *const _ as *mut _,
3487                &kdv as *const _ as *mut _,
3488                &ktb as *const _ as *mut _,
3489                &vtb as *const _ as *mut _,
3490            ];
3491            unsafe {
3492                self.launch_pdl_flash(
3493                    g,
3494                    "append_quantize_kv_q8_0_q5_1_dc",
3495                    (nblk, 1, 1),
3496                    (32, 1, 1),
3497                    0,
3498                    &mut ps,
3499                )?;
3500            }
3501            return Ok(());
3502        }
3503        let f = if g {
3504            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3505        } else {
3506            self.func("append_quantize_kv_q8_0_q5_1_dc")
3507        };
3508        let cfg = LaunchConfig {
3509            grid_dim: (nblk, 1, 1),
3510            block_dim: (32, 1, 1),
3511            shared_mem_bytes: 0,
3512        };
3513        let __s_b = self.gpu.stream();
3514        let mut b = __s_b.launch_builder(&f);
3515        b.arg(k_row)
3516            .arg(v_row)
3517            .arg(kc)
3518            .arg(vc)
3519            .arg(t_dev)
3520            .arg(&kdk)
3521            .arg(&kdv)
3522            .arg(&ktb)
3523            .arg(&vtb);
3524        unsafe {
3525            b.launch(cfg)?;
3526        }
3527        Ok(())
3528    }
3529
3530    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3531    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3532    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3533    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3534    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3535    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3536    #[allow(clippy::too_many_arguments)]
3537    pub fn append_kv_quantized_rows(
3538        &self,
3539        k_rows: &CudaSlice<f32>,
3540        v_rows: &CudaSlice<f32>,
3541        kc: &mut CudaSlice<u8>,
3542        vc: &mut CudaSlice<u8>,
3543        t0: usize,
3544        t: usize,
3545        kv_dim_k: usize,
3546        kv_dim_v: usize,
3547        k_tok_bytes: usize,
3548        v_tok_bytes: usize,
3549        g: bool,
3550    ) -> Result<(), Box<dyn std::error::Error>> {
3551        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3552            for i in 0..t {
3553                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3554                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3555                self.append_kv_quantized_view(
3556                    &k_row,
3557                    &v_row,
3558                    kc,
3559                    vc,
3560                    t0 + i,
3561                    kv_dim_k,
3562                    kv_dim_v,
3563                    k_tok_bytes,
3564                    v_tok_bytes,
3565                    g,
3566                )?;
3567            }
3568            return Ok(());
3569        }
3570        let f = if g {
3571            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3572        } else {
3573            self.func("append_quantize_kv_q8_0_q5_1_rows")
3574        };
3575        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3576        let cfg = LaunchConfig {
3577            grid_dim: (nblk, t as u32, 1),
3578            block_dim: (32, 1, 1),
3579            shared_mem_bytes: 0,
3580        };
3581        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3582        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3583        let __s_b = self.gpu.stream();
3584        let mut b = __s_b.launch_builder(&f);
3585        b.arg(k_rows)
3586            .arg(v_rows)
3587            .arg(kc)
3588            .arg(vc)
3589            .arg(&t0i)
3590            .arg(&kdk)
3591            .arg(&kdv)
3592            .arg(&ktb)
3593            .arg(&vtb);
3594        unsafe {
3595            b.launch(cfg)?;
3596        }
3597        Ok(())
3598    }
3599
3600    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3601    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3602    /// later, inside a captured graph) without a host round-trip.
3603    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3604        let f = self.func("inc_i32");
3605        let cfg = LaunchConfig {
3606            grid_dim: (1, 1, 1),
3607            block_dim: (1, 1, 1),
3608            shared_mem_bytes: 0,
3609        };
3610        let __s_b = self.gpu.stream();
3611        let mut b = __s_b.launch_builder(&f);
3612        b.arg(p);
3613        unsafe {
3614            b.launch(cfg)?;
3615        }
3616        Ok(())
3617    }
3618
3619    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3620    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3621    pub fn append_kv_quantized_view(
3622        &self,
3623        k_row: &cudarc::driver::CudaView<f32>,
3624        v_row: &cudarc::driver::CudaView<f32>,
3625        kc: &mut CudaSlice<u8>,
3626        vc: &mut CudaSlice<u8>,
3627        t: usize,
3628        kv_dim_k: usize,
3629        kv_dim_v: usize,
3630        k_tok_bytes: usize,
3631        v_tok_bytes: usize,
3632        g: bool,
3633    ) -> Result<(), Box<dyn std::error::Error>> {
3634        let f = if g {
3635            self.func_g("append_quantize_kv_q8_0_q5_1")
3636        } else {
3637            self.func("append_quantize_kv_q8_0_q5_1")
3638        };
3639        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3640        let cfg = LaunchConfig {
3641            grid_dim: (nblk, 1, 1),
3642            block_dim: (32, 1, 1),
3643            shared_mem_bytes: 0,
3644        };
3645        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3646        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3647        let __s_b = self.gpu.stream();
3648        let mut b = __s_b.launch_builder(&f);
3649        b.arg(k_row)
3650            .arg(v_row)
3651            .arg(kc)
3652            .arg(vc)
3653            .arg(&ti)
3654            .arg(&kdk)
3655            .arg(&kdv)
3656            .arg(&ktb)
3657            .arg(&vtb);
3658        unsafe {
3659            b.launch(cfg)?;
3660        }
3661        Ok(())
3662    }
3663
3664    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3665    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3666    pub fn copy_view_into(
3667        &self,
3668        dst: &mut CudaSlice<f32>,
3669        off: usize,
3670        src: &cudarc::driver::CudaView<f32>,
3671        len: usize,
3672    ) -> Result<(), Box<dyn std::error::Error>> {
3673        let mut view = dst.slice_mut(off..off + len);
3674        self.gpu
3675            .stream()
3676            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3677        Ok(())
3678    }
3679
3680    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3681    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3682    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3683    pub fn clone_dtod(
3684        &self,
3685        src: &CudaSlice<f32>,
3686    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3687        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3688        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3689        Ok(dst)
3690    }
3691
3692    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3693    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3694    pub fn dtod_copy_view(
3695        &self,
3696        src: &cudarc::driver::CudaView<f32>,
3697        dst: &mut CudaSlice<f32>,
3698    ) -> Result<(), Box<dyn std::error::Error>> {
3699        self.gpu.stream().memcpy_dtod(src, dst)?;
3700        Ok(())
3701    }
3702
3703    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3704    pub fn dtod_copy_view_i8(
3705        &self,
3706        src: &cudarc::driver::CudaView<i8>,
3707        dst: &mut CudaSlice<i8>,
3708    ) -> Result<(), Box<dyn std::error::Error>> {
3709        self.gpu.stream().memcpy_dtod(src, dst)?;
3710        Ok(())
3711    }
3712
3713    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3714    pub fn dtod_copy_into(
3715        &self,
3716        src: &CudaSlice<f32>,
3717        dst: &mut CudaSlice<f32>,
3718        offset: usize,
3719    ) -> Result<(), Box<dyn std::error::Error>> {
3720        let n = src.len();
3721        let mut dv = dst.slice_mut(offset..offset + n);
3722        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3723        Ok(())
3724    }
3725
3726    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3727    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3728        self.alloc_uninit::<i8>(n)
3729    }
3730
3731    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3732    pub fn qmatvec(
3733        &self,
3734        w: &CudaSlice<u8>,
3735        x: &CudaSlice<f32>,
3736        m: usize,
3737        in_f: usize,
3738        out_f: usize,
3739        qtype: i32,
3740        row_bytes: usize,
3741    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3742        let f = self.func("qmatvec_f32");
3743        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3744        let cfg = LaunchConfig {
3745            grid_dim: (out_f as u32, m as u32, 1),
3746            block_dim: (256, 1, 1),
3747            shared_mem_bytes: 0,
3748        };
3749        let (inf, outf, mi, qt, rb) =
3750            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3751        let __s_b = self.gpu.stream();
3752        let mut b = __s_b.launch_builder(&f);
3753        b.arg(w)
3754            .arg(x)
3755            .arg(&mut y)
3756            .arg(&inf)
3757            .arg(&outf)
3758            .arg(&mi)
3759            .arg(&qt)
3760            .arg(&rb);
3761        unsafe {
3762            b.launch(cfg)?;
3763        }
3764        Ok(y)
3765    }
3766
3767    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3768    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3769        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3770        self.keep_if_capturing(&s);
3771        Ok(s)
3772    }
3773
3774    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3775    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3776    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3777    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3778        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3779        self.keep_if_capturing(&s);
3780        Ok(s)
3781    }
3782
3783    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3784    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3785    pub fn memset_zeros_view(
3786        &self,
3787        dst: &mut cudarc::driver::CudaViewMut<f32>,
3788    ) -> Result<(), Box<dyn std::error::Error>> {
3789        self.gpu.stream().memset_zeros(dst)?;
3790        Ok(())
3791    }
3792
3793    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3794    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3795    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3796    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3797    /// stream would require an event).
3798    pub fn stage_expert(
3799        &self,
3800        host_bytes: &[u8],
3801        scratch: &mut CudaSlice<u8>,
3802        off: usize,
3803    ) -> Result<(), Box<dyn std::error::Error>> {
3804        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3805        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3806        Ok(())
3807    }
3808
3809    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3810    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3811    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3812    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3813    /// One CTA per token row, 256 threads (one per expert).
3814    pub fn moe_router_topk(
3815        &self,
3816        logits: &CudaSlice<f32>,
3817        t: usize,
3818        n_expert: usize,
3819        n_used: usize,
3820    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3821        let f = self.func("moe_router_topk_f32");
3822        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3823        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3824        let cfg = LaunchConfig {
3825            grid_dim: (t as u32, 1, 1),
3826            block_dim: (n_expert as u32, 1, 1),
3827            shared_mem_bytes: 0,
3828        };
3829        let (ne, nu) = (n_expert as i32, n_used as i32);
3830        let __s_b = self.gpu.stream();
3831        let mut b = __s_b.launch_builder(&f);
3832        b.arg(logits)
3833            .arg(&mut sel_idx)
3834            .arg(&mut sel_w)
3835            .arg(&ne)
3836            .arg(&nu);
3837        unsafe {
3838            b.launch(cfg)?;
3839        }
3840        Ok((sel_idx, sel_w))
3841    }
3842
3843    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3844    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3845    pub fn moe_router_topk_scaled(
3846        &self,
3847        logits: &CudaSlice<f32>,
3848        t: usize,
3849        n_expert: usize,
3850        n_used: usize,
3851        ex_scale: &CudaSlice<f32>,
3852    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3853        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3854        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3855        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3856        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3857        let f = self.func("moe_router_topk_scaled_f32");
3858        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3859        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3860        let cfg = LaunchConfig {
3861            grid_dim: (t as u32, 1, 1),
3862            block_dim: (n_expert as u32, 1, 1),
3863            shared_mem_bytes: 0,
3864        };
3865        let (ne, nu) = (n_expert as i32, n_used as i32);
3866        let __s_b = self.gpu.stream();
3867        let mut b = __s_b.launch_builder(&f);
3868        b.arg(logits)
3869            .arg(&mut sel_idx)
3870            .arg(&mut sel_w)
3871            .arg(&ne)
3872            .arg(&nu)
3873            .arg(ex_scale);
3874        unsafe {
3875            b.launch(cfg)?;
3876        }
3877        Ok((sel_idx, sel_w))
3878    }
3879
3880    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3881    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3882    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3883    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3884    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3885    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3886    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3887    pub fn moe_router_topk_host(
3888        &self,
3889        logits: &CudaSlice<f32>,
3890        t: usize,
3891        n_expert: usize,
3892        n_used: usize,
3893    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3894        let f = self.func("moe_router_topk_f32");
3895        let n = t * n_used;
3896        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
3897        let mut sel_w = self.alloc_uninit::<f32>(n)?;
3898        let cfg = LaunchConfig {
3899            grid_dim: (t as u32, 1, 1),
3900            block_dim: (n_expert as u32, 1, 1),
3901            shared_mem_bytes: 0,
3902        };
3903        let (ne, nu) = (n_expert as i32, n_used as i32);
3904        let __s_b = self.gpu.stream();
3905        let mut b = __s_b.launch_builder(&f);
3906        b.arg(logits)
3907            .arg(&mut sel_idx)
3908            .arg(&mut sel_w)
3909            .arg(&ne)
3910            .arg(&nu);
3911        unsafe {
3912            b.launch(cfg)?;
3913        }
3914        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
3915        let bytes = n * 8;
3916        let mut guard = self.router_stage.lock().unwrap();
3917        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
3918            *guard = Some(PinnedStage::new(bytes.max(4096))?);
3919        }
3920        let stage = guard.as_mut().unwrap();
3921        let (si, sw) = unsafe {
3922            (
3923                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
3924                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
3925            )
3926        };
3927        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
3928        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
3929        self.gpu.stream().synchronize()?; // ONE sync for both
3930        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
3931    }
3932
3933    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
3934    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
3935    /// original expert ids before top-k. Exact key ties choose the smaller original id.
3936    #[allow(clippy::too_many_arguments)]
3937    pub fn moe_router_sigmoid_topk(
3938        &self,
3939        logits: &CudaSlice<f32>,
3940        t: usize,
3941        n_expert: usize,
3942        n_used: usize,
3943        active_count: usize,
3944        correction_bias: &CudaSlice<f32>,
3945        active: &CudaSlice<u8>,
3946        scaling_factor: f32,
3947        route_norm: bool,
3948    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3949        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
3950        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
3951            return Err(format!(
3952                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
3953            )
3954            .into());
3955        }
3956        if logits.len() < t * n_expert
3957            || correction_bias.len() != n_expert
3958            || active.len() != n_expert
3959        {
3960            return Err(format!(
3961                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
3962                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
3963            ).into());
3964        }
3965        let f = self.func("moe_router_sigmoid_topk_f32");
3966        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3967        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3968        let threads = n_expert.div_ceil(32) * 32;
3969        let cfg = LaunchConfig {
3970            grid_dim: (t as u32, 1, 1),
3971            block_dim: (threads as u32, 1, 1),
3972            shared_mem_bytes: 0,
3973        };
3974        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
3975        let __s_b = self.gpu.stream();
3976        let mut b = __s_b.launch_builder(&f);
3977        b.arg(logits)
3978            .arg(correction_bias)
3979            .arg(active)
3980            .arg(&mut sel_idx)
3981            .arg(&mut sel_w)
3982            .arg(&ne)
3983            .arg(&nu)
3984            .arg(&scaling_factor)
3985            .arg(&rn);
3986        unsafe {
3987            b.launch(cfg)?;
3988        }
3989        Ok((sel_idx, sel_w))
3990    }
3991
3992    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
3993    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
3994    #[allow(clippy::too_many_arguments)]
3995    pub fn moe_router_sigmoid_topk_host(
3996        &self,
3997        logits: &CudaSlice<f32>,
3998        t: usize,
3999        n_expert: usize,
4000        n_used: usize,
4001        active_count: usize,
4002        correction_bias: &CudaSlice<f32>,
4003        active: &CudaSlice<u8>,
4004        scaling_factor: f32,
4005        route_norm: bool,
4006    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
4007        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
4008            logits,
4009            t,
4010            n_expert,
4011            n_used,
4012            active_count,
4013            correction_bias,
4014            active,
4015            scaling_factor,
4016            route_norm,
4017        )?;
4018        let n = t * n_used;
4019        let bytes = n * 8;
4020        let mut guard = self.router_stage.lock().unwrap();
4021        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
4022            *guard = Some(PinnedStage::new(bytes.max(4096))?);
4023        }
4024        let stage = guard.as_mut().unwrap();
4025        let (si, sw) = unsafe {
4026            (
4027                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
4028                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
4029            )
4030        };
4031        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
4032        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
4033        self.gpu.stream().synchronize()?;
4034        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
4035    }
4036
4037    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
4038    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
4039    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
4040    pub fn stage_expert_async(
4041        &self,
4042        host_bytes: &[u8],
4043        scratch: &mut CudaSlice<u8>,
4044        off: usize,
4045    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4046        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4047        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4048        Ok(self.copy_stream.record_event(None)?)
4049    }
4050
4051    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4052    pub fn compute_wait(
4053        &self,
4054        ev: &cudarc::driver::CudaEvent,
4055    ) -> Result<(), Box<dyn std::error::Error>> {
4056        self.gpu.stream().wait(ev)?;
4057        Ok(())
4058    }
4059
4060    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4061    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4062    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4063    /// CudaView base+offset pointer is honored by the launch arg.
4064    pub fn qmatvec_view(
4065        &self,
4066        w: &CudaSlice<u8>,
4067        range: std::ops::Range<usize>,
4068        x: &cudarc::driver::CudaView<f32>,
4069        m: usize,
4070        in_f: usize,
4071        out_f: usize,
4072        qtype: i32,
4073        row_bytes: usize,
4074    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4075        let f = self.func("qmatvec_f32");
4076        let wv = w.slice(range); // CudaView<u8>, offset honored
4077        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4078        let cfg = LaunchConfig {
4079            grid_dim: (out_f as u32, m as u32, 1),
4080            block_dim: (256, 1, 1),
4081            shared_mem_bytes: 0,
4082        };
4083        let (inf, outf, mi, qt, rb) =
4084            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4085        let __s_b = self.gpu.stream();
4086        let mut b = __s_b.launch_builder(&f);
4087        b.arg(&wv)
4088            .arg(x)
4089            .arg(&mut y)
4090            .arg(&inf)
4091            .arg(&outf)
4092            .arg(&mi)
4093            .arg(&qt)
4094            .arg(&rb);
4095        unsafe {
4096            b.launch(cfg)?;
4097        }
4098        Ok(y)
4099    }
4100
4101    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4102    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4103    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4104    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4105    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4106    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4107    #[allow(clippy::too_many_arguments)]
4108    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4109    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4110    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4111    pub fn moe_gate_up_silu8_q8(
4112        &self,
4113        gp: WPtr8,
4114        up: WPtr8,
4115        aq: &CudaSlice<i8>,
4116        ad: &CudaSlice<f32>,
4117        in_f: usize,
4118        n_ff: usize,
4119        n_used: usize,
4120        qt_g: i32,
4121        qt_u: i32,
4122        rb_g: usize,
4123        rb_u: usize,
4124    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4125        let f = self.func("moe_gate_up_silu8_q8");
4126        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4127        let cfg = LaunchConfig {
4128            grid_dim: (n_ff as u32, n_used as u32, 1),
4129            block_dim: (32, 1, 1),
4130            shared_mem_bytes: 0,
4131        };
4132        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4133        let __s_b = self.gpu.stream();
4134        let mut b = __s_b.launch_builder(&f);
4135        b.arg(&gp)
4136            .arg(&up)
4137            .arg(aq)
4138            .arg(ad)
4139            .arg(&mut act)
4140            .arg(&inf)
4141            .arg(&nff)
4142            .arg(&qt_g)
4143            .arg(&qt_u)
4144            .arg(&rbg)
4145            .arg(&rbu);
4146        unsafe {
4147            b.launch(cfg)?;
4148        }
4149        Ok(act)
4150    }
4151
4152    #[allow(clippy::too_many_arguments)]
4153    pub fn moe_down8_fma_q8(
4154        &self,
4155        dp: WPtr8,
4156        w: F32x8,
4157        aq2: &CudaSlice<i8>,
4158        ad2: &CudaSlice<f32>,
4159        dst: &mut cudarc::driver::CudaViewMut<f32>,
4160        in_f: usize,
4161        out_f: usize,
4162        n_used: usize,
4163        qt: i32,
4164        rb: usize,
4165    ) -> Result<(), Box<dyn std::error::Error>> {
4166        let f = self.func("moe_down8_fma_q8");
4167        let cfg = LaunchConfig {
4168            grid_dim: (out_f as u32, 1, 1),
4169            block_dim: (32, 1, 1),
4170            shared_mem_bytes: 0,
4171        };
4172        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4173        let __s_b = self.gpu.stream();
4174        let mut b = __s_b.launch_builder(&f);
4175        b.arg(&dp)
4176            .arg(&w)
4177            .arg(aq2)
4178            .arg(ad2)
4179            .arg(dst)
4180            .arg(&inf)
4181            .arg(&outf)
4182            .arg(&nu)
4183            .arg(&qt)
4184            .arg(&rbi);
4185        unsafe {
4186            b.launch(cfg)?;
4187        }
4188        Ok(())
4189    }
4190
4191    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4192    pub fn qmatvec_expert_q8(
4193        &self,
4194        w: &CudaSlice<u8>,
4195        range: std::ops::Range<usize>,
4196        aq: &CudaSlice<i8>,
4197        ad: &CudaSlice<f32>,
4198        m: usize,
4199        in_f: usize,
4200        out_f: usize,
4201        qtype: i32,
4202        row_bytes: usize,
4203    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4204        let f = self.func("qmatvec_expert_q8");
4205        let wv = w.slice(range);
4206        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4207        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4208        let cfg = LaunchConfig {
4209            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4210            block_dim: (32, ROWS, 1),
4211            shared_mem_bytes: 0,
4212        };
4213        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4214        let __s_b = self.gpu.stream();
4215        let mut b = __s_b.launch_builder(&f);
4216        b.arg(&wv)
4217            .arg(aq)
4218            .arg(ad)
4219            .arg(&mut y)
4220            .arg(&inf)
4221            .arg(&outf)
4222            .arg(&mi)
4223            .arg(&qtype)
4224            .arg(&rbi);
4225        unsafe {
4226            b.launch(cfg)?;
4227        }
4228        Ok(y)
4229    }
4230
4231    pub fn moe_gate_up_silu8(
4232        &self,
4233        gp: WPtr8,
4234        up: WPtr8,
4235        x: &cudarc::driver::CudaView<f32>,
4236        in_f: usize,
4237        n_ff: usize,
4238        n_used: usize,
4239        qt_g: i32,
4240        qt_u: i32,
4241        rb_g: usize,
4242        rb_u: usize,
4243    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4244        let f = self.func("moe_gate_up_silu8_f32");
4245        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4246        let cfg = LaunchConfig {
4247            grid_dim: (n_ff as u32, n_used as u32, 1),
4248            block_dim: (256, 1, 1),
4249            shared_mem_bytes: 0,
4250        };
4251        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4252        let __s_b = self.gpu.stream();
4253        let mut b = __s_b.launch_builder(&f);
4254        b.arg(&gp)
4255            .arg(&up)
4256            .arg(x)
4257            .arg(&mut act)
4258            .arg(&inf)
4259            .arg(&nff)
4260            .arg(&qt_g)
4261            .arg(&qt_u)
4262            .arg(&rbg)
4263            .arg(&rbu);
4264        unsafe {
4265            b.launch(cfg)?;
4266        }
4267        Ok(act)
4268    }
4269
4270    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4271    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4272    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4273    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4274    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4275    #[allow(clippy::too_many_arguments)]
4276    pub fn moe_down8_fma_into(
4277        &self,
4278        dp: WPtr8,
4279        w: F32x8,
4280        act: &CudaSlice<f32>,
4281        dst: &mut cudarc::driver::CudaViewMut<f32>,
4282        in_f: usize,
4283        out_f: usize,
4284        n_used: usize,
4285        qt: i32,
4286        rb: usize,
4287    ) -> Result<(), Box<dyn std::error::Error>> {
4288        let f = self.func("moe_down8_fma_f32");
4289        let cfg = LaunchConfig {
4290            grid_dim: (out_f as u32, 1, 1),
4291            block_dim: (256, 1, 1),
4292            shared_mem_bytes: 0,
4293        };
4294        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4295        let __s_b = self.gpu.stream();
4296        let mut b = __s_b.launch_builder(&f);
4297        b.arg(&dp)
4298            .arg(&w)
4299            .arg(act)
4300            .arg(dst)
4301            .arg(&inf)
4302            .arg(&outf)
4303            .arg(&nu)
4304            .arg(&qt)
4305            .arg(&rbv);
4306        unsafe {
4307            b.launch(cfg)?;
4308        }
4309        Ok(())
4310    }
4311
4312    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4313    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4314    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4315    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4316    #[allow(clippy::too_many_arguments)]
4317    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4318    ///
4319    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4320    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4321    /// down's FMA chain stays slot-ordered serial). Seams:
4322    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4323    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4324    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4325    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4326    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4327    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4328    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4329    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4330    ///                       only) | w8h2 (h2 x slot-parallel)
4331    #[allow(clippy::too_many_arguments)]
4332    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4333    #[allow(clippy::too_many_arguments)]
4334    pub fn moe_pairs_matvec_q8(
4335        &self,
4336        table: &CudaSlice<u64>,
4337        proj: i32,
4338        pair_tok: &CudaSlice<i32>,
4339        pair_ex: &CudaSlice<i32>,
4340        aq: &CudaSlice<i8>,
4341        ad: &CudaSlice<f32>,
4342        in_f: usize,
4343        out_f: usize,
4344        n_expert: usize,
4345        n_pairs: usize,
4346        qtype: i32,
4347        row_bytes: usize,
4348    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4349        let f = self.func("moe_pairs_matvec_q8");
4350        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4351        const ROWS: u32 = 4;
4352        let cfg = LaunchConfig {
4353            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4354            block_dim: (32, ROWS, 1),
4355            shared_mem_bytes: 0,
4356        };
4357        let (inf, outf, ne, np, rbi) = (
4358            in_f as i32,
4359            out_f as i32,
4360            n_expert as i32,
4361            n_pairs as i32,
4362            row_bytes as i64,
4363        );
4364        let __s_b = self.gpu.stream();
4365        let mut b = __s_b.launch_builder(&f);
4366        b.arg(table)
4367            .arg(&proj)
4368            .arg(pair_tok)
4369            .arg(pair_ex)
4370            .arg(aq)
4371            .arg(ad)
4372            .arg(&mut y)
4373            .arg(&inf)
4374            .arg(&outf)
4375            .arg(&ne)
4376            .arg(&np)
4377            .arg(&qtype)
4378            .arg(&rbi);
4379        unsafe {
4380            b.launch(cfg)?;
4381        }
4382        Ok(y)
4383    }
4384
4385    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4386    #[allow(clippy::too_many_arguments)]
4387    pub fn moe_pairs_matvec_q8_em(
4388        &self,
4389        table: &CudaSlice<u64>,
4390        proj: i32,
4391        ex_ids: &CudaSlice<i32>,
4392        ex_off: &CudaSlice<i32>,
4393        ex_pairs: &CudaSlice<i32>,
4394        pair_tok: &CudaSlice<i32>,
4395        aq: &CudaSlice<i8>,
4396        ad: &CudaSlice<f32>,
4397        in_f: usize,
4398        out_f: usize,
4399        n_expert: usize,
4400        n_active: usize,
4401        n_pairs: usize,
4402        qtype: i32,
4403        row_bytes: usize,
4404    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4405        let f = self.func("moe_pairs_matvec_q8_em");
4406        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4407        const ROWS: u32 = 4;
4408        let cfg = LaunchConfig {
4409            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4410            block_dim: (32, ROWS, 1),
4411            shared_mem_bytes: 0,
4412        };
4413        let (inf, outf, ne, na, rbi) = (
4414            in_f as i32,
4415            out_f as i32,
4416            n_expert as i32,
4417            n_active as i32,
4418            row_bytes as i64,
4419        );
4420        let __s_b = self.gpu.stream();
4421        let mut b = __s_b.launch_builder(&f);
4422        b.arg(table)
4423            .arg(&proj)
4424            .arg(ex_ids)
4425            .arg(ex_off)
4426            .arg(ex_pairs)
4427            .arg(pair_tok)
4428            .arg(aq)
4429            .arg(ad)
4430            .arg(&mut y)
4431            .arg(&inf)
4432            .arg(&outf)
4433            .arg(&ne)
4434            .arg(&na)
4435            .arg(&qtype)
4436            .arg(&rbi);
4437        unsafe {
4438            b.launch(cfg)?;
4439        }
4440        Ok(y)
4441    }
4442
4443    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4444    // weight group once per (row,group) then dp4a's across the expert's token group.
4445    #[allow(clippy::too_many_arguments)]
4446    pub fn moe_pairs_matvec_q8_dec(
4447        &self,
4448        table: &CudaSlice<u64>,
4449        proj: i32,
4450        ex_ids: &CudaSlice<i32>,
4451        ex_off: &CudaSlice<i32>,
4452        ex_pairs: &CudaSlice<i32>,
4453        pair_tok: &CudaSlice<i32>,
4454        aq: &CudaSlice<i8>,
4455        ad: &CudaSlice<f32>,
4456        in_f: usize,
4457        out_f: usize,
4458        n_expert: usize,
4459        n_active: usize,
4460        n_pairs: usize,
4461        qtype: i32,
4462        row_bytes: usize,
4463    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4464        let f = self.func("moe_pairs_matvec_q8_dec");
4465        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4466        const ROWS: u32 = 4;
4467        let cfg = LaunchConfig {
4468            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4469            block_dim: (32, ROWS, 1),
4470            shared_mem_bytes: 0,
4471        };
4472        let (inf, outf, ne, na, rbi) = (
4473            in_f as i32,
4474            out_f as i32,
4475            n_expert as i32,
4476            n_active as i32,
4477            row_bytes as i64,
4478        );
4479        let __s_b = self.gpu.stream();
4480        let mut b = __s_b.launch_builder(&f);
4481        b.arg(table)
4482            .arg(&proj)
4483            .arg(ex_ids)
4484            .arg(ex_off)
4485            .arg(ex_pairs)
4486            .arg(pair_tok)
4487            .arg(aq)
4488            .arg(ad)
4489            .arg(&mut y)
4490            .arg(&inf)
4491            .arg(&outf)
4492            .arg(&ne)
4493            .arg(&na)
4494            .arg(&qtype)
4495            .arg(&rbi);
4496        unsafe {
4497            b.launch(cfg)?;
4498        }
4499        Ok(y)
4500    }
4501
4502    pub fn moe_pairs_gelu_mul(
4503        &self,
4504        gate: &CudaSlice<f32>,
4505        up: &CudaSlice<f32>,
4506        n: usize,
4507    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4508        let f = self.func("moe_pairs_gelu_mul");
4509        let mut act = self.alloc_uninit::<f32>(n)?;
4510        let cfg = LaunchConfig::for_num_elems(n as u32);
4511        let nl = n as i64;
4512        let __s_b = self.gpu.stream();
4513        let mut b = __s_b.launch_builder(&f);
4514        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4515        unsafe {
4516            b.launch(cfg)?;
4517        }
4518        Ok(act)
4519    }
4520
4521    pub fn moe_pairs_silu_mul(
4522        &self,
4523        gate: &CudaSlice<f32>,
4524        up: &CudaSlice<f32>,
4525        n: usize,
4526    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4527        let f = self.func("moe_pairs_silu_mul");
4528        let mut act = self.alloc_uninit::<f32>(n)?;
4529        let cfg = LaunchConfig::for_num_elems(n as u32);
4530        let nl = n as i64;
4531        let __s_b = self.gpu.stream();
4532        let mut b = __s_b.launch_builder(&f);
4533        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4534        unsafe {
4535            b.launch(cfg)?;
4536        }
4537        Ok(act)
4538    }
4539
4540    #[allow(clippy::too_many_arguments)]
4541    pub fn moe_pairs_scatter(
4542        &self,
4543        y_down: &CudaSlice<f32>,
4544        pair_w: &CudaSlice<f32>,
4545        tok_pair_off: &CudaSlice<i32>,
4546        tok_pair_ids: &CudaSlice<i32>,
4547        moe_out: &mut CudaSlice<f32>,
4548        t: usize,
4549        n_embd: usize,
4550    ) -> Result<(), Box<dyn std::error::Error>> {
4551        let f = self.func("moe_pairs_scatter");
4552        let cfg = LaunchConfig {
4553            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4554            block_dim: (256, 1, 1),
4555            shared_mem_bytes: 0,
4556        };
4557        let ne = n_embd as i32;
4558        let __s_b = self.gpu.stream();
4559        let mut b = __s_b.launch_builder(&f);
4560        b.arg(y_down)
4561            .arg(pair_w)
4562            .arg(tok_pair_off)
4563            .arg(tok_pair_ids)
4564            .arg(moe_out)
4565            .arg(&ne);
4566        unsafe {
4567            b.launch(cfg)?;
4568        }
4569        Ok(())
4570    }
4571
4572    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4573    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4574    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4575    #[allow(clippy::too_many_arguments)]
4576    pub fn moe_gate_up_gelu8_dev_q8(
4577        &self,
4578        table: &CudaSlice<u64>,
4579        sel: &cudarc::driver::CudaView<i32>,
4580        aq: &CudaSlice<i8>,
4581        ad: &CudaSlice<f32>,
4582        in_f: usize,
4583        n_ff: usize,
4584        n_used: usize,
4585        n_expert: usize,
4586        qt_g: i32,
4587        qt_u: i32,
4588        rb_g: usize,
4589        rb_u: usize,
4590    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4591        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4592        let (inf, nff, ne, rbg, rbu) = (
4593            in_f as i32,
4594            n_ff as i32,
4595            n_expert as i32,
4596            rb_g as i64,
4597            rb_u as i64,
4598        );
4599        let f = self.func("moe_gate_up_gelu8_dev_q8");
4600        let cfg = LaunchConfig {
4601            grid_dim: (n_ff as u32, n_used as u32, 1),
4602            block_dim: (32, 1, 1),
4603            shared_mem_bytes: 0,
4604        };
4605        let __s_b = self.gpu.stream();
4606        let mut b = __s_b.launch_builder(&f);
4607        b.arg(table)
4608            .arg(sel)
4609            .arg(aq)
4610            .arg(ad)
4611            .arg(&mut act)
4612            .arg(&inf)
4613            .arg(&nff)
4614            .arg(&ne)
4615            .arg(&qt_g)
4616            .arg(&qt_u)
4617            .arg(&rbg)
4618            .arg(&rbu);
4619        unsafe {
4620            b.launch(cfg)?;
4621        }
4622        Ok(act)
4623    }
4624
4625    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4626    #[allow(clippy::too_many_arguments)]
4627    pub fn moe_gate_up_gelu8_dev_q8_rows(
4628        &self,
4629        table: &CudaSlice<u64>,
4630        sel: &CudaSlice<i32>,
4631        aq: &CudaSlice<i8>,
4632        ad: &CudaSlice<f32>,
4633        t: usize,
4634        in_f: usize,
4635        n_ff: usize,
4636        n_used: usize,
4637        n_expert: usize,
4638        qt_g: i32,
4639        qt_u: i32,
4640        rb_g: usize,
4641        rb_u: usize,
4642    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4643        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4644        let (inf, nff, ne, rbg, rbu, nu) = (
4645            in_f as i32,
4646            n_ff as i32,
4647            n_expert as i32,
4648            rb_g as i64,
4649            rb_u as i64,
4650            n_used as i32,
4651        );
4652        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4653        let cfg = LaunchConfig {
4654            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4655            block_dim: (32, 1, 1),
4656            shared_mem_bytes: 0,
4657        };
4658        let __s_b = self.gpu.stream();
4659        let mut b = __s_b.launch_builder(&f);
4660        b.arg(table)
4661            .arg(sel)
4662            .arg(aq)
4663            .arg(ad)
4664            .arg(&mut act)
4665            .arg(&inf)
4666            .arg(&nff)
4667            .arg(&ne)
4668            .arg(&qt_g)
4669            .arg(&qt_u)
4670            .arg(&rbg)
4671            .arg(&rbu)
4672            .arg(&nu);
4673        unsafe {
4674            b.launch(cfg)?;
4675        }
4676        Ok(act)
4677    }
4678
4679    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4680    #[allow(clippy::too_many_arguments)]
4681    pub fn moe_gate_up_gelu8_dev_q8_csr(
4682        &self,
4683        table: &CudaSlice<u64>,
4684        sel: &CudaSlice<i32>,
4685        aq: &CudaSlice<i8>,
4686        ad: &CudaSlice<f32>,
4687        n_pairs: usize,
4688        in_f: usize,
4689        n_ff: usize,
4690        n_used: usize,
4691        n_expert: usize,
4692        qt_g: i32,
4693        qt_u: i32,
4694        rb_g: usize,
4695        rb_u: usize,
4696    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4697        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4698        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4699            in_f as i32,
4700            n_ff as i32,
4701            n_expert as i32,
4702            rb_g as i64,
4703            rb_u as i64,
4704            n_used as i32,
4705            n_pairs as i32,
4706        );
4707        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4708        let cfg = LaunchConfig {
4709            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4710            block_dim: (32, 1, 1),
4711            shared_mem_bytes: 0,
4712        };
4713        let __s_b = self.gpu.stream();
4714        let mut b = __s_b.launch_builder(&f);
4715        b.arg(table)
4716            .arg(sel)
4717            .arg(aq)
4718            .arg(ad)
4719            .arg(&mut act)
4720            .arg(&inf)
4721            .arg(&nff)
4722            .arg(&ne)
4723            .arg(&qt_g)
4724            .arg(&qt_u)
4725            .arg(&rbg)
4726            .arg(&rbu)
4727            .arg(&nu)
4728            .arg(&npi);
4729        unsafe {
4730            b.launch(cfg)?;
4731        }
4732        Ok(act)
4733    }
4734
4735    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4736    #[allow(clippy::too_many_arguments)]
4737    pub fn moe_down8_fma_dev_q8_rows_g(
4738        &self,
4739        table: &CudaSlice<u64>,
4740        sel: &CudaSlice<i32>,
4741        w: &CudaSlice<f32>,
4742        aq2: &CudaSlice<i8>,
4743        ad2: &CudaSlice<f32>,
4744        dst: &mut CudaSlice<f32>,
4745        t: usize,
4746        in_f: usize,
4747        out_f: usize,
4748        n_used: usize,
4749        n_expert: usize,
4750        qt: i32,
4751        rb: usize,
4752    ) -> Result<(), Box<dyn std::error::Error>> {
4753        let (inf, outf, nu, ne, rbi) = (
4754            in_f as i32,
4755            out_f as i32,
4756            n_used as i32,
4757            n_expert as i32,
4758            rb as i64,
4759        );
4760        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4761        // eight warps, then replay the original slot-ordered FMA chain. Every
4762        // other shape retains the generic one-warp rows kernel.
4763        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4764        let f = self.func(if step_b1_w8 {
4765            "moe_down8_fma_dev_q8_rows_w8"
4766        } else {
4767            "moe_down8_fma_dev_q8_rows_g"
4768        });
4769        let cfg = LaunchConfig {
4770            grid_dim: (out_f as u32, 1, t as u32),
4771            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4772            shared_mem_bytes: 0,
4773        };
4774        let __s_b = self.gpu.stream();
4775        let mut b = __s_b.launch_builder(&f);
4776        b.arg(table)
4777            .arg(sel)
4778            .arg(w)
4779            .arg(aq2)
4780            .arg(ad2)
4781            .arg(dst)
4782            .arg(&inf)
4783            .arg(&outf)
4784            .arg(&nu)
4785            .arg(&ne)
4786            .arg(&qt)
4787            .arg(&rbi);
4788        unsafe {
4789            b.launch(cfg)?;
4790        }
4791        Ok(())
4792    }
4793
4794    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4795    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4796    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4797    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4798        let (out_f, in_f) = (2048usize, 2816usize);
4799        let nblk = in_f / 32;
4800        let mut seed = 0x9E3779B97F4A7C15u64;
4801        let mut rng = move || {
4802            seed = seed
4803                .wrapping_mul(6364136223846793005)
4804                .wrapping_add(1442695040888963407);
4805            (seed >> 33) as u8
4806        };
4807        let mut w = vec![0u8; out_f * nblk * 18];
4808        for b in w.iter_mut() {
4809            *b = rng();
4810        }
4811        for r in 0..out_f {
4812            for g in 0..nblk {
4813                let off = (r * nblk + g) * 18;
4814                w[off] = 0x00;
4815                w[off + 1] = 0x2C; // sane half d
4816            }
4817        }
4818        let qplane = out_f * nblk * 16;
4819        let mut wrp = vec![0u8; w.len()];
4820        for r in 0..out_f {
4821            for g in 0..nblk {
4822                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4823                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4824                    .copy_from_slice(&src[0..2]);
4825                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4826            }
4827        }
4828        let w_d = self.htod_bytes(&w)?;
4829        let wrp_d = self.htod_bytes(&wrp)?;
4830        let mut aq = vec![0i8; m * in_f];
4831        for v in aq.iter_mut() {
4832            *v = rng() as i8;
4833        }
4834        let aq_d = self.htod_i8(&aq)?;
4835        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4836        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4837        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4838        const RPB: u32 = 4;
4839        let cfg = LaunchConfig {
4840            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4841            block_dim: (32, RPB, 1),
4842            shared_mem_bytes: 0,
4843        };
4844        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4845        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4846        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4847        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4848        {
4849            let __s_b = self.gpu.stream();
4850            let mut b = __s_b.launch_builder(&fb);
4851            b.arg(&w_d)
4852                .arg(&aq_d)
4853                .arg(&ad_d)
4854                .arg(&mut y0)
4855                .arg(&inf)
4856                .arg(&outf)
4857                .arg(&mi)
4858                .arg(&rb);
4859            unsafe {
4860                b.launch(cfg)?;
4861            }
4862            let __s_b = self.gpu.stream();
4863            let mut b = __s_b.launch_builder(&fr);
4864            b.arg(&wrp_d)
4865                .arg(&aq_d)
4866                .arg(&ad_d)
4867                .arg(&mut y1)
4868                .arg(&inf)
4869                .arg(&outf)
4870                .arg(&mi)
4871                .arg(&qp);
4872            unsafe {
4873                b.launch(cfg)?;
4874            }
4875        }
4876        self.gpu.stream().synchronize()?;
4877        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4878        let nd = h0
4879            .iter()
4880            .zip(&h1)
4881            .filter(|(a, b)| a.to_bits() != b.to_bits())
4882            .count();
4883        if nd != 0 {
4884            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4885        }
4886        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4887            self.gpu.stream().synchronize()?;
4888            let t0 = std::time::Instant::now();
4889            for _ in 0..500 {
4890                if rp {
4891                    let __s_b = self.gpu.stream();
4892                    let mut b = __s_b.launch_builder(&fr);
4893                    b.arg(&wrp_d)
4894                        .arg(&aq_d)
4895                        .arg(&ad_d)
4896                        .arg(&mut y1)
4897                        .arg(&inf)
4898                        .arg(&outf)
4899                        .arg(&mi)
4900                        .arg(&qp);
4901                    unsafe {
4902                        b.launch(cfg)?;
4903                    }
4904                } else {
4905                    let __s_b = self.gpu.stream();
4906                    let mut b = __s_b.launch_builder(&fb);
4907                    b.arg(&w_d)
4908                        .arg(&aq_d)
4909                        .arg(&ad_d)
4910                        .arg(&mut y0)
4911                        .arg(&inf)
4912                        .arg(&outf)
4913                        .arg(&mi)
4914                        .arg(&rb);
4915                    unsafe {
4916                        b.launch(cfg)?;
4917                    }
4918                }
4919            }
4920            self.gpu.stream().synchronize()?;
4921            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
4922        };
4923        let _ = time(false)?;
4924        let _ = time(true)?; // warm
4925        Ok((time(false)?, time(true)?))
4926    }
4927
4928    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
4929    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
4930    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
4931    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
4932    pub fn build_q4_rp4(
4933        &self,
4934        t: &mut crate::model::GpuTensor,
4935    ) -> Result<(), Box<dyn std::error::Error>> {
4936        use crate::model::GpuTensor;
4937        let GpuTensor::Quant {
4938            bytes,
4939            qtype,
4940            row_bytes,
4941            ne,
4942            rp4,
4943            ..
4944        } = t
4945        else {
4946            return Ok(());
4947        };
4948        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
4949            return Ok(());
4950        }
4951        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
4952        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
4953            return Ok(());
4954        }
4955        let nblk = in_f / 32;
4956        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
4957        let f = self.func("q4_0_split_rp_build");
4958        let n = (out_f * nblk) as i32;
4959        let cfg = LaunchConfig {
4960            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
4961            block_dim: (256, 1, 1),
4962            shared_mem_bytes: 0,
4963        };
4964        let (of, nb) = (out_f as i32, nblk as i32);
4965        let _ = n;
4966        let __s_b = self.gpu.stream();
4967        let mut b = __s_b.launch_builder(&f);
4968        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
4969        unsafe {
4970            b.launch(cfg)?;
4971        }
4972        *rp4 = Some(dst);
4973        Ok(())
4974    }
4975
4976    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
4977    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
4978    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
4979    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
4980    pub fn build_q8_rp4(
4981        &self,
4982        t: &mut crate::model::GpuTensor,
4983    ) -> Result<(), Box<dyn std::error::Error>> {
4984        use crate::model::GpuTensor;
4985        let GpuTensor::Quant {
4986            bytes,
4987            qtype,
4988            row_bytes,
4989            ne,
4990            rp4,
4991            ..
4992        } = t
4993        else {
4994            return Ok(());
4995        };
4996        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
4997            return Ok(());
4998        }
4999        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5000        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
5001            return Ok(());
5002        }
5003        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
5004        Ok(())
5005    }
5006
5007    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
5008    /// mirror without a GpuTensor (same kernel the loader path above uses).
5009    pub fn build_q8_rp4_raw(
5010        &self,
5011        bytes: &CudaSlice<u8>,
5012        in_f: usize,
5013        out_f: usize,
5014    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5015        assert!(in_f % 32 == 0);
5016        let nblk = in_f / 32;
5017        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
5018        let f = self.func("q8_0_split_rp_build");
5019        let cfg = LaunchConfig {
5020            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
5021            block_dim: (256, 1, 1),
5022            shared_mem_bytes: 0,
5023        };
5024        let (of, nb) = (out_f as i32, nblk as i32);
5025        let __s_b = self.gpu.stream();
5026        let mut b = __s_b.launch_builder(&f);
5027        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5028        unsafe {
5029            b.launch(cfg)?;
5030        }
5031        Ok(dst)
5032    }
5033
5034    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
5035    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
5036    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
5037    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
5038    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
5039    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
5040    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
5041    pub fn build_q4k_rp4(
5042        &self,
5043        t: &mut crate::model::GpuTensor,
5044    ) -> Result<(), Box<dyn std::error::Error>> {
5045        use crate::model::GpuTensor;
5046        let GpuTensor::Quant {
5047            bytes,
5048            qtype,
5049            row_bytes,
5050            ne,
5051            rp4,
5052            ..
5053        } = t
5054        else {
5055            return Ok(());
5056        };
5057        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5058            return Ok(());
5059        }
5060        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5061        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5062            return Ok(());
5063        }
5064        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5065        Ok(())
5066    }
5067
5068    pub fn build_q6k_rp4(
5069        &self,
5070        t: &mut crate::model::GpuTensor,
5071    ) -> Result<(), Box<dyn std::error::Error>> {
5072        use crate::model::GpuTensor;
5073        let GpuTensor::Quant {
5074            bytes,
5075            qtype,
5076            row_bytes,
5077            ne,
5078            rp4,
5079            ..
5080        } = t
5081        else {
5082            return Ok(());
5083        };
5084        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5085            return Ok(());
5086        }
5087        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5088        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5089            return Ok(());
5090        }
5091        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5092        Ok(())
5093    }
5094
5095    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5096    pub fn build_kq_rp4_raw(
5097        &self,
5098        bytes: &CudaSlice<u8>,
5099        in_f: usize,
5100        out_f: usize,
5101        qtype: i32,
5102    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5103        assert!(in_f % 256 == 0);
5104        let nsbk = in_f / 256;
5105        let (sb_bytes, kname) = match qtype {
5106            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5107            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5108            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5109        };
5110        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5111        let f = self.func(kname);
5112        let cfg = LaunchConfig {
5113            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5114            block_dim: (256, 1, 1),
5115            shared_mem_bytes: 0,
5116        };
5117        let (of, nb) = (out_f as i32, nsbk as i32);
5118        let __s_b = self.gpu.stream();
5119        let mut b = __s_b.launch_builder(&f);
5120        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5121        unsafe {
5122            b.launch(cfg)?;
5123        }
5124        Ok(dst)
5125    }
5126
5127    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5128    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5129    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5130    pub fn kqrp_enabled() -> bool {
5131        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5132        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5133            Ok("0") => false,
5134            Ok(_) => true,
5135            Err(_) => cfg!(memra_hopper_mma),
5136        })
5137    }
5138
5139    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5140    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5141    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5142    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5143    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5144    pub fn build_q4_rp_swap(
5145        &self,
5146        t: &mut crate::model::GpuTensor,
5147    ) -> Result<bool, Box<dyn std::error::Error>> {
5148        self.build_q4_rp4(t)?;
5149        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5150        use crate::model::GpuTensor;
5151        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5152            return Ok(false);
5153        };
5154        match rp4.take() {
5155            Some(split) => {
5156                *bytes = split; // the GGUF-layout buffer drops here
5157                *rp = true;
5158                Ok(true)
5159            }
5160            None => Ok(false),
5161        }
5162    }
5163
5164    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5165    pub fn q4rp_enabled() -> bool {
5166        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5167        *ON.get_or_init(|| {
5168            std::env::var("MEMRA_Q4RP")
5169                .map(|v| v != "0")
5170                .unwrap_or(true)
5171        })
5172    }
5173
5174    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5175    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5176    pub fn copy_rows_strided(
5177        &self,
5178        src: &CudaSlice<f32>,
5179        dst: &mut CudaSlice<f32>,
5180        row_elems: usize,
5181        n_rows: usize,
5182        src_stride: usize,
5183        src_off: usize,
5184    ) -> Result<(), Box<dyn std::error::Error>> {
5185        let f = self.func("copy_rows_strided_f32");
5186        let cfg = LaunchConfig {
5187            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5188            block_dim: (256, 1, 1),
5189            shared_mem_bytes: 0,
5190        };
5191        let (re, nr) = (row_elems as i32, n_rows as i32);
5192        let (st, off) = (src_stride as i64, src_off as i64);
5193        let __s_b = self.gpu.stream();
5194        let mut b = __s_b.launch_builder(&f);
5195        b.arg(src)
5196            .arg(&mut *dst)
5197            .arg(&re)
5198            .arg(&nr)
5199            .arg(&st)
5200            .arg(&off);
5201        unsafe {
5202            b.launch(cfg)?;
5203        }
5204        Ok(())
5205    }
5206
5207    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5208    pub fn u32_set_k(
5209        &self,
5210        dst: &mut CudaSlice<u32>,
5211        v: u32,
5212        idx: usize,
5213    ) -> Result<(), Box<dyn std::error::Error>> {
5214        let f = self.func("u32_set_k");
5215        let cfg = LaunchConfig {
5216            grid_dim: (1, 1, 1),
5217            block_dim: (1, 1, 1),
5218            shared_mem_bytes: 0,
5219        };
5220        let ii = idx as i32;
5221        let __s_b = self.gpu.stream();
5222        let mut b = __s_b.launch_builder(&f);
5223        b.arg(dst).arg(&v).arg(&ii);
5224        unsafe {
5225            b.launch(cfg)?;
5226        }
5227        Ok(())
5228    }
5229
5230    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5231    pub fn i32_add_k(
5232        &self,
5233        d: &mut CudaSlice<i32>,
5234        v: i32,
5235    ) -> Result<(), Box<dyn std::error::Error>> {
5236        let f = self.func("i32_add_k");
5237        let cfg = LaunchConfig {
5238            grid_dim: (1, 1, 1),
5239            block_dim: (32, 1, 1),
5240            shared_mem_bytes: 0,
5241        };
5242        let __s_b = self.gpu.stream();
5243        let mut b = __s_b.launch_builder(&f);
5244        b.arg(d).arg(&v);
5245        unsafe {
5246            b.launch(cfg)?;
5247        }
5248        Ok(())
5249    }
5250
5251    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5252    pub fn i32_iota_from(
5253        &self,
5254        ctr: &CudaSlice<i32>,
5255        dst: &mut CudaSlice<i32>,
5256        n: usize,
5257    ) -> Result<(), Box<dyn std::error::Error>> {
5258        let f = self.func("i32_iota_from");
5259        let cfg = LaunchConfig::for_num_elems(n as u32);
5260        let ni = n as i32;
5261        let __s_b = self.gpu.stream();
5262        let mut b = __s_b.launch_builder(&f);
5263        b.arg(ctr).arg(dst).arg(&ni);
5264        unsafe {
5265            b.launch(cfg)?;
5266        }
5267        Ok(())
5268    }
5269
5270    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5271    pub fn u32_map_k(
5272        &self,
5273        buf: &mut CudaSlice<u32>,
5274        map: &CudaSlice<u32>,
5275        idx: usize,
5276    ) -> Result<(), Box<dyn std::error::Error>> {
5277        let f = self.func("u32_map_k");
5278        let cfg = LaunchConfig {
5279            grid_dim: (1, 1, 1),
5280            block_dim: (1, 1, 1),
5281            shared_mem_bytes: 0,
5282        };
5283        let ii = idx as i32;
5284        let __s_b = self.gpu.stream();
5285        let mut b = __s_b.launch_builder(&f);
5286        b.arg(buf).arg(map).arg(&ii);
5287        unsafe {
5288            b.launch(cfg)?;
5289        }
5290        Ok(())
5291    }
5292
5293    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5294    #[allow(clippy::too_many_arguments)]
5295    pub fn u32_pack2(
5296        &self,
5297        a: &CudaSlice<u32>,
5298        off_a: usize,
5299        n1: usize,
5300        b_in: &CudaSlice<u32>,
5301        n2: usize,
5302        out: &mut CudaSlice<u32>,
5303    ) -> Result<(), Box<dyn std::error::Error>> {
5304        let f = self.func("u32_pack2");
5305        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5306        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5307        let __s_b = self.gpu.stream();
5308        let mut b = __s_b.launch_builder(&f);
5309        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5310        unsafe {
5311            b.launch(cfg)?;
5312        }
5313        Ok(())
5314    }
5315
5316    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5317    pub fn moe_w_exscale(
5318        &self,
5319        w: &mut CudaSlice<f32>,
5320        sel: &CudaSlice<i32>,
5321        s: &CudaSlice<f32>,
5322        n: usize,
5323    ) -> Result<(), Box<dyn std::error::Error>> {
5324        let f = self.func("moe_w_exscale");
5325        let cfg = LaunchConfig::for_num_elems(n as u32);
5326        let ni = n as i32;
5327        let __s_b = self.gpu.stream();
5328        let mut b = __s_b.launch_builder(&f);
5329        b.arg(w).arg(sel).arg(s).arg(&ni);
5330        unsafe {
5331            b.launch(cfg)?;
5332        }
5333        Ok(())
5334    }
5335
5336    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5337    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5338    pub fn moe_w_scale_by_expert(
5339        &self,
5340        w: &mut CudaSlice<f32>,
5341        sel: &CudaSlice<i32>,
5342        macros: &CudaSlice<f32>,
5343        n_expert: usize,
5344        n: usize,
5345    ) -> Result<(), Box<dyn std::error::Error>> {
5346        let f = self.func("moe_w_scale_by_expert");
5347        let cfg = LaunchConfig {
5348            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5349            block_dim: (64, 1, 1),
5350            shared_mem_bytes: 0,
5351        };
5352        let (ne, nn) = (n_expert as i32, n as i32);
5353        let __s_b = self.gpu.stream();
5354        let mut b = __s_b.launch_builder(&f);
5355        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5356        unsafe {
5357            b.launch(cfg)?;
5358        }
5359        Ok(())
5360    }
5361
5362    pub fn moe_gate_up_silu8_dev_q8(
5363        &self,
5364        table: &CudaSlice<u64>,
5365        sel: &cudarc::driver::CudaView<i32>,
5366        aq: &CudaSlice<i8>,
5367        ad: &CudaSlice<f32>,
5368        in_f: usize,
5369        n_ff: usize,
5370        n_used: usize,
5371        n_expert: usize,
5372        qt_g: i32,
5373        qt_u: i32,
5374        rb_g: usize,
5375        rb_u: usize,
5376        macros: &CudaSlice<f32>,
5377    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5378        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5379        let (mode, wpb) = GU.get_or_init(|| {
5380            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5381            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5382                .ok()
5383                .and_then(|v| v.parse().ok())
5384                .unwrap_or(4u32)
5385                .clamp(1, 16);
5386            (mode, wpb)
5387        });
5388        let (mode, wpb) = (mode.as_str(), *wpb);
5389        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5390        let (inf, nff, ne, rbg, rbu) = (
5391            in_f as i32,
5392            n_ff as i32,
5393            n_expert as i32,
5394            rb_g as i64,
5395            rb_u as i64,
5396        );
5397        let (f, cfg) = match mode {
5398            "1" | "2" | "4" => {
5399                let rpw: u32 = mode.parse().unwrap();
5400                let f = self.func(match rpw {
5401                    1 => "moe_gate_up_silu8_dev_q8_r1",
5402                    2 => "moe_gate_up_silu8_dev_q8_r2",
5403                    _ => "moe_gate_up_silu8_dev_q8_r4",
5404                });
5405                let rows_per_block = (rpw * wpb) as usize;
5406                let gx = n_ff.div_ceil(rows_per_block) as u32;
5407                (
5408                    f,
5409                    LaunchConfig {
5410                        grid_dim: (gx, n_used as u32, 1),
5411                        block_dim: (32, wpb, 1),
5412                        shared_mem_bytes: 0,
5413                    },
5414                )
5415            }
5416            "j8" if n_used <= 32 => (
5417                self.func("moe_gate_up_silu8_dev_q8_j8"),
5418                LaunchConfig {
5419                    grid_dim: (n_ff as u32, 1, 1),
5420                    block_dim: (32, n_used as u32, 1),
5421                    shared_mem_bytes: 0,
5422                },
5423            ),
5424            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5425            "vsm2" => {
5426                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5427                let sh = (rb_g + rb_u) as u32;
5428                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5429                f.set_attribute(
5430                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5431                    sh as i32,
5432                )?;
5433                (
5434                    f,
5435                    LaunchConfig {
5436                        grid_dim: (n_ff as u32, n_used as u32, 1),
5437                        block_dim: (32, 1, 1),
5438                        shared_mem_bytes: sh,
5439                    },
5440                )
5441            }
5442            "vsm" => {
5443                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5444                let sh = (rb_g + rb_u) as u32;
5445                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5446                f.set_attribute(
5447                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5448                    sh as i32,
5449                )?;
5450                (
5451                    f,
5452                    LaunchConfig {
5453                        grid_dim: (n_ff as u32, n_used as u32, 1),
5454                        block_dim: (32, 1, 1),
5455                        shared_mem_bytes: sh,
5456                    },
5457                )
5458            }
5459            "sg" => (
5460                self.func("moe_gate_up_silu8_dev_q8_sg"),
5461                LaunchConfig {
5462                    grid_dim: (n_ff as u32, n_used as u32, 1),
5463                    block_dim: (32, 1, 1),
5464                    shared_mem_bytes: 0,
5465                },
5466            ),
5467            "j8sg" if n_used <= 32 => (
5468                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5469                LaunchConfig {
5470                    grid_dim: (n_ff as u32, 1, 1),
5471                    block_dim: (32, n_used as u32, 1),
5472                    shared_mem_bytes: 0,
5473                },
5474            ),
5475            "u64" if in_f == 2048 => (
5476                self.func("moe_gate_up_silu8_dev_q8_u64"),
5477                LaunchConfig {
5478                    grid_dim: (n_ff as u32, n_used as u32, 1),
5479                    block_dim: (32, 1, 1),
5480                    shared_mem_bytes: 0,
5481                },
5482            ),
5483            "gs4" if in_f == 2048 => (
5484                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5485                LaunchConfig {
5486                    grid_dim: (n_ff as u32, n_used as u32, 1),
5487                    block_dim: (32, 4, 1),
5488                    shared_mem_bytes: 0,
5489                },
5490            ),
5491            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5492            "v" | "" => (
5493                self.func("moe_gate_up_silu8_dev_q8_v"),
5494                LaunchConfig {
5495                    grid_dim: (n_ff as u32, n_used as u32, 1),
5496                    block_dim: (32, 1, 1),
5497                    shared_mem_bytes: 0,
5498                },
5499            ),
5500            "s2" => (
5501                self.func("moe_gate_up_silu8_dev_q8_s2"),
5502                LaunchConfig {
5503                    grid_dim: (n_ff as u32, n_used as u32, 1),
5504                    block_dim: (32, 2, 1),
5505                    shared_mem_bytes: 0,
5506                },
5507            ),
5508            "s2z" => {
5509                let rz = wpb.min(16); // s2z smem tile is [16][2]
5510                (
5511                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5512                    LaunchConfig {
5513                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5514                        block_dim: (32, 2, rz),
5515                        shared_mem_bytes: 0,
5516                    },
5517                )
5518            }
5519            _ => (
5520                self.func("moe_gate_up_silu8_dev_q8"),
5521                LaunchConfig {
5522                    grid_dim: (n_ff as u32, n_used as u32, 1),
5523                    block_dim: (32, 1, 1),
5524                    shared_mem_bytes: 0,
5525                },
5526            ),
5527        };
5528        let __s_b = self.gpu.stream();
5529        let mut b = __s_b.launch_builder(&f);
5530        b.arg(table)
5531            .arg(sel)
5532            .arg(aq)
5533            .arg(ad)
5534            .arg(&mut act)
5535            .arg(&inf)
5536            .arg(&nff)
5537            .arg(&ne)
5538            .arg(&qt_g)
5539            .arg(&qt_u)
5540            .arg(&rbg)
5541            .arg(&rbu)
5542            .arg(macros);
5543        unsafe {
5544            b.launch(cfg)?;
5545        }
5546        Ok(act)
5547    }
5548
5549    #[allow(clippy::too_many_arguments)]
5550    pub fn moe_down8_fma_dev_q8(
5551        &self,
5552        table: &CudaSlice<u64>,
5553        sel: &cudarc::driver::CudaView<i32>,
5554        w: &cudarc::driver::CudaView<f32>,
5555        aq2: &CudaSlice<i8>,
5556        ad2: &CudaSlice<f32>,
5557        dst: &mut cudarc::driver::CudaViewMut<f32>,
5558        in_f: usize,
5559        out_f: usize,
5560        n_used: usize,
5561        n_expert: usize,
5562        qt: i32,
5563        rb: usize,
5564    ) -> Result<(), Box<dyn std::error::Error>> {
5565        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5566        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5567        let (inf, outf, nu, ne, rbi) = (
5568            in_f as i32,
5569            out_f as i32,
5570            n_used as i32,
5571            n_expert as i32,
5572            rb as i64,
5573        );
5574        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5575        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5576        let (f, cfg) = match mode.as_str() {
5577            m @ ("1" | "2" | "4") if n_used <= 8 => {
5578                let rpw: usize = m.parse().unwrap();
5579                let f = self.func(match rpw {
5580                    1 => "moe_down8_fma_dev_q8_w8r1",
5581                    2 => "moe_down8_fma_dev_q8_w8r2",
5582                    _ => "moe_down8_fma_dev_q8_w8r4",
5583                });
5584                (
5585                    f,
5586                    LaunchConfig {
5587                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5588                        block_dim: (32, n_used as u32, 1),
5589                        shared_mem_bytes: 0,
5590                    },
5591                )
5592            }
5593            "h2" if in_f == 512 => (
5594                self.func("moe_down8_fma_dev_q8_h2"),
5595                LaunchConfig {
5596                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5597                    block_dim: (32, 1, 1),
5598                    shared_mem_bytes: 0,
5599                },
5600            ),
5601            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5602            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5603            "" if in_f == 704 && n_used <= 8 => (
5604                self.func("moe_down8_fma_dev_q8_w8r2"),
5605                LaunchConfig {
5606                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5607                    block_dim: (32, n_used as u32, 1),
5608                    shared_mem_bytes: 0,
5609                },
5610            ),
5611            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5612            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5613            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5614            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5615                self.func("moe_down8_fma_dev_q8_w8h2v"),
5616                LaunchConfig {
5617                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5618                    block_dim: (32, n_used as u32, 1),
5619                    shared_mem_bytes: 0,
5620                },
5621            ),
5622            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5623                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5624                LaunchConfig {
5625                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5626                    block_dim: (32, n_used as u32, 1),
5627                    shared_mem_bytes: 0,
5628                },
5629            ),
5630            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5631                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5632                LaunchConfig {
5633                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5634                    block_dim: (32, n_used as u32, 1),
5635                    shared_mem_bytes: 0,
5636                },
5637            ),
5638            "w8h2" if in_f == 512 && n_used <= 8 => (
5639                self.func("moe_down8_fma_dev_q8_w8h2"),
5640                LaunchConfig {
5641                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5642                    block_dim: (32, n_used as u32, 1),
5643                    shared_mem_bytes: 0,
5644                },
5645            ),
5646            _ => (
5647                self.func("moe_down8_fma_dev_q8"),
5648                LaunchConfig {
5649                    grid_dim: (out_f as u32, 1, 1),
5650                    block_dim: (32, 1, 1),
5651                    shared_mem_bytes: 0,
5652                },
5653            ),
5654        };
5655        let __s_b = self.gpu.stream();
5656        let mut b = __s_b.launch_builder(&f);
5657        b.arg(table)
5658            .arg(sel)
5659            .arg(w)
5660            .arg(aq2)
5661            .arg(ad2)
5662            .arg(dst)
5663            .arg(&inf)
5664            .arg(&outf)
5665            .arg(&nu)
5666            .arg(&ne)
5667            .arg(&qt)
5668            .arg(&rbi);
5669        unsafe {
5670            b.launch(cfg)?;
5671        }
5672        Ok(())
5673    }
5674
5675    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5676    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5677    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5678    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5679    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5680    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5681    #[allow(clippy::too_many_arguments)]
5682    pub fn moe_gate_up_silu8_dev_q8_rows(
5683        &self,
5684        table: &CudaSlice<u64>,
5685        sel: &CudaSlice<i32>,
5686        aq: &CudaSlice<i8>,
5687        ad: &CudaSlice<f32>,
5688        t: usize,
5689        in_f: usize,
5690        n_ff: usize,
5691        n_used: usize,
5692        n_expert: usize,
5693        qt_g: i32,
5694        qt_u: i32,
5695        rb_g: usize,
5696        rb_u: usize,
5697        macros: &CudaSlice<f32>,
5698    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5699        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5700        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5701        let cfg = LaunchConfig {
5702            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5703            block_dim: (32, 1, 1),
5704            shared_mem_bytes: 0,
5705        };
5706        let (inf, nff, ne, nu, rbg, rbu) = (
5707            in_f as i32,
5708            n_ff as i32,
5709            n_expert as i32,
5710            n_used as i32,
5711            rb_g as i64,
5712            rb_u as i64,
5713        );
5714        let __s_b = self.gpu.stream();
5715        let mut b = __s_b.launch_builder(&f);
5716        b.arg(table)
5717            .arg(sel)
5718            .arg(aq)
5719            .arg(ad)
5720            .arg(&mut act)
5721            .arg(&inf)
5722            .arg(&nff)
5723            .arg(&ne)
5724            .arg(&qt_g)
5725            .arg(&qt_u)
5726            .arg(&rbg)
5727            .arg(&rbu)
5728            .arg(&nu)
5729            .arg(macros);
5730        unsafe {
5731            b.launch(cfg)?;
5732        }
5733        Ok(act)
5734    }
5735
5736    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5737    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5738    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5739    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5740    #[allow(clippy::too_many_arguments)]
5741    pub fn moe_down8_fma_dev_q8_rows(
5742        &self,
5743        table: &CudaSlice<u64>,
5744        sel: &CudaSlice<i32>,
5745        w: &CudaSlice<f32>,
5746        aq2: &CudaSlice<i8>,
5747        ad2: &CudaSlice<f32>,
5748        dst: &mut CudaSlice<f32>,
5749        t: usize,
5750        in_f: usize,
5751        out_f: usize,
5752        n_used: usize,
5753        n_expert: usize,
5754        qt: i32,
5755        rb: usize,
5756    ) -> Result<(), Box<dyn std::error::Error>> {
5757        assert!(
5758            in_f == 512 && n_used <= 8,
5759            "down rows twin is w8h2v shape-gated"
5760        );
5761        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5762        let cfg = LaunchConfig {
5763            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5764            block_dim: (32, n_used as u32, 1),
5765            shared_mem_bytes: 0,
5766        };
5767        let (inf, outf, nu, ne, rbi) = (
5768            in_f as i32,
5769            out_f as i32,
5770            n_used as i32,
5771            n_expert as i32,
5772            rb as i64,
5773        );
5774        let __s_b = self.gpu.stream();
5775        let mut b = __s_b.launch_builder(&f);
5776        b.arg(table)
5777            .arg(sel)
5778            .arg(w)
5779            .arg(aq2)
5780            .arg(ad2)
5781            .arg(dst)
5782            .arg(&inf)
5783            .arg(&outf)
5784            .arg(&nu)
5785            .arg(&ne)
5786            .arg(&qt)
5787            .arg(&rbi);
5788        unsafe {
5789            b.launch(cfg)?;
5790        }
5791        Ok(())
5792    }
5793
5794    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5795    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5796    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5797    #[allow(clippy::too_many_arguments)]
5798    pub fn moe_gate_up_silu8_dev_q8_csr(
5799        &self,
5800        table: &CudaSlice<u64>,
5801        sel: &CudaSlice<i32>,
5802        aq: &CudaSlice<i8>,
5803        ad: &CudaSlice<f32>,
5804        n_pairs: usize,
5805        in_f: usize,
5806        n_ff: usize,
5807        n_used: usize,
5808        n_expert: usize,
5809        qt_g: i32,
5810        qt_u: i32,
5811        rb_g: usize,
5812        rb_u: usize,
5813    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5814        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
5815        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5816        let cfg = LaunchConfig {
5817            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5818            block_dim: (32, 1, 1),
5819            shared_mem_bytes: 0,
5820        };
5821        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5822            in_f as i32,
5823            n_ff as i32,
5824            n_expert as i32,
5825            n_used as i32,
5826            n_pairs as i32,
5827            rb_g as i64,
5828            rb_u as i64,
5829        );
5830        let __s_b = self.gpu.stream();
5831        let mut b = __s_b.launch_builder(&f);
5832        b.arg(table)
5833            .arg(sel)
5834            .arg(aq)
5835            .arg(ad)
5836            .arg(&mut act)
5837            .arg(&inf)
5838            .arg(&nff)
5839            .arg(&ne)
5840            .arg(&qt_g)
5841            .arg(&qt_u)
5842            .arg(&rbg)
5843            .arg(&rbu)
5844            .arg(&nu)
5845            .arg(&npi);
5846        unsafe {
5847            b.launch(cfg)?;
5848        }
5849        Ok(act)
5850    }
5851
5852    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5853    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5854    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5855    #[allow(clippy::too_many_arguments)]
5856    pub fn moe_down8_fma_dev_q8_variant(
5857        &self,
5858        variant: &str,
5859        table: &CudaSlice<u64>,
5860        sel: &cudarc::driver::CudaView<i32>,
5861        w: &cudarc::driver::CudaView<f32>,
5862        aq2: &CudaSlice<i8>,
5863        ad2: &CudaSlice<f32>,
5864        dst: &mut cudarc::driver::CudaViewMut<f32>,
5865        in_f: usize,
5866        out_f: usize,
5867        n_used: usize,
5868        n_expert: usize,
5869        qt: i32,
5870        rb: usize,
5871    ) -> Result<(), Box<dyn std::error::Error>> {
5872        let (inf, outf, nu, ne, rbi) = (
5873            in_f as i32,
5874            out_f as i32,
5875            n_used as i32,
5876            n_expert as i32,
5877            rb as i64,
5878        );
5879        let (f, cfg) = match variant {
5880            "w8h2" | "w8h2v" => (
5881                self.func(if variant == "w8h2" {
5882                    "moe_down8_fma_dev_q8_w8h2"
5883                } else {
5884                    "moe_down8_fma_dev_q8_w8h2v"
5885                }),
5886                LaunchConfig {
5887                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5888                    block_dim: (32, n_used as u32, 1),
5889                    shared_mem_bytes: 0,
5890                },
5891            ),
5892            "w8h2r2" | "w8h2r2v" => (
5893                self.func(if variant == "w8h2r2" {
5894                    "moe_down8_fma_dev_q8_w8h2r2"
5895                } else {
5896                    "moe_down8_fma_dev_q8_w8h2r2v"
5897                }),
5898                LaunchConfig {
5899                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5900                    block_dim: (32, n_used as u32, 1),
5901                    shared_mem_bytes: 0,
5902                },
5903            ),
5904            _ => (
5905                self.func("moe_down8_fma_dev_q8"),
5906                LaunchConfig {
5907                    grid_dim: (out_f as u32, 1, 1),
5908                    block_dim: (32, 1, 1),
5909                    shared_mem_bytes: 0,
5910                },
5911            ),
5912        };
5913        let __s_b = self.gpu.stream();
5914        let mut b = __s_b.launch_builder(&f);
5915        b.arg(table)
5916            .arg(sel)
5917            .arg(w)
5918            .arg(aq2)
5919            .arg(ad2)
5920            .arg(dst)
5921            .arg(&inf)
5922            .arg(&outf)
5923            .arg(&nu)
5924            .arg(&ne)
5925            .arg(&qt)
5926            .arg(&rbi);
5927        unsafe {
5928            b.launch(cfg)?;
5929        }
5930        Ok(())
5931    }
5932
5933    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
5934    #[allow(clippy::too_many_arguments)]
5935    pub fn moe_gate_up_silu8_dev_q8_variant(
5936        &self,
5937        variant: &str,
5938        table: &CudaSlice<u64>,
5939        sel: &cudarc::driver::CudaView<i32>,
5940        aq: &CudaSlice<i8>,
5941        ad: &CudaSlice<f32>,
5942        in_f: usize,
5943        n_ff: usize,
5944        n_used: usize,
5945        n_expert: usize,
5946        qt_g: i32,
5947        qt_u: i32,
5948        rb_g: usize,
5949        rb_u: usize,
5950    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5951        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5952        let (inf, nff, ne, rbg, rbu) = (
5953            in_f as i32,
5954            n_ff as i32,
5955            n_expert as i32,
5956            rb_g as i64,
5957            rb_u as i64,
5958        );
5959        let f = self.func(if variant == "v" {
5960            "moe_gate_up_silu8_dev_q8_v"
5961        } else {
5962            "moe_gate_up_silu8_dev_q8"
5963        });
5964        let cfg = LaunchConfig {
5965            grid_dim: (n_ff as u32, n_used as u32, 1),
5966            block_dim: (32, 1, 1),
5967            shared_mem_bytes: 0,
5968        };
5969        let __s_b = self.gpu.stream();
5970        let mut b = __s_b.launch_builder(&f);
5971        b.arg(table)
5972            .arg(sel)
5973            .arg(aq)
5974            .arg(ad)
5975            .arg(&mut act)
5976            .arg(&inf)
5977            .arg(&nff)
5978            .arg(&ne)
5979            .arg(&qt_g)
5980            .arg(&qt_u)
5981            .arg(&rbg)
5982            .arg(&rbu);
5983        unsafe {
5984            b.launch(cfg)?;
5985        }
5986        Ok(act)
5987    }
5988
5989    pub fn moe_gate_up_silu8_dev(
5990        &self,
5991        table: &CudaSlice<u64>,
5992        sel: &cudarc::driver::CudaView<i32>,
5993        x: &cudarc::driver::CudaView<f32>,
5994        in_f: usize,
5995        n_ff: usize,
5996        n_used: usize,
5997        n_expert: usize,
5998        qt_g: i32,
5999        qt_u: i32,
6000        rb_g: usize,
6001        rb_u: usize,
6002        macros: &CudaSlice<f32>,
6003    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6004        let f = self.func("moe_gate_up_silu8_dev");
6005        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
6006        let cfg = LaunchConfig {
6007            grid_dim: (n_ff as u32, n_used as u32, 1),
6008            block_dim: (256, 1, 1),
6009            shared_mem_bytes: 0,
6010        };
6011        let (inf, nff, ne, rbg, rbu) = (
6012            in_f as i32,
6013            n_ff as i32,
6014            n_expert as i32,
6015            rb_g as i64,
6016            rb_u as i64,
6017        );
6018        let __s_b = self.gpu.stream();
6019        let mut b = __s_b.launch_builder(&f);
6020        b.arg(table)
6021            .arg(sel)
6022            .arg(x)
6023            .arg(&mut act)
6024            .arg(&inf)
6025            .arg(&nff)
6026            .arg(&ne)
6027            .arg(&qt_g)
6028            .arg(&qt_u)
6029            .arg(&rbg)
6030            .arg(&rbu)
6031            .arg(macros);
6032        unsafe {
6033            b.launch(cfg)?;
6034        }
6035        Ok(act)
6036    }
6037
6038    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
6039    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
6040    #[allow(clippy::too_many_arguments)]
6041    pub fn moe_down8_fma_dev(
6042        &self,
6043        table: &CudaSlice<u64>,
6044        sel: &cudarc::driver::CudaView<i32>,
6045        w: &cudarc::driver::CudaView<f32>,
6046        act: &CudaSlice<f32>,
6047        dst: &mut cudarc::driver::CudaViewMut<f32>,
6048        in_f: usize,
6049        out_f: usize,
6050        n_used: usize,
6051        n_expert: usize,
6052        qt: i32,
6053        rb: usize,
6054    ) -> Result<(), Box<dyn std::error::Error>> {
6055        let f = self.func("moe_down8_fma_dev");
6056        let cfg = LaunchConfig {
6057            grid_dim: (out_f as u32, 1, 1),
6058            block_dim: (256, 1, 1),
6059            shared_mem_bytes: 0,
6060        };
6061        let (inf, outf, nu, ne, rbv) = (
6062            in_f as i32,
6063            out_f as i32,
6064            n_used as i32,
6065            n_expert as i32,
6066            rb as i64,
6067        );
6068        let __s_b = self.gpu.stream();
6069        let mut b = __s_b.launch_builder(&f);
6070        b.arg(table)
6071            .arg(sel)
6072            .arg(w)
6073            .arg(act)
6074            .arg(dst)
6075            .arg(&inf)
6076            .arg(&outf)
6077            .arg(&nu)
6078            .arg(&ne)
6079            .arg(&qt)
6080            .arg(&rbv);
6081        unsafe {
6082            b.launch(cfg)?;
6083        }
6084        Ok(())
6085    }
6086
6087    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6088    pub fn axpy_into(
6089        &self,
6090        src: &CudaSlice<f32>,
6091        alpha: f32,
6092        dst: &mut cudarc::driver::CudaViewMut<f32>,
6093        n: usize,
6094    ) -> Result<(), Box<dyn std::error::Error>> {
6095        let f = self.func("axpy_f32");
6096        let cfg = LaunchConfig::for_num_elems(n as u32);
6097        let (a, ni) = (alpha, n as i32);
6098        let __s_b = self.gpu.stream();
6099        let mut b = __s_b.launch_builder(&f);
6100        b.arg(src).arg(dst).arg(&a).arg(&ni);
6101        unsafe {
6102            b.launch(cfg)?;
6103        }
6104        Ok(())
6105    }
6106
6107    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6108    pub fn add_scaled_rows(
6109        &self,
6110        src: &CudaSlice<f32>,
6111        scale: &CudaSlice<f32>,
6112        dst: &mut CudaSlice<f32>,
6113        ncols: usize,
6114        nrows: usize,
6115    ) -> Result<(), Box<dyn std::error::Error>> {
6116        let f = self.func("add_scaled_rows_f32");
6117        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6118        let (nc, nr) = (ncols as i32, nrows as i32);
6119        let __s_b = self.gpu.stream();
6120        let mut b = __s_b.launch_builder(&f);
6121        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6122        unsafe {
6123            b.launch(cfg)?;
6124        }
6125        Ok(())
6126    }
6127
6128    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6129
6130    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6131    pub fn gather_rows(
6132        &self,
6133        src: &CudaSlice<f32>,
6134        idx: &CudaSlice<i32>,
6135        dst: &mut CudaSlice<f32>,
6136        ncols: usize,
6137        m_e: usize,
6138    ) -> Result<(), Box<dyn std::error::Error>> {
6139        let f = self.func("gather_rows_f32");
6140        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6141        let (nc, me) = (ncols as i32, m_e as i32);
6142        let __s_b = self.gpu.stream();
6143        let mut b = __s_b.launch_builder(&f);
6144        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6145        unsafe {
6146            b.launch(cfg)?;
6147        }
6148        Ok(())
6149    }
6150
6151    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6152    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6153    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6154    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6155    pub fn scatter_slot(
6156        &self,
6157        src: &CudaSlice<f32>,
6158        tok_idx: &CudaSlice<i32>,
6159        slot_idx: &CudaSlice<i32>,
6160        weight: &CudaSlice<f32>,
6161        dst: &mut CudaSlice<f32>,
6162        wbuf: &mut CudaSlice<f32>,
6163        ncols: usize,
6164        n_used: usize,
6165        m_e: usize,
6166    ) -> Result<(), Box<dyn std::error::Error>> {
6167        let f = self.func("scatter_add_slot_f32");
6168        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6169        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6170        let __s_b = self.gpu.stream();
6171        let mut b = __s_b.launch_builder(&f);
6172        b.arg(src)
6173            .arg(tok_idx)
6174            .arg(slot_idx)
6175            .arg(weight)
6176            .arg(dst)
6177            .arg(wbuf)
6178            .arg(&nc)
6179            .arg(&nu)
6180            .arg(&me);
6181        unsafe {
6182            b.launch(cfg)?;
6183        }
6184        Ok(())
6185    }
6186
6187    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6188    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6189    /// Uses FMA for bit-identity with the sequential axpy path.
6190    pub fn reduce_slots(
6191        &self,
6192        slots: &CudaSlice<f32>,
6193        wbuf: &CudaSlice<f32>,
6194        dst: &mut CudaSlice<f32>,
6195        ncols: usize,
6196        n_used: usize,
6197        t: usize,
6198    ) -> Result<(), Box<dyn std::error::Error>> {
6199        let f = self.func("reduce_slots_f32");
6200        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6201        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6202        let __s_b = self.gpu.stream();
6203        let mut b = __s_b.launch_builder(&f);
6204        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6205        unsafe {
6206            b.launch(cfg)?;
6207        }
6208        Ok(())
6209    }
6210
6211    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6212    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6213    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6214    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6215    /// GPU time, ~half of it redundant re-quantization of the same row.
6216    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6217    pub fn quantize_q8_1_view(
6218        &self,
6219        x: &cudarc::driver::CudaView<f32>,
6220        m: usize,
6221        in_f: usize,
6222    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6223        let f = self.func("quantize_q8_1");
6224        let nblk = in_f / 32;
6225        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6226        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6227        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6228        let (inf, mi) = (in_f as i32, m as i32);
6229        let __s_b = self.gpu.stream();
6230        let mut b = __s_b.launch_builder(&f);
6231        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6232        unsafe {
6233            b.launch(cfg)?;
6234        }
6235        Ok((q, d))
6236    }
6237
6238    pub fn quantize_q8_1(
6239        &self,
6240        x: &CudaSlice<f32>,
6241        m: usize,
6242        in_f: usize,
6243    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6244        let nblk = in_f / 32;
6245        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6246        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6247        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6248        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6249        let (inf, mi) = (in_f as i32, m as i32);
6250        if Self::pdl_on() && Self::pdl_wb_on() {
6251            {
6252                use cudarc::driver::{DevicePtr, DevicePtrMut};
6253                let s = &self.gpu.stream();
6254                let (px, _g0) = x.device_ptr(s);
6255                let (pq, _g1) = q.device_ptr_mut(s);
6256                let (pd, _g2) = d.device_ptr_mut(s);
6257                let mut ps = [
6258                    &px as *const _ as *mut std::ffi::c_void,
6259                    &pq as *const _ as *mut _,
6260                    &pd as *const _ as *mut _,
6261                    &inf as *const _ as *mut _,
6262                    &mi as *const _ as *mut _,
6263                ];
6264                unsafe {
6265                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6266                }
6267            }
6268            return Ok((q, d));
6269        }
6270        let f = self.func("quantize_q8_1");
6271        let __s_b = self.gpu.stream();
6272        let mut b = __s_b.launch_builder(&f);
6273        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6274        unsafe {
6275            b.launch(cfg)?;
6276        }
6277        Ok((q, d))
6278    }
6279
6280    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6281    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6282    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6283    pub fn quantize_fp4_act(
6284        &self,
6285        x: &CudaSlice<f32>,
6286        m: usize,
6287        in_f: usize,
6288    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6289        let f = self.func("quantize_fp4_act");
6290        let nb16 = in_f / 16;
6291        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6292        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6293        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6294        let (inf, mi) = (in_f as i32, m as i32);
6295        let __s_b = self.gpu.stream();
6296        let mut b = __s_b.launch_builder(&f);
6297        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6298        unsafe {
6299            b.launch(cfg)?;
6300        }
6301        Ok((aq4, ad4))
6302    }
6303
6304    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6305    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6306    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6307    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6308    pub fn qmatvec_gemm_nvfp4_fp4(
6309        &self,
6310        bytes: &CudaSlice<u8>,
6311        x: &CudaSlice<f32>,
6312        m: usize,
6313        in_f: usize,
6314        out_f: usize,
6315        row_bytes: usize,
6316        scale: f32,
6317    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6318        assert!(
6319            in_f % 64 == 0,
6320            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6321        );
6322        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6323        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6324        if scale != 1.0 {
6325            self.scale_inplace(&mut y, scale, m * out_f)?;
6326        }
6327        Ok(y)
6328    }
6329
6330    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6331    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6332    fn fp4_gemm_launch(
6333        &self,
6334        bytes: &CudaSlice<u8>,
6335        aq4: &CudaSlice<u32>,
6336        ad4: &CudaSlice<u8>,
6337        m: usize,
6338        in_f: usize,
6339        out_f: usize,
6340        row_bytes: usize,
6341    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6342        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6343        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6344        const BM: u32 = 64;
6345        const BN: u32 = 256;
6346        let cfg = LaunchConfig {
6347            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6348            block_dim: (32, 4, 1),
6349            shared_mem_bytes: 0,
6350        };
6351        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6352        let __s_b = self.gpu.stream();
6353        let mut b = __s_b.launch_builder(&f);
6354        b.arg(bytes)
6355            .arg(aq4)
6356            .arg(ad4)
6357            .arg(&mut y)
6358            .arg(&inf)
6359            .arg(&outf)
6360            .arg(&mi)
6361            .arg(&rb);
6362        unsafe {
6363            b.launch(cfg)?;
6364        }
6365        Ok(y)
6366    }
6367
6368    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6369    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6370        &self,
6371        bytes: &CudaSlice<u8>,
6372        x: &CudaSlice<f32>,
6373        m: usize,
6374        in_f: usize,
6375        out_f: usize,
6376        row_bytes: usize,
6377    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6378        assert!(
6379            in_f % 64 == 0,
6380            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6381        );
6382        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6383        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6384    }
6385
6386    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6387    pub fn qmatvec_q8_0_fast(
6388        &self,
6389        w: &CudaSlice<u8>,
6390        x: &CudaSlice<f32>,
6391        m: usize,
6392        in_f: usize,
6393        out_f: usize,
6394        row_bytes: usize,
6395    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6396        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6397        let f = self.func("qmatvec_q8_0_dp4a");
6398        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6399        let cfg = LaunchConfig {
6400            grid_dim: (out_f as u32, m as u32, 1),
6401            block_dim: (128, 1, 1),
6402            shared_mem_bytes: 0,
6403        };
6404        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6405        let __s_b = self.gpu.stream();
6406        let mut b = __s_b.launch_builder(&f);
6407        b.arg(w)
6408            .arg(&aq)
6409            .arg(&ad)
6410            .arg(&mut y)
6411            .arg(&inf)
6412            .arg(&outf)
6413            .arg(&mi)
6414            .arg(&rb);
6415        unsafe {
6416            b.launch(cfg)?;
6417        }
6418        Ok(y)
6419    }
6420
6421    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6422    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6423    pub fn qmatvec_q4_K_fast(
6424        &self,
6425        w: &CudaSlice<u8>,
6426        x: &CudaSlice<f32>,
6427        m: usize,
6428        in_f: usize,
6429        out_f: usize,
6430        row_bytes: usize,
6431    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6432        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6433        let f = self.func("qmatvec_q4_K_dp4a");
6434        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6435        let cfg = LaunchConfig {
6436            grid_dim: (out_f as u32, m as u32, 1),
6437            block_dim: (128, 1, 1),
6438            shared_mem_bytes: 0,
6439        };
6440        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6441        let __s_b = self.gpu.stream();
6442        let mut b = __s_b.launch_builder(&f);
6443        b.arg(w)
6444            .arg(&aq)
6445            .arg(&ad)
6446            .arg(&mut y)
6447            .arg(&inf)
6448            .arg(&outf)
6449            .arg(&mi)
6450            .arg(&rb);
6451        unsafe {
6452            b.launch(cfg)?;
6453        }
6454        Ok(y)
6455    }
6456
6457    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6458    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6459    pub fn qmatvec_q6_K_fast(
6460        &self,
6461        w: &CudaSlice<u8>,
6462        x: &CudaSlice<f32>,
6463        m: usize,
6464        in_f: usize,
6465        out_f: usize,
6466        row_bytes: usize,
6467    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6468        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6469        let f = self.func("qmatvec_q6_K_dp4a");
6470        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6471        let cfg = LaunchConfig {
6472            grid_dim: (out_f as u32, m as u32, 1),
6473            block_dim: (128, 1, 1),
6474            shared_mem_bytes: 0,
6475        };
6476        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6477        let __s_b = self.gpu.stream();
6478        let mut b = __s_b.launch_builder(&f);
6479        b.arg(w)
6480            .arg(&aq)
6481            .arg(&ad)
6482            .arg(&mut y)
6483            .arg(&inf)
6484            .arg(&outf)
6485            .arg(&mi)
6486            .arg(&rb);
6487        unsafe {
6488            b.launch(cfg)?;
6489        }
6490        Ok(y)
6491    }
6492
6493    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6494    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6495    pub fn qmatvec_q5_K_fast(
6496        &self,
6497        w: &CudaSlice<u8>,
6498        x: &CudaSlice<f32>,
6499        m: usize,
6500        in_f: usize,
6501        out_f: usize,
6502        row_bytes: usize,
6503    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6504        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6505    }
6506    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6507    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6508    pub fn qmatvec_q3_K_fast(
6509        &self,
6510        w: &CudaSlice<u8>,
6511        x: &CudaSlice<f32>,
6512        m: usize,
6513        in_f: usize,
6514        out_f: usize,
6515        row_bytes: usize,
6516    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6517        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6518    }
6519    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6520    pub fn qmatvec_nvfp4_fast_rp(
6521        &self,
6522        w: &CudaSlice<u8>,
6523        x: &CudaSlice<f32>,
6524        m: usize,
6525        in_f: usize,
6526        out_f: usize,
6527        row_bytes: usize,
6528    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6529        assert!(
6530            in_f % 64 == 0,
6531            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6532        );
6533        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6534    }
6535    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6536    pub fn qmatvec_nvfp4_fast(
6537        &self,
6538        w: &CudaSlice<u8>,
6539        x: &CudaSlice<f32>,
6540        m: usize,
6541        in_f: usize,
6542        out_f: usize,
6543        row_bytes: usize,
6544    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6545        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6546        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6547        assert!(
6548            in_f % 64 == 0,
6549            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6550        );
6551        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6552    }
6553    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6554    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6555    pub fn qmatvec_iq4_XS_fast(
6556        &self,
6557        w: &CudaSlice<u8>,
6558        x: &CudaSlice<f32>,
6559        m: usize,
6560        in_f: usize,
6561        out_f: usize,
6562        row_bytes: usize,
6563    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6564        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6565    }
6566
6567    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6568    fn qmatvec_dp4a_named(
6569        &self,
6570        name: &str,
6571        w: &CudaSlice<u8>,
6572        x: &CudaSlice<f32>,
6573        m: usize,
6574        in_f: usize,
6575        out_f: usize,
6576        row_bytes: usize,
6577    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6578        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6579        let f = self.func(name);
6580        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6581        let cfg = LaunchConfig {
6582            grid_dim: (out_f as u32, m as u32, 1),
6583            block_dim: (128, 1, 1),
6584            shared_mem_bytes: 0,
6585        };
6586        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6587        let __s_b = self.gpu.stream();
6588        let mut b = __s_b.launch_builder(&f);
6589        b.arg(w)
6590            .arg(&aq)
6591            .arg(&ad)
6592            .arg(&mut y)
6593            .arg(&inf)
6594            .arg(&outf)
6595            .arg(&mi)
6596            .arg(&rb);
6597        unsafe {
6598            b.launch(cfg)?;
6599        }
6600        Ok(y)
6601    }
6602
6603    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6604        Ok(self.gpu.stream().clone_htod(v)?)
6605    }
6606    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6607        Ok(self.gpu.stream().clone_htod(v)?)
6608    }
6609    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6610    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6611        Ok(self.gpu.stream().clone_htod(v)?)
6612    }
6613    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6614        Ok(self.gpu.stream().clone_htod(v)?)
6615    }
6616    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6617    pub fn dtoh_view(
6618        &self,
6619        d: &cudarc::driver::CudaView<f32>,
6620    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6621        let v = self.gpu.stream().clone_dtoh(d)?;
6622        self.gpu.stream().synchronize()?;
6623        Ok(v)
6624    }
6625    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6626        let v = self.gpu.stream().clone_dtoh(d)?;
6627        self.gpu.stream().synchronize()?;
6628        Ok(v)
6629    }
6630    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6631    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6632    /// issuing them together avoids a second stream synchronization in every trunk layer.
6633    pub fn dtoh_pair(
6634        &self,
6635        a: &CudaSlice<f32>,
6636        b: &CudaSlice<f32>,
6637    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6638        let av = self.gpu.stream().clone_dtoh(a)?;
6639        let bv = self.gpu.stream().clone_dtoh(b)?;
6640        self.gpu.stream().synchronize()?;
6641        Ok((av, bv))
6642    }
6643    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6644    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6645        let v = self.gpu.stream().clone_dtoh(d)?;
6646        self.gpu.stream().synchronize()?;
6647        Ok(v)
6648    }
6649    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6650    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, 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_u8_view(
6656        &self,
6657        d: &cudarc::driver::CudaView<u8>,
6658    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6659        let v = self.gpu.stream().clone_dtoh(d)?;
6660        self.gpu.stream().synchronize()?;
6661        Ok(v)
6662    }
6663    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6664        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6665        self.keep_if_capturing(&s);
6666        Ok(s)
6667    }
6668
6669    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6670    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6671    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6672    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6673    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6674    /// back (or kept resident for graph replay). Returns the device token buffer.
6675    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6676    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6677    pub fn prob_of_token_device(
6678        &self,
6679        logits: &CudaSlice<f32>,
6680        tok: &CudaSlice<u32>,
6681        n_vocab: usize,
6682    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6683        let nb = ARGMAX_NB;
6684        let mut part = self.alloc_uninit::<f32>(nb)?;
6685        let mut p = self.alloc_uninit::<f32>(1)?;
6686        let f1 = self.func("prob_of_token_partial_f32");
6687        let cfg1 = LaunchConfig {
6688            grid_dim: (nb as u32, 1, 1),
6689            block_dim: (256, 1, 1),
6690            shared_mem_bytes: 0,
6691        };
6692        let nv = n_vocab as i32;
6693        let __s_b1 = self.gpu.stream();
6694        let mut b1 = __s_b1.launch_builder(&f1);
6695        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6696        unsafe {
6697            b1.launch(cfg1)?;
6698        }
6699        let f2 = self.func("prob_of_token_final_f32");
6700        let cfg2 = LaunchConfig {
6701            grid_dim: (1, 1, 1),
6702            block_dim: (256, 1, 1),
6703            shared_mem_bytes: 0,
6704        };
6705        let nbi = nb as i32;
6706        let __s_b2 = self.gpu.stream();
6707        let mut b2 = __s_b2.launch_builder(&f2);
6708        b2.arg(&part).arg(&mut p).arg(&nbi);
6709        unsafe {
6710            b2.launch(cfg2)?;
6711        }
6712        Ok(p)
6713    }
6714
6715    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6716    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6717    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6718    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6719    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6720    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6721    pub fn prob_of_token_device_col(
6722        &self,
6723        logits: &CudaSlice<f32>,
6724        tok_all: &CudaSlice<u32>,
6725        tok_idx: usize,
6726        p_out: &mut CudaSlice<f32>,
6727        p_idx: usize,
6728        n_vocab: usize,
6729    ) -> Result<(), Box<dyn std::error::Error>> {
6730        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6731        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6732        let nb = ARGMAX_NB;
6733        let mut part = self.alloc_uninit::<f32>(nb)?;
6734        let f1 = self.func("prob_of_token_partial_f32");
6735        let cfg1 = LaunchConfig {
6736            grid_dim: (nb as u32, 1, 1),
6737            block_dim: (256, 1, 1),
6738            shared_mem_bytes: 0,
6739        };
6740        let nv = n_vocab as i32;
6741        let __s_b1 = self.gpu.stream();
6742        let mut b1 = __s_b1.launch_builder(&f1);
6743        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6744        unsafe {
6745            b1.launch(cfg1)?;
6746        }
6747        let f2 = self.func("prob_of_token_final_f32");
6748        let cfg2 = LaunchConfig {
6749            grid_dim: (1, 1, 1),
6750            block_dim: (256, 1, 1),
6751            shared_mem_bytes: 0,
6752        };
6753        let nbi = nb as i32;
6754        let __s_b2 = self.gpu.stream();
6755        let mut b2 = __s_b2.launch_builder(&f2);
6756        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6757        unsafe {
6758            b2.launch(cfg2)?;
6759        }
6760        Ok(())
6761    }
6762
6763    pub fn prob_of_token_device_into(
6764        &self,
6765        logits: &CudaSlice<f32>,
6766        tok: &CudaSlice<u32>,
6767        p_out: &mut CudaSlice<f32>,
6768        n_vocab: usize,
6769    ) -> Result<(), Box<dyn std::error::Error>> {
6770        let nb = ARGMAX_NB;
6771        let mut part = self.alloc_uninit::<f32>(nb)?;
6772        let f1 = self.func("prob_of_token_partial_f32");
6773        let cfg1 = LaunchConfig {
6774            grid_dim: (nb as u32, 1, 1),
6775            block_dim: (256, 1, 1),
6776            shared_mem_bytes: 0,
6777        };
6778        let nv = n_vocab as i32;
6779        let __s_b1 = self.gpu.stream();
6780        let mut b1 = __s_b1.launch_builder(&f1);
6781        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6782        unsafe {
6783            b1.launch(cfg1)?;
6784        }
6785        let f2 = self.func("prob_of_token_final_f32");
6786        let cfg2 = LaunchConfig {
6787            grid_dim: (1, 1, 1),
6788            block_dim: (256, 1, 1),
6789            shared_mem_bytes: 0,
6790        };
6791        let nbi = nb as i32;
6792        let __s_b2 = self.gpu.stream();
6793        let mut b2 = __s_b2.launch_builder(&f2);
6794        b2.arg(&part).arg(p_out).arg(&nbi);
6795        unsafe {
6796            b2.launch(cfg2)?;
6797        }
6798        Ok(())
6799    }
6800
6801    pub fn argmax_token_device(
6802        &self,
6803        logits: &CudaSlice<f32>,
6804        n_vocab: usize,
6805    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6806        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6807        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6808        Ok(tok)
6809    }
6810    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6811    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6812    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6813    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6814    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6815    /// captured passes bake fixed addresses.
6816    pub fn argmax_token_device_into(
6817        &self,
6818        logits: &CudaSlice<f32>,
6819        tok: &mut CudaSlice<u32>,
6820        n_vocab: usize,
6821    ) -> Result<(), Box<dyn std::error::Error>> {
6822        let nb = ARGMAX_NB;
6823        let f1 = self.func("argmax_partial_f32");
6824        let f2 = self.func("argmax_final_f32");
6825        let mut guard = self.argmax_partials.lock().unwrap();
6826        if guard.is_none() {
6827            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6828            // buffers carry no cudarc events (illegal inside capture).
6829            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6830            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6831            *guard = Some((pv, pi));
6832        }
6833        let (part_v, part_i) = guard.as_mut().unwrap();
6834        let nv = n_vocab as i32;
6835        let nbi = nb as i32;
6836        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6837        let cfg1 = LaunchConfig {
6838            grid_dim: (nb as u32, 1, 1),
6839            block_dim: (256, 1, 1),
6840            shared_mem_bytes: 0,
6841        };
6842        let __s_b1 = self.gpu.stream();
6843        let mut b1 = __s_b1.launch_builder(&f1);
6844        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6845        unsafe {
6846            b1.launch(cfg1)?;
6847        }
6848        // pass 2: one block reduces NB partials -> token_out[0].
6849        let cfg2 = LaunchConfig {
6850            grid_dim: (1, 1, 1),
6851            block_dim: (256, 1, 1),
6852            shared_mem_bytes: 0,
6853        };
6854        let __s_b2 = self.gpu.stream();
6855        let mut b2 = __s_b2.launch_builder(&f2);
6856        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6857        unsafe {
6858            b2.launch(cfg2)?;
6859        }
6860        Ok(())
6861    }
6862    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6863    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6864    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6865    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6866    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6867    pub fn argmax_token_device_col(
6868        &self,
6869        logits: &CudaSlice<f32>,
6870        col: usize,
6871        n_vocab: usize,
6872        toks: &mut CudaSlice<u32>,
6873        out_idx: usize,
6874    ) -> Result<(), Box<dyn std::error::Error>> {
6875        let nb = ARGMAX_NB;
6876        let f1 = self.func("argmax_partial_f32");
6877        let f2 = self.func("argmax_final_f32");
6878        let mut guard = self.argmax_partials.lock().unwrap();
6879        if guard.is_none() {
6880            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6881            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6882            *guard = Some((pv, pi));
6883        }
6884        let (part_v, part_i) = guard.as_mut().unwrap();
6885        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
6886        let nv = n_vocab as i32;
6887        let nbi = nb as i32;
6888        let cfg1 = LaunchConfig {
6889            grid_dim: (nb as u32, 1, 1),
6890            block_dim: (256, 1, 1),
6891            shared_mem_bytes: 0,
6892        };
6893        let __s_b1 = self.gpu.stream();
6894        let mut b1 = __s_b1.launch_builder(&f1);
6895        b1.arg(&col_view)
6896            .arg(&mut *part_v)
6897            .arg(&mut *part_i)
6898            .arg(&nv);
6899        unsafe {
6900            b1.launch(cfg1)?;
6901        }
6902        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
6903        let cfg2 = LaunchConfig {
6904            grid_dim: (1, 1, 1),
6905            block_dim: (256, 1, 1),
6906            shared_mem_bytes: 0,
6907        };
6908        let __s_b2 = self.gpu.stream();
6909        let mut b2 = __s_b2.launch_builder(&f2);
6910        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
6911        unsafe {
6912            b2.launch(cfg2)?;
6913        }
6914        Ok(())
6915    }
6916    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
6917    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6918        Ok(self.gpu.stream().clone_htod(v)?)
6919    }
6920    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
6921        let v = self.gpu.stream().clone_dtoh(d)?;
6922        self.gpu.stream().synchronize()?;
6923        Ok(v)
6924    }
6925    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
6926    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
6927    /// contents change every step, the address must not, so a captured graph can read it).
6928    pub fn htod_u32_into(
6929        &self,
6930        dst: &mut CudaSlice<u32>,
6931        src: &[u32],
6932    ) -> Result<(), Box<dyn std::error::Error>> {
6933        let mut view = dst.slice_mut(0..src.len());
6934        self.gpu.stream().memcpy_htod(src, &mut view)?;
6935        Ok(())
6936    }
6937
6938    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
6939    /// table without changing the device address its reconcile kernel consumes.
6940    pub fn htod_i32_into(
6941        &self,
6942        dst: &mut CudaSlice<i32>,
6943        src: &[i32],
6944    ) -> Result<(), Box<dyn std::error::Error>> {
6945        let mut view = dst.slice_mut(0..src.len());
6946        self.gpu.stream().memcpy_htod(src, &mut view)?;
6947        Ok(())
6948    }
6949
6950    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6951        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
6952        self.keep_if_capturing(&s);
6953        Ok(s)
6954    }
6955    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
6956    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
6957    pub fn embed_gather_device_into(
6958        &self,
6959        embd: &CudaSlice<u8>,
6960        token_d: &CudaSlice<u32>,
6961        x_out: &mut CudaSlice<f32>,
6962        n_embd: usize,
6963        qtype: i32,
6964        row_bytes: usize,
6965    ) -> Result<(), Box<dyn std::error::Error>> {
6966        let f = self.func("embed_gather_u32");
6967        let cfg = LaunchConfig {
6968            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
6969            block_dim: (256, 1, 1),
6970            shared_mem_bytes: 0,
6971        };
6972        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
6973        let __s_b = self.gpu.stream();
6974        let mut b = __s_b.launch_builder(&f);
6975        b.arg(embd)
6976            .arg(token_d)
6977            .arg(x_out)
6978            .arg(&ne)
6979            .arg(&qt)
6980            .arg(&rb);
6981        unsafe {
6982            b.launch(cfg)?;
6983        }
6984        Ok(())
6985    }
6986    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
6987    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
6988        let v = self.gpu.stream().clone_dtoh(d)?;
6989        self.gpu.stream().synchronize()?;
6990        Ok(v[0])
6991    }
6992    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
6993    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
6994    /// the counter value after the throwaway capture warmups corrupt it.
6995    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
6996    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
6997    /// copy (fine at stream-idle boundaries, poison mid-round).
6998    pub fn i32_set_k(
6999        &self,
7000        dst: &mut CudaSlice<i32>,
7001        v: i32,
7002    ) -> Result<(), Box<dyn std::error::Error>> {
7003        let f = self.func("i32_set_k");
7004        let cfg = LaunchConfig {
7005            grid_dim: (1, 1, 1),
7006            block_dim: (1, 1, 1),
7007            shared_mem_bytes: 0,
7008        };
7009        let idx = 0i32;
7010        let __s_b = self.gpu.stream();
7011        let mut b = __s_b.launch_builder(&f);
7012        b.arg(dst).arg(&v).arg(&idx);
7013        unsafe {
7014            b.launch(cfg)?;
7015        }
7016        Ok(())
7017    }
7018
7019    pub fn set_i32_one(
7020        &self,
7021        d: &mut CudaSlice<i32>,
7022        v: i32,
7023    ) -> Result<(), Box<dyn std::error::Error>> {
7024        self.gpu.stream().memcpy_htod(&[v], d)?;
7025        Ok(())
7026    }
7027    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
7028    /// during priming / capture-state restore.
7029    pub fn set_u32_one(
7030        &self,
7031        d: &mut CudaSlice<u32>,
7032        v: u32,
7033    ) -> Result<(), Box<dyn std::error::Error>> {
7034        self.gpu.stream().memcpy_htod(&[v], d)?;
7035        Ok(())
7036    }
7037    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
7038    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
7039        let v = self.gpu.stream().clone_dtoh(d)?;
7040        self.gpu.stream().synchronize()?;
7041        Ok(v[0])
7042    }
7043    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
7044    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
7045        Ok(self.gpu.stream().clone_htod(bytes)?)
7046    }
7047    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7048    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7049    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7050    pub fn embed_gather_device(
7051        &self,
7052        embd: &CudaSlice<u8>,
7053        token_d: &CudaSlice<u32>,
7054        n_embd: usize,
7055        qtype: i32,
7056        row_bytes: usize,
7057    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7058        let f = self.func("embed_gather_u32");
7059        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7060        let cfg = LaunchConfig {
7061            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7062            block_dim: (256, 1, 1),
7063            shared_mem_bytes: 0,
7064        };
7065        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7066        let __s_b = self.gpu.stream();
7067        let mut b = __s_b.launch_builder(&f);
7068        b.arg(embd)
7069            .arg(token_d)
7070            .arg(&mut x)
7071            .arg(&ne)
7072            .arg(&qt)
7073            .arg(&rb);
7074        unsafe {
7075            b.launch(cfg)?;
7076        }
7077        Ok(x)
7078    }
7079
7080    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7081    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7082    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7083    pub fn embed_gather_device_t(
7084        &self,
7085        embd: &CudaSlice<u8>,
7086        tokens: &[u32],
7087        n_embd: usize,
7088        qtype: i32,
7089        row_bytes: usize,
7090    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7091        let t = tokens.len();
7092        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7093        let f = self.func("embed_gather_u32_t");
7094        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7095        let cfg = LaunchConfig {
7096            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7097            block_dim: (256, 1, 1),
7098            shared_mem_bytes: 0,
7099        };
7100        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7101        let __s_b = self.gpu.stream();
7102        let mut b = __s_b.launch_builder(&f);
7103        b.arg(embd)
7104            .arg(&tok_d)
7105            .arg(&mut x)
7106            .arg(&ne)
7107            .arg(&qt)
7108            .arg(&rb)
7109            .arg(&ti);
7110        unsafe {
7111            b.launch(cfg)?;
7112        }
7113        Ok(x)
7114    }
7115
7116    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7117    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7118    /// as embed_gather_device_t — bit-identical rows.
7119    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7120    pub fn embed_gather_device_tv(
7121        &self,
7122        embd: &CudaSlice<u8>,
7123        tok_v: &cudarc::driver::CudaView<u32>,
7124        t: usize,
7125        n_embd: usize,
7126        qtype: i32,
7127        row_bytes: usize,
7128    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7129        let f = self.func("embed_gather_u32_t");
7130        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7131        let cfg = LaunchConfig {
7132            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7133            block_dim: (256, 1, 1),
7134            shared_mem_bytes: 0,
7135        };
7136        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7137        let __s_b = self.gpu.stream();
7138        let mut b = __s_b.launch_builder(&f);
7139        b.arg(embd)
7140            .arg(tok_v)
7141            .arg(&mut x)
7142            .arg(&ne)
7143            .arg(&qt)
7144            .arg(&rb)
7145            .arg(&ti);
7146        unsafe {
7147            b.launch(cfg)?;
7148        }
7149        Ok(x)
7150    }
7151
7152    pub fn embed_gather_device_td(
7153        &self,
7154        embd: &CudaSlice<u8>,
7155        tok_d: &CudaSlice<u32>,
7156        t: usize,
7157        n_embd: usize,
7158        qtype: i32,
7159        row_bytes: usize,
7160    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7161        let f = self.func("embed_gather_u32_t");
7162        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7163        let cfg = LaunchConfig {
7164            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7165            block_dim: (256, 1, 1),
7166            shared_mem_bytes: 0,
7167        };
7168        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7169        let __s_b = self.gpu.stream();
7170        let mut b = __s_b.launch_builder(&f);
7171        b.arg(embd)
7172            .arg(tok_d)
7173            .arg(&mut x)
7174            .arg(&ne)
7175            .arg(&qt)
7176            .arg(&rb)
7177            .arg(&ti);
7178        unsafe {
7179            b.launch(cfg)?;
7180        }
7181        Ok(x)
7182    }
7183
7184    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7185    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7186    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7187    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7188    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7189    #[inline]
7190    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7191    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7192        if self
7193            .capture_keep_on
7194            .load(std::sync::atomic::Ordering::Relaxed)
7195        {
7196            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7197        }
7198    }
7199
7200    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7201        &self,
7202        n: usize,
7203    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7204        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7205        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7206        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7207        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7208        {
7209            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7210            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7211                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7212                use cudarc::driver::DevicePtrMut;
7213                let n_bytes = s.len() * std::mem::size_of::<T>();
7214                let stream = self.gpu.stream();
7215                let (p_, _g) = s.device_ptr_mut(&stream);
7216                unsafe {
7217                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7218                        .result()?;
7219                }
7220            }
7221        }
7222        self.keep_if_capturing(&s);
7223        Ok(s)
7224    }
7225
7226    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7227    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7228    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7229    /// consumers alloc through this (m=1 decode arms).
7230    pub fn uninit_q8_pair(
7231        &self,
7232        n: usize,
7233    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7234        Ok((
7235            self.alloc_uninit::<i8>(n)?,
7236            self.alloc_uninit::<f32>(n / 32)?,
7237        ))
7238    }
7239
7240    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7241        self.alloc_uninit::<f32>(n)
7242    }
7243
7244    /// i8 uninitialized scratch (same contract as `uninit`).
7245    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7246        self.alloc_uninit::<i8>(n)
7247    }
7248
7249    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7250    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7251    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7252    #[allow(clippy::too_many_arguments)]
7253    pub fn rms_norm3(
7254        &self,
7255        x: &CudaSlice<f32>,
7256        w0: &CudaSlice<f32>,
7257        w1: &CudaSlice<f32>,
7258        w2: &CudaSlice<f32>,
7259        d0: &mut CudaSlice<f32>,
7260        d1: &mut CudaSlice<f32>,
7261        d2: &mut CudaSlice<f32>,
7262        ncols: usize,
7263        nrows: usize,
7264        eps: f32,
7265    ) -> Result<(), Box<dyn std::error::Error>> {
7266        let f = self.func("rms_norm3_f32");
7267        let cfg = LaunchConfig {
7268            grid_dim: (nrows as u32, 1, 1),
7269            block_dim: (rms_block(), 1, 1),
7270            shared_mem_bytes: 0,
7271        };
7272        let (nc, e) = (ncols as i32, eps);
7273        let __s_b = self.gpu.stream();
7274        let mut b = __s_b.launch_builder(&f);
7275        b.arg(x)
7276            .arg(w0)
7277            .arg(w1)
7278            .arg(w2)
7279            .arg(d0)
7280            .arg(d1)
7281            .arg(d2)
7282            .arg(&nc)
7283            .arg(&e);
7284        unsafe {
7285            b.launch(cfg)?;
7286        }
7287        Ok(())
7288    }
7289
7290    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7291    #[allow(clippy::too_many_arguments)]
7292    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7293    /// piggybacks on the same conditions.
7294    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7295        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7296        *WARP_ON.get_or_init(|| {
7297            std::env::var("MEMRA_QKVNORM_W")
7298                .map(|v| v != "0")
7299                .unwrap_or(true)
7300        }) && ncols % 4 == 0
7301            && rows >= 64
7302    }
7303
7304    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7305    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7306    #[allow(clippy::too_many_arguments)]
7307    pub fn rms_norm_qkv_w4b(
7308        &self,
7309        q: &CudaSlice<f32>,
7310        k: &CudaSlice<f32>,
7311        v: &CudaSlice<f32>,
7312        wq: &CudaSlice<f32>,
7313        wk: &CudaSlice<f32>,
7314        wv: &CudaSlice<f32>,
7315        dq: &mut CudaSlice<f32>,
7316        dk: &mut CudaSlice<f32>,
7317        dv: &mut CudaSlice<f32>,
7318        dvb: &mut CudaSlice<u8>,
7319        ncols: usize,
7320        rq: usize,
7321        rk: usize,
7322        eps: f32,
7323        vf16: bool,
7324    ) -> Result<(), Box<dyn std::error::Error>> {
7325        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7326        let f = self.func("rms_norm_qkv_w4b_f32");
7327        let rows = (rq + 2 * rk) as u32;
7328        let cfg = LaunchConfig {
7329            grid_dim: (rows.div_ceil(8), 1, 1),
7330            block_dim: (256, 1, 1),
7331            shared_mem_bytes: 0,
7332        };
7333        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7334        let vf = vf16 as i32;
7335        let __s_b = self.gpu.stream();
7336        let mut b = __s_b.launch_builder(&f);
7337        b.arg(q)
7338            .arg(k)
7339            .arg(v)
7340            .arg(wq)
7341            .arg(wk)
7342            .arg(wv)
7343            .arg(dq)
7344            .arg(dk)
7345            .arg(dv)
7346            .arg(&mut *dvb)
7347            .arg(&nc)
7348            .arg(&rqi)
7349            .arg(&rki)
7350            .arg(&rvi)
7351            .arg(&e)
7352            .arg(&vf);
7353        unsafe {
7354            b.launch(cfg)?;
7355        }
7356        Ok(())
7357    }
7358
7359    pub fn rms_norm_qkv(
7360        &self,
7361        q: &CudaSlice<f32>,
7362        k: &CudaSlice<f32>,
7363        v: &CudaSlice<f32>,
7364        wq: &CudaSlice<f32>,
7365        wk: &CudaSlice<f32>,
7366        wv: &CudaSlice<f32>,
7367        dq: &mut CudaSlice<f32>,
7368        dk: &mut CudaSlice<f32>,
7369        dv: &mut CudaSlice<f32>,
7370        ncols: usize,
7371        rq: usize,
7372        rk: usize,
7373        eps: f32,
7374    ) -> Result<(), Box<dyn std::error::Error>> {
7375        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7376        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7377        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7378        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7379        let warp_on = *WARP_ON.get_or_init(|| {
7380            std::env::var("MEMRA_QKVNORM_W")
7381                .map(|v| v != "0")
7382                .unwrap_or(true)
7383        });
7384        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7385        // replay numerics are untouched on every model; only prefill depth takes the new config.
7386        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7387            let f = self.func("rms_norm_qkv_w4_f32");
7388            let rows = (rq + 2 * rk) as u32;
7389            let cfg = LaunchConfig {
7390                grid_dim: (rows.div_ceil(8), 1, 1),
7391                block_dim: (256, 1, 1),
7392                shared_mem_bytes: 0,
7393            };
7394            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7395            let __s_b = self.gpu.stream();
7396            let mut b = __s_b.launch_builder(&f);
7397            b.arg(q)
7398                .arg(k)
7399                .arg(v)
7400                .arg(wq)
7401                .arg(wk)
7402                .arg(wv)
7403                .arg(dq)
7404                .arg(dk)
7405                .arg(dv)
7406                .arg(&nc)
7407                .arg(&rqi)
7408                .arg(&rki)
7409                .arg(&rvi)
7410                .arg(&e);
7411            unsafe {
7412                b.launch(cfg)?;
7413            }
7414            return Ok(());
7415        }
7416        let f = self.func("rms_norm_qkv_f32");
7417        let grid = (rq + 2 * rk) as u32;
7418        let cfg = LaunchConfig {
7419            grid_dim: (grid, 1, 1),
7420            block_dim: (rms_block(), 1, 1),
7421            shared_mem_bytes: 0,
7422        };
7423        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7424        let __s_b = self.gpu.stream();
7425        let mut b = __s_b.launch_builder(&f);
7426        b.arg(q)
7427            .arg(k)
7428            .arg(v)
7429            .arg(wq)
7430            .arg(wk)
7431            .arg(wv)
7432            .arg(dq)
7433            .arg(dk)
7434            .arg(dv)
7435            .arg(&nc)
7436            .arg(&rqi)
7437            .arg(&rki)
7438            .arg(&e);
7439        unsafe {
7440            b.launch(cfg)?;
7441        }
7442        Ok(())
7443    }
7444
7445    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7446    #[allow(clippy::too_many_arguments)]
7447    pub fn rms_norm2x(
7448        &self,
7449        a: &CudaSlice<f32>,
7450        bb: &CudaSlice<f32>,
7451        wa: &CudaSlice<f32>,
7452        wb: &CudaSlice<f32>,
7453        da: &mut CudaSlice<f32>,
7454        db: &mut CudaSlice<f32>,
7455        ncols: usize,
7456        nrows: usize,
7457        eps: f32,
7458    ) -> Result<(), Box<dyn std::error::Error>> {
7459        let f = self.func("rms_norm2x_f32");
7460        let cfg = LaunchConfig {
7461            grid_dim: (2 * nrows as u32, 1, 1),
7462            block_dim: (rms_block(), 1, 1),
7463            shared_mem_bytes: 0,
7464        };
7465        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7466        let __s_b = self.gpu.stream();
7467        let mut b = __s_b.launch_builder(&f);
7468        b.arg(a)
7469            .arg(bb)
7470            .arg(wa)
7471            .arg(wb)
7472            .arg(da)
7473            .arg(db)
7474            .arg(&nc)
7475            .arg(&nr)
7476            .arg(&e);
7477        unsafe {
7478            b.launch(cfg)?;
7479        }
7480        Ok(())
7481    }
7482
7483    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7484    pub fn softcap(
7485        &self,
7486        y: &mut CudaSlice<f32>,
7487        cap: f32,
7488        n: usize,
7489    ) -> Result<(), Box<dyn std::error::Error>> {
7490        let f = self.func("softcap_f32");
7491        let cfg = LaunchConfig::for_num_elems(n as u32);
7492        let ni = n as i32;
7493        let __s_b = self.gpu.stream();
7494        let mut b = __s_b.launch_builder(&f);
7495        b.arg(y).arg(&cap).arg(&ni);
7496        unsafe {
7497            b.launch(cfg)?;
7498        }
7499        Ok(())
7500    }
7501
7502    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7503    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7504    pub fn mask_ids_rows(
7505        &self,
7506        y: &mut CudaSlice<f32>,
7507        ids: &CudaSlice<i32>,
7508        n_ids: usize,
7509        n_vocab: usize,
7510        t: usize,
7511    ) -> Result<(), Box<dyn std::error::Error>> {
7512        let f = self.func("mask_ids_rows_f32");
7513        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7514        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7515        let __s_b = self.gpu.stream();
7516        let mut b = __s_b.launch_builder(&f);
7517        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7518        unsafe {
7519            b.launch(cfg)?;
7520        }
7521        Ok(())
7522    }
7523
7524    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7525    #[allow(clippy::too_many_arguments)]
7526    pub fn add_scale_rms_norm(
7527        &self,
7528        a: &CudaSlice<f32>,
7529        b_in: &CudaSlice<f32>,
7530        c: f32,
7531        w: &CudaSlice<f32>,
7532        res: &mut CudaSlice<f32>,
7533        dst: &mut CudaSlice<f32>,
7534        ncols: usize,
7535        nrows: usize,
7536        eps: f32,
7537    ) -> Result<(), Box<dyn std::error::Error>> {
7538        let f = self.func("add_scale_rms_norm_f32");
7539        let cfg = LaunchConfig {
7540            grid_dim: (nrows as u32, 1, 1),
7541            block_dim: (rms_block(), 1, 1),
7542            shared_mem_bytes: 0,
7543        };
7544        let (nc, e2) = (ncols as i32, eps);
7545        let __s_b = self.gpu.stream();
7546        let mut b = __s_b.launch_builder(&f);
7547        b.arg(a)
7548            .arg(b_in)
7549            .arg(&c)
7550            .arg(w)
7551            .arg(res)
7552            .arg(dst)
7553            .arg(&nc)
7554            .arg(&e2);
7555        unsafe {
7556            b.launch(cfg)?;
7557        }
7558        Ok(())
7559    }
7560
7561    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7562    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7563    #[allow(clippy::too_many_arguments)]
7564    pub fn add_scale_rms_norm_q8_1(
7565        &self,
7566        a: &CudaSlice<f32>,
7567        b_in: &CudaSlice<f32>,
7568        c: f32,
7569        w: &CudaSlice<f32>,
7570        res: &mut CudaSlice<f32>,
7571        ncols: usize,
7572        nrows: usize,
7573        eps: f32,
7574    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7575        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7576        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7577        let (nc, e2) = (ncols as i32, eps);
7578        if Self::pdl_on() && Self::pdl_wb_on() {
7579            {
7580                use cudarc::driver::{DevicePtr, DevicePtrMut};
7581                let s = &self.gpu.stream();
7582                let (pa, _g0) = a.device_ptr(s);
7583                let (pb, _g1) = b_in.device_ptr(s);
7584                let (pw, _g2) = w.device_ptr(s);
7585                let (pr, _g3) = res.device_ptr_mut(s);
7586                let (pq, _g4) = out_q.device_ptr_mut(s);
7587                let (pd, _g5) = out_d.device_ptr_mut(s);
7588                let mut ps = [
7589                    &pa as *const _ as *mut std::ffi::c_void,
7590                    &pb as *const _ as *mut _,
7591                    &c as *const _ as *mut _,
7592                    &pw as *const _ as *mut _,
7593                    &pr as *const _ as *mut _,
7594                    &pq as *const _ as *mut _,
7595                    &pd as *const _ as *mut _,
7596                    &nc as *const _ as *mut _,
7597                    &e2 as *const _ as *mut _,
7598                ];
7599                unsafe {
7600                    self.launch_pdl(
7601                        "add_scale_rms_norm_q8_1",
7602                        (nrows as u32, 1, 1),
7603                        (rms_block(), 1, 1),
7604                        &mut ps,
7605                    )?;
7606                }
7607            }
7608            return Ok((out_q, out_d));
7609        }
7610        let f = self.func("add_scale_rms_norm_q8_1");
7611        let cfg = LaunchConfig {
7612            grid_dim: (nrows as u32, 1, 1),
7613            block_dim: (rms_block(), 1, 1),
7614            shared_mem_bytes: 0,
7615        };
7616        let __s_b = self.gpu.stream();
7617        let mut b = __s_b.launch_builder(&f);
7618        b.arg(a)
7619            .arg(b_in)
7620            .arg(&c)
7621            .arg(w)
7622            .arg(res)
7623            .arg(&mut out_q)
7624            .arg(&mut out_d)
7625            .arg(&nc)
7626            .arg(&e2);
7627        unsafe {
7628            b.launch(cfg)?;
7629        }
7630        Ok((out_q, out_d))
7631    }
7632
7633    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7634    #[allow(clippy::too_many_arguments)]
7635    pub fn add_scale_rms_norm_q8_1_into(
7636        &self,
7637        a: &CudaSlice<f32>,
7638        b_in: &CudaSlice<f32>,
7639        c: f32,
7640        w: &CudaSlice<f32>,
7641        res: &mut CudaSlice<f32>,
7642        ncols: usize,
7643        nrows: usize,
7644        eps: f32,
7645        out_q: &mut CudaSlice<i8>,
7646        out_d: &mut CudaSlice<f32>,
7647    ) -> Result<(), Box<dyn std::error::Error>> {
7648        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7649        let (nc, e2) = (ncols as i32, eps);
7650        if Self::pdl_on() && Self::pdl_wb_on() {
7651            use cudarc::driver::{DevicePtr, DevicePtrMut};
7652            let s = &self.gpu.stream();
7653            let (pa, _g0) = a.device_ptr(s);
7654            let (pb, _g1) = b_in.device_ptr(s);
7655            let (pw, _g2) = w.device_ptr(s);
7656            let (pr, _g3) = res.device_ptr_mut(s);
7657            let (pq, _g4) = out_q.device_ptr_mut(s);
7658            let (pd, _g5) = out_d.device_ptr_mut(s);
7659            let mut ps = [
7660                &pa as *const _ as *mut std::ffi::c_void,
7661                &pb as *const _ as *mut _,
7662                &c as *const _ as *mut _,
7663                &pw as *const _ as *mut _,
7664                &pr as *const _ as *mut _,
7665                &pq as *const _ as *mut _,
7666                &pd as *const _ as *mut _,
7667                &nc as *const _ as *mut _,
7668                &e2 as *const _ as *mut _,
7669            ];
7670            unsafe {
7671                self.launch_pdl(
7672                    "add_scale_rms_norm_q8_1",
7673                    (nrows as u32, 1, 1),
7674                    (rms_block(), 1, 1),
7675                    &mut ps,
7676                )?;
7677            }
7678            return Ok(());
7679        }
7680        let f = self.func("add_scale_rms_norm_q8_1");
7681        let cfg = LaunchConfig {
7682            grid_dim: (nrows as u32, 1, 1),
7683            block_dim: (rms_block(), 1, 1),
7684            shared_mem_bytes: 0,
7685        };
7686        let __s_b = self.gpu.stream();
7687        let mut b = __s_b.launch_builder(&f);
7688        b.arg(a)
7689            .arg(b_in)
7690            .arg(&c)
7691            .arg(w)
7692            .arg(res)
7693            .arg(&mut *out_q)
7694            .arg(&mut *out_d)
7695            .arg(&nc)
7696            .arg(&e2);
7697        unsafe {
7698            b.launch(cfg)?;
7699        }
7700        Ok(())
7701    }
7702
7703    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7704    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7705    #[allow(clippy::too_many_arguments)]
7706    pub fn rms_pre_add_scale_rms_norm_q8_1(
7707        &self,
7708        a: &CudaSlice<f32>,
7709        wa: &CudaSlice<f32>,
7710        b_in: &CudaSlice<f32>,
7711        c: f32,
7712        w: &CudaSlice<f32>,
7713        res: &mut CudaSlice<f32>,
7714        ncols: usize,
7715        nrows: usize,
7716        eps: f32,
7717    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7718        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7719        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7720        let (nc, e2) = (ncols as i32, eps);
7721        if Self::pdl_on() {
7722            {
7723                use cudarc::driver::{DevicePtr, DevicePtrMut};
7724                let s = &self.gpu.stream();
7725                let (pa, _g0) = a.device_ptr(s);
7726                let (pwa, _g1) = wa.device_ptr(s);
7727                let (pb, _g2) = b_in.device_ptr(s);
7728                let (pw, _g3) = w.device_ptr(s);
7729                let (pr, _g4) = res.device_ptr_mut(s);
7730                let (pq, _g5) = out_q.device_ptr_mut(s);
7731                let (pd, _g6) = out_d.device_ptr_mut(s);
7732                let mut ps = [
7733                    &pa as *const _ as *mut std::ffi::c_void,
7734                    &pwa as *const _ as *mut _,
7735                    &pb as *const _ as *mut _,
7736                    &c as *const _ as *mut _,
7737                    &pw as *const _ as *mut _,
7738                    &pr as *const _ as *mut _,
7739                    &pq as *const _ as *mut _,
7740                    &pd as *const _ as *mut _,
7741                    &nc as *const _ as *mut _,
7742                    &e2 as *const _ as *mut _,
7743                ];
7744                unsafe {
7745                    self.launch_pdl(
7746                        "rms_pre_add_scale_rms_norm_q8_1",
7747                        (nrows as u32, 1, 1),
7748                        (rms_block(), 1, 1),
7749                        &mut ps,
7750                    )?;
7751                }
7752            }
7753            return Ok((out_q, out_d));
7754        }
7755        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7756        let cfg = LaunchConfig {
7757            grid_dim: (nrows as u32, 1, 1),
7758            block_dim: (rms_block(), 1, 1),
7759            shared_mem_bytes: 0,
7760        };
7761        let __s_b = self.gpu.stream();
7762        let mut b = __s_b.launch_builder(&f);
7763        b.arg(a)
7764            .arg(wa)
7765            .arg(b_in)
7766            .arg(&c)
7767            .arg(w)
7768            .arg(res)
7769            .arg(&mut out_q)
7770            .arg(&mut out_d)
7771            .arg(&nc)
7772            .arg(&e2);
7773        unsafe {
7774            b.launch(cfg)?;
7775        }
7776        Ok((out_q, out_d))
7777    }
7778
7779    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7780    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7781    pub fn gelu_tanh_mul_q8_1(
7782        &self,
7783        gate: &CudaSlice<f32>,
7784        up: &cudarc::driver::CudaView<f32>,
7785        act: &mut CudaSlice<f32>,
7786        ncols: usize,
7787        nrows: usize,
7788    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7789        debug_assert!(ncols % 128 == 0);
7790        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7791        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7792        let nc = ncols as i32;
7793        if Self::pdl_on() {
7794            {
7795                use cudarc::driver::{DevicePtr, DevicePtrMut};
7796                let s = &self.gpu.stream();
7797                let (pg, _g0) = gate.device_ptr(s);
7798                let (pu, _g1) = up.device_ptr(s);
7799                let (pact, _g2) = act.device_ptr_mut(s);
7800                let (pq, _g3) = out_q.device_ptr_mut(s);
7801                let (pd, _g4) = out_d.device_ptr_mut(s);
7802                let mut ps = [
7803                    &pg as *const _ as *mut std::ffi::c_void,
7804                    &pu as *const _ as *mut _,
7805                    &pact as *const _ as *mut _,
7806                    &pq as *const _ as *mut _,
7807                    &pd as *const _ as *mut _,
7808                    &nc as *const _ as *mut _,
7809                ];
7810                unsafe {
7811                    self.launch_pdl(
7812                        "gelu_tanh_mul_q8_1",
7813                        (nrows as u32, 1, 1),
7814                        (rms_block(), 1, 1),
7815                        &mut ps,
7816                    )?;
7817                }
7818            }
7819            return Ok((out_q, out_d));
7820        }
7821        let f = self.func("gelu_tanh_mul_q8_1");
7822        let cfg = LaunchConfig {
7823            grid_dim: (nrows as u32, 1, 1),
7824            block_dim: (rms_block(), 1, 1),
7825            shared_mem_bytes: 0,
7826        };
7827        let __s_b = self.gpu.stream();
7828        let mut b = __s_b.launch_builder(&f);
7829        b.arg(gate)
7830            .arg(up)
7831            .arg(act)
7832            .arg(&mut out_q)
7833            .arg(&mut out_d)
7834            .arg(&nc);
7835        unsafe {
7836            b.launch(cfg)?;
7837        }
7838        Ok((out_q, out_d))
7839    }
7840
7841    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7842    #[allow(clippy::too_many_arguments)]
7843    pub fn gelu_tanh_mul_q8_1_into(
7844        &self,
7845        gate: &CudaSlice<f32>,
7846        up: &cudarc::driver::CudaView<f32>,
7847        act: &mut CudaSlice<f32>,
7848        ncols: usize,
7849        nrows: usize,
7850        out_q: &mut CudaSlice<i8>,
7851        out_d: &mut CudaSlice<f32>,
7852    ) -> Result<(), Box<dyn std::error::Error>> {
7853        debug_assert!(ncols % 128 == 0);
7854        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7855        let nc = ncols as i32;
7856        if Self::pdl_on() {
7857            use cudarc::driver::{DevicePtr, DevicePtrMut};
7858            let s = &self.gpu.stream();
7859            let (pg, _g0) = gate.device_ptr(s);
7860            let (pu, _g1) = up.device_ptr(s);
7861            let (pact, _g2) = act.device_ptr_mut(s);
7862            let (pq, _g3) = out_q.device_ptr_mut(s);
7863            let (pd, _g4) = out_d.device_ptr_mut(s);
7864            let mut ps = [
7865                &pg as *const _ as *mut std::ffi::c_void,
7866                &pu as *const _ as *mut _,
7867                &pact as *const _ as *mut _,
7868                &pq as *const _ as *mut _,
7869                &pd as *const _ as *mut _,
7870                &nc as *const _ as *mut _,
7871            ];
7872            unsafe {
7873                self.launch_pdl(
7874                    "gelu_tanh_mul_q8_1",
7875                    (nrows as u32, 1, 1),
7876                    (rms_block(), 1, 1),
7877                    &mut ps,
7878                )?;
7879            }
7880            return Ok(());
7881        }
7882        let f = self.func("gelu_tanh_mul_q8_1");
7883        let cfg = LaunchConfig {
7884            grid_dim: (nrows as u32, 1, 1),
7885            block_dim: (rms_block(), 1, 1),
7886            shared_mem_bytes: 0,
7887        };
7888        let __s_b = self.gpu.stream();
7889        let mut b = __s_b.launch_builder(&f);
7890        b.arg(gate)
7891            .arg(up)
7892            .arg(&mut *act)
7893            .arg(&mut *out_q)
7894            .arg(&mut *out_d)
7895            .arg(&nc);
7896        unsafe {
7897            b.launch(cfg)?;
7898        }
7899        Ok(())
7900    }
7901
7902    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
7903    #[allow(clippy::too_many_arguments)]
7904    pub fn add_rms_norm3_q8z(
7905        &self,
7906        a: &CudaSlice<f32>,
7907        b_in: &CudaSlice<f32>,
7908        w0: &CudaSlice<f32>,
7909        w1: &CudaSlice<f32>,
7910        w2: &CudaSlice<f32>,
7911        res: &mut CudaSlice<f32>,
7912        out1: &mut CudaSlice<f32>,
7913        ncols: usize,
7914        nrows: usize,
7915        eps: f32,
7916    ) -> Result<
7917        (
7918            (CudaSlice<i8>, CudaSlice<f32>),
7919            (CudaSlice<i8>, CudaSlice<f32>),
7920        ),
7921        Box<dyn std::error::Error>,
7922    > {
7923        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
7924        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7925        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
7926        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7927        let f = self.func("add_rms_norm3_q8z_f32");
7928        let cfg = LaunchConfig {
7929            grid_dim: (nrows as u32, 1, 1),
7930            block_dim: (rms_block(), 1, 1),
7931            shared_mem_bytes: 0,
7932        };
7933        let (nc, e2) = (ncols as i32, eps);
7934        let __s_b = self.gpu.stream();
7935        let mut b = __s_b.launch_builder(&f);
7936        b.arg(a)
7937            .arg(b_in)
7938            .arg(w0)
7939            .arg(w1)
7940            .arg(w2)
7941            .arg(res)
7942            .arg(&mut q0)
7943            .arg(&mut d0)
7944            .arg(out1)
7945            .arg(&mut q2)
7946            .arg(&mut d2)
7947            .arg(&nc)
7948            .arg(&e2);
7949        unsafe {
7950            b.launch(cfg)?;
7951        }
7952        Ok(((q0, d0), (q2, d2)))
7953    }
7954
7955    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
7956    #[allow(clippy::too_many_arguments)]
7957    pub fn add_rms_norm3(
7958        &self,
7959        a: &CudaSlice<f32>,
7960        b_in: &CudaSlice<f32>,
7961        w0: &CudaSlice<f32>,
7962        w1: &CudaSlice<f32>,
7963        w2: &CudaSlice<f32>,
7964        res: &mut CudaSlice<f32>,
7965        d0: &mut CudaSlice<f32>,
7966        d1: &mut CudaSlice<f32>,
7967        d2: &mut CudaSlice<f32>,
7968        ncols: usize,
7969        nrows: usize,
7970        eps: f32,
7971    ) -> Result<(), Box<dyn std::error::Error>> {
7972        let f = self.func("add_rms_norm3_f32");
7973        let cfg = LaunchConfig {
7974            grid_dim: (nrows as u32, 1, 1),
7975            block_dim: (rms_block(), 1, 1),
7976            shared_mem_bytes: 0,
7977        };
7978        let (nc, e2) = (ncols as i32, eps);
7979        let __s_b = self.gpu.stream();
7980        let mut b = __s_b.launch_builder(&f);
7981        b.arg(a)
7982            .arg(b_in)
7983            .arg(w0)
7984            .arg(w1)
7985            .arg(w2)
7986            .arg(res)
7987            .arg(d0)
7988            .arg(d1)
7989            .arg(d2)
7990            .arg(&nc)
7991            .arg(&e2);
7992        unsafe {
7993            b.launch(cfg)?;
7994        }
7995        Ok(())
7996    }
7997
7998    /// dst = (a + b) * c (residual add + layer scale, one launch).
7999    pub fn add_scale(
8000        &self,
8001        a: &CudaSlice<f32>,
8002        b_in: &CudaSlice<f32>,
8003        c: f32,
8004        dst: &mut CudaSlice<f32>,
8005        n: usize,
8006    ) -> Result<(), Box<dyn std::error::Error>> {
8007        let f = self.func("add_scale_f32");
8008        let cfg = LaunchConfig::for_num_elems(n as u32);
8009        let ni = n as i32;
8010        let __s_b = self.gpu.stream();
8011        let mut b = __s_b.launch_builder(&f);
8012        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
8013        unsafe {
8014            b.launch(cfg)?;
8015        }
8016        Ok(())
8017    }
8018
8019    pub fn rms_norm(
8020        &self,
8021        x: &CudaSlice<f32>,
8022        w: &CudaSlice<f32>,
8023        dst: &mut CudaSlice<f32>,
8024        ncols: usize,
8025        nrows: usize,
8026        eps: f32,
8027    ) -> Result<(), Box<dyn std::error::Error>> {
8028        let (nc, e) = (ncols as i32, eps);
8029        if Self::pdl_on() && Self::pdl_wb_on() {
8030            use cudarc::driver::{DevicePtr, DevicePtrMut};
8031            let s = &self.gpu.stream();
8032            let (px, _g0) = x.device_ptr(s);
8033            let (pw, _g1) = w.device_ptr(s);
8034            let (pd, _g2) = dst.device_ptr_mut(s);
8035            let mut ps = [
8036                &px as *const _ as *mut std::ffi::c_void,
8037                &pw as *const _ as *mut _,
8038                &pd as *const _ as *mut _,
8039                &nc as *const _ as *mut _,
8040                &e as *const _ as *mut _,
8041            ];
8042            unsafe {
8043                self.launch_pdl(
8044                    "rms_norm_f32",
8045                    (nrows as u32, 1, 1),
8046                    (rms_block(), 1, 1),
8047                    &mut ps,
8048                )?;
8049            }
8050            return Ok(());
8051        }
8052        let f = self.func("rms_norm_f32");
8053        let cfg = LaunchConfig {
8054            grid_dim: (nrows as u32, 1, 1),
8055            block_dim: (rms_block(), 1, 1),
8056            shared_mem_bytes: 0,
8057        };
8058        let __s_b = self.gpu.stream();
8059        let mut b = __s_b.launch_builder(&f);
8060        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8061        unsafe {
8062            b.launch(cfg)?;
8063        }
8064        Ok(())
8065    }
8066
8067    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8068    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8069    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8070    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8071    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8072    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8073    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8074    pub fn rms_norm_decode(
8075        &self,
8076        x: &CudaSlice<f32>,
8077        w: &CudaSlice<f32>,
8078        dst: &mut CudaSlice<f32>,
8079        ncols: usize,
8080        nrows: usize,
8081        eps: f32,
8082    ) -> Result<(), Box<dyn std::error::Error>> {
8083        let f = self.func("rms_norm_f32");
8084        let cfg = LaunchConfig {
8085            grid_dim: (nrows as u32, 1, 1),
8086            block_dim: (1024, 1, 1),
8087            shared_mem_bytes: 0,
8088        };
8089        let (nc, e) = (ncols as i32, eps);
8090        let __s_b = self.gpu.stream();
8091        let mut b = __s_b.launch_builder(&f);
8092        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8093        unsafe {
8094            b.launch(cfg)?;
8095        }
8096        Ok(())
8097    }
8098
8099    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8100    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8101    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8102    pub fn rms_norm_q8_1(
8103        &self,
8104        x: &CudaSlice<f32>,
8105        w: &CudaSlice<f32>,
8106        ncols: usize,
8107        nrows: usize,
8108        eps: f32,
8109    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8110        let nblk = ncols / 32;
8111        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8112        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8113        let (nc, e) = (ncols as i32, eps);
8114        if Self::pdl_on() {
8115            {
8116                use cudarc::driver::{DevicePtr, DevicePtrMut};
8117                let s = &self.gpu.stream();
8118                let (px, _g0) = x.device_ptr(s);
8119                let (pw, _g1) = w.device_ptr(s);
8120                let (pq, _g2) = q.device_ptr_mut(s);
8121                let (pd, _g3) = d.device_ptr_mut(s);
8122                let mut ps = [
8123                    &px as *const _ as *mut std::ffi::c_void,
8124                    &pw as *const _ as *mut _,
8125                    &pq as *const _ as *mut _,
8126                    &pd as *const _ as *mut _,
8127                    &nc as *const _ as *mut _,
8128                    &e as *const _ as *mut _,
8129                ];
8130                unsafe {
8131                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8132                }
8133            }
8134            return Ok((q, d));
8135        }
8136        let f = self.func("rms_norm_q8_1");
8137        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8138        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8139        let cfg = LaunchConfig {
8140            grid_dim: (nrows as u32, 1, 1),
8141            block_dim: (1024, 1, 1),
8142            shared_mem_bytes: 0,
8143        };
8144        let __s_b = self.gpu.stream();
8145        let mut b = __s_b.launch_builder(&f);
8146        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8147        unsafe {
8148            b.launch(cfg)?;
8149        }
8150        Ok((q, d))
8151    }
8152
8153    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8154    /// PDL arm), caller-owned outputs.
8155    pub fn rms_norm_q8_1_into(
8156        &self,
8157        x: &CudaSlice<f32>,
8158        w: &CudaSlice<f32>,
8159        ncols: usize,
8160        nrows: usize,
8161        eps: f32,
8162        q: &mut CudaSlice<i8>,
8163        d: &mut CudaSlice<f32>,
8164    ) -> Result<(), Box<dyn std::error::Error>> {
8165        let nblk = ncols / 32;
8166        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8167        let (nc, e) = (ncols as i32, eps);
8168        if Self::pdl_on() {
8169            use cudarc::driver::{DevicePtr, DevicePtrMut};
8170            let s = &self.gpu.stream();
8171            let (px, _g0) = x.device_ptr(s);
8172            let (pw, _g1) = w.device_ptr(s);
8173            let (pq, _g2) = q.device_ptr_mut(s);
8174            let (pd, _g3) = d.device_ptr_mut(s);
8175            let mut ps = [
8176                &px as *const _ as *mut std::ffi::c_void,
8177                &pw as *const _ as *mut _,
8178                &pq as *const _ as *mut _,
8179                &pd as *const _ as *mut _,
8180                &nc as *const _ as *mut _,
8181                &e as *const _ as *mut _,
8182            ];
8183            unsafe {
8184                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8185            }
8186            return Ok(());
8187        }
8188        let f = self.func("rms_norm_q8_1");
8189        let cfg = LaunchConfig {
8190            grid_dim: (nrows as u32, 1, 1),
8191            block_dim: (1024, 1, 1),
8192            shared_mem_bytes: 0,
8193        };
8194        let __s_b = self.gpu.stream();
8195        let mut b = __s_b.launch_builder(&f);
8196        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8197        unsafe {
8198            b.launch(cfg)?;
8199        }
8200        Ok(())
8201    }
8202
8203    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8204    pub fn quantize_q8_1_into(
8205        &self,
8206        x: &CudaSlice<f32>,
8207        m: usize,
8208        in_f: usize,
8209        q: &mut CudaSlice<i8>,
8210        d: &mut CudaSlice<f32>,
8211    ) -> Result<(), Box<dyn std::error::Error>> {
8212        let nblk = in_f / 32;
8213        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8214        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8215        let (inf, mi) = (in_f as i32, m as i32);
8216        if Self::pdl_on() && Self::pdl_wb_on() {
8217            use cudarc::driver::{DevicePtr, DevicePtrMut};
8218            let s = &self.gpu.stream();
8219            let (px, _g0) = x.device_ptr(s);
8220            let (pq, _g1) = q.device_ptr_mut(s);
8221            let (pd, _g2) = d.device_ptr_mut(s);
8222            let mut ps = [
8223                &px as *const _ as *mut std::ffi::c_void,
8224                &pq as *const _ as *mut _,
8225                &pd as *const _ as *mut _,
8226                &inf as *const _ as *mut _,
8227                &mi as *const _ as *mut _,
8228            ];
8229            unsafe {
8230                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8231            }
8232            return Ok(());
8233        }
8234        let f = self.func("quantize_q8_1");
8235        let __s_b = self.gpu.stream();
8236        let mut b = __s_b.launch_builder(&f);
8237        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8238        unsafe {
8239            b.launch(cfg)?;
8240        }
8241        Ok(())
8242    }
8243
8244    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8245    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8246    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8247    pub fn add_rms_norm_q8_1(
8248        &self,
8249        a: &CudaSlice<f32>,
8250        b_in: &CudaSlice<f32>,
8251        w: &CudaSlice<f32>,
8252        res: &mut CudaSlice<f32>,
8253        ncols: usize,
8254        nrows: usize,
8255        eps: f32,
8256    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8257        let nblk = ncols / 32;
8258        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8259        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8260        let f = self.func("add_rms_norm_q8_1");
8261        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8262        let cfg = LaunchConfig {
8263            grid_dim: (nrows as u32, 1, 1),
8264            block_dim: (1024, 1, 1),
8265            shared_mem_bytes: 0,
8266        };
8267        let (nc, e) = (ncols as i32, eps);
8268        let __s_bld = self.gpu.stream();
8269        let mut bld = __s_bld.launch_builder(&f);
8270        bld.arg(a)
8271            .arg(b_in)
8272            .arg(w)
8273            .arg(res)
8274            .arg(&mut q)
8275            .arg(&mut d)
8276            .arg(&nc)
8277            .arg(&e);
8278        unsafe {
8279            bld.launch(cfg)?;
8280        }
8281        Ok((q, d))
8282    }
8283
8284    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8285    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8286    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8287    pub fn add_rms_norm(
8288        &self,
8289        a: &CudaSlice<f32>,
8290        b: &CudaSlice<f32>,
8291        w: &CudaSlice<f32>,
8292        res: &mut CudaSlice<f32>,
8293        dst: &mut CudaSlice<f32>,
8294        ncols: usize,
8295        nrows: usize,
8296        eps: f32,
8297    ) -> Result<(), Box<dyn std::error::Error>> {
8298        let (nc, e) = (ncols as i32, eps);
8299        if Self::pdl_on() && Self::pdl_wb_on() {
8300            use cudarc::driver::{DevicePtr, DevicePtrMut};
8301            let s = &self.gpu.stream();
8302            let (pa, _g0) = a.device_ptr(s);
8303            let (pb, _g1) = b.device_ptr(s);
8304            let (pw, _g2) = w.device_ptr(s);
8305            let (pr, _g3) = res.device_ptr_mut(s);
8306            let (pd, _g4) = dst.device_ptr_mut(s);
8307            let mut ps = [
8308                &pa as *const _ as *mut std::ffi::c_void,
8309                &pb as *const _ as *mut _,
8310                &pw as *const _ as *mut _,
8311                &pr as *const _ as *mut _,
8312                &pd as *const _ as *mut _,
8313                &nc as *const _ as *mut _,
8314                &e as *const _ as *mut _,
8315            ];
8316            unsafe {
8317                self.launch_pdl(
8318                    "add_rms_norm_f32",
8319                    (nrows as u32, 1, 1),
8320                    (rms_block(), 1, 1),
8321                    &mut ps,
8322                )?;
8323            }
8324            return Ok(());
8325        }
8326        let f = self.func("add_rms_norm_f32");
8327        let cfg = LaunchConfig {
8328            grid_dim: (nrows as u32, 1, 1),
8329            block_dim: (rms_block(), 1, 1),
8330            shared_mem_bytes: 0,
8331        };
8332        let __s_b2 = self.gpu.stream();
8333        let mut b2 = __s_b2.launch_builder(&f);
8334        b2.arg(a)
8335            .arg(b)
8336            .arg(w)
8337            .arg(&mut *res)
8338            .arg(&mut *dst)
8339            .arg(&nc)
8340            .arg(&e);
8341        unsafe {
8342            b2.launch(cfg)?;
8343        }
8344        Ok(())
8345    }
8346
8347    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8348    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8349    #[allow(clippy::too_many_arguments)]
8350    pub fn rms_pre_add_rms_norm(
8351        &self,
8352        a: &CudaSlice<f32>,
8353        wa: &CudaSlice<f32>,
8354        b: &CudaSlice<f32>,
8355        w: &CudaSlice<f32>,
8356        res: &mut CudaSlice<f32>,
8357        dst: &mut CudaSlice<f32>,
8358        ncols: usize,
8359        nrows: usize,
8360        eps: f32,
8361    ) -> Result<(), Box<dyn std::error::Error>> {
8362        let f = self.func("rms_pre_add_rms_norm_f32");
8363        let cfg = LaunchConfig {
8364            grid_dim: (nrows as u32, 1, 1),
8365            block_dim: (rms_block(), 1, 1),
8366            shared_mem_bytes: 0,
8367        };
8368        let (nc, e) = (ncols as i32, eps);
8369        let __s_b2 = self.gpu.stream();
8370        let mut b2 = __s_b2.launch_builder(&f);
8371        b2.arg(a)
8372            .arg(wa)
8373            .arg(b)
8374            .arg(w)
8375            .arg(&mut *res)
8376            .arg(&mut *dst)
8377            .arg(&nc)
8378            .arg(&e);
8379        unsafe {
8380            b2.launch(cfg)?;
8381        }
8382        Ok(())
8383    }
8384
8385    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8386    #[allow(clippy::too_many_arguments)]
8387    pub fn rms_pre_add_rms_norm_q8z(
8388        &self,
8389        a: &CudaSlice<f32>,
8390        wa: &CudaSlice<f32>,
8391        b: &CudaSlice<f32>,
8392        w: &CudaSlice<f32>,
8393        res: &mut CudaSlice<f32>,
8394        dst: &mut CudaSlice<f32>,
8395        ncols: usize,
8396        nrows: usize,
8397        eps: f32,
8398    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8399        debug_assert!(ncols % 128 == 0);
8400        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8401        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8402        let (nc, e) = (ncols as i32, eps);
8403        if Self::pdl_on() {
8404            {
8405                use cudarc::driver::{DevicePtr, DevicePtrMut};
8406                let s = &self.gpu.stream();
8407                let (pa, _g0) = a.device_ptr(s);
8408                let (pwa, _g1) = wa.device_ptr(s);
8409                let (pb, _g2) = b.device_ptr(s);
8410                let (pw, _g3) = w.device_ptr(s);
8411                let (pr, _g4) = res.device_ptr_mut(s);
8412                let (pdst, _g5) = dst.device_ptr_mut(s);
8413                let (pq, _g6) = out_q.device_ptr_mut(s);
8414                let (pd, _g7) = out_d.device_ptr_mut(s);
8415                let mut ps = [
8416                    &pa as *const _ as *mut std::ffi::c_void,
8417                    &pwa as *const _ as *mut _,
8418                    &pb as *const _ as *mut _,
8419                    &pw as *const _ as *mut _,
8420                    &pr as *const _ as *mut _,
8421                    &pdst as *const _ as *mut _,
8422                    &pq as *const _ as *mut _,
8423                    &pd as *const _ as *mut _,
8424                    &nc as *const _ as *mut _,
8425                    &e as *const _ as *mut _,
8426                ];
8427                unsafe {
8428                    self.launch_pdl(
8429                        "rms_pre_add_rms_norm_q8z_f32",
8430                        (nrows as u32, 1, 1),
8431                        (rms_block(), 1, 1),
8432                        &mut ps,
8433                    )?;
8434                }
8435            }
8436            return Ok((out_q, out_d));
8437        }
8438        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8439        let cfg = LaunchConfig {
8440            grid_dim: (nrows as u32, 1, 1),
8441            block_dim: (rms_block(), 1, 1),
8442            shared_mem_bytes: 0,
8443        };
8444        let __s_b2 = self.gpu.stream();
8445        let mut b2 = __s_b2.launch_builder(&f);
8446        b2.arg(a)
8447            .arg(wa)
8448            .arg(b)
8449            .arg(w)
8450            .arg(&mut *res)
8451            .arg(&mut *dst)
8452            .arg(&mut out_q)
8453            .arg(&mut out_d)
8454            .arg(&nc)
8455            .arg(&e);
8456        unsafe {
8457            b2.launch(cfg)?;
8458        }
8459        Ok((out_q, out_d))
8460    }
8461
8462    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8463    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8464    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8465    pub fn build_q4_out_concat3(
8466        &self,
8467        w0: &crate::model::GpuTensor,
8468        w1: &crate::model::GpuTensor,
8469        w2: &crate::model::GpuTensor,
8470    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8471        use crate::model::GpuTensor;
8472        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8473            match w {
8474                GpuTensor::Quant {
8475                    qtype,
8476                    row_bytes,
8477                    rp,
8478                    ..
8479                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8480                _ => None,
8481            }
8482        };
8483        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8484        else {
8485            return Ok(None);
8486        };
8487        if rb0 != rb1
8488            || rb0 != rb2
8489            || w0.in_features() != w1.in_features()
8490            || w0.in_features() != w2.in_features()
8491        {
8492            return Ok(None);
8493        }
8494        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8495            match w {
8496                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8497                _ => unreachable!(),
8498            }
8499        }
8500        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8501        let total = rb0 * (o0 + o1 + o2);
8502        let mut cat = self.alloc_u8(total)?;
8503        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8504        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8505        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8506        Ok(Some(GpuTensor::Quant {
8507            bytes: cat,
8508            qtype: QT_Q4_0,
8509            row_bytes: rb0,
8510            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8511            scale: 1.0,
8512            rp: false,
8513            #[cfg(memra_cutlass)]
8514            cutlass: None,
8515            fp8: None,
8516            blk: None,
8517            rp4: None,
8518            f16: None,
8519        }))
8520    }
8521
8522    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8523    #[allow(clippy::too_many_arguments)]
8524    pub fn rms_norm_qkv_rope_cat(
8525        &self,
8526        qkv: &CudaSlice<f32>,
8527        wq: &CudaSlice<f32>,
8528        wk: &CudaSlice<f32>,
8529        wv: &CudaSlice<f32>,
8530        q: &mut CudaSlice<f32>,
8531        k: &mut CudaSlice<f32>,
8532        v: &mut CudaSlice<f32>,
8533        head_dim: usize,
8534        rq: usize,
8535        rk: usize,
8536        pos: &CudaSlice<i32>,
8537        nh_q: usize,
8538        nh_k: usize,
8539        base: f32,
8540        freq_scale: f32,
8541        ff: Option<&CudaSlice<f32>>,
8542        eps: f32,
8543    ) -> Result<(), Box<dyn std::error::Error>> {
8544        let rows = rq + rk + rk;
8545        let theta_scale = base.powf(-2.0 / head_dim as f32);
8546        let (nc, rqi, rki, nhq, nhk) = (
8547            head_dim as i32,
8548            rq as i32,
8549            rk as i32,
8550            nh_q as i32,
8551            nh_k as i32,
8552        );
8553        if Self::pdl_on() {
8554            use cudarc::driver::{DevicePtr, DevicePtrMut};
8555            let s = &self.gpu.stream();
8556            let (pqkv, _g0) = qkv.device_ptr(s);
8557            let (pwq, _g1) = wq.device_ptr(s);
8558            let (pwk, _g2) = wk.device_ptr(s);
8559            let (pwv, _g3) = wv.device_ptr(s);
8560            let (pq, _g4) = q.device_ptr_mut(s);
8561            let (pk, _g5) = k.device_ptr_mut(s);
8562            let (pv, _g6) = v.device_ptr_mut(s);
8563            let (ppos, _g7) = pos.device_ptr(s);
8564            let (pff, _g8) = match ff {
8565                Some(t) => {
8566                    let (p, g) = t.device_ptr(s);
8567                    (p, Some(g))
8568                }
8569                None => (0, None),
8570            };
8571            let mut ps = [
8572                &pqkv as *const _ as *mut std::ffi::c_void,
8573                &pwq as *const _ as *mut _,
8574                &pwk as *const _ as *mut _,
8575                &pwv as *const _ as *mut _,
8576                &pq as *const _ as *mut _,
8577                &pk as *const _ as *mut _,
8578                &pv as *const _ as *mut _,
8579                &nc as *const _ as *mut _,
8580                &rqi as *const _ as *mut _,
8581                &rki as *const _ as *mut _,
8582                &ppos as *const _ as *mut _,
8583                &nhq as *const _ as *mut _,
8584                &nhk as *const _ as *mut _,
8585                &theta_scale as *const _ as *mut _,
8586                &freq_scale as *const _ as *mut _,
8587                &pff as *const _ as *mut _,
8588                &eps as *const _ as *mut _,
8589            ];
8590            unsafe {
8591                self.launch_pdl(
8592                    "rms_norm_qkv_rope_cat_f32",
8593                    (rows as u32, 1, 1),
8594                    (rms_block(), 1, 1),
8595                    &mut ps,
8596                )?;
8597            }
8598            return Ok(());
8599        }
8600        let f = self.func("rms_norm_qkv_rope_cat_f32");
8601        let cfg = LaunchConfig {
8602            grid_dim: (rows as u32, 1, 1),
8603            block_dim: (rms_block(), 1, 1),
8604            shared_mem_bytes: 0,
8605        };
8606        let __s_b = self.gpu.stream();
8607        let mut b = __s_b.launch_builder(&f);
8608        match ff {
8609            Some(t) => {
8610                b.arg(qkv)
8611                    .arg(wq)
8612                    .arg(wk)
8613                    .arg(wv)
8614                    .arg(&mut *q)
8615                    .arg(&mut *k)
8616                    .arg(&mut *v)
8617                    .arg(&nc)
8618                    .arg(&rqi)
8619                    .arg(&rki)
8620                    .arg(pos)
8621                    .arg(&nhq)
8622                    .arg(&nhk)
8623                    .arg(&theta_scale)
8624                    .arg(&freq_scale)
8625                    .arg(t)
8626                    .arg(&eps);
8627                unsafe {
8628                    b.launch(cfg)?;
8629                }
8630            }
8631            None => {
8632                let null: u64 = 0;
8633                b.arg(qkv)
8634                    .arg(wq)
8635                    .arg(wk)
8636                    .arg(wv)
8637                    .arg(&mut *q)
8638                    .arg(&mut *k)
8639                    .arg(&mut *v)
8640                    .arg(&nc)
8641                    .arg(&rqi)
8642                    .arg(&rki)
8643                    .arg(pos)
8644                    .arg(&nhq)
8645                    .arg(&nhk)
8646                    .arg(&theta_scale)
8647                    .arg(&freq_scale)
8648                    .arg(&null)
8649                    .arg(&eps);
8650                unsafe {
8651                    b.launch(cfg)?;
8652                }
8653            }
8654        }
8655        Ok(())
8656    }
8657
8658    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8659    #[allow(clippy::too_many_arguments)]
8660    pub fn rms_norm_qkv_rope(
8661        &self,
8662        q0: &CudaSlice<f32>,
8663        k0: &CudaSlice<f32>,
8664        v0: &CudaSlice<f32>,
8665        wq: &CudaSlice<f32>,
8666        wk: &CudaSlice<f32>,
8667        wv: &CudaSlice<f32>,
8668        q: &mut CudaSlice<f32>,
8669        k: &mut CudaSlice<f32>,
8670        v: &mut CudaSlice<f32>,
8671        head_dim: usize,
8672        rq: usize,
8673        rk: usize,
8674        pos: &CudaSlice<i32>,
8675        nh_q: usize,
8676        nh_k: usize,
8677        base: f32,
8678        freq_scale: f32,
8679        ff: Option<&CudaSlice<f32>>,
8680        eps: f32,
8681    ) -> Result<(), Box<dyn std::error::Error>> {
8682        let f = self.func("rms_norm_qkv_rope_f32");
8683        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
8684        let cfg = LaunchConfig {
8685            grid_dim: (rows as u32, 1, 1),
8686            block_dim: (rms_block(), 1, 1),
8687            shared_mem_bytes: 0,
8688        };
8689        let theta_scale = base.powf(-2.0 / head_dim as f32);
8690        let (nc, rqi, rki, nhq, nhk) = (
8691            head_dim as i32,
8692            rq as i32,
8693            rk as i32,
8694            nh_q as i32,
8695            nh_k as i32,
8696        );
8697        let __s_b = self.gpu.stream();
8698        let mut b = __s_b.launch_builder(&f);
8699        match ff {
8700            Some(t) => {
8701                b.arg(q0)
8702                    .arg(k0)
8703                    .arg(v0)
8704                    .arg(wq)
8705                    .arg(wk)
8706                    .arg(wv)
8707                    .arg(&mut *q)
8708                    .arg(&mut *k)
8709                    .arg(&mut *v)
8710                    .arg(&nc)
8711                    .arg(&rqi)
8712                    .arg(&rki)
8713                    .arg(pos)
8714                    .arg(&nhq)
8715                    .arg(&nhk)
8716                    .arg(&theta_scale)
8717                    .arg(&freq_scale)
8718                    .arg(t)
8719                    .arg(&eps);
8720                unsafe {
8721                    b.launch(cfg)?;
8722                }
8723            }
8724            None => {
8725                let null: u64 = 0;
8726                b.arg(q0)
8727                    .arg(k0)
8728                    .arg(v0)
8729                    .arg(wq)
8730                    .arg(wk)
8731                    .arg(wv)
8732                    .arg(&mut *q)
8733                    .arg(&mut *k)
8734                    .arg(&mut *v)
8735                    .arg(&nc)
8736                    .arg(&rqi)
8737                    .arg(&rki)
8738                    .arg(pos)
8739                    .arg(&nhq)
8740                    .arg(&nhk)
8741                    .arg(&theta_scale)
8742                    .arg(&freq_scale)
8743                    .arg(&null)
8744                    .arg(&eps);
8745                unsafe {
8746                    b.launch(cfg)?;
8747                }
8748            }
8749        }
8750        Ok(())
8751    }
8752
8753    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
8754    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
8755    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
8756    #[allow(clippy::too_many_arguments)]
8757    pub fn rms_norm_qkv_rope_append_dc(
8758        &self,
8759        q0: &CudaSlice<f32>,
8760        k0: &CudaSlice<f32>,
8761        v0: &CudaSlice<f32>,
8762        wq: &CudaSlice<f32>,
8763        wk: &CudaSlice<f32>,
8764        wv: &CudaSlice<f32>,
8765        q: &mut CudaSlice<f32>,
8766        k: &mut CudaSlice<f32>,
8767        v: &mut CudaSlice<f32>,
8768        head_dim: usize,
8769        rq: usize,
8770        rk: usize,
8771        pos: &CudaSlice<i32>,
8772        nh_q: usize,
8773        nh_k: usize,
8774        base: f32,
8775        freq_scale: f32,
8776        ff: Option<&CudaSlice<f32>>,
8777        eps: f32,
8778        kc: &mut CudaSlice<u8>,
8779        vc: &mut CudaSlice<u8>,
8780        t_dev: &CudaSlice<i32>,
8781        k_tok_bytes: usize,
8782        v_tok_bytes: usize,
8783        g: bool,
8784    ) -> Result<(), Box<dyn std::error::Error>> {
8785        let rows = rq + rk + rk;
8786        let theta_scale = base.powf(-2.0 / head_dim as f32);
8787        let (nc, rqi, rki, nhq, nhk) = (
8788            head_dim as i32,
8789            rq as i32,
8790            rk as i32,
8791            nh_q as i32,
8792            nh_k as i32,
8793        );
8794        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
8795        if Self::pdl_on() && Self::pdl_wb_on() {
8796            use cudarc::driver::{DevicePtr, DevicePtrMut};
8797            let s = &self.gpu.stream();
8798            let (p0, _a0) = q0.device_ptr(s);
8799            let (p1, _a1) = k0.device_ptr(s);
8800            let (p2, _a2) = v0.device_ptr(s);
8801            let (pwq, _a3) = wq.device_ptr(s);
8802            let (pwk, _a4) = wk.device_ptr(s);
8803            let (pwv, _a5) = wv.device_ptr(s);
8804            let (pq, _a6) = q.device_ptr_mut(s);
8805            let (pk, _a7) = k.device_ptr_mut(s);
8806            let (pv, _a8) = v.device_ptr_mut(s);
8807            let (pp, _a9) = pos.device_ptr(s);
8808            let pff: u64 = match ff {
8809                Some(t) => {
8810                    let (p, _gg) = t.device_ptr(s);
8811                    p as u64
8812                }
8813                None => 0,
8814            };
8815            let (pkc, _a10) = kc.device_ptr_mut(s);
8816            let (pvc, _a11) = vc.device_ptr_mut(s);
8817            let (pt, _a12) = t_dev.device_ptr(s);
8818            let mut ps = [
8819                &p0 as *const _ as *mut std::ffi::c_void,
8820                &p1 as *const _ as *mut _,
8821                &p2 as *const _ as *mut _,
8822                &pwq as *const _ as *mut _,
8823                &pwk as *const _ as *mut _,
8824                &pwv as *const _ as *mut _,
8825                &pq as *const _ as *mut _,
8826                &pk as *const _ as *mut _,
8827                &pv as *const _ as *mut _,
8828                &nc as *const _ as *mut _,
8829                &rqi as *const _ as *mut _,
8830                &rki as *const _ as *mut _,
8831                &pp as *const _ as *mut _,
8832                &nhq as *const _ as *mut _,
8833                &nhk as *const _ as *mut _,
8834                &theta_scale as *const _ as *mut _,
8835                &freq_scale as *const _ as *mut _,
8836                &pff as *const _ as *mut _,
8837                &eps as *const _ as *mut _,
8838                &pkc as *const _ as *mut _,
8839                &pvc as *const _ as *mut _,
8840                &pt as *const _ as *mut _,
8841                &ktb as *const _ as *mut _,
8842                &vtb as *const _ as *mut _,
8843            ];
8844            unsafe {
8845                self.launch_pdl_flash(
8846                    g,
8847                    "rms_norm_qkv_rope_append_dc_f32",
8848                    (rows as u32, 1, 1),
8849                    (rms_block(), 1, 1),
8850                    0,
8851                    &mut ps,
8852                )?;
8853            }
8854            return Ok(());
8855        }
8856        let f = if g {
8857            self.func_g("rms_norm_qkv_rope_append_dc_f32")
8858        } else {
8859            self.func("rms_norm_qkv_rope_append_dc_f32")
8860        };
8861        let cfg = LaunchConfig {
8862            grid_dim: (rows as u32, 1, 1),
8863            block_dim: (rms_block(), 1, 1),
8864            shared_mem_bytes: 0,
8865        };
8866        let __s_b = self.gpu.stream();
8867        let mut b = __s_b.launch_builder(&f);
8868        match ff {
8869            Some(t) => {
8870                b.arg(q0)
8871                    .arg(k0)
8872                    .arg(v0)
8873                    .arg(wq)
8874                    .arg(wk)
8875                    .arg(wv)
8876                    .arg(&mut *q)
8877                    .arg(&mut *k)
8878                    .arg(&mut *v)
8879                    .arg(&nc)
8880                    .arg(&rqi)
8881                    .arg(&rki)
8882                    .arg(pos)
8883                    .arg(&nhq)
8884                    .arg(&nhk)
8885                    .arg(&theta_scale)
8886                    .arg(&freq_scale)
8887                    .arg(t)
8888                    .arg(&eps)
8889                    .arg(&mut *kc)
8890                    .arg(&mut *vc)
8891                    .arg(t_dev)
8892                    .arg(&ktb)
8893                    .arg(&vtb);
8894                unsafe {
8895                    b.launch(cfg)?;
8896                }
8897            }
8898            None => {
8899                let null: u64 = 0;
8900                b.arg(q0)
8901                    .arg(k0)
8902                    .arg(v0)
8903                    .arg(wq)
8904                    .arg(wk)
8905                    .arg(wv)
8906                    .arg(&mut *q)
8907                    .arg(&mut *k)
8908                    .arg(&mut *v)
8909                    .arg(&nc)
8910                    .arg(&rqi)
8911                    .arg(&rki)
8912                    .arg(pos)
8913                    .arg(&nhq)
8914                    .arg(&nhk)
8915                    .arg(&theta_scale)
8916                    .arg(&freq_scale)
8917                    .arg(&null)
8918                    .arg(&eps)
8919                    .arg(&mut *kc)
8920                    .arg(&mut *vc)
8921                    .arg(t_dev)
8922                    .arg(&ktb)
8923                    .arg(&vtb);
8924                unsafe {
8925                    b.launch(cfg)?;
8926                }
8927            }
8928        }
8929        Ok(())
8930    }
8931
8932    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
8933    pub fn add_q8_1(
8934        &self,
8935        a: &CudaSlice<f32>,
8936        b: &CudaSlice<f32>,
8937        res: &mut CudaSlice<f32>,
8938        ncols: usize,
8939        nrows: usize,
8940    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8941        debug_assert!(ncols % 128 == 0);
8942        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8943        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8944        let f = self.func("add_q8_1_f32");
8945        let cfg = LaunchConfig {
8946            grid_dim: (nrows as u32, 1, 1),
8947            block_dim: (rms_block(), 1, 1),
8948            shared_mem_bytes: 0,
8949        };
8950        let nc = ncols as i32;
8951        let __s_b2 = self.gpu.stream();
8952        let mut b2 = __s_b2.launch_builder(&f);
8953        b2.arg(a)
8954            .arg(b)
8955            .arg(&mut *res)
8956            .arg(&mut out_q)
8957            .arg(&mut out_d)
8958            .arg(&nc);
8959        unsafe {
8960            b2.launch(cfg)?;
8961        }
8962        Ok((out_q, out_d))
8963    }
8964
8965    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
8966    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
8967    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
8968    pub fn rms_pre_add_q8_1(
8969        &self,
8970        a: &CudaSlice<f32>,
8971        wa: &CudaSlice<f32>,
8972        b: &CudaSlice<f32>,
8973        res: &mut CudaSlice<f32>,
8974        ncols: usize,
8975        nrows: usize,
8976        eps: f32,
8977    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8978        debug_assert!(ncols % 128 == 0);
8979        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8980        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8981        let f = self.func("rms_pre_add_q8_1_f32");
8982        let cfg = LaunchConfig {
8983            grid_dim: (nrows as u32, 1, 1),
8984            block_dim: (rms_block(), 1, 1),
8985            shared_mem_bytes: 0,
8986        };
8987        let (nc, ep) = (ncols as i32, eps);
8988        let __s_b2 = self.gpu.stream();
8989        let mut b2 = __s_b2.launch_builder(&f);
8990        b2.arg(a)
8991            .arg(wa)
8992            .arg(b)
8993            .arg(&mut *res)
8994            .arg(&mut out_q)
8995            .arg(&mut out_d)
8996            .arg(&nc)
8997            .arg(&ep);
8998        unsafe {
8999            b2.launch(cfg)?;
9000        }
9001        Ok((out_q, out_d))
9002    }
9003
9004    /// L2 norm per row (head_dim), no weight.
9005    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
9006    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
9007    pub fn l2_v2_on(ncols: usize) -> bool {
9008        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
9009    }
9010
9011    pub fn l2_norm_pp(
9012        &self,
9013        x: &CudaSlice<f32>,
9014        dst: &mut CudaSlice<f32>,
9015        dst16: Option<&mut CudaSlice<u8>>,
9016        ncols: usize,
9017        nrows: usize,
9018        eps: f32,
9019    ) -> Result<(), Box<dyn std::error::Error>> {
9020        if Self::l2_v2_on(ncols) {
9021            let f = self.func("l2_norm_pp_v2_f32");
9022            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
9023            let cfg = LaunchConfig {
9024                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
9025                block_dim: (256, 1, 1),
9026                shared_mem_bytes: 0,
9027            };
9028            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
9029            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
9030            let d16: u64 = match dst16 {
9031                Some(d) => self.addr_u8(d),
9032                None => 0,
9033            };
9034            let __s_b = self.gpu.stream();
9035            let mut b = __s_b.launch_builder(&f);
9036            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
9037            unsafe {
9038                b.launch(cfg)?;
9039            }
9040            return Ok(());
9041        }
9042        self.l2_norm(x, dst, ncols, nrows, eps)
9043    }
9044
9045    pub fn l2_norm(
9046        &self,
9047        x: &CudaSlice<f32>,
9048        dst: &mut CudaSlice<f32>,
9049        ncols: usize,
9050        nrows: usize,
9051        eps: f32,
9052    ) -> Result<(), Box<dyn std::error::Error>> {
9053        let f = self.func("l2_norm_f32");
9054        let cfg = LaunchConfig {
9055            grid_dim: (nrows as u32, 1, 1),
9056            block_dim: (256, 1, 1),
9057            shared_mem_bytes: 0,
9058        };
9059        let (nc, e) = (ncols as i32, eps);
9060        let __s_b = self.gpu.stream();
9061        let mut b = __s_b.launch_builder(&f);
9062        b.arg(x).arg(dst).arg(&nc).arg(&e);
9063        unsafe {
9064            b.launch(cfg)?;
9065        }
9066        Ok(())
9067    }
9068
9069    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9070    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9071    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9072    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9073    /// propagate through gdn_scan and flip argmax on marginal logits.
9074    pub fn l2_norm_decode(
9075        &self,
9076        x: &CudaSlice<f32>,
9077        dst: &mut CudaSlice<f32>,
9078        ncols: usize,
9079        nrows: usize,
9080        eps: f32,
9081    ) -> Result<(), Box<dyn std::error::Error>> {
9082        let f = self.func("l2_norm_f32");
9083        let cfg = LaunchConfig {
9084            grid_dim: (nrows as u32, 1, 1),
9085            block_dim: (32, 1, 1),
9086            shared_mem_bytes: 0,
9087        };
9088        let (nc, e) = (ncols as i32, eps);
9089        let __s_b = self.gpu.stream();
9090        let mut b = __s_b.launch_builder(&f);
9091        b.arg(x).arg(dst).arg(&nc).arg(&e);
9092        unsafe {
9093            b.launch(cfg)?;
9094        }
9095        Ok(())
9096    }
9097
9098    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9099    pub fn rope_neox(
9100        &self,
9101        x: &mut CudaSlice<f32>,
9102        pos: &CudaSlice<i32>,
9103        head_dim: usize,
9104        n_dims: usize,
9105        n_heads: usize,
9106        n_tokens: usize,
9107        freq_base: f32,
9108        freq_scale: f32,
9109    ) -> Result<(), Box<dyn std::error::Error>> {
9110        let f = self.func("rope_neox_f32");
9111        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9112        let grid = (n_heads * n_tokens) as u32;
9113        let cfg = LaunchConfig {
9114            grid_dim: (grid, 1, 1),
9115            block_dim: ((head_dim / 2) as u32, 1, 1),
9116            shared_mem_bytes: 0,
9117        };
9118        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9119        let __s_b = self.gpu.stream();
9120        let mut b = __s_b.launch_builder(&f);
9121        b.arg(x)
9122            .arg(pos)
9123            .arg(&hd)
9124            .arg(&nd)
9125            .arg(&nh)
9126            .arg(&theta_scale)
9127            .arg(&freq_scale);
9128        unsafe {
9129            b.launch(cfg)?;
9130        }
9131        Ok(())
9132    }
9133
9134    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9135    pub fn rope_neox_ff(
9136        &self,
9137        x: &mut CudaSlice<f32>,
9138        pos: &CudaSlice<i32>,
9139        head_dim: usize,
9140        n_dims: usize,
9141        n_heads: usize,
9142        n_tokens: usize,
9143        freq_base: f32,
9144        freq_scale: f32,
9145        ff: &CudaSlice<f32>,
9146    ) -> Result<(), Box<dyn std::error::Error>> {
9147        let f = self.func("rope_neox_ff_f32");
9148        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9149        let grid = (n_heads * n_tokens) as u32;
9150        let cfg = LaunchConfig {
9151            grid_dim: (grid, 1, 1),
9152            block_dim: ((head_dim / 2) as u32, 1, 1),
9153            shared_mem_bytes: 0,
9154        };
9155        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9156        let __s_b = self.gpu.stream();
9157        let mut b = __s_b.launch_builder(&f);
9158        b.arg(x)
9159            .arg(pos)
9160            .arg(&hd)
9161            .arg(&nd)
9162            .arg(&nh)
9163            .arg(&theta_scale)
9164            .arg(&freq_scale)
9165            .arg(ff);
9166        unsafe {
9167            b.launch(cfg)?;
9168        }
9169        Ok(())
9170    }
9171
9172    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9173    #[allow(clippy::too_many_arguments)]
9174    pub fn rope_neox2(
9175        &self,
9176        q: &mut CudaSlice<f32>,
9177        k: &mut CudaSlice<f32>,
9178        pos: &CudaSlice<i32>,
9179        head_dim: usize,
9180        n_dims: usize,
9181        nh_q: usize,
9182        nh_k: usize,
9183        n_tokens: usize,
9184        freq_base: f32,
9185        freq_scale: f32,
9186        ff: Option<&CudaSlice<f32>>,
9187    ) -> Result<(), Box<dyn std::error::Error>> {
9188        let f = self.func("rope_neox2_f32");
9189        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9190        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9191        let cfg = LaunchConfig {
9192            grid_dim: (grid, 1, 1),
9193            block_dim: ((head_dim / 2) as u32, 1, 1),
9194            shared_mem_bytes: 0,
9195        };
9196        let (hd, nd, nq, nk, nt) = (
9197            head_dim as i32,
9198            n_dims as i32,
9199            nh_q as i32,
9200            nh_k as i32,
9201            n_tokens as i32,
9202        );
9203        let __s_b = self.gpu.stream();
9204        let mut b = __s_b.launch_builder(&f);
9205        b.arg(q)
9206            .arg(k)
9207            .arg(pos)
9208            .arg(&hd)
9209            .arg(&nd)
9210            .arg(&nq)
9211            .arg(&nk)
9212            .arg(&nt)
9213            .arg(&theta_scale)
9214            .arg(&freq_scale);
9215        match ff {
9216            Some(ffv) => {
9217                b.arg(ffv);
9218                unsafe {
9219                    b.launch(cfg)?;
9220                }
9221            }
9222            None => {
9223                let null: u64 = 0;
9224                b.arg(&null);
9225                unsafe {
9226                    b.launch(cfg)?;
9227                }
9228            }
9229        }
9230        Ok(())
9231    }
9232
9233    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9234    pub fn gelu_tanh_mul(
9235        &self,
9236        gate: &CudaSlice<f32>,
9237        up: &CudaSlice<f32>,
9238        dst: &mut CudaSlice<f32>,
9239        n: usize,
9240    ) -> Result<(), Box<dyn std::error::Error>> {
9241        let f = self.func("gelu_tanh_mul_f32");
9242        let cfg = LaunchConfig::for_num_elems(n as u32);
9243        let ni = n as i32;
9244        let __s_b = self.gpu.stream();
9245        let mut b = __s_b.launch_builder(&f);
9246        b.arg(gate).arg(up).arg(dst).arg(&ni);
9247        unsafe {
9248            b.launch(cfg)?;
9249        }
9250        Ok(())
9251    }
9252
9253    pub fn silu_mul(
9254        &self,
9255        gate: &CudaSlice<f32>,
9256        up: &CudaSlice<f32>,
9257        dst: &mut CudaSlice<f32>,
9258        n: usize,
9259    ) -> Result<(), Box<dyn std::error::Error>> {
9260        let f = self.func("silu_mul_f32");
9261        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9262        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9263        let ni = n as i32;
9264        let __s_b = self.gpu.stream();
9265        let mut b = __s_b.launch_builder(&f);
9266        b.arg(gate).arg(up).arg(dst).arg(&ni);
9267        unsafe {
9268            b.launch(cfg)?;
9269        }
9270        Ok(())
9271    }
9272
9273    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9274    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9275    pub fn silu_mul_f16out(
9276        &self,
9277        gate: &CudaSlice<f32>,
9278        up: &CudaSlice<f32>,
9279        dst: &mut CudaSlice<f32>,
9280        dst16: &mut CudaSlice<u8>,
9281        n: usize,
9282    ) -> Result<(), Box<dyn std::error::Error>> {
9283        let f = self.func("silu_mul_f16out_f32");
9284        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9285        let ni = n as i32;
9286        let __s_b = self.gpu.stream();
9287        let mut b = __s_b.launch_builder(&f);
9288        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9289        unsafe {
9290            b.launch(cfg)?;
9291        }
9292        Ok(())
9293    }
9294
9295    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9296    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9297    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9298    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9299    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9300    /// launches per dense FFN layer (the gate+up post-matmul scales).
9301    pub fn silu_mul_scaled(
9302        &self,
9303        gate: &CudaSlice<f32>,
9304        up: &CudaSlice<f32>,
9305        gs: f32,
9306        us: f32,
9307        dst: &mut CudaSlice<f32>,
9308        n: usize,
9309    ) -> Result<(), Box<dyn std::error::Error>> {
9310        let f = self.func("silu_mul_scaled_f32");
9311        let cfg = LaunchConfig::for_num_elems(n as u32);
9312        let ni = n as i32;
9313        let (gsf, usf) = (gs, us);
9314        let __s_b = self.gpu.stream();
9315        let mut b = __s_b.launch_builder(&f);
9316        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9317        unsafe {
9318            b.launch(cfg)?;
9319        }
9320        Ok(())
9321    }
9322
9323    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9324    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9325    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9326    #[allow(clippy::too_many_arguments)]
9327    pub fn swigluoai_mul_scaled(
9328        &self,
9329        gate: &CudaSlice<f32>,
9330        up: &CudaSlice<f32>,
9331        gs: f32,
9332        us: f32,
9333        alpha: f32,
9334        limit: f32,
9335        dst: &mut CudaSlice<f32>,
9336        n: usize,
9337    ) -> Result<(), Box<dyn std::error::Error>> {
9338        let f = self.func("swigluoai_mul_scaled_f32");
9339        let cfg = LaunchConfig::for_num_elems(n as u32);
9340        let ni = n as i32;
9341        let __s_b = self.gpu.stream();
9342        let mut b = __s_b.launch_builder(&f);
9343        b.arg(gate)
9344            .arg(up)
9345            .arg(&gs)
9346            .arg(&us)
9347            .arg(&alpha)
9348            .arg(&limit)
9349            .arg(dst)
9350            .arg(&ni);
9351        unsafe {
9352            b.launch(cfg)?;
9353        }
9354        Ok(())
9355    }
9356
9357    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9358    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9359    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9360    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9361    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9362    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9363    /// n must be a multiple of 32 (n_ff always is).
9364    pub fn silu_mul_scaled_q8_1(
9365        &self,
9366        gate: &CudaSlice<f32>,
9367        up: &CudaSlice<f32>,
9368        gs: f32,
9369        us: f32,
9370        n: usize,
9371    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9372        let f = self.func("silu_mul_scaled_q8_1");
9373        let nblk = n / 32;
9374        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9375        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9376        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9377        let cfg = LaunchConfig::for_num_elems(n as u32);
9378        let (gsf, usf, ni) = (gs, us, n as i32);
9379        let __s_b = self.gpu.stream();
9380        let mut b = __s_b.launch_builder(&f);
9381        b.arg(gate)
9382            .arg(up)
9383            .arg(&gsf)
9384            .arg(&usf)
9385            .arg(&mut aq)
9386            .arg(&mut ad)
9387            .arg(&ni);
9388        unsafe {
9389            b.launch(cfg)?;
9390        }
9391        Ok((aq, ad))
9392    }
9393
9394    pub fn add(
9395        &self,
9396        a: &CudaSlice<f32>,
9397        b_in: &CudaSlice<f32>,
9398        dst: &mut CudaSlice<f32>,
9399        n: usize,
9400    ) -> Result<(), Box<dyn std::error::Error>> {
9401        let f = self.func("add_f32");
9402        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9403        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9404        let ni = n as i32;
9405        let __s_bld = self.gpu.stream();
9406        let mut bld = __s_bld.launch_builder(&f);
9407        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9408        unsafe {
9409            bld.launch(cfg)?;
9410        }
9411        Ok(())
9412    }
9413
9414    pub fn mul(
9415        &self,
9416        a: &CudaSlice<f32>,
9417        b_in: &CudaSlice<f32>,
9418        dst: &mut CudaSlice<f32>,
9419        n: usize,
9420    ) -> Result<(), Box<dyn std::error::Error>> {
9421        let f = self.func("mul_f32");
9422        let cfg = LaunchConfig::for_num_elems(n as u32);
9423        let ni = n as i32;
9424        let __s_bld = self.gpu.stream();
9425        let mut bld = __s_bld.launch_builder(&f);
9426        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9427        unsafe {
9428            bld.launch(cfg)?;
9429        }
9430        Ok(())
9431    }
9432
9433    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9434    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9435    pub fn matmul(
9436        &self,
9437        w: &crate::model::GpuTensor,
9438        x: &CudaSlice<f32>,
9439        m: usize,
9440    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9441        use crate::model::GpuTensor;
9442        let in_f = w.in_features();
9443        let out_f = w.out_features();
9444        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9445        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9446        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9447        // gives nothing). Quantize the activation once here then call the GEMM.
9448        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9449        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9450        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9451        #[allow(non_snake_case)]
9452        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9453        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9454        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9455            usize::MAX
9456        } else {
9457            16usize
9458        };
9459
9460        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9461        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9462        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9463        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9464        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9465        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9466        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9467        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9468        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9469        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9470        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9471        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9472        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9473        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9474        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9475        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9476        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9477        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9478        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9479        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9480        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9481        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9482        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9483        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9484        if m >= GEMM_M_THRESHOLD {
9485            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9486                return Ok(y);
9487            }
9488            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9489            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9490            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9491            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9492            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9493            // tile defaults differently by operand source.
9494            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9495                return Ok(y);
9496            }
9497            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
9498            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
9499            if let Some(y) = self.try_f16_gemm(w, x, m)? {
9500                return Ok(y);
9501            }
9502        }
9503        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
9504        // m threshold the rest of this method uses:
9505        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
9506        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
9507        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
9508        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
9509        //     across every tier by construction with no batched twin needed.
9510        //
9511        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
9512        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
9513        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
9514        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
9515        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
9516        // arms is what makes sure it never gets there.
9517        if let GpuTensor::Quant { qtype, .. } = w {
9518            if *qtype == QT_F8_E4M3_BLK {
9519                if m >= GEMM_M_THRESHOLD {
9520                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
9521                        return Ok(y);
9522                    }
9523                }
9524                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9525                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
9526                    return Ok(y);
9527                }
9528            }
9529        }
9530        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
9531            return self.qmatvec_mmq(w, x, m);
9532        }
9533        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
9534            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9535            return self.qmatvec_gemm(w, &aq, &ad, m);
9536        }
9537        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
9538        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
9539        if m >= GEMM_M_THRESHOLD {
9540            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
9541                return Ok(y);
9542            }
9543        }
9544        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
9545        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
9546        // to Stage-A f32-dequant (the correctness oracle path).
9547        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
9548        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
9549        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
9550        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
9551        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
9552        if m == 1 && fast {
9553            if let GpuTensor::Quant {
9554                bytes,
9555                qtype,
9556                row_bytes,
9557                rp,
9558                rp4,
9559                scale,
9560                ..
9561            } = w
9562            {
9563                if self.mmvq_supports(*qtype) {
9564                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
9565                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
9566                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
9567                    let (bytes, rp) = match rp4 {
9568                        Some(m4) => (m4, true),
9569                        None => (bytes, *rp),
9570                    };
9571                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9572                    return self.qmatvec_mmvq(
9573                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
9574                    );
9575                }
9576            }
9577        }
9578        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
9579        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
9580        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
9581        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
9582        // block below. MEMRA_NO_BATCHED -> per-m path.
9583        //
9584        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
9585        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
9586        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
9587        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
9588        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
9589        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
9590        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
9591        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
9592        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
9593        if (2..=16).contains(&m)
9594            && fast
9595            && std::env::var("MEMRA_NO_BATCHED").is_err()
9596            && (m <= 4 || Self::b8_enabled())
9597        {
9598            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
9599            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
9600            // is present (rp4) — the mirror pick below then routes to the _rp family.
9601            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
9602            // because the native e4m3 row layout is already aligned and needs no mirror.
9603            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
9604            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
9605            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
9606            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
9607            let m_ok = m <= 8
9608                || matches!(w, GpuTensor::Quant { qtype, .. }
9609                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
9610                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
9611            if m_ok {
9612                if let GpuTensor::Quant {
9613                    bytes,
9614                    qtype,
9615                    row_bytes,
9616                    rp,
9617                    rp4,
9618                    ..
9619                } = w
9620                {
9621                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
9622                        let (bytes, rp) = match rp4 {
9623                            Some(m4) => (m4, true),
9624                            None => (bytes, *rp),
9625                        };
9626                        let mcols = Self::batched_mcols(m);
9627                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9628                        let mut y = self.qmatvec_mmvq_batched(
9629                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
9630                        )?;
9631                        if let GpuTensor::Quant { scale, .. } = w {
9632                            if *scale != 1.0 {
9633                                self.scale_inplace(&mut y, *scale, m * out_f)?;
9634                            }
9635                        }
9636                        return Ok(y);
9637                    }
9638                }
9639            }
9640        }
9641        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
9642        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
9643        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
9644        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
9645        // for this dtype, so the generic match below must never see it under `fast`.
9646        if fast {
9647            if let GpuTensor::Quant {
9648                bytes,
9649                qtype,
9650                row_bytes,
9651                scale,
9652                ..
9653            } = w
9654            {
9655                if *qtype == QT_F8_E4M3 {
9656                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9657                    return self.qmatvec_mmvq(
9658                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
9659                    );
9660                }
9661            }
9662        }
9663        let mut y = match w {
9664            GpuTensor::Quant {
9665                bytes,
9666                qtype,
9667                row_bytes,
9668                ..
9669            } if fast && *qtype == QT_Q8_0 => {
9670                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9671            }
9672            GpuTensor::Quant {
9673                bytes,
9674                qtype,
9675                row_bytes,
9676                ..
9677            } if fast && *qtype == QT_Q4_K => {
9678                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9679            }
9680            GpuTensor::Quant {
9681                bytes,
9682                qtype,
9683                row_bytes,
9684                ..
9685            } if fast && *qtype == QT_Q6_K => {
9686                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9687            }
9688            GpuTensor::Quant {
9689                bytes,
9690                qtype,
9691                row_bytes,
9692                ..
9693            } if fast && *qtype == QT_Q5_K => {
9694                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9695            }
9696            GpuTensor::Quant {
9697                bytes,
9698                qtype,
9699                row_bytes,
9700                ..
9701            } if fast && *qtype == QT_Q3_K => {
9702                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9703            }
9704            GpuTensor::Quant {
9705                bytes,
9706                qtype,
9707                row_bytes,
9708                rp,
9709                ..
9710            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
9711                if *rp {
9712                    "qmatvec_nvfp4_dp4a_rp"
9713                } else {
9714                    "qmatvec_nvfp4_dp4a"
9715                },
9716                bytes,
9717                x,
9718                m,
9719                in_f,
9720                out_f,
9721                *row_bytes,
9722            )?,
9723            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
9724            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
9725            // anomaly (research/kat-anomaly-20260802/).
9726            GpuTensor::Quant {
9727                bytes,
9728                qtype,
9729                row_bytes,
9730                ..
9731            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
9732                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9733            }
9734            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
9735            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
9736            // without first writing the matching kernel, or func() will panic
9737            // "kernel ... not in any fatbin".
9738            GpuTensor::Quant {
9739                bytes,
9740                qtype,
9741                row_bytes,
9742                rp,
9743                ..
9744            } =>
9745            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
9746            // deq(row,j) form cannot address the planes; same value/product order).
9747            {
9748                self.qmatvec(
9749                    bytes,
9750                    x,
9751                    m,
9752                    in_f,
9753                    out_f,
9754                    if *rp && *qtype == QT_NVFP4 {
9755                        QT_NVFP4_RP
9756                    } else {
9757                        *qtype
9758                    },
9759                    *row_bytes,
9760                )?
9761            }
9762            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
9763            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
9764            // cuBLASLt f32 GEMV as the Float arm.
9765            GpuTensor::FloatBf16 { data, .. } => {
9766                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
9767            }
9768        };
9769        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
9770        if let GpuTensor::Quant { scale, .. } = w {
9771            if *scale != 1.0 {
9772                self.scale_inplace(&mut y, *scale, m * out_f)?;
9773            }
9774        }
9775        Ok(y)
9776    }
9777
9778    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
9779    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
9780    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
9781        use crate::model::GpuTensor;
9782        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
9783            return false;
9784        }
9785        match w {
9786            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
9787            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
9788            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
9789            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
9790            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
9791            // block class has no fused twin yet, so each of its projections takes its own launch.
9792            GpuTensor::Quant { qtype, .. } => {
9793                matches!(
9794                    *qtype,
9795                    QT_Q8_0
9796                        | QT_Q4_K
9797                        | QT_Q6_K
9798                        | QT_Q5_K
9799                        | QT_Q3_K
9800                        | QT_NVFP4
9801                        | QT_F8_E4M3
9802                        | QT_F8_E4M3_BLK
9803                        | QT_Q4_0
9804                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
9805            }
9806            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
9807        }
9808    }
9809
9810    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
9811    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
9812    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
9813    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
9814    pub fn matmul_pre(
9815        &self,
9816        w: &crate::model::GpuTensor,
9817        aq: &CudaSlice<i8>,
9818        ad: &CudaSlice<f32>,
9819        x_fallback: &CudaSlice<f32>,
9820        m: usize,
9821    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9822        use crate::model::GpuTensor;
9823        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
9824        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
9825        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
9826        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
9827        // rc=30013 dig, 2026-07-31).
9828        let x_raw_ok = x_fallback.len() >= m * w.in_features();
9829        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
9830        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
9831        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9832            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
9833                return Ok(y);
9834            }
9835            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
9836            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
9837            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
9838                return Ok(y);
9839            }
9840            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
9841            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
9842                return Ok(y);
9843            }
9844        }
9845        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
9846        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
9847        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
9848        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
9849        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
9850        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9851            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
9852                return Ok(y);
9853            }
9854        }
9855        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
9856            return Ok(y);
9857        }
9858        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
9859        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
9860        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
9861        // aq/ad.
9862        if m >= 16
9863            && w.out_features() >= 128
9864            && self.mmq_supports(w)
9865            && !self.verify_exact_on()
9866            && x_raw_ok
9867        {
9868            return self.qmatvec_mmq(w, x_fallback, m);
9869        }
9870        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
9871        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
9872        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9873            if let Some(y) =
9874                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
9875            {
9876                return Ok(y);
9877            }
9878        }
9879        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
9880        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
9881        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
9882            return self.qmatvec_gemm(w, aq, ad, m);
9883        }
9884        if !self.uses_q8_1_fast(w) {
9885            return self.matmul(w, x_fallback, m);
9886        }
9887        let in_f = w.in_features();
9888        let out_f = w.out_features();
9889        let (bytes, qtype, row_bytes, scale, rp) = match w {
9890            GpuTensor::Quant {
9891                bytes,
9892                qtype,
9893                row_bytes,
9894                scale,
9895                rp,
9896                ..
9897            } => (bytes, *qtype, *row_bytes, *scale, *rp),
9898            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
9899        };
9900        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
9901        // the dp4a/oracle tails below keep the raw GGUF bytes.
9902        let (mbytes, mrp) = match w {
9903            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
9904            _ => (bytes, rp),
9905        };
9906        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
9907        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
9908        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
9909        if m == 1 && self.mmvq_supports(qtype) {
9910            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
9911        }
9912        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
9913        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
9914        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
9915        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
9916        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
9917        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
9918        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
9919        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
9920        // m=5..8 on the old per-m path (b8-tier-only seam).
9921        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
9922        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
9923        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
9924        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
9925            && std::env::var("MEMRA_NO_BATCHED").is_err()
9926            && (m <= 4 || Self::b8_enabled())
9927            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
9928            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
9929            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
9930            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
9931                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
9932        {
9933            let mcols = Self::batched_mcols(m);
9934            return self.qmatvec_mmvq_batched(
9935                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
9936            );
9937        }
9938        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
9939        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
9940        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
9941        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
9942        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
9943        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
9944            let (b2, r2) = if qtype == QT_Q4_0 {
9945                (mbytes, mrp)
9946            } else {
9947                (bytes, rp)
9948            };
9949            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
9950        }
9951        let name = match qtype {
9952            QT_Q8_0 => "qmatvec_q8_0_dp4a",
9953            QT_Q4_K => "qmatvec_q4_K_dp4a",
9954            QT_Q6_K => "qmatvec_q6_K_dp4a",
9955            QT_Q5_K => "qmatvec_q5_K_dp4a",
9956            QT_Q3_K => "qmatvec_q3_K_dp4a",
9957            QT_NVFP4 => {
9958                if rp {
9959                    "qmatvec_nvfp4_dp4a_rp"
9960                } else {
9961                    "qmatvec_nvfp4_dp4a"
9962                }
9963            }
9964            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
9965            _ => unreachable!(),
9966        };
9967        let f = self.func(name);
9968        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
9969        let cfg = LaunchConfig {
9970            grid_dim: (out_f as u32, m as u32, 1),
9971            block_dim: (128, 1, 1),
9972            shared_mem_bytes: 0,
9973        };
9974        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
9975        let __s_b = self.gpu.stream();
9976        let mut b = __s_b.launch_builder(&f);
9977        b.arg(bytes)
9978            .arg(aq)
9979            .arg(ad)
9980            .arg(&mut y)
9981            .arg(&inf)
9982            .arg(&outf)
9983            .arg(&mi)
9984            .arg(&rb);
9985        unsafe {
9986            b.launch(cfg)?;
9987        }
9988        if scale != 1.0 {
9989            self.scale_inplace(&mut y, scale, m * out_f)?;
9990        }
9991        Ok(y)
9992    }
9993
9994    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
9995    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
9996    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
9997    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
9998    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
9999    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
10000    /// reduce as m=1); this method just forces that path unconditionally.
10001    pub fn matmul_decode_exact(
10002        &self,
10003        w: &crate::model::GpuTensor,
10004        x: &CudaSlice<f32>,
10005        m: usize,
10006    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10007        use crate::model::GpuTensor;
10008        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
10009        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
10010        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
10011        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
10012        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
10013        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
10014        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
10015        if let GpuTensor::Float { data, .. } = w {
10016            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
10017        }
10018        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
10019        // float linear (same n-independent reduction contract as the Float arm above).
10020        if let GpuTensor::FloatBf16 { data, .. } = w {
10021            let (in_f, out_f) = (w.in_features(), w.out_features());
10022            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
10023        }
10024        if !self.uses_q8_1_fast(w) {
10025            return self.matmul(w, x, m);
10026        }
10027        let in_f = w.in_features();
10028        let out_f = w.out_features();
10029        let (bytes, qtype, row_bytes, scale, rp) = match w {
10030            GpuTensor::Quant {
10031                bytes,
10032                qtype,
10033                row_bytes,
10034                scale,
10035                rp,
10036                ..
10037            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10038            _ => return self.matmul(w, x, m),
10039        };
10040        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
10041        // which does its own mirror pick).
10042        let (bytes, rp) = match w {
10043            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10044            _ => (bytes, rp),
10045        };
10046        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10047        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10048        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10049        // (token,row) by construction, which is exactly what this method exists to guarantee.
10050        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10051            return Ok(y);
10052        }
10053        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10054        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10055        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10056        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10057        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10058        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10059        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10060        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10061        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10062            && std::env::var("MEMRA_NO_BATCHED").is_err()
10063            && (m <= 4 || Self::b8_enabled())
10064            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10065            // no mirror precondition, `rp` selects the layout only.
10066            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10067                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10068        {
10069            let mcols = Self::batched_mcols(m);
10070            return self.qmatvec_mmvq_batched(
10071                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10072            );
10073        }
10074        if self.mmvq_supports(qtype) {
10075            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10076            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10077            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10078        }
10079        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10080        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10081        self.matmul_pre(w, &aq, &ad, x, m)
10082    }
10083
10084    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10085    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10086    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10087    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10088    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10089    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10090    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10091    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10092    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10093    pub fn matmul_decode_exact_pre(
10094        &self,
10095        w: &crate::model::GpuTensor,
10096        aq: &CudaSlice<i8>,
10097        ad: &CudaSlice<f32>,
10098        m: usize,
10099    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10100        use crate::model::GpuTensor;
10101        debug_assert!(
10102            self.uses_q8_1_fast(w),
10103            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10104        );
10105        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10106        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10107            return Ok(y);
10108        }
10109        let in_f = w.in_features();
10110        let out_f = w.out_features();
10111        let (bytes, qtype, row_bytes, scale, rp) = match w {
10112            GpuTensor::Quant {
10113                bytes,
10114                qtype,
10115                row_bytes,
10116                scale,
10117                rp,
10118                ..
10119            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10120            _ => {
10121                return Err(
10122                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10123                );
10124            }
10125        };
10126        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10127        let (bytes, rp) = match w {
10128            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10129            _ => (bytes, rp),
10130        };
10131        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10132        if (2..=16).contains(&m)
10133            && self.batched_supports(qtype)
10134            && self.mmvq_supports(qtype)
10135            && std::env::var("MEMRA_NO_BATCHED").is_err()
10136            && (m <= 4 || Self::b8_enabled())
10137            && (m <= 8
10138                || qtype == QT_Q4_0
10139                || qtype == QT_Q6_K
10140                || qtype == QT_F8_E4M3
10141                || qtype == QT_NVFP4
10142                || qtype == QT_Q4_K
10143                || qtype == QT_Q5_K
10144                || qtype == QT_Q8_0)
10145        {
10146            let mcols = Self::batched_mcols(m);
10147            return self.qmatvec_mmvq_batched(
10148                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10149            );
10150        }
10151        if self.mmvq_supports(qtype) {
10152            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10153        }
10154        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10155        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10156        let x0 = self.zeros(0)?;
10157        self.matmul_pre(w, aq, ad, &x0, m)
10158    }
10159
10160    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10161    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10162    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10163    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10164    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10165    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10166    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10167    /// per-tensor path.
10168    pub fn matmul_decode_exact_dual_pre(
10169        &self,
10170        w0: &crate::model::GpuTensor,
10171        w1: &crate::model::GpuTensor,
10172        aq: &CudaSlice<i8>,
10173        ad: &CudaSlice<f32>,
10174        m: usize,
10175    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10176    {
10177        use crate::model::GpuTensor;
10178        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10179        let on = *ON.get_or_init(|| {
10180            std::env::var("MEMRA_SPEC_DUAL_T")
10181                .map(|v| v != "0")
10182                .unwrap_or(true)
10183        });
10184        if !on
10185            || !(2..=7).contains(&m)
10186            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10187            || !self.uses_q8_1_fast(w0)
10188            || !self.uses_q8_1_fast(w1)
10189        {
10190            return Ok(None);
10191        }
10192        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10193        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10194        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10195        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10196        if !self.mmvq_supports(QT_NVFP4) {
10197            return Ok(None);
10198        }
10199        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10200        if w1.in_features() != in_f || w1.out_features() != out_f {
10201            return Ok(None);
10202        }
10203        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10204            (
10205                GpuTensor::Quant {
10206                    bytes: b0,
10207                    qtype: q0,
10208                    row_bytes: rb0,
10209                    scale: s0,
10210                    rp: rp0,
10211                    rp4: None,
10212                    ..
10213                },
10214                GpuTensor::Quant {
10215                    bytes: b1,
10216                    qtype: q1,
10217                    row_bytes: rb1,
10218                    scale: s1,
10219                    rp: rp1,
10220                    rp4: None,
10221                    ..
10222                },
10223            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10224                (b0, b1, *rb0, *s0, *s1, *rp0)
10225            }
10226            _ => return Ok(None),
10227        };
10228        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10229        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10230        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10231        {
10232            return Ok(None);
10233        }
10234        let (y0, y1) =
10235            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10236        Ok(Some(((y0, s0), (y1, s1))))
10237    }
10238
10239    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10240    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10241    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10242    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10243    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10244    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10245    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10246    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10247    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10248    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10249    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10250    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10251    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10252    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10253    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10254    pub fn matmul_decode_exact_dual(
10255        &self,
10256        w0: &crate::model::GpuTensor,
10257        w1: &crate::model::GpuTensor,
10258        x: &CudaSlice<f32>,
10259        m: usize,
10260    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10261        use crate::model::GpuTensor;
10262        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10263        let on = *ON.get_or_init(|| {
10264            std::env::var("MEMRA_SPEC_DUAL_T")
10265                .map(|v| v != "0")
10266                .unwrap_or(true)
10267        });
10268        if !on
10269            || !(2..=4).contains(&m)
10270            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10271            || !self.uses_q8_1_fast(w0)
10272            || !self.uses_q8_1_fast(w1)
10273        {
10274            return Ok(None);
10275        }
10276        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10277        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10278        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10279        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10280        if !self.mmvq_supports(QT_NVFP4) {
10281            return Ok(None);
10282        }
10283        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10284        if w1.in_features() != in_f || w1.out_features() != out_f {
10285            return Ok(None);
10286        }
10287        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10288            (
10289                GpuTensor::Quant {
10290                    bytes: b0,
10291                    qtype: q0,
10292                    row_bytes: rb0,
10293                    scale: s0,
10294                    rp: rp0,
10295                    rp4: None,
10296                    ..
10297                },
10298                GpuTensor::Quant {
10299                    bytes: b1,
10300                    qtype: q1,
10301                    row_bytes: rb1,
10302                    scale: s1,
10303                    rp: rp1,
10304                    rp4: None,
10305                    ..
10306                },
10307            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10308                (b0, b1, *rb0, *s0, *s1, *rp0)
10309            }
10310            _ => return Ok(None),
10311        };
10312        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10313        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10314        if std::env::var("MEMRA_DEBUG").is_ok() {
10315            static ONCE: std::sync::Once = std::sync::Once::new();
10316            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10317        }
10318        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10319        let (y0, y1) =
10320            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10321        let mut y0 = y0;
10322        let mut y1 = y1;
10323        if s0 != 1.0 {
10324            self.scale_inplace(&mut y0, s0, m * out_f)?;
10325        }
10326        if s1 != 1.0 {
10327            self.scale_inplace(&mut y1, s1, m * out_f)?;
10328        }
10329        Ok(Some((y0, y1)))
10330    }
10331
10332    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10333    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10334    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10335    /// twins (both buffers must be the repacked layout).
10336    #[allow(clippy::too_many_arguments)]
10337    pub fn qmatvec_batched_dual_raw(
10338        &self,
10339        b0: &CudaSlice<u8>,
10340        b1: &CudaSlice<u8>,
10341        aq: &CudaSlice<i8>,
10342        ad: &CudaSlice<f32>,
10343        m: usize,
10344        in_f: usize,
10345        out_f: usize,
10346        row_bytes: usize,
10347        rp: bool,
10348    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10349        const ROWS_PER_BLOCK: u32 = 4;
10350        let mcols = Self::batched_mcols(m);
10351        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10352        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10353        let tiny_rp1 = rp
10354            && mcols == 4
10355            && out_f <= 128
10356            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10357        let (name, rows_per_block) = if tiny_rp1 {
10358            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10359        } else {
10360            match (mcols, rp, m) {
10361                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10362                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10363                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10364                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10365                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10366                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10367                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10368                _ => {
10369                    return Err(
10370                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10371                    );
10372                }
10373            }
10374        };
10375        let f = self.func(name);
10376        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10377        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10378        let cfg = LaunchConfig {
10379            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10380            block_dim: (32, ROWS_PER_BLOCK, 1),
10381            shared_mem_bytes: 0,
10382        };
10383        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10384        let __s_b = self.gpu.stream();
10385        let mut b = __s_b.launch_builder(&f);
10386        b.arg(b0)
10387            .arg(b1)
10388            .arg(aq)
10389            .arg(ad)
10390            .arg(&mut y0)
10391            .arg(&mut y1)
10392            .arg(&inf)
10393            .arg(&outf)
10394            .arg(&mi)
10395            .arg(&rb);
10396        unsafe {
10397            b.launch(cfg)?;
10398        }
10399        Ok((y0, y1))
10400    }
10401
10402    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10403    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10404    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10405    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10406    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10407    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10408    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10409    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10410    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10411    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10412    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10413    pub fn matmul_pre_dual_noscale(
10414        &self,
10415        w0: &crate::model::GpuTensor,
10416        w1: &crate::model::GpuTensor,
10417        aq: &CudaSlice<i8>,
10418        ad: &CudaSlice<f32>,
10419        m: usize,
10420    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10421    {
10422        use crate::model::GpuTensor;
10423        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10424            return Ok(None);
10425        }
10426        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10427        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10428        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10429        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10430        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10431        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10432        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10433        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10434        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10435        if !self.mmvq_supports(QT_NVFP4) {
10436            return Ok(None);
10437        }
10438        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10439        if w1.in_features() != in_f || w1.out_features() != out_f {
10440            return Ok(None);
10441        }
10442        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10443        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10444        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10445        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10446        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10447        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10448        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10449        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10450        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
10451        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
10452        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
10453        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
10454        let no_mirror =
10455            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
10456        if self.q8_ffn_fuse2_on()
10457            && no_mirror(w0)
10458            && no_mirror(w1)
10459            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
10460        {
10461            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
10462            return Ok(Some(((y0, 1.0), (y1, 1.0))));
10463        }
10464        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
10465        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
10466        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
10467        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
10468        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
10469        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
10470        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
10471        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
10472        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
10473        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10474            let (y0, y1) =
10475                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
10476            return Ok(Some(((y0, p0.3), (y1, p1.3))));
10477        }
10478        let (b0, q0, rb0, s0, rp0) = match w0 {
10479            GpuTensor::Quant {
10480                bytes,
10481                qtype,
10482                row_bytes,
10483                scale,
10484                rp,
10485                ..
10486            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10487            _ => return Ok(None),
10488        };
10489        let (b1, q1, rb1, s1, rp1) = match w1 {
10490            GpuTensor::Quant {
10491                bytes,
10492                qtype,
10493                row_bytes,
10494                scale,
10495                rp,
10496                ..
10497            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10498            _ => return Ok(None),
10499        };
10500        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
10501            return Ok(None);
10502        }
10503        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10504        const RPW: u32 = 2;
10505        let rows_per_block = ROWS_PER_BLOCK * RPW;
10506        let f = self.func(if rp0 {
10507            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
10508        } else {
10509            "qmatvec_nvfp4_mmvq_dual_mr2"
10510        });
10511        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
10512        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
10513        let cfg = LaunchConfig {
10514            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10515            block_dim: (32, ROWS_PER_BLOCK, 1),
10516            shared_mem_bytes: 0,
10517        };
10518        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
10519        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
10520        // yscale args stay 1.0 here (they exist for the single-tensor callers).
10521        let one = 1.0f32;
10522        let __s_b = self.gpu.stream();
10523        let mut b = __s_b.launch_builder(&f);
10524        b.arg(b0)
10525            .arg(b1)
10526            .arg(aq)
10527            .arg(ad)
10528            .arg(&mut y0)
10529            .arg(&mut y1)
10530            .arg(&inf)
10531            .arg(&outf)
10532            .arg(&mi)
10533            .arg(&rb)
10534            .arg(&one)
10535            .arg(&one);
10536        unsafe {
10537            b.launch(cfg)?;
10538        }
10539        Ok(Some(((y0, s0), (y1, s1))))
10540    }
10541
10542    /// FUSED NVFP4 matvec TRIPLE with unequal out_f (rig-native decode increment 1,
10543    /// lane/rig-native-nvfp4): wq+wk+wv in ONE launch via the q8_0 fused2 block-offset
10544    /// recipe. Per (tensor,row,t) the kernel body is nvfp4_mmvq_multirow_rp VERBATIM ->
10545    /// bit-identical to three separate `matmul_pre` launches; yscales fold in-kernel exactly
10546    /// as the singles do. grid.y = m (the t-parallel verify rows ride the same launch).
10547    /// None when ineligible (not all rp NVFP4 / in_f mismatch / mmvq off) — callers fall
10548    /// back to the three singles.
10549    #[allow(clippy::too_many_arguments)]
10550    pub fn matmul_nvfp4_fused3(
10551        &self,
10552        w0: &crate::model::GpuTensor,
10553        w1: &crate::model::GpuTensor,
10554        w2: &crate::model::GpuTensor,
10555        aq: &CudaSlice<i8>,
10556        ad: &CudaSlice<f32>,
10557        m: usize,
10558    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
10559    {
10560        use crate::model::GpuTensor;
10561        // m==1 ONLY: at m>1 the singles ride the _b16 weight-once column program (one weight
10562        // read serves all m rows); the fused segments would re-read the weight per row. The
10563        // fusion win is the B=1 decode tick.
10564        if m != 1
10565            || !self.mmvq_supports(QT_NVFP4)
10566            || !self.uses_q8_1_fast(w0)
10567            || !self.uses_q8_1_fast(w1)
10568            || !self.uses_q8_1_fast(w2)
10569        {
10570            return Ok(None);
10571        }
10572        let unpack = |w: &crate::model::GpuTensor| match w {
10573            GpuTensor::Quant {
10574                bytes,
10575                qtype,
10576                scale,
10577                rp,
10578                ..
10579            } if *qtype == QT_NVFP4 && *rp => Some((bytes as *const CudaSlice<u8>, *scale)),
10580            _ => None,
10581        };
10582        let (Some(p0), Some(p1), Some(p2)) = (unpack(w0), unpack(w1), unpack(w2)) else {
10583            return Ok(None);
10584        };
10585        let in_f = w0.in_features();
10586        if w1.in_features() != in_f || w2.in_features() != in_f {
10587            return Ok(None);
10588        }
10589        let (o0, o1, o2) = (w0.out_features(), w1.out_features(), w2.out_features());
10590        const ROWS_PER_BLOCK: u32 = 4; // MEMRA_MMVQ_ROWS
10591        const RPW: u32 = 2;
10592        let rows_pb = ROWS_PER_BLOCK * RPW;
10593        let nb = |o: usize| (o as u32).div_ceil(rows_pb);
10594        let f = self.func("qmatvec_nvfp4_mmvq_fused3_rp");
10595        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
10596        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
10597        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
10598        let cfg = LaunchConfig {
10599            grid_dim: (nb(o0) + nb(o1) + nb(o2), m as u32, 1),
10600            block_dim: (32, ROWS_PER_BLOCK, 1),
10601            shared_mem_bytes: 0,
10602        };
10603        let (inf, oi0, oi1, oi2, mi) = (in_f as i32, o0 as i32, o1 as i32, o2 as i32, m as i32);
10604        // SAFETY: the raw pointers come straight from the &GpuTensor borrows above and are
10605        // only dereferenced for the launch-arg build inside this call.
10606        let (b0, b1, b2) = unsafe { (&*p0.0, &*p1.0, &*p2.0) };
10607        let __s_b = self.gpu.stream();
10608        let mut b = __s_b.launch_builder(&f);
10609        b.arg(b0)
10610            .arg(b1)
10611            .arg(b2)
10612            .arg(aq)
10613            .arg(ad)
10614            .arg(&mut y0)
10615            .arg(&mut y1)
10616            .arg(&mut y2)
10617            .arg(&inf)
10618            .arg(&oi0)
10619            .arg(&oi1)
10620            .arg(&oi2)
10621            .arg(&mi)
10622            .arg(&p0.1)
10623            .arg(&p1.1)
10624            .arg(&p2.1);
10625        unsafe {
10626            b.launch(cfg)?;
10627        }
10628        Ok(Some((y0, y1, y2)))
10629    }
10630
10631    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
10632    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
10633    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
10634    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
10635    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
10636    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
10637    /// back to the per-tensor path.
10638    pub fn matmul_q8_fused2(
10639        &self,
10640        w0: &crate::model::GpuTensor,
10641        w1: &crate::model::GpuTensor,
10642        aq: &CudaSlice<i8>,
10643        ad: &CudaSlice<f32>,
10644    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10645        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
10646        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
10647        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
10648        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
10649        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
10650        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10651            return Ok(Some(self.e4m3_fused2_core(
10652                p0.0,
10653                p1.0,
10654                aq,
10655                ad,
10656                w0.in_features(),
10657                p0.1,
10658                p1.1,
10659                p0.2,
10660                p0.3,
10661                p1.3,
10662            )?));
10663        }
10664        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10665            return Ok(None);
10666        };
10667        Ok(Some(self.q8_fused2_core(
10668            p0.0,
10669            p1.0,
10670            aq,
10671            ad,
10672            w0.in_features(),
10673            p0.1,
10674            p1.1,
10675            p0.2,
10676        )?))
10677    }
10678
10679    #[allow(clippy::too_many_arguments)]
10680    fn q8_fused2_core(
10681        &self,
10682        b0: &CudaSlice<u8>,
10683        b1: &CudaSlice<u8>,
10684        aq: &CudaSlice<i8>,
10685        ad: &CudaSlice<f32>,
10686        in_f: usize,
10687        out0: usize,
10688        out1: usize,
10689        row_bytes: usize,
10690    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10691        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10692        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
10693        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
10694        let f = self.func("qmatvec_q8_0_mmvq_fused2");
10695        let mut y0 = self.alloc_uninit::<f32>(out0)?;
10696        let mut y1 = self.alloc_uninit::<f32>(out1)?;
10697        let cfg = LaunchConfig {
10698            grid_dim: (nb0 + nb1, 1, 1),
10699            block_dim: (32, ROWS_PER_BLOCK, 1),
10700            shared_mem_bytes: 0,
10701        };
10702        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
10703        let __s_b = self.gpu.stream();
10704        let mut b = __s_b.launch_builder(&f);
10705        b.arg(b0)
10706            .arg(b1)
10707            .arg(aq)
10708            .arg(ad)
10709            .arg(&mut y0)
10710            .arg(&mut y1)
10711            .arg(&inf)
10712            .arg(&o0)
10713            .arg(&o1)
10714            .arg(&rbl);
10715        unsafe {
10716            b.launch(cfg)?;
10717        }
10718        Ok((y0, y1))
10719    }
10720
10721    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
10722    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
10723    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
10724    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
10725    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
10726    pub fn matmul_q8_fused2_x(
10727        &self,
10728        w0: &crate::model::GpuTensor,
10729        w1: &crate::model::GpuTensor,
10730        x: &CudaSlice<f32>,
10731    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10732        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10733            return Ok(None);
10734        }
10735        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10736            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10737            return Ok(Some(self.e4m3_fused2_core(
10738                p0.0,
10739                p1.0,
10740                &aq,
10741                &ad,
10742                w0.in_features(),
10743                p0.1,
10744                p1.1,
10745                p0.2,
10746                p0.3,
10747                p1.3,
10748            )?));
10749        }
10750        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10751            return Ok(None);
10752        };
10753        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10754        Ok(Some(self.q8_fused2_core(
10755            p0.0,
10756            p1.0,
10757            &aq,
10758            &ad,
10759            w0.in_features(),
10760            p0.1,
10761            p1.1,
10762            p0.2,
10763        )?))
10764    }
10765
10766    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
10767    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
10768    #[allow(clippy::too_many_arguments)]
10769    pub fn qmatvec_q8_fused2_raw(
10770        &self,
10771        b0: &CudaSlice<u8>,
10772        b1: &CudaSlice<u8>,
10773        x: &CudaSlice<f32>,
10774        in_f: usize,
10775        out0: usize,
10776        out1: usize,
10777        row_bytes: usize,
10778    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10779        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
10780        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
10781    }
10782
10783    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
10784    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
10785    /// (tensor,row) to three separate m=1 MMVQ launches.
10786    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
10787    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
10788    pub fn matmul_q4_fused3(
10789        &self,
10790        w0: &crate::model::GpuTensor,
10791        w1: &crate::model::GpuTensor,
10792        w2: &crate::model::GpuTensor,
10793        aq: &CudaSlice<i8>,
10794        ad: &CudaSlice<f32>,
10795    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
10796    {
10797        use crate::model::GpuTensor;
10798        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10799            match w {
10800                GpuTensor::Quant {
10801                    qtype, row_bytes, ..
10802                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10803                _ => None,
10804            }
10805        };
10806        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
10807            return Ok(None);
10808        };
10809        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
10810            return Ok(None);
10811        }
10812        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
10813        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
10814        // the separate matvecs (each routes its own rp).
10815        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
10816            match w {
10817                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
10818                    Some(m) => (m, true),
10819                    None => (bytes, *rp),
10820                },
10821                _ => unreachable!(),
10822            }
10823        }
10824        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
10825        if rp0 != rp1 || rp1 != rp2 {
10826            return Ok(None);
10827        }
10828        let rp = rp0;
10829        let rpb: u32 = 4;
10830        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
10831        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
10832        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
10833        let mr1 = rp && Self::q40_mr1_on();
10834        let nb = |o: usize| {
10835            if mr1 {
10836                (o as u32).div_ceil(rpb)
10837            } else {
10838                (o as u32).div_ceil(2).div_ceil(rpb)
10839            }
10840        };
10841        let grid = nb(o0) + nb(o1) + nb(o2);
10842        let mut y0 = self.alloc_uninit::<f32>(o0)?;
10843        let mut y1 = self.alloc_uninit::<f32>(o1)?;
10844        let mut y2 = self.alloc_uninit::<f32>(o2)?;
10845        let f = self.func(if mr1 {
10846            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
10847        } else if rp {
10848            "qmatvec_q4_0_mmvq_fused3_rp"
10849        } else {
10850            "qmatvec_q4_0_mmvq_fused3"
10851        });
10852        let cfg = LaunchConfig {
10853            grid_dim: (grid, 1, 1),
10854            block_dim: (32, rpb, 1),
10855            shared_mem_bytes: 0,
10856        };
10857        let inf = w0.in_features() as i32;
10858        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
10859        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
10860        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
10861        // variant may take the programmatic-serialization launch.
10862        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
10863            {
10864                use cudarc::driver::{DevicePtr, DevicePtrMut};
10865                let s = &self.gpu.stream();
10866                let (p0, _g0) = b0.device_ptr(s);
10867                let (p1, _g1) = b1.device_ptr(s);
10868                let (p2, _g2) = b2.device_ptr(s);
10869                let (paq, _g3) = aq.device_ptr(s);
10870                let (pad, _g4) = ad.device_ptr(s);
10871                let (py0, _g5) = y0.device_ptr_mut(s);
10872                let (py1, _g6) = y1.device_ptr_mut(s);
10873                let (py2, _g7) = y2.device_ptr_mut(s);
10874                let mut ps = [
10875                    &p0 as *const _ as *mut std::ffi::c_void,
10876                    &p1 as *const _ as *mut _,
10877                    &p2 as *const _ as *mut _,
10878                    &paq as *const _ as *mut _,
10879                    &pad as *const _ as *mut _,
10880                    &py0 as *const _ as *mut _,
10881                    &py1 as *const _ as *mut _,
10882                    &py2 as *const _ as *mut _,
10883                    &inf as *const _ as *mut _,
10884                    &oo0 as *const _ as *mut _,
10885                    &oo1 as *const _ as *mut _,
10886                    &oo2 as *const _ as *mut _,
10887                    &r0 as *const _ as *mut _,
10888                    &r1 as *const _ as *mut _,
10889                    &r2 as *const _ as *mut _,
10890                ];
10891                unsafe {
10892                    self.launch_pdl(
10893                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
10894                        (grid, 1, 1),
10895                        (32, rpb, 1),
10896                        &mut ps,
10897                    )?;
10898                }
10899            }
10900            return Ok(Some((y0, y1, y2)));
10901        }
10902        let __s_b = self.gpu.stream();
10903        let mut b = __s_b.launch_builder(&f);
10904        b.arg(b0)
10905            .arg(b1)
10906            .arg(b2)
10907            .arg(aq)
10908            .arg(ad)
10909            .arg(&mut y0)
10910            .arg(&mut y1)
10911            .arg(&mut y2)
10912            .arg(&inf)
10913            .arg(&oo0)
10914            .arg(&oo1)
10915            .arg(&oo2)
10916            .arg(&r0)
10917            .arg(&r1)
10918            .arg(&r2);
10919        unsafe {
10920            b.launch(cfg)?;
10921        }
10922        Ok(Some((y0, y1, y2)))
10923    }
10924
10925    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
10926    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
10927    #[allow(clippy::too_many_arguments)]
10928    pub fn matmul_q4_fused3_into(
10929        &self,
10930        w0: &crate::model::GpuTensor,
10931        w1: &crate::model::GpuTensor,
10932        w2: &crate::model::GpuTensor,
10933        aq: &CudaSlice<i8>,
10934        ad: &CudaSlice<f32>,
10935        y0: &mut CudaSlice<f32>,
10936        y1: &mut CudaSlice<f32>,
10937        y2: &mut CudaSlice<f32>,
10938    ) -> Result<bool, Box<dyn std::error::Error>> {
10939        use crate::model::GpuTensor;
10940        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10941            match w {
10942                GpuTensor::Quant {
10943                    qtype, row_bytes, ..
10944                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10945                _ => None,
10946            }
10947        };
10948        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
10949            return Ok(false);
10950        };
10951        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
10952            return Ok(false);
10953        }
10954        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
10955            match w {
10956                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
10957                    Some(m) => (m, true),
10958                    None => (bytes, *rp),
10959                },
10960                _ => unreachable!(),
10961            }
10962        }
10963        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
10964        if rp0 != rp1 || rp1 != rp2 {
10965            return Ok(false);
10966        }
10967        let rp = rp0;
10968        let rpb: u32 = 4;
10969        let mr1 = rp && Self::q40_mr1_on();
10970        let nb = |o: usize| {
10971            if mr1 {
10972                (o as u32).div_ceil(rpb)
10973            } else {
10974                (o as u32).div_ceil(2).div_ceil(rpb)
10975            }
10976        };
10977        let grid = nb(o0) + nb(o1) + nb(o2);
10978        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
10979        let f = self.func(if mr1 {
10980            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
10981        } else if rp {
10982            "qmatvec_q4_0_mmvq_fused3_rp"
10983        } else {
10984            "qmatvec_q4_0_mmvq_fused3"
10985        });
10986        let cfg = LaunchConfig {
10987            grid_dim: (grid, 1, 1),
10988            block_dim: (32, rpb, 1),
10989            shared_mem_bytes: 0,
10990        };
10991        let inf = w0.in_features() as i32;
10992        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
10993        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
10994        // PDL wave-A: identical to the owned twin (capture-lane parity).
10995        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
10996            use cudarc::driver::{DevicePtr, DevicePtrMut};
10997            let s = &self.gpu.stream();
10998            let (p0, _g0) = b0.device_ptr(s);
10999            let (p1, _g1) = b1.device_ptr(s);
11000            let (p2, _g2) = b2.device_ptr(s);
11001            let (paq, _g3) = aq.device_ptr(s);
11002            let (pad, _g4) = ad.device_ptr(s);
11003            let (py0, _g5) = y0.device_ptr_mut(s);
11004            let (py1, _g6) = y1.device_ptr_mut(s);
11005            let (py2, _g7) = y2.device_ptr_mut(s);
11006            let mut ps = [
11007                &p0 as *const _ as *mut std::ffi::c_void,
11008                &p1 as *const _ as *mut _,
11009                &p2 as *const _ as *mut _,
11010                &paq as *const _ as *mut _,
11011                &pad as *const _ as *mut _,
11012                &py0 as *const _ as *mut _,
11013                &py1 as *const _ as *mut _,
11014                &py2 as *const _ as *mut _,
11015                &inf as *const _ as *mut _,
11016                &oo0 as *const _ as *mut _,
11017                &oo1 as *const _ as *mut _,
11018                &oo2 as *const _ as *mut _,
11019                &r0 as *const _ as *mut _,
11020                &r1 as *const _ as *mut _,
11021                &r2 as *const _ as *mut _,
11022            ];
11023            unsafe {
11024                self.launch_pdl(
11025                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
11026                    (grid, 1, 1),
11027                    (32, rpb, 1),
11028                    &mut ps,
11029                )?;
11030            }
11031            return Ok(true);
11032        }
11033        let __s_b = self.gpu.stream();
11034        let mut b = __s_b.launch_builder(&f);
11035        b.arg(b0)
11036            .arg(b1)
11037            .arg(b2)
11038            .arg(aq)
11039            .arg(ad)
11040            .arg(&mut *y0)
11041            .arg(&mut *y1)
11042            .arg(&mut *y2)
11043            .arg(&inf)
11044            .arg(&oo0)
11045            .arg(&oo1)
11046            .arg(&oo2)
11047            .arg(&r0)
11048            .arg(&r1)
11049            .arg(&r2);
11050        unsafe {
11051            b.launch(cfg)?;
11052        }
11053        Ok(true)
11054    }
11055
11056    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
11057    pub fn matmul_q4_fused2(
11058        &self,
11059        w0: &crate::model::GpuTensor,
11060        w1: &crate::model::GpuTensor,
11061        aq: &CudaSlice<i8>,
11062        ad: &CudaSlice<f32>,
11063    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11064        use crate::model::GpuTensor;
11065        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11066            match w {
11067                GpuTensor::Quant {
11068                    qtype, row_bytes, ..
11069                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11070                _ => None,
11071            }
11072        };
11073        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11074            return Ok(None);
11075        };
11076        if w0.in_features() != w1.in_features() {
11077            return Ok(None);
11078        }
11079        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
11080        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11081            match w {
11082                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11083                    Some(m) => (m, true),
11084                    None => (bytes, *rp),
11085                },
11086                _ => unreachable!(),
11087            }
11088        }
11089        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11090        if rp0 != rp1 {
11091            return Ok(None);
11092        }
11093        let rp = rp0;
11094        let rpb: u32 = 4;
11095        // mr1 twin — see matmul_q4_fused3.
11096        let mr1 = rp && Self::q40_mr1_on();
11097        let nb = |o: usize| {
11098            if mr1 {
11099                (o as u32).div_ceil(rpb)
11100            } else {
11101                (o as u32).div_ceil(2).div_ceil(rpb)
11102            }
11103        };
11104        let grid = nb(o0) + nb(o1);
11105        let mut y0 = self.alloc_uninit::<f32>(o0)?;
11106        let mut y1 = self.alloc_uninit::<f32>(o1)?;
11107        let f = self.func(if mr1 {
11108            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11109        } else if rp {
11110            "qmatvec_q4_0_mmvq_fused2_rp"
11111        } else {
11112            "qmatvec_q4_0_mmvq_fused2"
11113        });
11114        let cfg = LaunchConfig {
11115            grid_dim: (grid, 1, 1),
11116            block_dim: (32, rpb, 1),
11117            shared_mem_bytes: 0,
11118        };
11119        let inf = w0.in_features() as i32;
11120        let (oo0, oo1) = (o0 as i32, o1 as i32);
11121        let (r0, r1) = (rb0 as i64, rb1 as i64);
11122        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
11123        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11124            {
11125                use cudarc::driver::{DevicePtr, DevicePtrMut};
11126                let s = &self.gpu.stream();
11127                let (p0, _g0) = b0.device_ptr(s);
11128                let (p1, _g1) = b1.device_ptr(s);
11129                let (paq, _g2) = aq.device_ptr(s);
11130                let (pad, _g3) = ad.device_ptr(s);
11131                let (py0, _g4) = y0.device_ptr_mut(s);
11132                let (py1, _g5) = y1.device_ptr_mut(s);
11133                let mut ps = [
11134                    &p0 as *const _ as *mut std::ffi::c_void,
11135                    &p1 as *const _ as *mut _,
11136                    &paq as *const _ as *mut _,
11137                    &pad as *const _ as *mut _,
11138                    &py0 as *const _ as *mut _,
11139                    &py1 as *const _ as *mut _,
11140                    &inf as *const _ as *mut _,
11141                    &oo0 as *const _ as *mut _,
11142                    &oo1 as *const _ as *mut _,
11143                    &r0 as *const _ as *mut _,
11144                    &r1 as *const _ as *mut _,
11145                ];
11146                unsafe {
11147                    self.launch_pdl(
11148                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11149                        (grid, 1, 1),
11150                        (32, rpb, 1),
11151                        &mut ps,
11152                    )?;
11153                }
11154            }
11155            return Ok(Some((y0, y1)));
11156        }
11157        let __s_b = self.gpu.stream();
11158        let mut b = __s_b.launch_builder(&f);
11159        b.arg(b0)
11160            .arg(b1)
11161            .arg(aq)
11162            .arg(ad)
11163            .arg(&mut y0)
11164            .arg(&mut y1)
11165            .arg(&inf)
11166            .arg(&oo0)
11167            .arg(&oo1)
11168            .arg(&r0)
11169            .arg(&r1);
11170        unsafe {
11171            b.launch(cfg)?;
11172        }
11173        Ok(Some((y0, y1)))
11174    }
11175
11176    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11177    pub fn matmul_q4_fused2_into(
11178        &self,
11179        w0: &crate::model::GpuTensor,
11180        w1: &crate::model::GpuTensor,
11181        aq: &CudaSlice<i8>,
11182        ad: &CudaSlice<f32>,
11183        y0: &mut CudaSlice<f32>,
11184        y1: &mut CudaSlice<f32>,
11185    ) -> Result<bool, Box<dyn std::error::Error>> {
11186        use crate::model::GpuTensor;
11187        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11188            match w {
11189                GpuTensor::Quant {
11190                    qtype, row_bytes, ..
11191                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11192                _ => None,
11193            }
11194        };
11195        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11196            return Ok(false);
11197        };
11198        if w0.in_features() != w1.in_features() {
11199            return Ok(false);
11200        }
11201        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11202            match w {
11203                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11204                    Some(m) => (m, true),
11205                    None => (bytes, *rp),
11206                },
11207                _ => unreachable!(),
11208            }
11209        }
11210        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11211        if rp0 != rp1 {
11212            return Ok(false);
11213        }
11214        let rp = rp0;
11215        let rpb: u32 = 4;
11216        let mr1 = rp && Self::q40_mr1_on();
11217        let nb = |o: usize| {
11218            if mr1 {
11219                (o as u32).div_ceil(rpb)
11220            } else {
11221                (o as u32).div_ceil(2).div_ceil(rpb)
11222            }
11223        };
11224        let grid = nb(o0) + nb(o1);
11225        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
11226        let f = self.func(if mr1 {
11227            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11228        } else if rp {
11229            "qmatvec_q4_0_mmvq_fused2_rp"
11230        } else {
11231            "qmatvec_q4_0_mmvq_fused2"
11232        });
11233        let cfg = LaunchConfig {
11234            grid_dim: (grid, 1, 1),
11235            block_dim: (32, rpb, 1),
11236            shared_mem_bytes: 0,
11237        };
11238        let inf = w0.in_features() as i32;
11239        let (oo0, oo1) = (o0 as i32, o1 as i32);
11240        let (r0, r1) = (rb0 as i64, rb1 as i64);
11241        // PDL wave-A: identical to the owned twin (capture-lane parity).
11242        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11243            use cudarc::driver::{DevicePtr, DevicePtrMut};
11244            let s = &self.gpu.stream();
11245            let (p0, _g0) = b0.device_ptr(s);
11246            let (p1, _g1) = b1.device_ptr(s);
11247            let (paq, _g2) = aq.device_ptr(s);
11248            let (pad, _g3) = ad.device_ptr(s);
11249            let (py0, _g4) = y0.device_ptr_mut(s);
11250            let (py1, _g5) = y1.device_ptr_mut(s);
11251            let mut ps = [
11252                &p0 as *const _ as *mut std::ffi::c_void,
11253                &p1 as *const _ as *mut _,
11254                &paq as *const _ as *mut _,
11255                &pad as *const _ as *mut _,
11256                &py0 as *const _ as *mut _,
11257                &py1 as *const _ as *mut _,
11258                &inf as *const _ as *mut _,
11259                &oo0 as *const _ as *mut _,
11260                &oo1 as *const _ as *mut _,
11261                &r0 as *const _ as *mut _,
11262                &r1 as *const _ as *mut _,
11263            ];
11264            unsafe {
11265                self.launch_pdl(
11266                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11267                    (grid, 1, 1),
11268                    (32, rpb, 1),
11269                    &mut ps,
11270                )?;
11271            }
11272            return Ok(true);
11273        }
11274        let __s_b = self.gpu.stream();
11275        let mut b = __s_b.launch_builder(&f);
11276        b.arg(b0)
11277            .arg(b1)
11278            .arg(aq)
11279            .arg(ad)
11280            .arg(&mut *y0)
11281            .arg(&mut *y1)
11282            .arg(&inf)
11283            .arg(&oo0)
11284            .arg(&oo1)
11285            .arg(&r0)
11286            .arg(&r1);
11287        unsafe {
11288            b.launch(cfg)?;
11289        }
11290        Ok(true)
11291    }
11292
11293    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
11294    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
11295    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
11296    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
11297    pub fn matmul_q4_fused2_batched(
11298        &self,
11299        w0: &crate::model::GpuTensor,
11300        w1: &crate::model::GpuTensor,
11301        aq: &CudaSlice<i8>,
11302        ad: &CudaSlice<f32>,
11303        m: usize,
11304    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11305        use crate::model::GpuTensor;
11306        if m < 2 || m > 8 {
11307            return Ok(None);
11308        }
11309        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11310            match w {
11311                GpuTensor::Quant {
11312                    qtype, row_bytes, ..
11313                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11314                _ => None,
11315            }
11316        };
11317        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
11318            return Ok(None);
11319        };
11320        if w0.in_features() != w1.in_features() {
11321            return Ok(None);
11322        }
11323        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11324            match w {
11325                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11326                    Some(mr) => (mr, true),
11327                    None => (bytes, *rp),
11328                },
11329                _ => unreachable!(),
11330            }
11331        }
11332        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11333        if !rp0 || !rp1 {
11334            return Ok(None);
11335        }
11336        let mcols = Self::batched_mcols(m);
11337        let rpb: u32 = 4;
11338        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11339        let grid = nb(o0) + nb(o1);
11340        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11341        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11342        let f = self.func(match mcols {
11343            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
11344            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
11345            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
11346        });
11347        let cfg = LaunchConfig {
11348            grid_dim: (grid, 1, 1),
11349            block_dim: (32, rpb, 1),
11350            shared_mem_bytes: 0,
11351        };
11352        let inf = w0.in_features() as i32;
11353        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
11354        let rb = rb0 as i64;
11355        let __s_b = self.gpu.stream();
11356        let mut b = __s_b.launch_builder(&f);
11357        b.arg(b0)
11358            .arg(b1)
11359            .arg(aq)
11360            .arg(ad)
11361            .arg(&mut y0)
11362            .arg(&mut y1)
11363            .arg(&inf)
11364            .arg(&oo0)
11365            .arg(&oo1)
11366            .arg(&mi)
11367            .arg(&rb);
11368        unsafe {
11369            b.launch(cfg)?;
11370        }
11371        Ok(Some((y0, y1)))
11372    }
11373
11374    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
11375    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
11376    #[allow(clippy::too_many_arguments)]
11377    pub fn matmul_q4_fused3_batched(
11378        &self,
11379        w0: &crate::model::GpuTensor,
11380        w1: &crate::model::GpuTensor,
11381        w2: &crate::model::GpuTensor,
11382        aq: &CudaSlice<i8>,
11383        ad: &CudaSlice<f32>,
11384        m: usize,
11385    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11386    {
11387        use crate::model::GpuTensor;
11388        if m < 2 || m > 8 {
11389            return Ok(None);
11390        }
11391        let q4 = |w: &GpuTensor| -> Option<usize> {
11392            match w {
11393                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
11394                _ => None,
11395            }
11396        };
11397        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
11398            return Ok(None);
11399        };
11400        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11401            return Ok(None);
11402        }
11403        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11404            match w {
11405                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11406                    Some(mr) => (mr, true),
11407                    None => (bytes, *rp),
11408                },
11409                _ => unreachable!(),
11410            }
11411        }
11412        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11413        if !rp0 || !rp1 || !rp2 {
11414            return Ok(None);
11415        }
11416        let mcols = Self::batched_mcols(m);
11417        let rpb: u32 = 4;
11418        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11419        let grid = nb(o0) + nb(o1) + nb(o2);
11420        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11421        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11422        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11423        let f = self.func(match mcols {
11424            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
11425            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
11426            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
11427        });
11428        let cfg = LaunchConfig {
11429            grid_dim: (grid, 1, 1),
11430            block_dim: (32, rpb, 1),
11431            shared_mem_bytes: 0,
11432        };
11433        let inf = w0.in_features() as i32;
11434        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
11435        let rb = 0i64;
11436        let __s_b = self.gpu.stream();
11437        let mut b = __s_b.launch_builder(&f);
11438        b.arg(b0)
11439            .arg(b1)
11440            .arg(b2)
11441            .arg(aq)
11442            .arg(ad)
11443            .arg(&mut y0)
11444            .arg(&mut y1)
11445            .arg(&mut y2)
11446            .arg(&inf)
11447            .arg(&oo0)
11448            .arg(&oo1)
11449            .arg(&oo2)
11450            .arg(&mi)
11451            .arg(&rb);
11452        unsafe {
11453            b.launch(cfg)?;
11454        }
11455        Ok(Some((y0, y1, y2)))
11456    }
11457
11458    pub fn matmul_q8_fused3(
11459        &self,
11460        w0: &crate::model::GpuTensor,
11461        w1: &crate::model::GpuTensor,
11462        w2: &crate::model::GpuTensor,
11463        aq: &CudaSlice<i8>,
11464        ad: &CudaSlice<f32>,
11465    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11466    {
11467        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
11468        // are per-tensor FP8, so native residency without this arm meant three separate launches.
11469        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11470            return Ok(Some(self.e4m3_fused3_core(
11471                p0.0,
11472                p1.0,
11473                p2.0,
11474                aq,
11475                ad,
11476                w0.in_features(),
11477                p0.1,
11478                p1.1,
11479                p2.1,
11480                p0.2,
11481                p0.3,
11482                p1.3,
11483                p2.3,
11484            )?));
11485        }
11486        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11487            return Ok(None);
11488        };
11489        Ok(Some(self.q8_fused3_core(
11490            p0.0,
11491            p1.0,
11492            p2.0,
11493            aq,
11494            ad,
11495            w0.in_features(),
11496            p0.1,
11497            p1.1,
11498            p2.1,
11499            p0.2,
11500        )?))
11501    }
11502
11503    #[allow(clippy::too_many_arguments)]
11504    fn q8_fused3_core(
11505        &self,
11506        b0: &CudaSlice<u8>,
11507        b1: &CudaSlice<u8>,
11508        b2: &CudaSlice<u8>,
11509        aq: &CudaSlice<i8>,
11510        ad: &CudaSlice<f32>,
11511        in_f: usize,
11512        out0: usize,
11513        out1: usize,
11514        out2: usize,
11515        row_bytes: usize,
11516    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11517        const ROWS_PER_BLOCK: u32 = 4;
11518        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11519        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11520        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11521        let f = self.func("qmatvec_q8_0_mmvq_fused3");
11522        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11523        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11524        let mut y2 = self.alloc_uninit::<f32>(out2)?;
11525        let cfg = LaunchConfig {
11526            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11527            block_dim: (32, ROWS_PER_BLOCK, 1),
11528            shared_mem_bytes: 0,
11529        };
11530        let (inf, o0, o1, o2, rbl) = (
11531            in_f as i32,
11532            out0 as i32,
11533            out1 as i32,
11534            out2 as i32,
11535            row_bytes as i64,
11536        );
11537        let __s_b = self.gpu.stream();
11538        let mut b = __s_b.launch_builder(&f);
11539        b.arg(b0)
11540            .arg(b1)
11541            .arg(b2)
11542            .arg(aq)
11543            .arg(ad)
11544            .arg(&mut y0)
11545            .arg(&mut y1)
11546            .arg(&mut y2)
11547            .arg(&inf)
11548            .arg(&o0)
11549            .arg(&o1)
11550            .arg(&o2)
11551            .arg(&rbl);
11552        unsafe {
11553            b.launch(cfg)?;
11554        }
11555        Ok((y0, y1, y2))
11556    }
11557
11558    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
11559    #[allow(clippy::too_many_arguments)]
11560    pub fn qmatvec_q8_fused3_raw(
11561        &self,
11562        b0: &CudaSlice<u8>,
11563        b1: &CudaSlice<u8>,
11564        b2: &CudaSlice<u8>,
11565        x: &CudaSlice<f32>,
11566        in_f: usize,
11567        out0: usize,
11568        out1: usize,
11569        out2: usize,
11570        row_bytes: usize,
11571    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11572        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11573        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
11574    }
11575
11576    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
11577    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
11578    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
11579    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
11580    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
11581    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
11582    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
11583    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
11584    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
11585    /// twin must not introduce a batched program the reference path would not run).
11586    pub fn matmul_q8_fused2_t(
11587        &self,
11588        w0: &crate::model::GpuTensor,
11589        w1: &crate::model::GpuTensor,
11590        aq: &CudaSlice<i8>,
11591        ad: &CudaSlice<f32>,
11592        m: usize,
11593    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11594        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
11595        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
11596        // fuses too — same template body, still bit-identical to the two _b8 launches.
11597        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11598            return Ok(None);
11599        }
11600        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
11601        // so the fused b8 launch would introduce a batched program the reference path would not run.
11602        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11603            if m > 4 && !Self::b8_enabled() {
11604                return Ok(None);
11605            }
11606            return Ok(Some(self.e4m3_fused2_t_core(
11607                p0.0,
11608                p1.0,
11609                aq,
11610                ad,
11611                m,
11612                w0.in_features(),
11613                p0.1,
11614                p1.1,
11615                p0.2,
11616                p0.3,
11617                p1.3,
11618            )?));
11619        }
11620        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11621            return Ok(None);
11622        };
11623        Ok(Some(self.q8_fused2_t_core(
11624            p0.0,
11625            p1.0,
11626            aq,
11627            ad,
11628            m,
11629            w0.in_features(),
11630            p0.1,
11631            p1.1,
11632            p0.2,
11633        )?))
11634    }
11635
11636    #[allow(clippy::too_many_arguments)]
11637    fn q8_fused2_t_core(
11638        &self,
11639        b0: &CudaSlice<u8>,
11640        b1: &CudaSlice<u8>,
11641        aq: &CudaSlice<i8>,
11642        ad: &CudaSlice<f32>,
11643        m: usize,
11644        in_f: usize,
11645        out0: usize,
11646        out1: usize,
11647        row_bytes: usize,
11648    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11649        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11650        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11651        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11652        let f = self.func(match Self::batched_mcols(m) {
11653            2 => "qmatvec_q8_0_mmvq_fused2_b2",
11654            4 => "qmatvec_q8_0_mmvq_fused2_b4",
11655            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
11656            _ => "qmatvec_q8_0_mmvq_fused2_b8",
11657        });
11658        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11659        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11660        let cfg = LaunchConfig {
11661            grid_dim: (nb0 + nb1, 1, 1),
11662            block_dim: (32, ROWS_PER_BLOCK, 1),
11663            shared_mem_bytes: 0,
11664        };
11665        let (inf, o0, o1, mi, rbl) = (
11666            in_f as i32,
11667            out0 as i32,
11668            out1 as i32,
11669            m as i32,
11670            row_bytes as i64,
11671        );
11672        let __s_b = self.gpu.stream();
11673        let mut b = __s_b.launch_builder(&f);
11674        b.arg(b0)
11675            .arg(b1)
11676            .arg(aq)
11677            .arg(ad)
11678            .arg(&mut y0)
11679            .arg(&mut y1)
11680            .arg(&inf)
11681            .arg(&o0)
11682            .arg(&o1)
11683            .arg(&mi)
11684            .arg(&rbl);
11685        unsafe {
11686            b.launch(cfg)?;
11687        }
11688        Ok((y0, y1))
11689    }
11690
11691    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
11692    /// q8_1 quant of the [m, in_f] activation), no env gating.
11693    #[allow(clippy::too_many_arguments)]
11694    pub fn qmatvec_q8_fused2_t_raw(
11695        &self,
11696        b0: &CudaSlice<u8>,
11697        b1: &CudaSlice<u8>,
11698        x: &CudaSlice<f32>,
11699        m: usize,
11700        in_f: usize,
11701        out0: usize,
11702        out1: usize,
11703        row_bytes: usize,
11704    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11705        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11706        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
11707    }
11708
11709    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
11710    /// `matmul_q8_fused2_t` with three ranges.
11711    #[allow(clippy::too_many_arguments)]
11712    pub fn matmul_q8_fused3_t(
11713        &self,
11714        w0: &crate::model::GpuTensor,
11715        w1: &crate::model::GpuTensor,
11716        w2: &crate::model::GpuTensor,
11717        aq: &CudaSlice<i8>,
11718        ad: &CudaSlice<f32>,
11719        m: usize,
11720    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11721    {
11722        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11723            return Ok(None);
11724        }
11725        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11726            return Ok(Some(self.e4m3_fused3_t_core(
11727                p0.0,
11728                p1.0,
11729                p2.0,
11730                aq,
11731                ad,
11732                m,
11733                w0.in_features(),
11734                p0.1,
11735                p1.1,
11736                p2.1,
11737                p0.2,
11738                p0.3,
11739                p1.3,
11740                p2.3,
11741            )?));
11742        }
11743        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11744            return Ok(None);
11745        };
11746        Ok(Some(self.q8_fused3_t_core(
11747            p0.0,
11748            p1.0,
11749            p2.0,
11750            aq,
11751            ad,
11752            m,
11753            w0.in_features(),
11754            p0.1,
11755            p1.1,
11756            p2.1,
11757            p0.2,
11758        )?))
11759    }
11760
11761    #[allow(clippy::too_many_arguments)]
11762    fn q8_fused3_t_core(
11763        &self,
11764        b0: &CudaSlice<u8>,
11765        b1: &CudaSlice<u8>,
11766        b2: &CudaSlice<u8>,
11767        aq: &CudaSlice<i8>,
11768        ad: &CudaSlice<f32>,
11769        m: usize,
11770        in_f: usize,
11771        out0: usize,
11772        out1: usize,
11773        out2: usize,
11774        row_bytes: usize,
11775    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11776        const ROWS_PER_BLOCK: u32 = 4;
11777        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11778        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11779        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11780        let f = self.func(if Self::batched_mcols(m) == 2 {
11781            "qmatvec_q8_0_mmvq_fused3_b2"
11782        } else {
11783            "qmatvec_q8_0_mmvq_fused3_b4"
11784        });
11785        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11786        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11787        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
11788        let cfg = LaunchConfig {
11789            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11790            block_dim: (32, ROWS_PER_BLOCK, 1),
11791            shared_mem_bytes: 0,
11792        };
11793        let (inf, o0, o1, o2, mi, rbl) = (
11794            in_f as i32,
11795            out0 as i32,
11796            out1 as i32,
11797            out2 as i32,
11798            m as i32,
11799            row_bytes as i64,
11800        );
11801        let __s_b = self.gpu.stream();
11802        let mut b = __s_b.launch_builder(&f);
11803        b.arg(b0)
11804            .arg(b1)
11805            .arg(b2)
11806            .arg(aq)
11807            .arg(ad)
11808            .arg(&mut y0)
11809            .arg(&mut y1)
11810            .arg(&mut y2)
11811            .arg(&inf)
11812            .arg(&o0)
11813            .arg(&o1)
11814            .arg(&o2)
11815            .arg(&mi)
11816            .arg(&rbl);
11817        unsafe {
11818            b.launch(cfg)?;
11819        }
11820        Ok((y0, y1, y2))
11821    }
11822
11823    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
11824    #[allow(clippy::too_many_arguments)]
11825    pub fn qmatvec_q8_fused3_t_raw(
11826        &self,
11827        b0: &CudaSlice<u8>,
11828        b1: &CudaSlice<u8>,
11829        b2: &CudaSlice<u8>,
11830        x: &CudaSlice<f32>,
11831        m: usize,
11832        in_f: usize,
11833        out0: usize,
11834        out1: usize,
11835        out2: usize,
11836        row_bytes: usize,
11837    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11838        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11839        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
11840    }
11841
11842    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
11843    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
11844    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
11845    pub fn q8_ffn_fuse2_on(&self) -> bool {
11846        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11847        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
11848    }
11849
11850    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
11851    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
11852    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
11853    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
11854    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
11855    #[allow(clippy::type_complexity)]
11856    fn q8_fused_params<'w, const N: usize>(
11857        &self,
11858        ws: &[&'w crate::model::GpuTensor; N],
11859    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
11860        use crate::model::GpuTensor;
11861        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
11862            return None;
11863        }
11864        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
11865            return None;
11866        }
11867        let in_f = ws[0].in_features();
11868        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
11869        for (i, w) in ws.iter().enumerate() {
11870            match w {
11871                GpuTensor::Quant {
11872                    bytes,
11873                    qtype,
11874                    row_bytes,
11875                    scale,
11876                    ..
11877                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
11878                    out[i] = Some((bytes, w.out_features(), *row_bytes))
11879                }
11880                _ => return None,
11881            }
11882        }
11883        Some(out.map(|o| o.unwrap()))
11884    }
11885
11886    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
11887    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
11888    pub fn e4m3_dual_on(&self) -> bool {
11889        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11890        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
11891    }
11892
11893    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
11894    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
11895    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
11896    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
11897    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
11898    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
11899    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
11900    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
11901    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
11902    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
11903    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
11904    #[allow(clippy::type_complexity)]
11905    fn e4m3_fused_params<'w, const N: usize>(
11906        &self,
11907        ws: &[&'w crate::model::GpuTensor; N],
11908    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
11909        use crate::model::GpuTensor;
11910        if !self.e4m3_dual_on() {
11911            return None;
11912        }
11913        let in_f = ws[0].in_features();
11914        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
11915        for (i, w) in ws.iter().enumerate() {
11916            match w {
11917                GpuTensor::Quant {
11918                    bytes,
11919                    qtype,
11920                    row_bytes,
11921                    scale,
11922                    rp,
11923                    rp4,
11924                    ..
11925                } if *qtype == QT_F8_E4M3
11926                    && w.in_features() == in_f
11927                    && *row_bytes == in_f
11928                    && !*rp
11929                    && rp4.is_none() =>
11930                {
11931                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
11932                }
11933                _ => return None,
11934            }
11935        }
11936        Some(out.map(|o| o.unwrap()))
11937    }
11938
11939    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
11940    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
11941    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
11942    #[allow(clippy::too_many_arguments)]
11943    fn e4m3_fused2_core(
11944        &self,
11945        b0: &CudaSlice<u8>,
11946        b1: &CudaSlice<u8>,
11947        aq: &CudaSlice<i8>,
11948        ad: &CudaSlice<f32>,
11949        in_f: usize,
11950        out0: usize,
11951        out1: usize,
11952        row_bytes: usize,
11953        ws0: f32,
11954        ws1: f32,
11955    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11956        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11957        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11958        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11959        let f = self.func("qmatvec_e4m3_mmvq_fused2");
11960        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11961        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11962        let cfg = LaunchConfig {
11963            grid_dim: (nb0 + nb1, 1, 1),
11964            block_dim: (32, ROWS_PER_BLOCK, 1),
11965            shared_mem_bytes: 0,
11966        };
11967        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
11968        let __s_b = self.gpu.stream();
11969        let mut b = __s_b.launch_builder(&f);
11970        b.arg(b0)
11971            .arg(b1)
11972            .arg(aq)
11973            .arg(ad)
11974            .arg(&mut y0)
11975            .arg(&mut y1)
11976            .arg(&inf)
11977            .arg(&o0)
11978            .arg(&o1)
11979            .arg(&rbl)
11980            .arg(&ws0)
11981            .arg(&ws1);
11982        unsafe {
11983            b.launch(cfg)?;
11984        }
11985        Ok((y0, y1))
11986    }
11987
11988    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
11989    #[allow(clippy::too_many_arguments)]
11990    fn e4m3_fused3_core(
11991        &self,
11992        b0: &CudaSlice<u8>,
11993        b1: &CudaSlice<u8>,
11994        b2: &CudaSlice<u8>,
11995        aq: &CudaSlice<i8>,
11996        ad: &CudaSlice<f32>,
11997        in_f: usize,
11998        out0: usize,
11999        out1: usize,
12000        out2: usize,
12001        row_bytes: usize,
12002        ws0: f32,
12003        ws1: f32,
12004        ws2: f32,
12005    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12006        const ROWS_PER_BLOCK: u32 = 4;
12007        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12008        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12009        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12010        let f = self.func("qmatvec_e4m3_mmvq_fused3");
12011        let mut y0 = self.alloc_uninit::<f32>(out0)?;
12012        let mut y1 = self.alloc_uninit::<f32>(out1)?;
12013        let mut y2 = self.alloc_uninit::<f32>(out2)?;
12014        let cfg = LaunchConfig {
12015            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12016            block_dim: (32, ROWS_PER_BLOCK, 1),
12017            shared_mem_bytes: 0,
12018        };
12019        let (inf, o0, o1, o2, rbl) = (
12020            in_f as i32,
12021            out0 as i32,
12022            out1 as i32,
12023            out2 as i32,
12024            row_bytes as i64,
12025        );
12026        let __s_b = self.gpu.stream();
12027        let mut b = __s_b.launch_builder(&f);
12028        b.arg(b0)
12029            .arg(b1)
12030            .arg(b2)
12031            .arg(aq)
12032            .arg(ad)
12033            .arg(&mut y0)
12034            .arg(&mut y1)
12035            .arg(&mut y2)
12036            .arg(&inf)
12037            .arg(&o0)
12038            .arg(&o1)
12039            .arg(&o2)
12040            .arg(&rbl)
12041            .arg(&ws0)
12042            .arg(&ws1)
12043            .arg(&ws2);
12044        unsafe {
12045            b.launch(cfg)?;
12046        }
12047        Ok((y0, y1, y2))
12048    }
12049
12050    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
12051    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
12052    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
12053    #[allow(clippy::too_many_arguments)]
12054    fn e4m3_fused2_t_core(
12055        &self,
12056        b0: &CudaSlice<u8>,
12057        b1: &CudaSlice<u8>,
12058        aq: &CudaSlice<i8>,
12059        ad: &CudaSlice<f32>,
12060        m: usize,
12061        in_f: usize,
12062        out0: usize,
12063        out1: usize,
12064        row_bytes: usize,
12065        ws0: f32,
12066        ws1: f32,
12067    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12068        const ROWS_PER_BLOCK: u32 = 4;
12069        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12070        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12071        let f = self.func(match Self::batched_mcols(m) {
12072            2 => "qmatvec_e4m3_mmvq_fused2_b2",
12073            4 => "qmatvec_e4m3_mmvq_fused2_b4",
12074            _ => "qmatvec_e4m3_mmvq_fused2_b8",
12075        });
12076        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12077        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12078        let cfg = LaunchConfig {
12079            grid_dim: (nb0 + nb1, 1, 1),
12080            block_dim: (32, ROWS_PER_BLOCK, 1),
12081            shared_mem_bytes: 0,
12082        };
12083        let (inf, o0, o1, mi, rbl) = (
12084            in_f as i32,
12085            out0 as i32,
12086            out1 as i32,
12087            m as i32,
12088            row_bytes as i64,
12089        );
12090        let __s_b = self.gpu.stream();
12091        let mut b = __s_b.launch_builder(&f);
12092        b.arg(b0)
12093            .arg(b1)
12094            .arg(aq)
12095            .arg(ad)
12096            .arg(&mut y0)
12097            .arg(&mut y1)
12098            .arg(&inf)
12099            .arg(&o0)
12100            .arg(&o1)
12101            .arg(&mi)
12102            .arg(&rbl);
12103        unsafe {
12104            b.launch(cfg)?;
12105        }
12106        if ws0 != 1.0 {
12107            self.scale_inplace(&mut y0, ws0, m * out0)?;
12108        }
12109        if ws1 != 1.0 {
12110            self.scale_inplace(&mut y1, ws1, m * out1)?;
12111        }
12112        Ok((y0, y1))
12113    }
12114
12115    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
12116    #[allow(clippy::too_many_arguments)]
12117    fn e4m3_fused3_t_core(
12118        &self,
12119        b0: &CudaSlice<u8>,
12120        b1: &CudaSlice<u8>,
12121        b2: &CudaSlice<u8>,
12122        aq: &CudaSlice<i8>,
12123        ad: &CudaSlice<f32>,
12124        m: usize,
12125        in_f: usize,
12126        out0: usize,
12127        out1: usize,
12128        out2: usize,
12129        row_bytes: usize,
12130        ws0: f32,
12131        ws1: f32,
12132        ws2: f32,
12133    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12134        const ROWS_PER_BLOCK: u32 = 4;
12135        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12136        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12137        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12138        let f = self.func(if Self::batched_mcols(m) == 2 {
12139            "qmatvec_e4m3_mmvq_fused3_b2"
12140        } else {
12141            "qmatvec_e4m3_mmvq_fused3_b4"
12142        });
12143        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12144        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12145        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12146        let cfg = LaunchConfig {
12147            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12148            block_dim: (32, ROWS_PER_BLOCK, 1),
12149            shared_mem_bytes: 0,
12150        };
12151        let (inf, o0, o1, o2, mi, rbl) = (
12152            in_f as i32,
12153            out0 as i32,
12154            out1 as i32,
12155            out2 as i32,
12156            m as i32,
12157            row_bytes as i64,
12158        );
12159        let __s_b = self.gpu.stream();
12160        let mut b = __s_b.launch_builder(&f);
12161        b.arg(b0)
12162            .arg(b1)
12163            .arg(b2)
12164            .arg(aq)
12165            .arg(ad)
12166            .arg(&mut y0)
12167            .arg(&mut y1)
12168            .arg(&mut y2)
12169            .arg(&inf)
12170            .arg(&o0)
12171            .arg(&o1)
12172            .arg(&o2)
12173            .arg(&mi)
12174            .arg(&rbl);
12175        unsafe {
12176            b.launch(cfg)?;
12177        }
12178        if ws0 != 1.0 {
12179            self.scale_inplace(&mut y0, ws0, m * out0)?;
12180        }
12181        if ws1 != 1.0 {
12182            self.scale_inplace(&mut y1, ws1, m * out1)?;
12183        }
12184        if ws2 != 1.0 {
12185            self.scale_inplace(&mut y2, ws2, m * out2)?;
12186        }
12187        Ok((y0, y1, y2))
12188    }
12189
12190    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
12191    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
12192    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
12193    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
12194    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
12195    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
12196    ///
12197    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
12198    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
12199    pub fn qmatvec_e4m3_blk_mmvq(
12200        &self,
12201        bytes: &CudaSlice<u8>,
12202        aq: &CudaSlice<i8>,
12203        ad: &CudaSlice<f32>,
12204        scales: &CudaSlice<f32>,
12205        m: usize,
12206        in_f: usize,
12207        out_f: usize,
12208        row_bytes: usize,
12209        scale_cols: usize,
12210    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12211        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
12212        self.qmatvec_e4m3_blk_mmvq_into(
12213            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
12214        )?;
12215        Ok(y)
12216    }
12217
12218    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
12219    #[allow(clippy::too_many_arguments)]
12220    pub fn qmatvec_e4m3_blk_mmvq_into(
12221        &self,
12222        bytes: &CudaSlice<u8>,
12223        aq: &CudaSlice<i8>,
12224        ad: &CudaSlice<f32>,
12225        scales: &CudaSlice<f32>,
12226        m: usize,
12227        in_f: usize,
12228        out_f: usize,
12229        row_bytes: usize,
12230        scale_cols: usize,
12231        y: &mut CudaSlice<f32>,
12232    ) -> Result<(), Box<dyn std::error::Error>> {
12233        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12234        let f = self.func("qmatvec_e4m3_blk_mmvq");
12235        let cfg = LaunchConfig {
12236            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
12237            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
12238            shared_mem_bytes: 0,                // warp-only reduce
12239        };
12240        let (inf, outf, mi, rb, sc) = (
12241            in_f as i32,
12242            out_f as i32,
12243            m as i32,
12244            row_bytes as i64,
12245            scale_cols as i32,
12246        );
12247        let __s_b = self.gpu.stream();
12248        let mut b = __s_b.launch_builder(&f);
12249        b.arg(bytes)
12250            .arg(aq)
12251            .arg(ad)
12252            .arg(scales)
12253            .arg(&mut *y)
12254            .arg(&inf)
12255            .arg(&outf)
12256            .arg(&mi)
12257            .arg(&rb)
12258            .arg(&sc);
12259        unsafe {
12260            b.launch(cfg)?;
12261        }
12262        Ok(())
12263    }
12264
12265    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
12266    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
12267    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
12268    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
12269    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
12270    #[allow(clippy::too_many_arguments)]
12271    pub fn qmatvec_e4m3_blk_mmvq_batched(
12272        &self,
12273        bytes: &CudaSlice<u8>,
12274        aq: &CudaSlice<i8>,
12275        ad: &CudaSlice<f32>,
12276        scales: &CudaSlice<f32>,
12277        m: usize,
12278        in_f: usize,
12279        out_f: usize,
12280        row_bytes: usize,
12281        scale_cols: usize,
12282        mcols: usize,
12283    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12284        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12285        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
12286        let name = match mcols {
12287            2 => "qmatvec_e4m3_blk_mmvq_b2",
12288            4 => "qmatvec_e4m3_blk_mmvq_b4",
12289            8 => "qmatvec_e4m3_blk_mmvq_b8",
12290            16 => "qmatvec_e4m3_blk_mmvq_b16",
12291            _ => {
12292                return Err(
12293                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
12294                );
12295            }
12296        };
12297        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12298        let f = self.func(name);
12299        let cfg = LaunchConfig {
12300            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
12301            block_dim: (32, ROWS_PER_BLOCK, 1),
12302            shared_mem_bytes: 0,
12303        };
12304        let (inf, outf, mi, rb, sc) = (
12305            in_f as i32,
12306            out_f as i32,
12307            m as i32,
12308            row_bytes as i64,
12309            scale_cols as i32,
12310        );
12311        let __s_b = self.gpu.stream();
12312        let mut b = __s_b.launch_builder(&f);
12313        b.arg(bytes)
12314            .arg(aq)
12315            .arg(ad)
12316            .arg(scales)
12317            .arg(&mut y)
12318            .arg(&inf)
12319            .arg(&outf)
12320            .arg(&mi)
12321            .arg(&rb)
12322            .arg(&sc);
12323        unsafe {
12324            b.launch(cfg)?;
12325        }
12326        Ok(y)
12327    }
12328
12329    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
12330    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
12331    #[allow(clippy::too_many_arguments)]
12332    pub fn qmatvec_e4m3_blk_batched_raw(
12333        &self,
12334        bytes: &CudaSlice<u8>,
12335        x: &CudaSlice<f32>,
12336        scales: &CudaSlice<f32>,
12337        m: usize,
12338        in_f: usize,
12339        out_f: usize,
12340        row_bytes: usize,
12341        scale_cols: usize,
12342        mcols: usize,
12343    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12344        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12345        self.qmatvec_e4m3_blk_mmvq_batched(
12346            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
12347        )
12348    }
12349
12350    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
12351    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
12352    #[allow(clippy::too_many_arguments)]
12353    pub fn qmatvec_e4m3_blk_mmvq_raw(
12354        &self,
12355        bytes: &CudaSlice<u8>,
12356        x: &CudaSlice<f32>,
12357        scales: &CudaSlice<f32>,
12358        m: usize,
12359        in_f: usize,
12360        out_f: usize,
12361        row_bytes: usize,
12362        scale_cols: usize,
12363    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12364        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12365        self.qmatvec_e4m3_blk_mmvq(
12366            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
12367        )
12368    }
12369
12370    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
12371    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
12372    #[allow(clippy::too_many_arguments)]
12373    pub fn qmatvec_e4m3_fused2_raw(
12374        &self,
12375        b0: &CudaSlice<u8>,
12376        b1: &CudaSlice<u8>,
12377        x: &CudaSlice<f32>,
12378        in_f: usize,
12379        out0: usize,
12380        out1: usize,
12381        row_bytes: usize,
12382        ws0: f32,
12383        ws1: f32,
12384    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12385        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12386        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
12387    }
12388
12389    #[allow(clippy::too_many_arguments)]
12390    pub fn qmatvec_e4m3_fused3_raw(
12391        &self,
12392        b0: &CudaSlice<u8>,
12393        b1: &CudaSlice<u8>,
12394        b2: &CudaSlice<u8>,
12395        x: &CudaSlice<f32>,
12396        in_f: usize,
12397        out0: usize,
12398        out1: usize,
12399        out2: usize,
12400        row_bytes: usize,
12401        ws0: f32,
12402        ws1: f32,
12403        ws2: f32,
12404    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12405        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12406        self.e4m3_fused3_core(
12407            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12408        )
12409    }
12410
12411    #[allow(clippy::too_many_arguments)]
12412    pub fn qmatvec_e4m3_fused2_t_raw(
12413        &self,
12414        b0: &CudaSlice<u8>,
12415        b1: &CudaSlice<u8>,
12416        x: &CudaSlice<f32>,
12417        m: usize,
12418        in_f: usize,
12419        out0: usize,
12420        out1: usize,
12421        row_bytes: usize,
12422        ws0: f32,
12423        ws1: f32,
12424    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12425        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12426        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
12427    }
12428
12429    #[allow(clippy::too_many_arguments)]
12430    pub fn qmatvec_e4m3_fused3_t_raw(
12431        &self,
12432        b0: &CudaSlice<u8>,
12433        b1: &CudaSlice<u8>,
12434        b2: &CudaSlice<u8>,
12435        x: &CudaSlice<f32>,
12436        m: usize,
12437        in_f: usize,
12438        out0: usize,
12439        out1: usize,
12440        out2: usize,
12441        row_bytes: usize,
12442        ws0: f32,
12443        ws1: f32,
12444        ws2: f32,
12445    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12446        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12447        self.e4m3_fused3_t_core(
12448            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12449        )
12450    }
12451
12452    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
12453    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
12454    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
12455    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
12456    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
12457    ///
12458    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
12459    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
12460    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
12461    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
12462    fn try_e4m3_blk_pre(
12463        &self,
12464        w: &crate::model::GpuTensor,
12465        aq: &CudaSlice<i8>,
12466        ad: &CudaSlice<f32>,
12467        m: usize,
12468    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12469        use crate::model::GpuTensor;
12470        if let GpuTensor::Quant {
12471            bytes,
12472            qtype,
12473            row_bytes,
12474            blk: Some(g),
12475            ..
12476        } = w
12477        {
12478            if *qtype == QT_F8_E4M3_BLK {
12479                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
12480                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
12481                // below, so the decode-exactness contract is preserved at every width. Gated by
12482                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
12483                // one rollback door covers every dtype's batched tier.
12484                if (2..=16).contains(&m)
12485                    && std::env::var("MEMRA_NO_BATCHED").is_err()
12486                    && (m <= 4 || Self::b8_enabled())
12487                {
12488                    let mcols = Self::batched_mcols(m);
12489                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
12490                        bytes,
12491                        aq,
12492                        ad,
12493                        &g.scales,
12494                        m,
12495                        w.in_features(),
12496                        w.out_features(),
12497                        *row_bytes,
12498                        g.cols,
12499                        mcols,
12500                    )?));
12501                }
12502                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
12503                    bytes,
12504                    aq,
12505                    ad,
12506                    &g.scales,
12507                    m,
12508                    w.in_features(),
12509                    w.out_features(),
12510                    *row_bytes,
12511                    g.cols,
12512                )?));
12513            }
12514        }
12515        Ok(None)
12516    }
12517
12518    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
12519    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
12520    ///
12521    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
12522    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
12523    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
12524    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
12525    /// prefill keeps the floor's arithmetic and the floor's kernels.
12526    ///
12527    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
12528    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
12529    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
12530    /// (projection, prefill call) and frees immediately.
12531    ///
12532    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
12533    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
12534    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
12535    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
12536    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
12537    /// single-variable comparison instead of a two-variable one.
12538    ///
12539    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
12540    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
12541    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
12542    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
12543    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
12544    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
12545    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
12546    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
12547    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
12548    ///
12549    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
12550    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
12551    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
12552    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
12553    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
12554    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
12555    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
12556    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
12557    /// because v2's denominator had its slab already resident while this class's floor must build it
12558    /// every call; same tile, opposite sign, because the question changed.
12559    ///
12560    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
12561    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
12562    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
12563    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
12564    fn try_e4m3_blk_prefill(
12565        &self,
12566        w: &crate::model::GpuTensor,
12567        x: &CudaSlice<f32>,
12568        m: usize,
12569    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12570        use crate::model::GpuTensor;
12571        let GpuTensor::Quant {
12572            bytes,
12573            qtype,
12574            blk: Some(g),
12575            ..
12576        } = w
12577        else {
12578            return Ok(None);
12579        };
12580        if *qtype != QT_F8_E4M3_BLK {
12581            return Ok(None);
12582        }
12583        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
12584        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
12585        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
12586        // through to the dequant below when they do, never silently produce nothing.
12587        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
12588            return Ok(Some(y));
12589        }
12590        let (in_f, out_f) = (w.in_features(), w.out_features());
12591        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
12592        let tmp = GpuTensor::Quant {
12593            bytes: slab,
12594            qtype: QT_Q8_0,
12595            row_bytes: in_f / 32 * 34,
12596            ne: vec![in_f as u64, out_f as u64],
12597            scale: 1.0,
12598            rp: false,
12599            #[cfg(memra_cutlass)]
12600            cutlass: None,
12601            fp8: None,
12602            blk: None,
12603            f16: None,
12604            rp4: None,
12605        };
12606        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
12607        Ok(Some(self.matmul(&tmp, x, m)?))
12608    }
12609
12610    pub fn matmul_pre_noscale(
12611        &self,
12612        w: &crate::model::GpuTensor,
12613        aq: &CudaSlice<i8>,
12614        ad: &CudaSlice<f32>,
12615        m: usize,
12616    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
12617        use crate::model::GpuTensor;
12618        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
12619        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
12620        // rather than let the tail below refuse and cost the caller a re-dispatch.
12621        if m == 1 {
12622            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
12623                return Ok(Some((y, 1.0)));
12624            }
12625        }
12626        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
12627        if m != 1 || !self.uses_q8_1_fast(w) {
12628            return Ok(None);
12629        }
12630        let in_f = w.in_features();
12631        let out_f = w.out_features();
12632        let (bytes, qtype, row_bytes, scale, rp) = match w {
12633            GpuTensor::Quant {
12634                bytes,
12635                qtype,
12636                row_bytes,
12637                scale,
12638                rp,
12639                ..
12640            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12641            _ => return Ok(None),
12642        };
12643        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
12644        if self.mmvq_supports(qtype) {
12645            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
12646            let (mbytes, mrp) = match w {
12647                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12648                _ => (bytes, rp),
12649            };
12650            let y = self.qmatvec_mmvq(
12651                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
12652            )?;
12653            return Ok(Some((y, scale)));
12654        }
12655        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
12656        let name = match qtype {
12657            QT_Q8_0 => "qmatvec_q8_0_dp4a",
12658            QT_Q4_K => "qmatvec_q4_K_dp4a",
12659            QT_Q6_K => "qmatvec_q6_K_dp4a",
12660            QT_Q5_K => "qmatvec_q5_K_dp4a",
12661            QT_Q3_K => "qmatvec_q3_K_dp4a",
12662            QT_NVFP4 => {
12663                if rp {
12664                    "qmatvec_nvfp4_dp4a_rp"
12665                } else {
12666                    "qmatvec_nvfp4_dp4a"
12667                }
12668            }
12669            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
12670            _ => return Ok(None),
12671        };
12672        let f = self.func(name);
12673        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12674        let cfg = LaunchConfig {
12675            grid_dim: (out_f as u32, m as u32, 1),
12676            block_dim: (128, 1, 1),
12677            shared_mem_bytes: 0,
12678        };
12679        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12680        let __s_b = self.gpu.stream();
12681        let mut b = __s_b.launch_builder(&f);
12682        b.arg(bytes)
12683            .arg(aq)
12684            .arg(ad)
12685            .arg(&mut y)
12686            .arg(&inf)
12687            .arg(&outf)
12688            .arg(&mi)
12689            .arg(&rb);
12690        unsafe {
12691            b.launch(cfg)?;
12692        }
12693        Ok(Some((y, scale)))
12694    }
12695
12696    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
12697    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
12698    pub fn mmvq_supports(&self, qtype: i32) -> bool {
12699        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
12700        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
12701        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
12702        // is a pure function of the dtype — the decode-parity law holds under every env.
12703        if qtype == QT_F8_E4M3 {
12704            return true;
12705        }
12706        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12707            return false;
12708        }
12709        matches!(
12710            qtype,
12711            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
12712        )
12713    }
12714
12715    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
12716    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
12717    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
12718    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
12719    pub fn qmatvec_mmvq(
12720        &self,
12721        bytes: &CudaSlice<u8>,
12722        aq: &CudaSlice<i8>,
12723        ad: &CudaSlice<f32>,
12724        m: usize,
12725        in_f: usize,
12726        out_f: usize,
12727        qtype: i32,
12728        row_bytes: usize,
12729        scale: f32,
12730        rp: bool,
12731    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12732        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
12733        self.qmatvec_mmvq_into(
12734            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
12735        )?;
12736        Ok(y)
12737    }
12738
12739    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
12740    #[allow(clippy::too_many_arguments)]
12741    pub fn qmatvec_mmvq_into(
12742        &self,
12743        bytes: &CudaSlice<u8>,
12744        aq: &CudaSlice<i8>,
12745        ad: &CudaSlice<f32>,
12746        m: usize,
12747        in_f: usize,
12748        out_f: usize,
12749        qtype: i32,
12750        row_bytes: usize,
12751        scale: f32,
12752        rp: bool,
12753        y: &mut CudaSlice<f32>,
12754    ) -> Result<(), Box<dyn std::error::Error>> {
12755        debug_assert!(y.len() >= m * out_f);
12756        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12757        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
12758        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
12759        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
12760        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
12761        if qtype == QT_Q8_0
12762            && rp
12763            && m == 1
12764            && out_f >= 64
12765            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
12766            && {
12767                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12768                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
12769            }
12770        {
12771            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
12772            let cfg = LaunchConfig {
12773                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
12774                block_dim: (32, 2, 1),
12775                shared_mem_bytes: 0,
12776            };
12777            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
12778            let __s_b = self.gpu.stream();
12779            let mut b = __s_b.launch_builder(&f);
12780            b.arg(bytes)
12781                .arg(aq)
12782                .arg(ad)
12783                .arg(&mut *y)
12784                .arg(&inf)
12785                .arg(&outf)
12786                .arg(&mi)
12787                .arg(&rb);
12788            unsafe {
12789                b.launch(cfg)?;
12790            }
12791            if scale != 1.0 {
12792                self.scale_inplace(y, scale, out_f)?;
12793            }
12794            return Ok(());
12795        }
12796        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
12797        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
12798        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
12799        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
12800        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
12801        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
12802        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
12803        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
12804        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
12805            2
12806        } else {
12807            1
12808        };
12809        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
12810        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
12811        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
12812        // valid-window interleaved, bit-identical per row — same dot program).
12813        if m == 1 && qtype == QT_Q4_0 {
12814            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
12815            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
12816            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
12817            mr = *Q40MR.get_or_init(|| {
12818                std::env::var("MEMRA_Q40_MR")
12819                    .ok()
12820                    .and_then(|v| v.parse().ok())
12821                    .unwrap_or(1)
12822            });
12823        }
12824        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
12825        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
12826        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
12827        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
12828        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
12829        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
12830        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
12831        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
12832        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
12833        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
12834        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
12835        let q5_force = q5_mode.as_deref() == Some("2");
12836        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
12837        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
12838        let q5_il = qtype == QT_Q5_K
12839            && m == 1
12840            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
12841        if q5_il && !q5_force && out_f > 65536 {
12842            mr = 1;
12843        }
12844        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
12845        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
12846        if qtype == QT_Q4_0 && rp && mr != 1 {
12847            mr = 2;
12848        }
12849        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
12850        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
12851        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
12852        if qtype == QT_Q8_0 && rp {
12853            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
12854            mr = *Q80MR.get_or_init(|| {
12855                std::env::var("MEMRA_Q80_MR")
12856                    .ok()
12857                    .and_then(|v| v.parse().ok())
12858                    .unwrap_or(1)
12859            });
12860        }
12861        let name = match (qtype, mr, rp) {
12862            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
12863            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
12864            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
12865            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
12866            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
12867            (QT_Q5_K, 2, _) => {
12868                if q5_il {
12869                    "qmatvec_q5_K_mmvq_mr2_il"
12870                } else {
12871                    "qmatvec_q5_K_mmvq_mr2"
12872                }
12873            }
12874            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
12875            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
12876            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
12877            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
12878            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
12879            (QT_Q8_0, _, true)
12880                if in_f % 1024 == 0 && {
12881                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12882                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
12883                } =>
12884            {
12885                "qmatvec_q8_0_mmvq_rpca"
12886            }
12887            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
12888            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
12889            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
12890            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
12891            // reach a GGUF-layout kernel or vice versa.
12892            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
12893            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
12894            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
12895            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
12896            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
12897            (QT_Q5_K, _, _) => {
12898                if q5_il {
12899                    "qmatvec_q5_K_mmvq_il"
12900                } else {
12901                    "qmatvec_q5_K_mmvq"
12902                }
12903            }
12904            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
12905            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
12906            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
12907            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
12908        };
12909        let f = self.func(name);
12910        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
12911        let rows_per_block = ROWS_PER_BLOCK * mr;
12912        let cfg = LaunchConfig {
12913            grid_dim: (
12914                (out_f as u32 + rows_per_block - 1) / rows_per_block,
12915                m as u32,
12916                1,
12917            ),
12918            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
12919            shared_mem_bytes: 0,                // warp-only reduce at m=1
12920        };
12921        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12922        let __s_b = self.gpu.stream();
12923        let mut b = __s_b.launch_builder(&f);
12924        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
12925        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
12926        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
12927        // weight_scale). Other mmvq kernels keep the 8-arg signature.
12928        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
12929            b.arg(bytes)
12930                .arg(aq)
12931                .arg(ad)
12932                .arg(&mut *y)
12933                .arg(&inf)
12934                .arg(&outf)
12935                .arg(&mi)
12936                .arg(&rb)
12937                .arg(&scale);
12938            unsafe {
12939                b.launch(cfg)?;
12940            }
12941        } else if Self::pdl_on()
12942            && Self::pdl_mmvq_on()
12943            && matches!(
12944                name,
12945                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
12946            )
12947        {
12948            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
12949            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
12950            // names may take this launch (unmarked kernels would read unordered).
12951            {
12952                use cudarc::driver::{DevicePtr, DevicePtrMut};
12953                let s = &self.gpu.stream();
12954                let (pw, _g0) = bytes.device_ptr(s);
12955                let (paq, _g1) = aq.device_ptr(s);
12956                let (pad, _g2) = ad.device_ptr(s);
12957                let (py, _g3) = y.device_ptr_mut(s);
12958                let mut ps = [
12959                    &pw as *const _ as *mut std::ffi::c_void,
12960                    &paq as *const _ as *mut _,
12961                    &pad as *const _ as *mut _,
12962                    &py as *const _ as *mut _,
12963                    &inf as *const _ as *mut _,
12964                    &outf as *const _ as *mut _,
12965                    &mi as *const _ as *mut _,
12966                    &rb as *const _ as *mut _,
12967                ];
12968                unsafe {
12969                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
12970                }
12971            }
12972            if scale != 1.0 {
12973                self.scale_inplace(y, scale, m * out_f)?;
12974            }
12975        } else {
12976            b.arg(bytes)
12977                .arg(aq)
12978                .arg(ad)
12979                .arg(&mut *y)
12980                .arg(&inf)
12981                .arg(&outf)
12982                .arg(&mi)
12983                .arg(&rb);
12984            unsafe {
12985                b.launch(cfg)?;
12986            }
12987            if scale != 1.0 {
12988                self.scale_inplace(y, scale, m * out_f)?;
12989            }
12990        }
12991        Ok(())
12992    }
12993
12994    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
12995    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
12996    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
12997    pub fn qmatvec_mmvq_raw(
12998        &self,
12999        bytes: &CudaSlice<u8>,
13000        x: &CudaSlice<f32>,
13001        m: usize,
13002        in_f: usize,
13003        out_f: usize,
13004        qtype: i32,
13005        row_bytes: usize,
13006        rp: bool,
13007    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13008        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13009        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
13010    }
13011
13012    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
13013    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
13014    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
13015    pub fn batched_supports(&self, qtype: i32) -> bool {
13016        matches!(
13017            qtype,
13018            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
13019        )
13020    }
13021
13022    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
13023    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
13024    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
13025    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
13026    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
13027    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
13028    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
13029    pub fn iq_fast_enabled() -> bool {
13030        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13031        *ON.get_or_init(|| {
13032            std::env::var("MEMRA_IQ_FAST")
13033                .map(|v| v != "0")
13034                .unwrap_or(true)
13035        })
13036    }
13037
13038    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
13039    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
13040    pub fn b8_enabled() -> bool {
13041        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13042        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
13043    }
13044
13045    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
13046    pub fn batched_mcols(m: usize) -> usize {
13047        if m == 2 {
13048            2
13049        } else if m <= 4 {
13050            4
13051        } else if m <= 8 {
13052            8
13053        } else {
13054            16
13055        }
13056    }
13057
13058    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
13059    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
13060    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
13061    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
13062    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
13063        Some(match (qtype, mcols) {
13064            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
13065            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
13066            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
13067            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
13068            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
13069            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
13070            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
13071            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
13072            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
13073            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
13074            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
13075            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
13076            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
13077            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
13078            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
13079            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
13080            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
13081            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
13082            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
13083            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
13084            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
13085            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
13086            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
13087            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
13088            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
13089            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
13090            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
13091            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
13092            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
13093            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
13094            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
13095            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
13096            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
13097            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
13098            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
13099            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
13100            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
13101            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
13102            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
13103            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
13104            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
13105            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
13106            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
13107            _ => return None,
13108        })
13109    }
13110
13111    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
13112    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
13113    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
13114    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
13115    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
13116    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
13117    ///
13118    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
13119    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
13120    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
13121    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
13122    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
13123    /// msweep on all six 27B shapes (2026-07-03):
13124    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
13125    ///          it applies for b4 (-3..-14%), never loses;
13126    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
13127    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
13128    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
13129    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
13130    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
13131    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
13132    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
13133    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
13134    /// b2: in_f>=6144 -> r2, else base.
13135    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
13136    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
13137    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
13138    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
13139    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
13140    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
13141    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
13142    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
13143    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
13144    /// Device SM count (cached) — grid-fill policy input.
13145    pub fn sm_count(&self) -> i32 {
13146        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13147        *SMS.get_or_init(|| {
13148            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13149            self.gpu
13150                .ctx
13151                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13152                .unwrap_or(82)
13153        })
13154    }
13155
13156    pub fn batched_variant(
13157        &self,
13158        _m: usize,
13159        in_f: usize,
13160        out_f: usize,
13161        qtype: i32,
13162        row_bytes: usize,
13163        mcols: usize,
13164        rp: bool,
13165    ) -> &'static str {
13166        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
13167        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
13168        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
13169        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
13170        if qtype == QT_Q8_0 {
13171            return if rp { "rp" } else { "base" };
13172        }
13173        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13174        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
13175            Ok("base") => "base",
13176            Ok("pf") => "pf",
13177            Ok("r2") => "r2",
13178            Ok("r2w8") => "r2w8",
13179            Ok("pfr2") => "pfr2",
13180            Ok("ca") => "ca",
13181            Ok("car2") => "car2",
13182            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
13183            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
13184            Ok("rp") => "rp",
13185            Ok("rpr2") => "rpr2",
13186            Ok("rpr2w8") => "rpr2w8",
13187            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
13188            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
13189            Ok("rpca") => "rpca",
13190            Ok("rpcar2") => "rpcar2",
13191            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
13192            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
13193            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
13194            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
13195            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
13196            // bit-identical to the decode path — measurement corpus ONLY, never auto).
13197            Ok("rpsc") => "rpsc",
13198            Ok("rpms") => "rpms",
13199            Ok("rpmsc") => "rpmsc",
13200            Ok("rpks") => "rpks",
13201            Ok("rpksc") => "rpksc",
13202            _ => "auto",
13203        });
13204        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
13205        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
13206        // shapes qualify; anything else falls back to the register variants.
13207        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
13208        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
13209        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
13210        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
13211        // forced MEMRA_MMVQ_BV values still work).
13212        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13213        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
13214        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
13215        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
13216        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13217        let sms = *SMS.get_or_init(|| {
13218            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13219            self.gpu
13220                .ctx
13221                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13222                .unwrap_or(82)
13223        });
13224        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
13225        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
13226        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
13227        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
13228        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
13229        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
13230        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
13231        // AUTO RULE = the measured winners table (differs from NVFP4's!):
13232        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
13233        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
13234        //     r2 1258us) — kernels kept behind the force seam for the corpus;
13235        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
13236        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
13237        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
13238        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
13239        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
13240        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
13241        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
13242        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
13243        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
13244        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
13245        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
13246        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13247        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
13248            Ok("base") => "base",
13249            Ok("r2") => "r2",
13250            Ok("r2w8") => "r2w8",
13251            _ => "auto",
13252        });
13253        let variant: &'static str = if qtype == QT_Q4_0 {
13254            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
13255            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
13256            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
13257            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13258            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
13259                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
13260                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
13261                // + syncs cost more than the stalls, bank-pad made no difference);
13262                // register load-ahead flat (nvcc already reorders). The b-tier limiter
13263                // is still unidentified — see the jsonl row.
13264                Ok("base") => "base",
13265                Ok("r2") => "r2",
13266                Ok("ms") => "ms",
13267                Ok("sm") => "sm",
13268                Ok("la") => "la",
13269                _ => "auto",
13270            });
13271            let v = if q40 != "auto" {
13272                q40
13273            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
13274                "r2"
13275            } else {
13276                "base"
13277            };
13278            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
13279            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
13280            // and the limiter is the per-column activation load chain (long_scoreboard
13281            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
13282            if rp {
13283                match v {
13284                    "ms" => "r2ms_rp",
13285                    "sm" => "r2sm_rp",
13286                    "la" => "r2la_rp",
13287                    "r2" => "r2_rp",
13288                    _ => "rp",
13289                }
13290            } else if matches!(v, "ms" | "sm" | "la") {
13291                "r2"
13292            } else {
13293                v
13294            }
13295        } else if qtype != QT_NVFP4 && !kq_r2 {
13296            "base"
13297        } else if kq_r2 && rp {
13298            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
13299            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
13300            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
13301            "rp"
13302        } else if kq_r2 {
13303            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
13304            // mcols != 4 forced r2w8 falls to unbounded r2.
13305            if kq_bv != "auto" {
13306                if kq_bv == "r2w8" && mcols != 4 {
13307                    "r2"
13308                } else {
13309                    kq_bv
13310                }
13311            } else if bv != "auto" {
13312                match bv {
13313                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
13314                    "r2w8" | "rpr2w8" => {
13315                        if mcols != 4 {
13316                            "r2"
13317                        } else {
13318                            "r2w8"
13319                        }
13320                    }
13321                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
13322                }
13323            } else {
13324                let blocks = (out_f + 7) / 8;
13325                let waves = blocks as f64 / (7 * sms as usize) as f64;
13326                let filled = blocks >= 4 * sms as usize;
13327                let use_r2 = if qtype == QT_Q4_K {
13328                    filled
13329                } else {
13330                    waves >= 2.0
13331                };
13332                if use_r2 { "r2" } else { "base" }
13333            }
13334        } else if bv != "auto" {
13335            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
13336            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
13337            // unsupported (shape, mcols) combos fall back to pf/r2.
13338            // On rp buffers, forced legacy names map to their rp twins (layout law).
13339            let v = if bv == "r2w8" && mcols == 2 {
13340                "r2"
13341            } else if bv == "ca" && (!ca_ok || mcols == 8) {
13342                "pf"
13343            } else if bv == "car2" && (!ca_ok || mcols == 8) {
13344                "r2"
13345            } else if bv == "pfr2" && mcols == 8 {
13346                "r2"
13347            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
13348                "rpr2"
13349            }
13350            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
13351            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
13352                if mcols == 8 { "rpr2w8" } else { "rpr2" }
13353            } else if bv == "rpcar2" && mcols == 2 {
13354                "rpca"
13355            }
13356            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
13357            // (rpms has no smem and no alignment need — always valid on rp buffers).
13358            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
13359                "rpr2"
13360            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
13361                "rpr2"
13362            } else {
13363                bv
13364            };
13365            if rp {
13366                match v {
13367                    "base" | "pf" | "ca" | "rp" => "rp",
13368                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
13369                    "r2w8" | "rpr2w8" => {
13370                        if mcols == 2 {
13371                            "rpr2"
13372                        } else {
13373                            "rpr2w8"
13374                        }
13375                    }
13376                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
13377                }
13378            } else {
13379                v
13380            }
13381        } else if mcols == 8 {
13382            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
13383            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
13384            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
13385            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
13386            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
13387            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
13388            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
13389            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
13390            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
13391            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
13392            if rp {
13393                if sc_ok { "rpsc" } else { "rpr2w8" }
13394            } else {
13395                "r2w8"
13396            }
13397        } else if mcols >= 4 {
13398            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
13399            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
13400            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
13401            let blocks = (out_f + 7) / 8;
13402            let r7 = 7 * sms as usize;
13403            let r8 = 8 * sms as usize;
13404            let waves = blocks as f64 / r7 as f64;
13405            let filled = blocks >= 4 * sms as usize;
13406            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
13407            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
13408            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
13409            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
13410                // the extra residency drops the INTEGER wave count -> the straggler wave a
13411                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
13412                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
13413                if rp { "rpr2w8" } else { "r2w8" }
13414            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
13415                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
13416                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
13417                if rp { "rpr2" } else { "r2" }
13418            } else {
13419                // fractional straggler-wave window with no crossing, or grid too small to fill
13420                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
13421                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
13422                if rp { "rp" } else { "pf" }
13423            }
13424        } else if in_f >= 6144 {
13425            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
13426            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
13427            // stays.
13428            if rp { "rpr2" } else { "r2" }
13429        } else if rp {
13430            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
13431            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
13432            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
13433            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
13434            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
13435            if sc_ok && waves >= 0.9 && waves <= 1.1 {
13436                "rpsc"
13437            } else {
13438                "rp"
13439            }
13440        } else {
13441            "base"
13442        };
13443        variant
13444    }
13445
13446    pub fn qmatvec_mmvq_batched(
13447        &self,
13448        bytes: &CudaSlice<u8>,
13449        aq: &CudaSlice<i8>,
13450        ad: &CudaSlice<f32>,
13451        m: usize,
13452        in_f: usize,
13453        out_f: usize,
13454        qtype: i32,
13455        row_bytes: usize,
13456        mcols: usize,
13457        scale: f32,
13458        rp: bool,
13459    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13460        const ROWS_PER_BLOCK: u32 = 4;
13461        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
13462        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
13463        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
13464        // weight keeps its rp-layout kernel family regardless of the override.
13465        let forced: Option<&'static str> = {
13466            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
13467            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
13468                .as_deref()
13469                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
13470        };
13471        let variant = match forced {
13472            Some(v) if !rp || v.contains("rp") => v,
13473            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
13474        };
13475        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
13476            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
13477        })?;
13478        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
13479        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
13480        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
13481        let variant = if mcols == 16 {
13482            if rp { "rp" } else { "base" }
13483        } else {
13484            variant
13485        };
13486        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
13487        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
13488        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
13489        // per-(token,row) chain (columns c >= m never execute in either form) ->
13490        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
13491        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
13492        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13493        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
13494        if b567
13495            && qtype == QT_NVFP4
13496            && rp
13497            && mcols == 8
13498            && (5..=7).contains(&m)
13499            && matches!(variant, "rpsc" | "rpr2w8")
13500        {
13501            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
13502            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
13503            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13504            let cfg = LaunchConfig {
13505                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13506                block_dim: (32, ROWS_PER_BLOCK, 1),
13507                shared_mem_bytes: 0,
13508            };
13509            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13510            let __s_b = self.gpu.stream();
13511            let mut b = __s_b.launch_builder(&f);
13512            b.arg(bytes)
13513                .arg(aq)
13514                .arg(ad)
13515                .arg(&mut y)
13516                .arg(&inf)
13517                .arg(&outf)
13518                .arg(&mi)
13519                .arg(&rb);
13520            unsafe {
13521                b.launch(cfg)?;
13522            }
13523            if scale != 1.0 {
13524                self.scale_inplace(&mut y, scale, m * out_f)?;
13525            }
13526            return Ok(y);
13527        }
13528        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
13529            "base" => (base_name.into(), ROWS_PER_BLOCK),
13530            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
13531            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
13532            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
13533            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
13534            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
13535            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
13536            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
13537            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
13538            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
13539            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
13540            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
13541            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
13542            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
13543            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
13544        };
13545        debug_assert!(
13546            !rp || name.contains("_rp"),
13547            "rp weight dispatched to a GGUF-layout kernel"
13548        );
13549        let f = self.func(&name);
13550        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13551        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
13552        let smem = if name.contains("_r2sm_rp") {
13553            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
13554        } else {
13555            0
13556        };
13557        let cfg = LaunchConfig {
13558            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13559            block_dim: (32, ROWS_PER_BLOCK, 1),
13560            shared_mem_bytes: smem,
13561        };
13562        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13563        let __s_b = self.gpu.stream();
13564        let mut b = __s_b.launch_builder(&f);
13565        b.arg(bytes)
13566            .arg(aq)
13567            .arg(ad)
13568            .arg(&mut y)
13569            .arg(&inf)
13570            .arg(&outf)
13571            .arg(&mi)
13572            .arg(&rb);
13573        unsafe {
13574            b.launch(cfg)?;
13575        }
13576        if scale != 1.0 {
13577            self.scale_inplace(&mut y, scale, m * out_f)?;
13578        }
13579        Ok(y)
13580    }
13581
13582    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
13583    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
13584    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
13585    pub fn qmatvec_batched_raw(
13586        &self,
13587        bytes: &CudaSlice<u8>,
13588        x: &CudaSlice<f32>,
13589        m: usize,
13590        in_f: usize,
13591        out_f: usize,
13592        qtype: i32,
13593        row_bytes: usize,
13594        mcols: usize,
13595        rp: bool,
13596    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13597        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13598        self.qmatvec_mmvq_batched(
13599            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
13600        )
13601    }
13602
13603    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
13604    pub fn qmatvec_nvfp4_batched_raw(
13605        &self,
13606        bytes: &CudaSlice<u8>,
13607        x: &CudaSlice<f32>,
13608        m: usize,
13609        in_f: usize,
13610        out_f: usize,
13611        row_bytes: usize,
13612        mcols: usize,
13613        rp: bool,
13614    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13615        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
13616    }
13617
13618    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
13619    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
13620    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
13621    fn try_fp4_gemm(
13622        &self,
13623        w: &crate::model::GpuTensor,
13624        x: &CudaSlice<f32>,
13625        m: usize,
13626        in_f: usize,
13627        out_f: usize,
13628    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13629        use crate::model::GpuTensor;
13630        if cfg!(memra_portable_cuda) {
13631            return Ok(None);
13632        }
13633        if std::env::var("MEMRA_FP4").is_err() {
13634            return Ok(None);
13635        }
13636        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
13637        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
13638        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
13639        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
13640        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
13641        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
13642        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
13643        // for the common no-macro-scale case.
13644        #[cfg(memra_cutlass)]
13645        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
13646            if let GpuTensor::Quant {
13647                bytes,
13648                qtype,
13649                scale,
13650                row_bytes,
13651                cutlass,
13652                ..
13653            } = w
13654            {
13655                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
13656                    if let Some(cw) = cutlass {
13657                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
13658                        let y = self.cutlass_fp4_gemm(
13659                            &cw.b_packed,
13660                            &cw.sfb_swizzled,
13661                            x,
13662                            *scale,
13663                            m,
13664                            out_f,
13665                            in_f,
13666                        )?;
13667                        return Ok(Some(y));
13668                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
13669                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
13670                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
13671                        // (the load-time repack ~doubles it) — needed for models that don't fit the
13672                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
13673                        let (b_packed, sfb_sw) =
13674                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
13675                        let y =
13676                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
13677                        return Ok(Some(y));
13678                    }
13679                }
13680            }
13681        }
13682        if let GpuTensor::Quant {
13683            bytes,
13684            qtype,
13685            row_bytes,
13686            scale,
13687            rp,
13688            ..
13689        } = w
13690        {
13691            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
13692            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
13693            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
13694                let y =
13695                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
13696                return Ok(Some(y));
13697            }
13698        }
13699        Ok(None)
13700    }
13701
13702    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
13703    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
13704    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
13705    pub fn rms_norm_f16out(
13706        &self,
13707        x: &CudaSlice<f32>,
13708        w: &CudaSlice<f32>,
13709        dst: &mut CudaSlice<f32>,
13710        dst16: &mut CudaSlice<u8>,
13711        ncols: usize,
13712        nrows: usize,
13713        eps: f32,
13714    ) -> Result<(), Box<dyn std::error::Error>> {
13715        let f = self.func("rms_norm_f16out_f32");
13716        let cfg = LaunchConfig {
13717            grid_dim: (nrows as u32, 1, 1),
13718            block_dim: (rms_block(), 1, 1),
13719            shared_mem_bytes: 0,
13720        };
13721        let (nc, e) = (ncols as i32, eps);
13722        let __s_b = self.gpu.stream();
13723        let mut b = __s_b.launch_builder(&f);
13724        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
13725        unsafe {
13726            b.launch(cfg)?;
13727        }
13728        Ok(())
13729    }
13730
13731    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
13732    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
13733    #[allow(clippy::too_many_arguments)]
13734    pub fn add_rms_norm_f16out(
13735        &self,
13736        a: &CudaSlice<f32>,
13737        b: &CudaSlice<f32>,
13738        w: &CudaSlice<f32>,
13739        res: &mut CudaSlice<f32>,
13740        dst: &mut CudaSlice<f32>,
13741        dst16: &mut CudaSlice<u8>,
13742        ncols: usize,
13743        nrows: usize,
13744        eps: f32,
13745    ) -> Result<(), Box<dyn std::error::Error>> {
13746        let f = self.func("add_rms_norm_f16out_f32");
13747        let cfg = LaunchConfig {
13748            grid_dim: (nrows as u32, 1, 1),
13749            block_dim: (rms_block(), 1, 1),
13750            shared_mem_bytes: 0,
13751        };
13752        let (nc, e) = (ncols as i32, eps);
13753        let __s_lb = self.gpu.stream();
13754        let mut lb = __s_lb.launch_builder(&f);
13755        lb.arg(a)
13756            .arg(b)
13757            .arg(w)
13758            .arg(res)
13759            .arg(dst)
13760            .arg(dst16)
13761            .arg(&nc)
13762            .arg(&e);
13763        unsafe {
13764            lb.launch(cfg)?;
13765        }
13766        Ok(())
13767    }
13768
13769    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
13770    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
13771    pub fn matmul_group_xh(
13772        &self,
13773        ws: &[&crate::model::GpuTensor],
13774        x: &CudaSlice<f32>,
13775        xh: &CudaSlice<u8>,
13776        m: usize,
13777    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13778        let mut out = Vec::with_capacity(ws.len());
13779        let in_f = ws[0].in_features();
13780        for w in ws {
13781            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
13782                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
13783                    out.push(y);
13784                    continue;
13785                }
13786            }
13787            out.push(self.matmul(w, x, m)?);
13788        }
13789        Ok(out)
13790    }
13791
13792    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
13793    /// GDN steps). Layouts [T, H].
13794    pub fn gdn_pad_mask(
13795        &self,
13796        beta: &mut CudaSlice<f32>,
13797        g_log: &mut CudaSlice<f32>,
13798        len_d: &CudaSlice<i32>,
13799        h: usize,
13800        t: usize,
13801    ) -> Result<(), Box<dyn std::error::Error>> {
13802        let f = self.func("gdn_pad_mask_f32");
13803        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
13804        let (hi, ti) = (h as i32, t as i32);
13805        let __s_b = self.gpu.stream();
13806        let mut b = __s_b.launch_builder(&f);
13807        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
13808        unsafe {
13809            b.launch(cfg)?;
13810        }
13811        Ok(())
13812    }
13813
13814    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
13815    /// gather for the padded prime graph's h_seed/hlast.
13816    pub fn row_gather_dev(
13817        &self,
13818        src: &CudaSlice<f32>,
13819        dst: &mut CudaSlice<f32>,
13820        len_d: &CudaSlice<i32>,
13821        ncols: usize,
13822    ) -> Result<(), Box<dyn std::error::Error>> {
13823        let f = self.func("row_gather_dev_f32");
13824        let cfg = LaunchConfig::for_num_elems(ncols as u32);
13825        let nc = ncols as i32;
13826        let __s_b = self.gpu.stream();
13827        let mut b = __s_b.launch_builder(&f);
13828        b.arg(src).arg(dst).arg(len_d).arg(&nc);
13829        unsafe {
13830            b.launch(cfg)?;
13831        }
13832        Ok(())
13833    }
13834
13835    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
13836    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
13837    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
13838    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
13839    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
13840    /// different in_f) falls back to its own `matmul` — behavior unchanged.
13841    pub fn matmul_group(
13842        &self,
13843        ws: &[&crate::model::GpuTensor],
13844        x: &CudaSlice<f32>,
13845        m: usize,
13846    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13847        use crate::model::GpuTensor;
13848        let mut out = Vec::with_capacity(ws.len());
13849        let any_mirror = ws
13850            .iter()
13851            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
13852        if m >= 16 && any_mirror && !self.verify_exact_on() {
13853            let in_f = ws[0].in_features();
13854            let xh = self.f16_act(x, m * in_f, in_f)?;
13855            for w in ws {
13856                if w.in_features() == in_f {
13857                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
13858                        out.push(y);
13859                        continue;
13860                    }
13861                }
13862                out.push(self.matmul(w, x, m)?);
13863            }
13864            return Ok(out);
13865        }
13866        for w in ws {
13867            out.push(self.matmul(w, x, m)?);
13868        }
13869        Ok(out)
13870    }
13871
13872    /// Cross-request grouped matmul (task #13): run ONE projection group over the
13873    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
13874    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
13875    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
13876    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
13877    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
13878    pub fn matmul_group_multi(
13879        &self,
13880        ws: &[&crate::model::GpuTensor],
13881        xs: &[&CudaSlice<f32>],
13882        ms: &[usize],
13883    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
13884        assert_eq!(xs.len(), ms.len());
13885        let in_f = ws[0].in_features();
13886        let total: usize = ms.iter().sum();
13887        let mut xcat = self.uninit(total * in_f)?;
13888        let mut off = 0usize;
13889        for (x, &m) in xs.iter().zip(ms) {
13890            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
13891            off += m;
13892        }
13893        let ys = self.matmul_group(ws, &xcat, total)?;
13894        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
13895        for (w, y) in ws.iter().zip(ys) {
13896            let out_f = w.out_features();
13897            let mut off = 0usize;
13898            for (s, &m) in ms.iter().enumerate() {
13899                let mut ys_s = self.uninit(m * out_f)?;
13900                let src = y.slice(off * out_f..(off + m) * out_f);
13901                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
13902                out[s].push(ys_s);
13903                off += m;
13904            }
13905        }
13906        Ok(out)
13907    }
13908
13909    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
13910    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
13911    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
13912    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
13913    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
13914    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
13915    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
13916    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
13917    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
13918    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
13919        use crate::model::GpuTensor;
13920        if !legacy_quant_gemm_allowed(
13921            cfg!(memra_portable_cuda),
13922            cfg!(memra_hopper_mma),
13923            std::env::var_os("MEMRA_NO_GEMM").is_some(),
13924        ) {
13925            return false;
13926        }
13927        match w {
13928            GpuTensor::Quant { qtype, .. } => {
13929                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
13930                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
13931            }
13932            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
13933        }
13934    }
13935
13936    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
13937    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
13938    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
13939    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
13940    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
13941    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
13942    pub fn qmatvec_gemm(
13943        &self,
13944        w: &crate::model::GpuTensor,
13945        aq: &CudaSlice<i8>,
13946        ad: &CudaSlice<f32>,
13947        m: usize,
13948    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13949        use crate::model::GpuTensor;
13950        let in_f = w.in_features();
13951        let out_f = w.out_features();
13952        let (bytes, qtype, row_bytes, scale, rp) = match w {
13953            GpuTensor::Quant {
13954                bytes,
13955                qtype,
13956                row_bytes,
13957                scale,
13958                rp,
13959                ..
13960            } => (bytes, *qtype, *row_bytes, *scale, *rp),
13961            _ => unreachable!("gemm_supports guaranteed Quant"),
13962        };
13963        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
13964        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
13965        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
13966        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
13967        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
13968        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
13969            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
13970                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
13971                if scale != 1.0 {
13972                    self.scale_inplace(&mut y, scale, m * out_f)?;
13973                }
13974                return Ok(y);
13975            }
13976        }
13977        let name = match qtype {
13978            QT_Q8_0 => "qmatvec_gemm_q8_0",
13979            QT_Q4_K => "qmatvec_gemm_q4_K",
13980            QT_Q4_0 => {
13981                if rp {
13982                    "qmatvec_gemm_q4_0_rp"
13983                } else {
13984                    "qmatvec_gemm_q4_0"
13985                }
13986            }
13987            QT_Q5_K => "qmatvec_gemm_q5_K",
13988            QT_Q6_K => "qmatvec_gemm_q6_K",
13989            QT_NVFP4 => {
13990                if rp {
13991                    "qmatvec_gemm_nvfp4_rp"
13992                } else {
13993                    "qmatvec_gemm_nvfp4"
13994                }
13995            }
13996            _ => unreachable!(),
13997        };
13998        let f = self.func(name);
13999        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14000        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
14001        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
14002        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
14003        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14004        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14005        let k1_tile = if is_k1 {
14006            k1_launch_override().unwrap_or((128, 128, 8))
14007        } else {
14008            (128, 128, 8)
14009        };
14010        let (bm, bn): (u32, u32) = if is_k1 {
14011            (k1_tile.0, k1_tile.1)
14012        } else {
14013            (64, 256)
14014        };
14015        let warps: u32 = if is_k1 {
14016            k1_tile.2
14017        } else {
14018            match qtype {
14019                QT_NVFP4 => 8,
14020                _ => 4,
14021            }
14022        };
14023        let cfg = LaunchConfig {
14024            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14025            block_dim: (32, warps, 1),
14026            shared_mem_bytes: 0,
14027        };
14028        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14029        let __s_b = self.gpu.stream();
14030        let mut b = __s_b.launch_builder(&f);
14031        b.arg(bytes)
14032            .arg(aq)
14033            .arg(ad)
14034            .arg(&mut y)
14035            .arg(&inf)
14036            .arg(&outf)
14037            .arg(&mi)
14038            .arg(&rb);
14039        unsafe {
14040            b.launch(cfg)?;
14041        }
14042        if scale != 1.0 {
14043            self.scale_inplace(&mut y, scale, m * out_f)?;
14044        }
14045        Ok(y)
14046    }
14047
14048    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
14049    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
14050    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
14051    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
14052    pub fn qmatvec_gemm_raw(
14053        &self,
14054        bytes: &CudaSlice<u8>,
14055        x: &CudaSlice<f32>,
14056        m: usize,
14057        in_f: usize,
14058        out_f: usize,
14059        qtype: i32,
14060        row_bytes: usize,
14061    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14062        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
14063        let name = match qtype {
14064            QT_Q8_0 => "qmatvec_gemm_q8_0",
14065            QT_Q4_K => "qmatvec_gemm_q4_K",
14066            QT_Q4_0 => "qmatvec_gemm_q4_0",
14067            QT_Q5_K => "qmatvec_gemm_q5_K",
14068            QT_Q6_K => "qmatvec_gemm_q6_K",
14069            QT_NVFP4 => "qmatvec_gemm_nvfp4",
14070            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
14071            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
14072        };
14073        let f = self.func(name);
14074        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
14075        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
14076        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
14077        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
14078        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
14079        let k1_tile = if is_k1 {
14080            k1_launch_override().unwrap_or((128, 128, 8))
14081        } else {
14082            (128, 128, 8)
14083        };
14084        let (bm, bn): (u32, u32) = if is_k1 {
14085            (k1_tile.0, k1_tile.1)
14086        } else {
14087            (64, 256)
14088        };
14089        let warps: u32 = if is_k1 {
14090            k1_tile.2
14091        } else {
14092            match qtype {
14093                QT_NVFP4 | QT_NVFP4_RP => 8,
14094                _ => 4,
14095            }
14096        };
14097        let cfg = LaunchConfig {
14098            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
14099            block_dim: (32, warps, 1),
14100            shared_mem_bytes: 0,
14101        };
14102        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
14103        let __s_b = self.gpu.stream();
14104        let mut b = __s_b.launch_builder(&f);
14105        b.arg(bytes)
14106            .arg(&aq)
14107            .arg(&ad)
14108            .arg(&mut y)
14109            .arg(&inf)
14110            .arg(&outf)
14111            .arg(&mi)
14112            .arg(&rb);
14113        unsafe {
14114            b.launch(cfg)?;
14115        }
14116        Ok(y)
14117    }
14118
14119    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
14120    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
14121    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
14122    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
14123    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
14124    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
14125    pub fn qmatvec_gemm_q8_0_wgmma_raw(
14126        &self,
14127        rp4: &CudaSlice<u8>,
14128        aq: &CudaSlice<i8>,
14129        ad: &CudaSlice<f32>,
14130        m: usize,
14131        in_f: usize,
14132        out_f: usize,
14133    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14134        assert!(
14135            out_f % 64 == 0 && in_f % 32 == 0,
14136            "wgmma GEMM needs out_f%64==0, in_f%32==0"
14137        );
14138        let f = self.func("qmatvec_gemm_q8_0_wgmma");
14139        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
14140        let cfg = LaunchConfig {
14141            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
14142            block_dim: (128, 1, 1),
14143            shared_mem_bytes: 0,
14144        };
14145        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
14146        let __s_b = self.gpu.stream();
14147        let mut b = __s_b.launch_builder(&f);
14148        b.arg(rp4)
14149            .arg(aq)
14150            .arg(ad)
14151            .arg(&mut y)
14152            .arg(&inf)
14153            .arg(&outf)
14154            .arg(&mi);
14155        unsafe {
14156            b.launch(cfg)?;
14157        }
14158        Ok(y)
14159    }
14160
14161    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
14162    pub fn scale_inplace(
14163        &self,
14164        y: &mut CudaSlice<f32>,
14165        s: f32,
14166        n: usize,
14167    ) -> Result<(), Box<dyn std::error::Error>> {
14168        let f = self.func("scale_f32");
14169        let cfg = LaunchConfig::for_num_elems(n as u32);
14170        let (sf, ni) = (s, n as i32);
14171        let __s_b = self.gpu.stream();
14172        let mut b = __s_b.launch_builder(&f);
14173        b.arg(y).arg(&sf).arg(&ni);
14174        unsafe {
14175            b.launch(cfg)?;
14176        }
14177        Ok(())
14178    }
14179
14180    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
14181    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
14182    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
14183    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
14184    pub fn bf16_to_f32(
14185        &self,
14186        data: &cudarc::driver::CudaView<'_, u8>,
14187        n: usize,
14188    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14189        let mut out = self.alloc_uninit::<f32>(n)?;
14190        let f = self.func("bf16_to_f32");
14191        let cfg = LaunchConfig::for_num_elems(n as u32);
14192        let ni = n as i32;
14193        let __s_b = self.gpu.stream();
14194        let mut b = __s_b.launch_builder(&f);
14195        b.arg(data).arg(&mut out).arg(&ni);
14196        unsafe {
14197            b.launch(cfg)?;
14198        }
14199        Ok(out)
14200    }
14201
14202    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
14203    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
14204    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
14205    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
14206    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
14207    /// calls, the spec-verify contract) vs plain linear.
14208    fn linear_bf16_chunked(
14209        &self,
14210        x: &CudaSlice<f32>,
14211        data: &CudaSlice<u8>,
14212        m: usize,
14213        in_f: usize,
14214        out_f: usize,
14215        exact: bool,
14216    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14217        const CHUNK_BYTES: usize = 256 << 20;
14218        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
14219        if chunk_rows >= out_f {
14220            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
14221            return if exact {
14222                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
14223            } else {
14224                self.linear(x, &wf32, m, in_f, out_f)
14225            };
14226        }
14227        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14228        let mut r0 = 0usize;
14229        while r0 < out_f {
14230            let rows = chunk_rows.min(out_f - r0);
14231            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
14232            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
14233            let yc = if exact {
14234                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
14235            } else {
14236                self.linear(x, &wf32, m, in_f, rows)?
14237            };
14238            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
14239            for mi in 0..m {
14240                let src = yc.slice(mi * rows..(mi + 1) * rows);
14241                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
14242                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
14243            }
14244            r0 += rows;
14245        }
14246        Ok(y)
14247    }
14248
14249    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
14250    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
14251    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
14252    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
14253    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
14254    /// router/shexp sites and matmul_decode_exact's Float arm.
14255    pub fn linear_decode_exact(
14256        &self,
14257        x: &CudaSlice<f32>,
14258        w: &CudaSlice<f32>,
14259        m_tokens: usize,
14260        in_f: usize,
14261        out_f: usize,
14262    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14263        if m_tokens == 1 {
14264            return self.linear(x, w, 1, in_f, out_f);
14265        }
14266        let xv = self.view(x, m_tokens * in_f);
14267        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
14268        for t in 0..m_tokens {
14269            let row = xv.slice(t * in_f..(t + 1) * in_f);
14270            let mut xr = self.alloc_uninit::<f32>(in_f)?;
14271            self.copy_view_into(&mut xr, 0, &row, in_f)?;
14272            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
14273            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
14274        }
14275        Ok(y)
14276    }
14277
14278    pub fn linear(
14279        &self,
14280        x: &CudaSlice<f32>,
14281        w: &CudaSlice<f32>,
14282        m_tokens: usize,
14283        in_f: usize,
14284        out_f: usize,
14285    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14286        use cudarc::cublaslt::{Matmul, MatmulConfig};
14287        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
14288        let cfg = MatmulConfig {
14289            transa: true,
14290            transb: false,
14291            transc: false,
14292            m: out_f as u64,
14293            n: m_tokens as u64,
14294            k: in_f as u64,
14295            alpha: 1.0,
14296            lda: in_f as i64,
14297            ldb: in_f as i64,
14298            beta: 0.0,
14299            ldc: out_f as i64,
14300            stride_a: None,
14301            stride_b: None,
14302            stride_c: None,
14303            stride_bias: None,
14304            batch_size: None,
14305        };
14306        unsafe {
14307            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
14308        }
14309        Ok(c)
14310    }
14311
14312    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
14313    pub fn sdpa_naive(
14314        &self,
14315        q: &CudaSlice<f32>,
14316        k: &CudaSlice<f32>,
14317        v: &CudaSlice<f32>,
14318        o: &mut CudaSlice<f32>,
14319        head_dim: usize,
14320        n_head: usize,
14321        n_head_kv: usize,
14322        t: usize,
14323        t_kv: usize,
14324        scale: f32,
14325        causal: bool,
14326    ) -> Result<(), Box<dyn std::error::Error>> {
14327        let f = self.func("sdpa_naive_f32");
14328        let cfg = LaunchConfig {
14329            grid_dim: (n_head as u32, t as u32, 1),
14330            block_dim: (128, 1, 1),
14331            shared_mem_bytes: (t_kv * 4) as u32,
14332        };
14333        let (hd, nh, nhkv, ti, tkvi, cz) = (
14334            head_dim as i32,
14335            n_head as i32,
14336            n_head_kv as i32,
14337            t as i32,
14338            t_kv as i32,
14339            causal as i32,
14340        );
14341        let __s_b = self.gpu.stream();
14342        let mut b = __s_b.launch_builder(&f);
14343        b.arg(q)
14344            .arg(k)
14345            .arg(v)
14346            .arg(o)
14347            .arg(&hd)
14348            .arg(&nh)
14349            .arg(&nhkv)
14350            .arg(&ti)
14351            .arg(&tkvi)
14352            .arg(&scale)
14353            .arg(&cz);
14354        unsafe {
14355            b.launch(cfg)?;
14356        }
14357        Ok(())
14358    }
14359
14360    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
14361    #[allow(clippy::too_many_arguments)]
14362    pub fn sdpa_naive_w(
14363        &self,
14364        q: &CudaSlice<f32>,
14365        k: &CudaSlice<f32>,
14366        v: &CudaSlice<f32>,
14367        o: &mut CudaSlice<f32>,
14368        head_dim: usize,
14369        n_head: usize,
14370        n_head_kv: usize,
14371        t: usize,
14372        t_kv: usize,
14373        scale: f32,
14374        causal: bool,
14375        window: usize,
14376    ) -> Result<(), Box<dyn std::error::Error>> {
14377        let f = self.func("sdpa_naive_w_f32");
14378        let cfg = LaunchConfig {
14379            grid_dim: (n_head as u32, t as u32, 1),
14380            block_dim: (128, 1, 1),
14381            shared_mem_bytes: (t_kv * 4) as u32,
14382        };
14383        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14384            head_dim as i32,
14385            n_head as i32,
14386            n_head_kv as i32,
14387            t as i32,
14388            t_kv as i32,
14389            causal as i32,
14390            window as i32,
14391        );
14392        let __s_b = self.gpu.stream();
14393        let mut b = __s_b.launch_builder(&f);
14394        b.arg(q)
14395            .arg(k)
14396            .arg(v)
14397            .arg(o)
14398            .arg(&hd)
14399            .arg(&nh)
14400            .arg(&nhkv)
14401            .arg(&ti)
14402            .arg(&tkvi)
14403            .arg(&scale)
14404            .arg(&cz)
14405            .arg(&wi);
14406        unsafe {
14407            b.launch(cfg)?;
14408        }
14409        Ok(())
14410    }
14411
14412    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
14413    pub fn sdpa_naive_view(
14414        &self,
14415        q: &CudaSlice<f32>,
14416        k: &cudarc::driver::CudaView<f32>,
14417        v: &cudarc::driver::CudaView<f32>,
14418        o: &mut CudaSlice<f32>,
14419        head_dim: usize,
14420        n_head: usize,
14421        n_head_kv: usize,
14422        t: usize,
14423        t_kv: usize,
14424        scale: f32,
14425        causal: bool,
14426    ) -> Result<(), Box<dyn std::error::Error>> {
14427        let f = self.func("sdpa_naive_f32");
14428        let cfg = LaunchConfig {
14429            grid_dim: (n_head as u32, t as u32, 1),
14430            block_dim: (128, 1, 1),
14431            shared_mem_bytes: (t_kv * 4) as u32,
14432        };
14433        let (hd, nh, nhkv, ti, tkvi, cz) = (
14434            head_dim as i32,
14435            n_head as i32,
14436            n_head_kv as i32,
14437            t as i32,
14438            t_kv as i32,
14439            causal as i32,
14440        );
14441        let __s_b = self.gpu.stream();
14442        let mut b = __s_b.launch_builder(&f);
14443        b.arg(q)
14444            .arg(k)
14445            .arg(v)
14446            .arg(o)
14447            .arg(&hd)
14448            .arg(&nh)
14449            .arg(&nhkv)
14450            .arg(&ti)
14451            .arg(&tkvi)
14452            .arg(&scale)
14453            .arg(&cz);
14454        unsafe {
14455            b.launch(cfg)?;
14456        }
14457        Ok(())
14458    }
14459
14460    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
14461    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
14462    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
14463    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
14464    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
14465    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
14466    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
14467    #[allow(clippy::too_many_arguments)]
14468    pub fn fa_dequant_kv_view_f32(
14469        &self,
14470        k: &cudarc::driver::CudaView<u8>,
14471        v: &cudarc::driver::CudaView<u8>,
14472        kf: &mut CudaSlice<f32>,
14473        vf: &mut CudaSlice<f32>,
14474        kv_dim_k: usize,
14475        kv_dim_v: usize,
14476        t_kv: usize,
14477        k_tok_bytes: usize,
14478        v_tok_bytes: usize,
14479        g: bool,
14480    ) -> Result<(), Box<dyn std::error::Error>> {
14481        let f = if g {
14482            self.func_g("fa_dequant_kv_ws_f32")
14483        } else {
14484            self.func("fa_dequant_kv_ws_f32")
14485        };
14486        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
14487        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14488        let cfg = LaunchConfig {
14489            grid_dim: (nblk.max(1), 1, 1),
14490            block_dim: (256, 1, 1),
14491            shared_mem_bytes: 0,
14492        };
14493        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
14494        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
14495        let __s_b = self.gpu.stream();
14496        let mut b = __s_b.launch_builder(&f);
14497        b.arg(k)
14498            .arg(v)
14499            .arg(&mut *kf)
14500            .arg(&mut *vf)
14501            .arg(&kdk)
14502            .arg(&kdv)
14503            .arg(&tkvi)
14504            .arg(&ktb)
14505            .arg(&vtb);
14506        unsafe {
14507            b.launch(cfg)?;
14508        }
14509        Ok(())
14510    }
14511
14512    #[allow(clippy::too_many_arguments)]
14513    pub fn sdpa_naive_quantized_view(
14514        &self,
14515        q: &CudaSlice<f32>,
14516        k: &cudarc::driver::CudaView<u8>,
14517        v: &cudarc::driver::CudaView<u8>,
14518        o: &mut CudaSlice<f32>,
14519        head_dim: usize,
14520        n_head: usize,
14521        n_head_kv: usize,
14522        t: usize,
14523        t_kv: usize,
14524        scale: f32,
14525        causal: bool,
14526        k_tok_bytes: usize,
14527        v_tok_bytes: usize,
14528    ) -> Result<(), Box<dyn std::error::Error>> {
14529        let kv_dim = n_head_kv * head_dim;
14530        let mut kf = self.uninit(t_kv * kv_dim)?;
14531        let mut vf = self.uninit(t_kv * kv_dim)?;
14532        let f = self.func("fa_dequant_kv_ws_f32");
14533        let total = (2 * t_kv * kv_dim) as u64;
14534        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14535        let cfg = LaunchConfig {
14536            grid_dim: (nblk.max(1), 1, 1),
14537            block_dim: (256, 1, 1),
14538            shared_mem_bytes: 0,
14539        };
14540        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14541        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14542        let __s_b = self.gpu.stream();
14543        let mut b = __s_b.launch_builder(&f);
14544        b.arg(k)
14545            .arg(v)
14546            .arg(&mut kf)
14547            .arg(&mut vf)
14548            .arg(&kv_dim_i)
14549            .arg(&kv_dim_i)
14550            .arg(&t_kv_i)
14551            .arg(&k_tok_bytes_i)
14552            .arg(&v_tok_bytes_i);
14553        unsafe { b.launch(cfg)? };
14554        self.sdpa_naive(
14555            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14556        )
14557    }
14558
14559    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
14560    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
14561    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
14562    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
14563    /// unwindowed function above and produces bit-identical output at window == 0.
14564    ///
14565    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
14566    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
14567    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
14568    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
14569    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
14570    #[allow(clippy::too_many_arguments)]
14571    pub fn sdpa_naive_w_quantized_view(
14572        &self,
14573        q: &CudaSlice<f32>,
14574        k: &cudarc::driver::CudaView<u8>,
14575        v: &cudarc::driver::CudaView<u8>,
14576        o: &mut CudaSlice<f32>,
14577        head_dim: usize,
14578        n_head: usize,
14579        n_head_kv: usize,
14580        t: usize,
14581        t_kv: usize,
14582        scale: f32,
14583        causal: bool,
14584        window: usize,
14585        k_tok_bytes: usize,
14586        v_tok_bytes: usize,
14587    ) -> Result<(), Box<dyn std::error::Error>> {
14588        let kv_dim = n_head_kv * head_dim;
14589        let mut kf = self.uninit(t_kv * kv_dim)?;
14590        let mut vf = self.uninit(t_kv * kv_dim)?;
14591        let f = self.func("fa_dequant_kv_ws_f32");
14592        let total = (2 * t_kv * kv_dim) as u64;
14593        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14594        let cfg = LaunchConfig {
14595            grid_dim: (nblk.max(1), 1, 1),
14596            block_dim: (256, 1, 1),
14597            shared_mem_bytes: 0,
14598        };
14599        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14600        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14601        let __s_b = self.gpu.stream();
14602        let mut b = __s_b.launch_builder(&f);
14603        b.arg(k)
14604            .arg(v)
14605            .arg(&mut kf)
14606            .arg(&mut vf)
14607            .arg(&kv_dim_i)
14608            .arg(&kv_dim_i)
14609            .arg(&t_kv_i)
14610            .arg(&k_tok_bytes_i)
14611            .arg(&v_tok_bytes_i);
14612        unsafe { b.launch(cfg)? };
14613        self.sdpa_naive_w(
14614            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
14615        )
14616    }
14617
14618    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
14619    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
14620    /// Q/K/V/O [head_dim, n_head(_kv), T].
14621    pub fn fa_prefill(
14622        &self,
14623        q: &CudaSlice<f32>,
14624        k: &CudaSlice<f32>,
14625        v: &CudaSlice<f32>,
14626        o: &mut CudaSlice<f32>,
14627        head_dim: usize,
14628        n_head: usize,
14629        n_head_kv: usize,
14630        t: usize,
14631        t_kv: usize,
14632        scale: f32,
14633        causal: bool,
14634    ) -> Result<(), Box<dyn std::error::Error>> {
14635        if portable_mma_gated() {
14636            return self.sdpa_naive(
14637                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14638            );
14639        }
14640        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
14641        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
14642        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
14643        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
14644        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
14645        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
14646        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
14647        let fa3_on = head_dim == 256
14648            && causal
14649            && t == t_kv
14650            && match std::env::var("MEMRA_FA3").as_deref() {
14651                Ok("0") => false,
14652                Ok("1") => true,
14653                _ => cfg!(memra_hopper_mma),
14654            };
14655        if fa3_on {
14656            let n = t * n_head * head_dim;
14657            let nkv = t * n_head_kv * head_dim;
14658            let mut q16 = self.alloc_u8_uninit(n * 2)?;
14659            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
14660            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
14661            self.f32_to_bf16_into(q, &mut q16, n)?;
14662            self.f32_to_bf16_into(k, &mut k16, nkv)?;
14663            self.f32_to_bf16_into(v, &mut v16, nkv)?;
14664            let rc = {
14665                use cudarc::driver::{DevicePtr, DevicePtrMut};
14666                let stream = self.gpu.stream();
14667                let (qp, _g1) = q16.device_ptr(&stream);
14668                let (kp, _g2) = k16.device_ptr(&stream);
14669                let (vp, _g3) = v16.device_ptr(&stream);
14670                let (op, _g4) = o.device_ptr_mut(&stream);
14671                unsafe {
14672                    memra_fa3_prefill(
14673                        qp as *const core::ffi::c_void,
14674                        kp as *const core::ffi::c_void,
14675                        vp as *const core::ffi::c_void,
14676                        op as *mut f32,
14677                        t as i32,
14678                        n_head as i32,
14679                        n_head_kv as i32,
14680                        head_dim as i32,
14681                        scale,
14682                        stream.cu_stream() as *mut core::ffi::c_void,
14683                    )
14684                }
14685            };
14686            if rc != 0 {
14687                return Err(format!("memra_fa3_prefill rc={rc}").into());
14688            }
14689            return Ok(());
14690        }
14691        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
14692        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
14693        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
14694        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
14695        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14696        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
14697        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
14698            const BLOCK_Q: usize = 64;
14699            const BKX: usize = 32;
14700            let f = self.func("fa_prefill_bf16_p1");
14701            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
14702                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
14703            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14704            f.set_attribute(
14705                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14706                shmem as i32,
14707            )?;
14708            let cfg = LaunchConfig {
14709                grid_dim: (
14710                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14711                    n_head as u32,
14712                    1,
14713                ),
14714                block_dim: (32, 4, 1),
14715                shared_mem_bytes: shmem,
14716            };
14717            let (hd, nh, nhkv, ti, tkvi, cz) = (
14718                head_dim as i32,
14719                n_head as i32,
14720                n_head_kv as i32,
14721                t as i32,
14722                t_kv as i32,
14723                causal as i32,
14724            );
14725            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
14726            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
14727            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
14728            let __s_b = self.gpu.stream();
14729            let mut b = __s_b.launch_builder(&f);
14730            b.arg(&qb)
14731                .arg(&kb)
14732                .arg(&vb)
14733                .arg(o)
14734                .arg(&hd)
14735                .arg(&nh)
14736                .arg(&nhkv)
14737                .arg(&ti)
14738                .arg(&tkvi)
14739                .arg(&scale)
14740                .arg(&cz);
14741            unsafe {
14742                b.launch(cfg)?;
14743            }
14744            return Ok(());
14745        }
14746        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
14747        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
14748        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
14749        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
14750        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
14751        const BK: usize = 32;
14752        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
14753        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
14754        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
14755        let (block_q, warps, w2_sfx): (usize, u32, &str) =
14756            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
14757        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
14758        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
14759        // other head_dims to sdpa_naive before reaching here.
14760        let hd_sfx = fa_hd_suffix(head_dim)?;
14761        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
14762        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
14763        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
14764        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
14765        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
14766        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
14767        let (kb16, vb16) = if bf16kv {
14768            let n = t_kv * n_head_kv * head_dim;
14769            let mut kb = self.alloc_u8_uninit(n * 2)?;
14770            let mut vb = self.alloc_u8_uninit(n * 2)?;
14771            let fcv = self.func("f32_to_bf16_bulk");
14772            let ni = n as i64;
14773            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
14774            let __s_b = self.gpu.stream();
14775            let mut b = __s_b.launch_builder(&fcv);
14776            b.arg(k).arg(&mut kb).arg(&ni);
14777            unsafe {
14778                b.launch(cfgc)?;
14779            }
14780            let __s_b = self.gpu.stream();
14781            let mut b = __s_b.launch_builder(&fcv);
14782            b.arg(v).arg(&mut vb).arg(&ni);
14783            unsafe {
14784                b.launch(cfgc)?;
14785            }
14786            (Some(kb), Some(vb))
14787        } else {
14788            (None, None)
14789        };
14790        let f = self.func(&if bf16kv {
14791            format!("fa_prefill_bf16kv_pp{hd_sfx}")
14792        } else {
14793            format!(
14794                "fa_prefill_f32{}{}{hd_sfx}",
14795                if floor { "" } else { "_pp" },
14796                if floor { "" } else { w2_sfx }
14797            )
14798        });
14799        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
14800        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
14801        let kv_stages = if bf16kv { 2 } else { 1 };
14802        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
14803            + 4 * (block_q * BK + 2 * block_q)) as u32;
14804        use cudarc::driver::sys::CUfunction_attribute_enum as A;
14805        f.set_attribute(
14806            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14807            shmem as i32,
14808        )?;
14809        let cfg = LaunchConfig {
14810            grid_dim: (
14811                (t as u32 + block_q as u32 - 1) / block_q as u32,
14812                n_head as u32,
14813                1,
14814            ),
14815            block_dim: (32, warps, 1),
14816            shared_mem_bytes: shmem,
14817        };
14818        let (hd, nh, nhkv, ti, tkvi, cz) = (
14819            head_dim as i32,
14820            n_head as i32,
14821            n_head_kv as i32,
14822            t as i32,
14823            t_kv as i32,
14824            causal as i32,
14825        );
14826        let __s_b = self.gpu.stream();
14827        let mut b = __s_b.launch_builder(&f);
14828        b.arg(q);
14829        match (&kb16, &vb16) {
14830            (Some(kb), Some(vb)) => {
14831                b.arg(kb).arg(vb);
14832            }
14833            _ => {
14834                b.arg(k).arg(v);
14835            }
14836        }
14837        b.arg(o)
14838            .arg(&hd)
14839            .arg(&nh)
14840            .arg(&nhkv)
14841            .arg(&ti)
14842            .arg(&tkvi)
14843            .arg(&scale)
14844            .arg(&cz);
14845        unsafe {
14846            b.launch(cfg)?;
14847        }
14848        Ok(())
14849    }
14850
14851    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
14852    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
14853    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
14854    #[allow(clippy::too_many_arguments)]
14855    pub fn fa_prefill_w(
14856        &self,
14857        q: &CudaSlice<f32>,
14858        k: &CudaSlice<f32>,
14859        v: &CudaSlice<f32>,
14860        o: &mut CudaSlice<f32>,
14861        head_dim: usize,
14862        n_head: usize,
14863        n_head_kv: usize,
14864        t: usize,
14865        t_kv: usize,
14866        scale: f32,
14867        causal: bool,
14868        window: usize,
14869    ) -> Result<(), Box<dyn std::error::Error>> {
14870        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
14871        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
14872        if portable_mma_gated() {
14873            return self.sdpa_naive_w(
14874                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
14875            );
14876        }
14877        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
14878        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
14879        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
14880        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14881        let faw_f32 =
14882            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
14883        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
14884        self.fa_prefill_w_arm(
14885            q,
14886            k,
14887            v,
14888            o,
14889            head_dim,
14890            n_head,
14891            n_head_kv,
14892            t,
14893            t_kv,
14894            scale,
14895            causal,
14896            window,
14897            floor || faw_f32,
14898            floor,
14899        )
14900    }
14901
14902    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
14903    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
14904    #[allow(clippy::too_many_arguments)]
14905    pub fn fa_prefill_w_pre(
14906        &self,
14907        qb: &CudaSlice<u8>,
14908        kb: &CudaSlice<u8>,
14909        vb: &CudaSlice<u8>,
14910        o: &mut CudaSlice<f32>,
14911        head_dim: usize,
14912        n_head: usize,
14913        n_head_kv: usize,
14914        t: usize,
14915        t_kv: usize,
14916        scale: f32,
14917        causal: bool,
14918        window: usize,
14919        v_f16: bool,
14920    ) -> Result<(), Box<dyn std::error::Error>> {
14921        const BLOCK_Q: usize = 64;
14922        const BK: usize = 32;
14923        debug_assert_eq!(head_dim, 256);
14924        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
14925        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
14926        if hp {
14927            const BLOCK_QH: usize = 32;
14928            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
14929            // else re-encode through the pooled scratch (stream-ordered reuse).
14930            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
14931            let vh: &CudaSlice<u8> = if v_f16 {
14932                vb
14933            } else {
14934                let n = t_kv * n_head_kv * head_dim;
14935                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
14936                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
14937                }
14938                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
14939                vguard.as_ref().unwrap()
14940            };
14941            let f = self.func("fa_prefill_w_bf16_p1h2");
14942            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
14943            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14944            f.set_attribute(
14945                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14946                shmem as i32,
14947            )?;
14948            let cfg = LaunchConfig {
14949                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
14950                block_dim: (32, 4, 1),
14951                shared_mem_bytes: shmem,
14952            };
14953            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14954                head_dim as i32,
14955                n_head as i32,
14956                n_head_kv as i32,
14957                t as i32,
14958                t_kv as i32,
14959                causal as i32,
14960                window as i32,
14961            );
14962            let __s_b = self.gpu.stream();
14963            let mut b = __s_b.launch_builder(&f);
14964            b.arg(qb)
14965                .arg(kb)
14966                .arg(vh)
14967                .arg(o)
14968                .arg(&hd)
14969                .arg(&nh)
14970                .arg(&nhkv)
14971                .arg(&ti)
14972                .arg(&tkvi)
14973                .arg(&scale)
14974                .arg(&cz)
14975                .arg(&wi);
14976            unsafe {
14977                b.launch(cfg)?;
14978            }
14979            return Ok(());
14980        }
14981        let f = self.func("fa_prefill_w_bf16_p1");
14982        let shmem =
14983            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
14984        use cudarc::driver::sys::CUfunction_attribute_enum as A;
14985        f.set_attribute(
14986            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14987            shmem as i32,
14988        )?;
14989        let cfg = LaunchConfig {
14990            grid_dim: (
14991                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14992                n_head as u32,
14993                1,
14994            ),
14995            block_dim: (32, 4, 1),
14996            shared_mem_bytes: shmem,
14997        };
14998        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14999            head_dim as i32,
15000            n_head as i32,
15001            n_head_kv as i32,
15002            t as i32,
15003            t_kv as i32,
15004            causal as i32,
15005            window as i32,
15006        );
15007        let __s_b = self.gpu.stream();
15008        let mut b = __s_b.launch_builder(&f);
15009        b.arg(qb)
15010            .arg(kb)
15011            .arg(vb)
15012            .arg(o)
15013            .arg(&hd)
15014            .arg(&nh)
15015            .arg(&nhkv)
15016            .arg(&ti)
15017            .arg(&tkvi)
15018            .arg(&scale)
15019            .arg(&cz)
15020            .arg(&wi);
15021        unsafe {
15022            b.launch(cfg)?;
15023        }
15024        Ok(())
15025    }
15026
15027    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
15028    #[allow(clippy::too_many_arguments)]
15029    pub fn fa_prefill_w_arm(
15030        &self,
15031        q: &CudaSlice<f32>,
15032        k: &CudaSlice<f32>,
15033        v: &CudaSlice<f32>,
15034        o: &mut CudaSlice<f32>,
15035        head_dim: usize,
15036        n_head: usize,
15037        n_head_kv: usize,
15038        t: usize,
15039        t_kv: usize,
15040        scale: f32,
15041        causal: bool,
15042        window: usize,
15043        f32_stage: bool,
15044        floor: bool,
15045    ) -> Result<(), Box<dyn std::error::Error>> {
15046        const BLOCK_Q: usize = 64;
15047        const BK: usize = 32;
15048        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
15049        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
15050        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
15051        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
15052        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15053        let p1 = !floor
15054            && !f32_stage
15055            && *P1_ON.get_or_init(|| {
15056                std::env::var("MEMRA_FAW_P1")
15057                    .map(|v| v != "0")
15058                    .unwrap_or(true)
15059            });
15060        let hp =
15061            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15062        if hp {
15063            const BLOCK_QH: usize = 32;
15064            let f = self.func("fa_prefill_w_bf16_p1h2");
15065            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
15066            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15067            f.set_attribute(
15068                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15069                shmem as i32,
15070            )?;
15071            let cfg = LaunchConfig {
15072                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
15073                block_dim: (32, 4, 1),
15074                shared_mem_bytes: shmem,
15075            };
15076            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15077                head_dim as i32,
15078                n_head as i32,
15079                n_head_kv as i32,
15080                t as i32,
15081                t_kv as i32,
15082                causal as i32,
15083                window as i32,
15084            );
15085            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15086            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15087            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
15088            let __s_b = self.gpu.stream();
15089            let mut b = __s_b.launch_builder(&f);
15090            b.arg(&qb)
15091                .arg(&kb)
15092                .arg(&vh)
15093                .arg(o)
15094                .arg(&hd)
15095                .arg(&nh)
15096                .arg(&nhkv)
15097                .arg(&ti)
15098                .arg(&tkvi)
15099                .arg(&scale)
15100                .arg(&cz)
15101                .arg(&wi);
15102            unsafe {
15103                b.launch(cfg)?;
15104            }
15105            return Ok(());
15106        }
15107        if p1 {
15108            let f = self.func("fa_prefill_w_bf16_p1");
15109            let shmem =
15110                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15111            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15112            f.set_attribute(
15113                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15114                shmem as i32,
15115            )?;
15116            let cfg = LaunchConfig {
15117                grid_dim: (
15118                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15119                    n_head as u32,
15120                    1,
15121                ),
15122                block_dim: (32, 4, 1),
15123                shared_mem_bytes: shmem,
15124            };
15125            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15126                head_dim as i32,
15127                n_head as i32,
15128                n_head_kv as i32,
15129                t as i32,
15130                t_kv as i32,
15131                causal as i32,
15132                window as i32,
15133            );
15134            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15135            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15136            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15137            let __s_b = self.gpu.stream();
15138            let mut b = __s_b.launch_builder(&f);
15139            b.arg(&qb)
15140                .arg(&kb)
15141                .arg(&vb)
15142                .arg(o)
15143                .arg(&hd)
15144                .arg(&nh)
15145                .arg(&nhkv)
15146                .arg(&ti)
15147                .arg(&tkvi)
15148                .arg(&scale)
15149                .arg(&cz)
15150                .arg(&wi);
15151            unsafe {
15152                b.launch(cfg)?;
15153            }
15154            return Ok(());
15155        }
15156        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
15157        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
15158        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15159        let g4 = !floor
15160            && !f32_stage
15161            && n_head_kv == 1
15162            && n_head % 4 == 0
15163            && *G4_ON.get_or_init(|| {
15164                std::env::var("MEMRA_FAW_G4")
15165                    .map(|v| v != "0")
15166                    .unwrap_or(true)
15167            });
15168        if g4 {
15169            const SP_M: usize = 16;
15170            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
15171            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
15172            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15173            let o2 = *O2_ON.get_or_init(|| {
15174                std::env::var("MEMRA_FAW_O2")
15175                    .map(|v| v != "0")
15176                    .unwrap_or(true)
15177            });
15178            let f = self.func(if o2 {
15179                "fa_prefill_w_bf16_g4o2"
15180            } else {
15181                "fa_prefill_w_bf16_g4"
15182            });
15183            let shmem = if o2 {
15184                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
15185            } else {
15186                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
15187                    as u32
15188            };
15189            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15190            f.set_attribute(
15191                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15192                shmem as i32,
15193            )?;
15194            let cfg = LaunchConfig {
15195                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
15196                block_dim: (32, 4, 1),
15197                shared_mem_bytes: shmem,
15198            };
15199            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15200                head_dim as i32,
15201                n_head as i32,
15202                n_head_kv as i32,
15203                t as i32,
15204                t_kv as i32,
15205                causal as i32,
15206                window as i32,
15207            );
15208            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15209            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15210            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15211            let __s_b = self.gpu.stream();
15212            let mut b = __s_b.launch_builder(&f);
15213            b.arg(&qb)
15214                .arg(&kb)
15215                .arg(&vb)
15216                .arg(o)
15217                .arg(&hd)
15218                .arg(&nh)
15219                .arg(&nhkv)
15220                .arg(&ti)
15221                .arg(&tkvi)
15222                .arg(&scale)
15223                .arg(&cz)
15224                .arg(&wi);
15225            unsafe {
15226                b.launch(cfg)?;
15227            }
15228            return Ok(());
15229        }
15230        let f = self.func(if floor {
15231            "fa_prefill_w_f32"
15232        } else if f32_stage {
15233            "fa_prefill_w_f32_pp"
15234        } else {
15235            "fa_prefill_w_bf16_pp"
15236        });
15237        let shmem =
15238            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15239        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15240        f.set_attribute(
15241            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15242            shmem as i32,
15243        )?;
15244        let cfg = LaunchConfig {
15245            grid_dim: (
15246                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15247                n_head as u32,
15248                1,
15249            ),
15250            block_dim: (32, 4, 1),
15251            shared_mem_bytes: shmem,
15252        };
15253        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15254            head_dim as i32,
15255            n_head as i32,
15256            n_head_kv as i32,
15257            t as i32,
15258            t_kv as i32,
15259            causal as i32,
15260            window as i32,
15261        );
15262        if f32_stage {
15263            let __s_b = self.gpu.stream();
15264            let mut b = __s_b.launch_builder(&f);
15265            b.arg(q)
15266                .arg(k)
15267                .arg(v)
15268                .arg(o)
15269                .arg(&hd)
15270                .arg(&nh)
15271                .arg(&nhkv)
15272                .arg(&ti)
15273                .arg(&tkvi)
15274                .arg(&scale)
15275                .arg(&cz)
15276                .arg(&wi);
15277            unsafe {
15278                b.launch(cfg)?;
15279            }
15280        } else {
15281            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15282            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15283            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15284            let __s_b = self.gpu.stream();
15285            let mut b = __s_b.launch_builder(&f);
15286            b.arg(&qb)
15287                .arg(&kb)
15288                .arg(&vb)
15289                .arg(o)
15290                .arg(&hd)
15291                .arg(&nh)
15292                .arg(&nhkv)
15293                .arg(&ti)
15294                .arg(&tkvi)
15295                .arg(&scale)
15296                .arg(&cz)
15297                .arg(&wi);
15298            unsafe {
15299                b.launch(cfg)?;
15300            }
15301        }
15302        Ok(())
15303    }
15304
15305    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
15306    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
15307    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
15308    #[allow(clippy::too_many_arguments)]
15309    pub fn fa_prefill_hd512(
15310        &self,
15311        q: &CudaSlice<f32>,
15312        k: &CudaSlice<f32>,
15313        v: &CudaSlice<f32>,
15314        o: &mut CudaSlice<f32>,
15315        head_dim: usize,
15316        n_head: usize,
15317        n_head_kv: usize,
15318        t: usize,
15319        t_kv: usize,
15320        scale: f32,
15321        causal: bool,
15322    ) -> Result<(), Box<dyn std::error::Error>> {
15323        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
15324        if portable_mma_gated() {
15325            return self.sdpa_naive(
15326                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15327            );
15328        }
15329        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
15330        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
15331        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
15332        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
15333        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
15334        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15335        let f32_stage =
15336            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
15337        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
15338        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
15339        // Own numeric config (partial-sum order) — battery-gated.
15340        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15341        let sp = !f32_stage
15342            && *SP_ON.get_or_init(|| {
15343                std::env::var("MEMRA_FA512_SP")
15344                    .map(|v| v != "0")
15345                    .unwrap_or(true)
15346            });
15347        self.fa_prefill_hd512_arm(
15348            q,
15349            k,
15350            v,
15351            o,
15352            head_dim,
15353            n_head,
15354            n_head_kv,
15355            t,
15356            t_kv,
15357            scale,
15358            causal,
15359            f32_stage,
15360            sp,
15361            sp && fa_f16pv_on(),
15362        )
15363    }
15364
15365    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
15366    #[allow(clippy::too_many_arguments)]
15367    pub fn fa_prefill_hd512_pre(
15368        &self,
15369        qb: &CudaSlice<u8>,
15370        kb: &CudaSlice<u8>,
15371        vb: &CudaSlice<u8>,
15372        o: &mut CudaSlice<f32>,
15373        head_dim: usize,
15374        n_head: usize,
15375        n_head_kv: usize,
15376        t: usize,
15377        t_kv: usize,
15378        scale: f32,
15379        causal: bool,
15380        v_f16: bool,
15381    ) -> Result<(), Box<dyn std::error::Error>> {
15382        debug_assert_eq!(head_dim, 512);
15383        const SP_M: usize = 16;
15384        const BKS: usize = 32;
15385        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
15386        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
15387        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
15388        let f16pv = fa_f16pv_on();
15389        let nw = if f16pv { fa512_wide_warps() } else { 2 };
15390        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15391        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
15392        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15393        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
15394            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
15395            let n = t_kv * n_head_kv * head_dim;
15396            let need = n * 2;
15397            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
15398                *vguard = Some(self.alloc_uninit::<u8>(need)?);
15399            }
15400            let dst = vguard.as_mut().unwrap();
15401            self.bf16_to_f16_into(vb, n, dst)?;
15402            vguard.as_ref().unwrap()
15403        } else {
15404            vb
15405        };
15406        let f = self.func(if hp {
15407            "fa_prefill_bf16_hd512_sp16h2"
15408        } else {
15409            match (f16pv, nw) {
15410                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15411                (true, _) => "fa_prefill_bf16_hd512_sp16",
15412                _ => "fa_prefill_bf16_hd512_sp",
15413            }
15414        });
15415        let (nwarp, npart) = if hp {
15416            (4usize, 4usize)
15417        } else if nw > 2 {
15418            (nw, nw)
15419        } else {
15420            (2, 1)
15421        };
15422        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
15423        let shmem = if hp {
15424            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
15425                as u32
15426        } else {
15427            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15428                + 4 * (npart * SP_M * BKS + SP_M)) as u32
15429        };
15430        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15431        f.set_attribute(
15432            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15433            shmem as i32,
15434        )?;
15435        let grid_y = if hp {
15436            (n_head / 2) as u32
15437        } else {
15438            n_head as u32
15439        };
15440        let cfg = LaunchConfig {
15441            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15442            block_dim: (32, nwarp as u32, 1),
15443            shared_mem_bytes: shmem,
15444        };
15445        let (hd, nh, nhkv, ti, tkvi, cz) = (
15446            head_dim as i32,
15447            n_head as i32,
15448            n_head_kv as i32,
15449            t as i32,
15450            t_kv as i32,
15451            causal as i32,
15452        );
15453        let __s_b = self.gpu.stream();
15454        let mut b = __s_b.launch_builder(&f);
15455        b.arg(qb)
15456            .arg(kb)
15457            .arg(vref)
15458            .arg(o)
15459            .arg(&hd)
15460            .arg(&nh)
15461            .arg(&nhkv)
15462            .arg(&ti)
15463            .arg(&tkvi)
15464            .arg(&scale)
15465            .arg(&cz);
15466        unsafe {
15467            b.launch(cfg)?;
15468        }
15469        Ok(())
15470    }
15471
15472    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
15473    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
15474    #[allow(clippy::too_many_arguments)]
15475    pub fn fa_prefill_hd512_arm(
15476        &self,
15477        q: &CudaSlice<f32>,
15478        k: &CudaSlice<f32>,
15479        v: &CudaSlice<f32>,
15480        o: &mut CudaSlice<f32>,
15481        head_dim: usize,
15482        n_head: usize,
15483        n_head_kv: usize,
15484        t: usize,
15485        t_kv: usize,
15486        scale: f32,
15487        causal: bool,
15488        f32_stage: bool,
15489        sp: bool,
15490        f16pv: bool,
15491    ) -> Result<(), Box<dyn std::error::Error>> {
15492        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
15493        if sp && !f32_stage {
15494            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
15495            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
15496            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
15497            const SP_M: usize = 16;
15498            const BKS: usize = 32;
15499            let nw = if f16pv { fa512_wide_warps() } else { 2 };
15500            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15501            let f = self.func(if hp {
15502                "fa_prefill_bf16_hd512_sp16h2"
15503            } else {
15504                match (f16pv, nw) {
15505                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15506                    (true, _) => "fa_prefill_bf16_hd512_sp16",
15507                    _ => "fa_prefill_bf16_hd512_sp",
15508                }
15509            });
15510            let (nwarp, npart) = if hp {
15511                (4usize, 4usize)
15512            } else if nw > 2 {
15513                (nw, nw)
15514            } else {
15515                (2, 1)
15516            };
15517            let shmem = if hp {
15518                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
15519                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
15520            } else {
15521                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15522                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
15523            };
15524            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15525            f.set_attribute(
15526                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15527                shmem as i32,
15528            )?;
15529            let grid_y = if hp {
15530                (n_head / 2) as u32
15531            } else {
15532                n_head as u32
15533            };
15534            let cfg = LaunchConfig {
15535                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15536                block_dim: (32, nwarp as u32, 1),
15537                shared_mem_bytes: shmem,
15538            };
15539            let (hd, nh, nhkv, ti, tkvi, cz) = (
15540                head_dim as i32,
15541                n_head as i32,
15542                n_head_kv as i32,
15543                t as i32,
15544                t_kv as i32,
15545                causal as i32,
15546            );
15547            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15548            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15549            let vb = if f16pv {
15550                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
15551            } else {
15552                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
15553            };
15554            let __s_b = self.gpu.stream();
15555            let mut b = __s_b.launch_builder(&f);
15556            b.arg(&qb)
15557                .arg(&kb)
15558                .arg(&vb)
15559                .arg(o)
15560                .arg(&hd)
15561                .arg(&nh)
15562                .arg(&nhkv)
15563                .arg(&ti)
15564                .arg(&tkvi)
15565                .arg(&scale)
15566                .arg(&cz);
15567            unsafe {
15568                b.launch(cfg)?;
15569            }
15570            return Ok(());
15571        }
15572        const BLOCK_Q: usize = 32;
15573        const BK: usize = 32;
15574        const HALF: usize = 256;
15575        let f = self.func(if f32_stage {
15576            "fa_prefill_f32_hd512"
15577        } else {
15578            "fa_prefill_bf16_hd512"
15579        });
15580        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
15581        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
15582            + 4 * BLOCK_Q) as u32;
15583        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15584        f.set_attribute(
15585            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15586            shmem as i32,
15587        )?;
15588        let cfg = LaunchConfig {
15589            grid_dim: (
15590                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15591                n_head as u32,
15592                2,
15593            ),
15594            block_dim: (32, 2, 1),
15595            shared_mem_bytes: shmem,
15596        };
15597        let (hd, nh, nhkv, ti, tkvi, cz) = (
15598            head_dim as i32,
15599            n_head as i32,
15600            n_head_kv as i32,
15601            t as i32,
15602            t_kv as i32,
15603            causal as i32,
15604        );
15605        if f32_stage {
15606            let __s_b = self.gpu.stream();
15607            let mut b = __s_b.launch_builder(&f);
15608            b.arg(q)
15609                .arg(k)
15610                .arg(v)
15611                .arg(o)
15612                .arg(&hd)
15613                .arg(&nh)
15614                .arg(&nhkv)
15615                .arg(&ti)
15616                .arg(&tkvi)
15617                .arg(&scale)
15618                .arg(&cz);
15619            unsafe {
15620                b.launch(cfg)?;
15621            }
15622        } else {
15623            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15624            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15625            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15626            let __s_b = self.gpu.stream();
15627            let mut b = __s_b.launch_builder(&f);
15628            b.arg(&qb)
15629                .arg(&kb)
15630                .arg(&vb)
15631                .arg(o)
15632                .arg(&hd)
15633                .arg(&nh)
15634                .arg(&nhkv)
15635                .arg(&ti)
15636                .arg(&tkvi)
15637                .arg(&scale)
15638                .arg(&cz);
15639            unsafe {
15640                b.launch(cfg)?;
15641            }
15642        }
15643        Ok(())
15644    }
15645
15646    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
15647    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
15648    /// separate f32_to_bf16 the FA entries would run).
15649    #[allow(clippy::too_many_arguments)]
15650    pub fn rope_neox2_bf16e(
15651        &self,
15652        q: &mut CudaSlice<f32>,
15653        k: &mut CudaSlice<f32>,
15654        qb: &mut CudaSlice<u8>,
15655        kb: &mut CudaSlice<u8>,
15656        pos: &CudaSlice<i32>,
15657        head_dim: usize,
15658        n_dims: usize,
15659        nh_q: usize,
15660        nh_k: usize,
15661        n_tokens: usize,
15662        base: f32,
15663        freq_scale: f32,
15664        ff: Option<&CudaSlice<f32>>,
15665    ) -> Result<(), Box<dyn std::error::Error>> {
15666        let f = self.func("rope_neox2_bf16e_f32");
15667        let rows = ((nh_q + nh_k) * n_tokens) as u32;
15668        let cfg = LaunchConfig {
15669            grid_dim: (rows, 1, 1),
15670            block_dim: ((head_dim / 2) as u32, 1, 1),
15671            shared_mem_bytes: 0,
15672        };
15673        let theta_scale = base.powf(-2.0 / n_dims as f32);
15674        let (hd, nd, nhq, nhk, nt) = (
15675            head_dim as i32,
15676            n_dims as i32,
15677            nh_q as i32,
15678            nh_k as i32,
15679            n_tokens as i32,
15680        );
15681        let __s_b = self.gpu.stream();
15682        let mut b = __s_b.launch_builder(&f);
15683        match ff {
15684            Some(t) => {
15685                b.arg(&mut *q)
15686                    .arg(&mut *k)
15687                    .arg(&mut *qb)
15688                    .arg(&mut *kb)
15689                    .arg(pos)
15690                    .arg(&hd)
15691                    .arg(&nd)
15692                    .arg(&nhq)
15693                    .arg(&nhk)
15694                    .arg(&nt)
15695                    .arg(&theta_scale)
15696                    .arg(&freq_scale)
15697                    .arg(t);
15698                unsafe {
15699                    b.launch(cfg)?;
15700                }
15701            }
15702            None => {
15703                let null: u64 = 0;
15704                b.arg(&mut *q)
15705                    .arg(&mut *k)
15706                    .arg(&mut *qb)
15707                    .arg(&mut *kb)
15708                    .arg(pos)
15709                    .arg(&hd)
15710                    .arg(&nd)
15711                    .arg(&nhq)
15712                    .arg(&nhk)
15713                    .arg(&nt)
15714                    .arg(&theta_scale)
15715                    .arg(&freq_scale)
15716                    .arg(&null);
15717                unsafe {
15718                    b.launch(cfg)?;
15719                }
15720            }
15721        }
15722        Ok(())
15723    }
15724
15725    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
15726    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
15727    pub fn f32_to_bf16(
15728        &self,
15729        x: &CudaSlice<f32>,
15730        n: usize,
15731    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15732        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
15733        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15734        let f = self.func("f32_to_bf16_flat");
15735        let n_i = n as i64;
15736        let cfg = LaunchConfig {
15737            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15738            block_dim: (256, 1, 1),
15739            shared_mem_bytes: 0,
15740        };
15741        let __s_b = self.gpu.stream();
15742        let mut b = __s_b.launch_builder(&f);
15743        b.arg(x).arg(&mut y).arg(&n_i);
15744        unsafe {
15745            b.launch(cfg)?;
15746        }
15747        Ok(y)
15748    }
15749
15750    pub fn f32_to_f16(
15751        &self,
15752        x: &CudaSlice<f32>,
15753        n: usize,
15754    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15755        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
15756        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15757        let f = self.func("f32_to_f16_flat");
15758        let n_i = n as i64;
15759        let cfg = LaunchConfig {
15760            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15761            block_dim: (256, 1, 1),
15762            shared_mem_bytes: 0,
15763        };
15764        let __s_b = self.gpu.stream();
15765        let mut b = __s_b.launch_builder(&f);
15766        b.arg(x).arg(&mut y).arg(&n_i);
15767        unsafe {
15768            b.launch(cfg)?;
15769        }
15770        Ok(y)
15771    }
15772
15773    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
15774    pub fn bf16_to_f16(
15775        &self,
15776        xb: &CudaSlice<u8>,
15777        n: usize,
15778    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15779        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15780        self.bf16_to_f16_into(xb, n, &mut y)?;
15781        Ok(y)
15782    }
15783
15784    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
15785    pub fn bf16_to_f16_into(
15786        &self,
15787        xb: &CudaSlice<u8>,
15788        n: usize,
15789        y: &mut CudaSlice<u8>,
15790    ) -> Result<(), Box<dyn std::error::Error>> {
15791        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
15792        assert!(y.len() >= n * 2);
15793        let f = self.func("bf16_to_f16_flat");
15794        let n2 = (n / 2) as i64;
15795        let cfg = LaunchConfig {
15796            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
15797            block_dim: (256, 1, 1),
15798            shared_mem_bytes: 0,
15799        };
15800        let __s_b = self.gpu.stream();
15801        let mut b = __s_b.launch_builder(&f);
15802        b.arg(xb).arg(y).arg(&n2);
15803        unsafe {
15804            b.launch(cfg)?;
15805        }
15806        Ok(())
15807    }
15808
15809    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
15810    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
15811    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
15812    /// head_dim in {256, 128}, bf16kv lane on.
15813    #[allow(clippy::too_many_arguments)]
15814    pub fn fa_prefill_vl8(
15815        &self,
15816        seqs: &[FaSeqVl],
15817        head_dim: usize,
15818        n_head: usize,
15819        n_head_kv: usize,
15820        scale: f32,
15821    ) -> Result<(), Box<dyn std::error::Error>> {
15822        const BK: usize = 32;
15823        let b = seqs.len();
15824        assert!(b >= 1 && b <= 8);
15825        let mut packed = [FaSeqVl::default(); 8];
15826        packed[..b].copy_from_slice(seqs);
15827        let v = FaVl8(packed);
15828        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
15829        let ept = (n_head_kv * head_dim) as i32;
15830        {
15831            let f = self.func("fa_mirror_vl");
15832            let max_n = (max_t as i64) * ept as i64;
15833            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
15834            for which in 0..2i32 {
15835                let cfg = LaunchConfig {
15836                    grid_dim: (blocks, 1, b as u32),
15837                    block_dim: (256, 1, 1),
15838                    shared_mem_bytes: 0,
15839                };
15840                let __s_lb = self.gpu.stream();
15841                let mut lb = __s_lb.launch_builder(&f);
15842                lb.arg(&v).arg(&ept).arg(&which);
15843                unsafe {
15844                    lb.launch(cfg)?;
15845                }
15846            }
15847        }
15848        let hd_sfx = fa_hd_suffix(head_dim)?;
15849        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
15850        let block_q = 64usize;
15851        let kv_stages = 2usize;
15852        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
15853            + 4 * (block_q * BK + 2 * block_q)) as u32;
15854        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15855        f.set_attribute(
15856            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15857            shmem as i32,
15858        )?;
15859        let cfg = LaunchConfig {
15860            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
15861            block_dim: (32, 4, 1),
15862            shared_mem_bytes: shmem,
15863        };
15864        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
15865        let __s_lb = self.gpu.stream();
15866        let mut lb = __s_lb.launch_builder(&f);
15867        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
15868        unsafe {
15869            lb.launch(cfg)?;
15870        }
15871        Ok(())
15872    }
15873
15874    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
15875    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
15876    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
15877    #[allow(clippy::too_many_arguments)]
15878    pub fn attn_pre_vl8(
15879        &self,
15880        seqs: &[AttnPreVl],
15881        wq: &CudaSlice<f32>,
15882        wk: &CudaSlice<f32>,
15883        head_dim: usize,
15884        rope_dims: usize,
15885        n_head: usize,
15886        n_head_kv: usize,
15887        eps: f32,
15888        freq_base: f32,
15889        freq_scale: f32,
15890        kv_dim_k: usize,
15891        kv_dim_v: usize,
15892        k_tok_bytes: usize,
15893        v_tok_bytes: usize,
15894    ) -> Result<(), Box<dyn std::error::Error>> {
15895        let b = seqs.len();
15896        assert!(b >= 1 && b <= 8);
15897        let mut packed = [AttnPreVl::default(); 8];
15898        packed[..b].copy_from_slice(seqs);
15899        let v = AttnPreVl8(packed);
15900        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
15901        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
15902        {
15903            let f = self.func("q_gate_split_vl");
15904            let n = max_t * (n_head * head_dim) as u32;
15905            let cfg = LaunchConfig {
15906                grid_dim: (n.div_ceil(256), 1, b as u32),
15907                block_dim: (256, 1, 1),
15908                shared_mem_bytes: 0,
15909            };
15910            let __s_lb = self.gpu.stream();
15911            let mut lb = __s_lb.launch_builder(&f);
15912            lb.arg(&v).arg(&hd).arg(&nh);
15913            unsafe {
15914                lb.launch(cfg)?;
15915            }
15916        }
15917        {
15918            let f = self.func("attn_rms_vl");
15919            let cfg = LaunchConfig {
15920                grid_dim: (max_t * n_head as u32, 2, b as u32),
15921                block_dim: (rms_block(), 1, 1),
15922                shared_mem_bytes: 0,
15923            };
15924            let __s_lb = self.gpu.stream();
15925            let mut lb = __s_lb.launch_builder(&f);
15926            lb.arg(&v)
15927                .arg(wq)
15928                .arg(wk)
15929                .arg(&hd)
15930                .arg(&nh)
15931                .arg(&nhkv)
15932                .arg(&eps);
15933            unsafe {
15934                lb.launch(cfg)?;
15935            }
15936        }
15937        {
15938            let f = self.func("attn_rope_vl");
15939            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
15940            let nd = rope_dims as i32;
15941            let cfg = LaunchConfig {
15942                grid_dim: (max_t * n_head as u32, 2, b as u32),
15943                block_dim: ((head_dim / 2) as u32, 1, 1),
15944                shared_mem_bytes: 0,
15945            };
15946            let __s_lb = self.gpu.stream();
15947            let mut lb = __s_lb.launch_builder(&f);
15948            lb.arg(&v)
15949                .arg(&hd)
15950                .arg(&nd)
15951                .arg(&nh)
15952                .arg(&nhkv)
15953                .arg(&theta_scale)
15954                .arg(&freq_scale);
15955            unsafe {
15956                lb.launch(cfg)?;
15957            }
15958        }
15959        {
15960            let f = self.func("append_kv_vl");
15961            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
15962            let cfg = LaunchConfig {
15963                grid_dim: (nblk, max_t, b as u32),
15964                block_dim: (32, 1, 1),
15965                shared_mem_bytes: 0,
15966            };
15967            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
15968            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15969            let __s_lb = self.gpu.stream();
15970            let mut lb = __s_lb.launch_builder(&f);
15971            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
15972            unsafe {
15973                lb.launch(cfg)?;
15974            }
15975        }
15976        Ok(())
15977    }
15978
15979    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
15980    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
15981    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
15982    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
15983    pub fn fa_prefill_view(
15984        &self,
15985        q: &CudaSlice<f32>,
15986        k: &cudarc::driver::CudaView<u8>,
15987        v: &cudarc::driver::CudaView<u8>,
15988        o: &mut CudaSlice<f32>,
15989        head_dim: usize,
15990        n_head: usize,
15991        n_head_kv: usize,
15992        t: usize,
15993        t_kv: usize,
15994        scale: f32,
15995        causal: bool,
15996        k_tok_bytes: usize,
15997        v_tok_bytes: usize,
15998        g: bool,
15999    ) -> Result<(), Box<dyn std::error::Error>> {
16000        if portable_mma_gated() {
16001            return self.sdpa_naive_quantized_view(
16002                q,
16003                k,
16004                v,
16005                o,
16006                head_dim,
16007                n_head,
16008                n_head_kv,
16009                t,
16010                t_kv,
16011                scale,
16012                causal,
16013                k_tok_bytes,
16014                v_tok_bytes,
16015            );
16016        }
16017        const BLOCK_Q: usize = 64;
16018        const BK: usize = 32;
16019        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
16020        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
16021        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
16022        let f = if g {
16023            self.func_g(&name)
16024        } else {
16025            self.func(&name)
16026        };
16027        let shmem =
16028            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
16029        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16030        f.set_attribute(
16031            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16032            shmem as i32,
16033        )?;
16034        let cfg = LaunchConfig {
16035            grid_dim: (
16036                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16037                n_head as u32,
16038                1,
16039            ),
16040            block_dim: (32, 4, 1),
16041            shared_mem_bytes: shmem,
16042        };
16043        let (hd, nh, nhkv, ti, tkvi, cz) = (
16044            head_dim as i32,
16045            n_head as i32,
16046            n_head_kv as i32,
16047            t as i32,
16048            t_kv as i32,
16049            causal as i32,
16050        );
16051        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16052        let __s_b = self.gpu.stream();
16053        let mut b = __s_b.launch_builder(&f);
16054        b.arg(q)
16055            .arg(k)
16056            .arg(v)
16057            .arg(o)
16058            .arg(&hd)
16059            .arg(&nh)
16060            .arg(&nhkv)
16061            .arg(&ti)
16062            .arg(&tkvi)
16063            .arg(&scale)
16064            .arg(&cz)
16065            .arg(&ktb)
16066            .arg(&vtb);
16067        unsafe {
16068            b.launch(cfg)?;
16069        }
16070        Ok(())
16071    }
16072
16073    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
16074    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
16075    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
16076    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
16077    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
16078    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
16079    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
16080    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
16081    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
16082    #[allow(clippy::too_many_arguments)]
16083    pub fn fa_prefill_view_ws(
16084        &self,
16085        q: &CudaSlice<f32>,
16086        k: &cudarc::driver::CudaView<u8>,
16087        v: &cudarc::driver::CudaView<u8>,
16088        o: &mut CudaSlice<f32>,
16089        head_dim: usize,
16090        n_head: usize,
16091        n_head_kv: usize,
16092        t: usize,
16093        t_kv: usize,
16094        scale: f32,
16095        causal: bool,
16096        k_tok_bytes: usize,
16097        v_tok_bytes: usize,
16098        g: bool,
16099    ) -> Result<(), Box<dyn std::error::Error>> {
16100        if portable_mma_gated() {
16101            return self.sdpa_naive_quantized_view(
16102                q,
16103                k,
16104                v,
16105                o,
16106                head_dim,
16107                n_head,
16108                n_head_kv,
16109                t,
16110                t_kv,
16111                scale,
16112                causal,
16113                k_tok_bytes,
16114                v_tok_bytes,
16115            );
16116        }
16117        const BLOCK_Q: usize = 64;
16118        const BK: usize = 32;
16119        let kv_dim_k = n_head_kv * head_dim;
16120        let kv_dim_v = n_head_kv * head_dim;
16121        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16122        let v_ws_bytes = t_kv * kv_dim_v * 2;
16123        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
16124        let mut guard = self.prime_deqw_ws.lock().unwrap();
16125        let need_grow = match guard.as_ref() {
16126            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16127            None => true,
16128        };
16129        if need_grow {
16130            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16131            let (ck, cv) = guard
16132                .as_ref()
16133                .map(|(a, b)| (a.len(), b.len()))
16134                .unwrap_or((0, 0));
16135            *guard = Some((
16136                self.alloc_u8(grow(ck, k_ws_bytes))?,
16137                self.alloc_u8(grow(cv, v_ws_bytes))?,
16138            ));
16139        }
16140        let (kw, vw) = guard.as_mut().unwrap();
16141        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
16142        {
16143            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
16144            let f = if g {
16145                self.func_g("fa_dequant_kv_ws_bf16")
16146            } else {
16147                self.func("fa_dequant_kv_ws_bf16")
16148            };
16149            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16150            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16151            let cfg = LaunchConfig {
16152                grid_dim: (nblk.max(1), 1, 1),
16153                block_dim: (256, 1, 1),
16154                shared_mem_bytes: 0,
16155            };
16156            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16157            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16158            let __s_b = self.gpu.stream();
16159            let mut b = __s_b.launch_builder(&f);
16160            b.arg(k)
16161                .arg(v)
16162                .arg(&mut *kw)
16163                .arg(&mut *vw)
16164                .arg(&kdk)
16165                .arg(&kdv)
16166                .arg(&tkvi)
16167                .arg(&ktb)
16168                .arg(&vtb);
16169            unsafe {
16170                b.launch(cfg)?;
16171            }
16172        }
16173        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
16174        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
16175        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
16176        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
16177        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
16178        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
16179        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
16180        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16181            .map(|v| v != "0")
16182            .unwrap_or(true);
16183        {
16184            let hd_sfx = fa_hd_suffix(head_dim)?;
16185            let f = self.func(&format!(
16186                "fa_prefill_qw{}{hd_sfx}",
16187                if db { "_db" } else { "" }
16188            ));
16189            let shmem = if db {
16190                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
16191                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16192            } else {
16193                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16194            };
16195            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16196            f.set_attribute(
16197                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16198                shmem as i32,
16199            )?;
16200            let cfg = LaunchConfig {
16201                grid_dim: (
16202                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16203                    n_head as u32,
16204                    1,
16205                ),
16206                block_dim: (32, 4, 1),
16207                shared_mem_bytes: shmem,
16208            };
16209            let (hd, nh, nhkv, ti, tkvi, cz) = (
16210                head_dim as i32,
16211                n_head as i32,
16212                n_head_kv as i32,
16213                t as i32,
16214                t_kv as i32,
16215                causal as i32,
16216            );
16217            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16218            let __s_b = self.gpu.stream();
16219            let mut b = __s_b.launch_builder(&f);
16220            b.arg(q)
16221                .arg(&*kw)
16222                .arg(&*vw)
16223                .arg(o)
16224                .arg(&hd)
16225                .arg(&nh)
16226                .arg(&nhkv)
16227                .arg(&ti)
16228                .arg(&tkvi)
16229                .arg(&scale)
16230                .arg(&cz)
16231                .arg(&kdk)
16232                .arg(&kdv);
16233            unsafe {
16234                b.launch(cfg)?;
16235            }
16236        }
16237        Ok(())
16238    }
16239
16240    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
16241    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
16242    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
16243    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
16244    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
16245    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
16246    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
16247    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
16248    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
16249    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
16250    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
16251    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
16252    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
16253    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
16254    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
16255    #[allow(clippy::too_many_arguments)]
16256    pub fn fa_prefill_view_ws_w_hd128(
16257        &self,
16258        q: &CudaSlice<f32>,
16259        k: &cudarc::driver::CudaView<u8>,
16260        v: &cudarc::driver::CudaView<u8>,
16261        o: &mut CudaSlice<f32>,
16262        head_dim: usize,
16263        n_head: usize,
16264        n_head_kv: usize,
16265        t: usize,
16266        t_kv: usize,
16267        scale: f32,
16268        causal: bool,
16269        window: usize,
16270        k_tok_bytes: usize,
16271        v_tok_bytes: usize,
16272    ) -> Result<(), Box<dyn std::error::Error>> {
16273        assert_eq!(
16274            head_dim, 128,
16275            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
16276        );
16277        if portable_mma_gated() {
16278            return self.sdpa_naive_w_quantized_view(
16279                q,
16280                k,
16281                v,
16282                o,
16283                head_dim,
16284                n_head,
16285                n_head_kv,
16286                t,
16287                t_kv,
16288                scale,
16289                causal,
16290                window,
16291                k_tok_bytes,
16292                v_tok_bytes,
16293            );
16294        }
16295        const BLOCK_Q: usize = 64;
16296        const BK: usize = 32;
16297        let kv_dim_k = n_head_kv * head_dim;
16298        let kv_dim_v = n_head_kv * head_dim;
16299        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16300        let v_ws_bytes = t_kv * kv_dim_v * 2;
16301        let mut guard = self.prime_deqw_ws.lock().unwrap();
16302        let need_grow = match guard.as_ref() {
16303            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16304            None => true,
16305        };
16306        if need_grow {
16307            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16308            let (ck, cv) = guard
16309                .as_ref()
16310                .map(|(a, b)| (a.len(), b.len()))
16311                .unwrap_or((0, 0));
16312            *guard = Some((
16313                self.alloc_u8(grow(ck, k_ws_bytes))?,
16314                self.alloc_u8(grow(cv, v_ws_bytes))?,
16315            ));
16316        }
16317        let (kw, vw) = guard.as_mut().unwrap();
16318        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
16319        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
16320        {
16321            let f = self.func("fa_dequant_kv_ws_bf16");
16322            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16323            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16324            let cfg = LaunchConfig {
16325                grid_dim: (nblk.max(1), 1, 1),
16326                block_dim: (256, 1, 1),
16327                shared_mem_bytes: 0,
16328            };
16329            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16330            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16331            let __s_b = self.gpu.stream();
16332            let mut b = __s_b.launch_builder(&f);
16333            b.arg(k)
16334                .arg(v)
16335                .arg(&mut *kw)
16336                .arg(&mut *vw)
16337                .arg(&kdk)
16338                .arg(&kdv)
16339                .arg(&tkvi)
16340                .arg(&ktb)
16341                .arg(&vtb);
16342            unsafe {
16343                b.launch(cfg)?;
16344            }
16345        }
16346        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
16347        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16348            .map(|v| v != "0")
16349            .unwrap_or(true);
16350        {
16351            let f = self.func(if db {
16352                "fa_prefill_qw_db_w_hd128"
16353            } else {
16354                "fa_prefill_qw_w_hd128"
16355            });
16356            let shmem = if db {
16357                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16358            } else {
16359                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16360            };
16361            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16362            f.set_attribute(
16363                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16364                shmem as i32,
16365            )?;
16366            let cfg = LaunchConfig {
16367                grid_dim: (
16368                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16369                    n_head as u32,
16370                    1,
16371                ),
16372                block_dim: (32, 4, 1),
16373                shared_mem_bytes: shmem,
16374            };
16375            let (hd, nh, nhkv, ti, tkvi, cz) = (
16376                head_dim as i32,
16377                n_head as i32,
16378                n_head_kv as i32,
16379                t as i32,
16380                t_kv as i32,
16381                causal as i32,
16382            );
16383            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
16384            let __s_b = self.gpu.stream();
16385            let mut b = __s_b.launch_builder(&f);
16386            b.arg(q)
16387                .arg(&*kw)
16388                .arg(&*vw)
16389                .arg(o)
16390                .arg(&hd)
16391                .arg(&nh)
16392                .arg(&nhkv)
16393                .arg(&ti)
16394                .arg(&tkvi)
16395                .arg(&scale)
16396                .arg(&cz)
16397                .arg(&kdk)
16398                .arg(&kdv)
16399                .arg(&wnd);
16400            unsafe {
16401                b.launch(cfg)?;
16402            }
16403        }
16404        Ok(())
16405    }
16406
16407    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
16408    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
16409    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
16410    pub fn fa_decode(
16411        &self,
16412        q: &CudaSlice<f32>,
16413        k: &cudarc::driver::CudaView<u8>,
16414        v: &cudarc::driver::CudaView<u8>,
16415        o: &mut CudaSlice<f32>,
16416        head_dim: usize,
16417        n_head: usize,
16418        n_head_kv: usize,
16419        t_kv: usize,
16420        scale: f32,
16421        k_tok_bytes: usize,
16422        v_tok_bytes: usize,
16423    ) -> Result<(), Box<dyn std::error::Error>> {
16424        self.fa_decode_kvmod(
16425            q,
16426            k,
16427            v,
16428            o,
16429            head_dim,
16430            n_head,
16431            n_head_kv,
16432            t_kv,
16433            scale,
16434            k_tok_bytes,
16435            v_tok_bytes,
16436            false,
16437        )
16438    }
16439
16440    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
16441    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
16442    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
16443    #[allow(clippy::too_many_arguments)]
16444    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
16445    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
16446    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
16447    #[allow(clippy::too_many_arguments)]
16448    #[allow(clippy::too_many_arguments)]
16449    fn fa_decode_scalar_unified(
16450        &self,
16451        q: &cudarc::driver::CudaView<f32>,
16452        k: &cudarc::driver::CudaView<u8>,
16453        v: &cudarc::driver::CudaView<u8>,
16454        o: &mut cudarc::driver::CudaViewMut<f32>,
16455        head_dim: usize,
16456        n_head: usize,
16457        n_head_kv: usize,
16458        t_kv_host: usize,
16459        t_kv_dev: Option<&CudaSlice<i32>>,
16460        scale: f32,
16461        n_splits: usize,
16462        split_keys: usize,
16463        k_tok_bytes: usize,
16464        v_tok_bytes: usize,
16465        g: bool,
16466        part_o: &mut CudaSlice<f32>,
16467        part_m: &mut CudaSlice<f32>,
16468        part_l: &mut CudaSlice<f32>,
16469        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
16470    ) -> Result<(), Box<dyn std::error::Error>> {
16471        let f = if g {
16472            self.func_g("fa_decode_f32")
16473        } else {
16474            self.fa_func("fa_decode_f32", head_dim)
16475        };
16476        let cfg = LaunchConfig {
16477            grid_dim: (n_head as u32, n_splits as u32, 1),
16478            block_dim: (head_dim as u32, 1, 1),
16479            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
16480        };
16481        let (hd, nh, nhkv, nsp) = (
16482            head_dim as i32,
16483            n_head as i32,
16484            n_head_kv as i32,
16485            n_splits as i32,
16486        );
16487        let (ktb, vtb, tkvi, ski) = (
16488            k_tok_bytes as i64,
16489            v_tok_bytes as i64,
16490            t_kv_host as i32,
16491            split_keys as i32,
16492        );
16493        let __s_b = self.gpu.stream();
16494        let mut b = __s_b.launch_builder(&f);
16495        match t_kv_dev {
16496            Some(d) => {
16497                b.arg(q)
16498                    .arg(k)
16499                    .arg(v)
16500                    .arg(&mut *part_o)
16501                    .arg(&mut *part_m)
16502                    .arg(&mut *part_l)
16503                    .arg(&hd)
16504                    .arg(&nh)
16505                    .arg(&nhkv)
16506                    .arg(&tkvi)
16507                    .arg(d)
16508                    .arg(&scale)
16509                    .arg(&nsp)
16510                    .arg(&ski)
16511                    .arg(&ktb)
16512                    .arg(&vtb);
16513                unsafe {
16514                    b.launch(cfg)?;
16515                }
16516            }
16517            None => {
16518                let null: u64 = 0;
16519                b.arg(q)
16520                    .arg(k)
16521                    .arg(v)
16522                    .arg(&mut *part_o)
16523                    .arg(&mut *part_m)
16524                    .arg(&mut *part_l)
16525                    .arg(&hd)
16526                    .arg(&nh)
16527                    .arg(&nhkv)
16528                    .arg(&tkvi)
16529                    .arg(&null)
16530                    .arg(&scale)
16531                    .arg(&nsp)
16532                    .arg(&ski)
16533                    .arg(&ktb)
16534                    .arg(&vtb);
16535                unsafe {
16536                    b.launch(cfg)?;
16537                }
16538            }
16539        }
16540        let cfg2 = LaunchConfig {
16541            grid_dim: (n_head as u32, 1, 1),
16542            block_dim: (head_dim as u32, 1, 1),
16543            shared_mem_bytes: 0,
16544        };
16545        if let Some((oq, od)) = q8_out {
16546            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
16547            let fc = if g {
16548                self.func_g("fa_decode_combine_q8_1")
16549            } else {
16550                self.fa_func("fa_decode_combine_q8_1", head_dim)
16551            };
16552            let __s_b2 = self.gpu.stream();
16553            let mut b2 = __s_b2.launch_builder(&fc);
16554            b2.arg(&*part_o)
16555                .arg(&*part_m)
16556                .arg(&*part_l)
16557                .arg(oq)
16558                .arg(od)
16559                .arg(&hd)
16560                .arg(&nh)
16561                .arg(&nsp);
16562            unsafe {
16563                b2.launch(cfg2)?;
16564            }
16565            return Ok(());
16566        }
16567        let fc = if g {
16568            self.func_g("fa_decode_combine_f32")
16569        } else {
16570            self.fa_func("fa_decode_combine_f32", head_dim)
16571        };
16572        let __s_b2 = self.gpu.stream();
16573        let mut b2 = __s_b2.launch_builder(&fc);
16574        b2.arg(&*part_o)
16575            .arg(&*part_m)
16576            .arg(&*part_l)
16577            .arg(o)
16578            .arg(&hd)
16579            .arg(&nh)
16580            .arg(&nsp);
16581        unsafe {
16582            b2.launch(cfg2)?;
16583        }
16584        Ok(())
16585    }
16586
16587    pub fn fa_decode_kvmod(
16588        &self,
16589        q: &CudaSlice<f32>,
16590        k: &cudarc::driver::CudaView<u8>,
16591        v: &cudarc::driver::CudaView<u8>,
16592        o: &mut CudaSlice<f32>,
16593        head_dim: usize,
16594        n_head: usize,
16595        n_head_kv: usize,
16596        t_kv: usize,
16597        scale: f32,
16598        k_tok_bytes: usize,
16599        v_tok_bytes: usize,
16600        g: bool,
16601    ) -> Result<(), Box<dyn std::error::Error>> {
16602        let q_view = q.as_view();
16603        let mut o_view = o.as_view_mut();
16604        self.fa_decode_kvmod_view(
16605            &q_view,
16606            k,
16607            v,
16608            &mut o_view,
16609            head_dim,
16610            n_head,
16611            n_head_kv,
16612            t_kv,
16613            scale,
16614            k_tok_bytes,
16615            v_tok_bytes,
16616            g,
16617        )
16618    }
16619
16620    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
16621    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
16622    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
16623    /// per-session KV view and FA launch.
16624    #[allow(clippy::too_many_arguments)]
16625    pub fn fa_decode_kvmod_view(
16626        &self,
16627        q: &cudarc::driver::CudaView<f32>,
16628        k: &cudarc::driver::CudaView<u8>,
16629        v: &cudarc::driver::CudaView<u8>,
16630        o: &mut cudarc::driver::CudaViewMut<f32>,
16631        head_dim: usize,
16632        n_head: usize,
16633        n_head_kv: usize,
16634        t_kv: usize,
16635        scale: f32,
16636        k_tok_bytes: usize,
16637        v_tok_bytes: usize,
16638        g: bool,
16639    ) -> Result<(), Box<dyn std::error::Error>> {
16640        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
16641        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
16642        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
16643        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
16644        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
16645        //
16646        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
16647        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
16648        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
16649        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
16650        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
16651        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
16652        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
16653        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
16654        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
16655        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
16656        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
16657        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
16658        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
16659        // fall to the exact scalar there instead of the broken register arm.
16660        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
16661        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
16662        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
16663        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
16664        if g && head_dim == 256 && !fa_v4_at(t_kv) {
16665            fa_vec = false;
16666        }
16667        let sp = fa_split_keys(t_kv, n_head_kv);
16668        let n_splits = if fa_vec {
16669            ((t_kv + sp - 1) / sp).max(1)
16670        } else {
16671            ((t_kv + 255) / 256).max(1)
16672        };
16673        let o_len = n_head * n_splits * head_dim;
16674        let ml_len = n_head * n_splits;
16675        let mut part_guard = self.fa_part_pool.lock().unwrap();
16676        if part_guard
16677            .as_ref()
16678            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
16679            .unwrap_or(true)
16680        {
16681            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
16682            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
16683            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
16684            // later live allocations land at those addresses, and the next graph REPLAY writes
16685            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
16686            // output corruption began the burst after the trunk's t_kv growth first realloc'd
16687            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
16688            // the baked addresses alive (single-stream: eager writes the new buffers, replays
16689            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
16690            // (total retired < final size).
16691            let old = part_guard.take();
16692            let (co, cm) = old
16693                .as_ref()
16694                .map(|pp| (pp.0.len(), pp.1.len()))
16695                .unwrap_or((0, 0));
16696            if let Some(old) = old {
16697                self.fa_part_retired.lock().unwrap().push(old);
16698            }
16699            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
16700                eprintln!(
16701                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
16702                    co, o_len, cm, ml_len
16703                );
16704            }
16705            *part_guard = Some((
16706                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
16707                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16708                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16709            ));
16710        }
16711        let pg = part_guard.as_mut().unwrap();
16712        self.gpu
16713            .stream()
16714            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
16715        self.gpu
16716            .stream()
16717            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
16718        self.gpu
16719            .stream()
16720            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
16721        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
16722        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
16723        let (hd, nh, nhkv, tkvi, nsp) = (
16724            head_dim as i32,
16725            n_head as i32,
16726            n_head_kv as i32,
16727            t_kv as i32,
16728            n_splits as i32,
16729        );
16730        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16731        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
16732        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
16733        // silently truncating the accumulator.
16734        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
16735        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
16736        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
16737        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
16738        // 178.4 -> 173.7 when 512 rode vec unconditionally).
16739        let fa512_min = fa512_min_tkv();
16740        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
16741        // g-module keeps the v4 pick (its class is not the depth-decay class).
16742        let deep = fa_vec
16743            && head_dim == 256
16744            && fa_v4_at(t_kv)
16745            && !g
16746            && fa_deep_at(t_kv)
16747            && !matches!(fa_v4_mode(), "noB3" | "stage");
16748        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
16749            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
16750            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
16751            let gqa = (n_head / n_head_kv).max(1) as u32;
16752            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
16753            (
16754                fv,
16755                LaunchConfig {
16756                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16757                    block_dim: (32, gqa, 1),
16758                    shared_mem_bytes: 0,
16759                },
16760            )
16761        } else if fa_vec && head_dim <= 256 {
16762            let gqa = (n_head / n_head_kv).max(1) as u32;
16763            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
16764            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
16765            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
16766            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
16767            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
16768            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
16769            // dequant each tile ONCE per block.
16770            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
16771            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
16772            // there by 12x — latency, not bandwidth, rules small KV).
16773            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
16774            let smem_tkv = *SMEM_TKV.get_or_init(|| {
16775                std::env::var("MEMRA_FA_SMEM_TKV")
16776                    .ok()
16777                    .and_then(|v| v.parse().ok())
16778                    .unwrap_or_else(|| {
16779                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
16780                    })
16781            });
16782            if fa_v4_at(t_kv) && head_dim == 256 {
16783                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
16784                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
16785                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
16786                let v4name = match fa_v4_mode() {
16787                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
16788                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
16789                    _ if deep => "fa_decode_vec_q_v4_deep",
16790                    _ => "fa_decode_vec_q_v4",
16791                };
16792                let fv = if g {
16793                    self.func_g(v4name)
16794                } else {
16795                    self.func(v4name)
16796                };
16797                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
16798                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
16799                let shmem = (if deep { 12160 } else { 11520 }
16800                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
16801                use cudarc::driver::sys::CUfunction_attribute_enum as A;
16802                fv.set_attribute(
16803                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16804                    shmem as i32,
16805                )?;
16806                (
16807                    fv,
16808                    LaunchConfig {
16809                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16810                        block_dim: (32, gqa, 1),
16811                        shared_mem_bytes: shmem,
16812                    },
16813                )
16814            } else if fa_v3_active(head_dim) {
16815                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
16816                // smem = sV only (half of v2's).
16817                let fv = if g {
16818                    self.func_g("fa_decode_vec_q_v3")
16819                } else {
16820                    self.func("fa_decode_vec_q_v3")
16821                };
16822                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
16823                (
16824                    fv,
16825                    LaunchConfig {
16826                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16827                        block_dim: (32, gqa, 1),
16828                        shared_mem_bytes: shmem,
16829                    },
16830                )
16831            } else if fa_v2_on() {
16832                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
16833                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
16834                // partials; same 32KB sK+sV tile as the smem twin.
16835                let fv = if g {
16836                    self.func_g("fa_decode_vec_q_v2")
16837                } else {
16838                    self.func("fa_decode_vec_q_v2")
16839                };
16840                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
16841                (
16842                    fv,
16843                    LaunchConfig {
16844                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16845                        block_dim: (32, gqa, 1),
16846                        shared_mem_bytes: shmem,
16847                    },
16848                )
16849            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
16850            {
16851                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
16852                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
16853                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
16854                let fv = if g {
16855                    self.func_g("fa_decode_vec_q_smem")
16856                } else {
16857                    self.func("fa_decode_vec_q_smem")
16858                };
16859                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
16860                use cudarc::driver::sys::CUfunction_attribute_enum as A;
16861                fv.set_attribute(
16862                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16863                    shmem as i32,
16864                )?;
16865                (
16866                    fv,
16867                    LaunchConfig {
16868                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16869                        block_dim: (32, gqa, 1),
16870                        shared_mem_bytes: shmem,
16871                    },
16872                )
16873            } else {
16874                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
16875                // dequant, zero dynamic shared memory.
16876                let fv = if g {
16877                    self.func_g("fa_decode_vec_q")
16878                } else {
16879                    self.func("fa_decode_vec_q")
16880                };
16881                (
16882                    fv,
16883                    LaunchConfig {
16884                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16885                        block_dim: (32, gqa, 1),
16886                        shared_mem_bytes: 0,
16887                    },
16888                )
16889            }
16890        } else {
16891            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
16892            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
16893            return self.fa_decode_scalar_unified(
16894                q,
16895                k,
16896                v,
16897                o,
16898                head_dim,
16899                n_head,
16900                n_head_kv,
16901                t_kv,
16902                None,
16903                scale,
16904                n_splits,
16905                if fa_vec { sp } else { 256 },
16906                k_tok_bytes,
16907                v_tok_bytes,
16908                g,
16909                part_o,
16910                part_m,
16911                part_l,
16912                None,
16913            );
16914        };
16915        let __s_b = self.gpu.stream();
16916        let mut b = __s_b.launch_builder(&f);
16917        b.arg(q)
16918            .arg(k)
16919            .arg(v)
16920            .arg(&mut *part_o)
16921            .arg(&mut *part_m)
16922            .arg(&mut *part_l)
16923            .arg(&hd)
16924            .arg(&nh)
16925            .arg(&nhkv)
16926            .arg(&tkvi)
16927            .arg(&scale)
16928            .arg(&nsp)
16929            .arg(&ktb)
16930            .arg(&vtb);
16931        unsafe {
16932            b.launch(cfg)?;
16933        }
16934        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
16935        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
16936        let (fc, cfg2) = (
16937            if g {
16938                self.func_g("fa_decode_combine_f32")
16939            } else {
16940                self.fa_func("fa_decode_combine_f32", head_dim)
16941            },
16942            LaunchConfig {
16943                grid_dim: (n_head as u32, 1, 1),
16944                block_dim: (head_dim as u32, 1, 1),
16945                shared_mem_bytes: 0,
16946            },
16947        );
16948        let __s_b2 = self.gpu.stream();
16949        let mut b2 = __s_b2.launch_builder(&fc);
16950        b2.arg(&*part_o)
16951            .arg(&*part_m)
16952            .arg(&*part_l)
16953            .arg(o)
16954            .arg(&hd)
16955            .arg(&nh)
16956            .arg(&nsp);
16957        unsafe {
16958            b2.launch(cfg2)?;
16959        }
16960        Ok(())
16961    }
16962
16963    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
16964    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
16965    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
16966    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
16967    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
16968    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
16969    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
16970    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
16971    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
16972    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
16973    #[allow(clippy::too_many_arguments)]
16974    pub fn fa_decode_batch_seqs_v4(
16975        &self,
16976        q: &CudaSlice<f32>,
16977        kv_ptrs: &cudarc::driver::CudaView<u64>,
16978        pos_seq: &CudaSlice<i32>,
16979        o: &mut CudaSlice<f32>,
16980        head_dim: usize,
16981        n_head: usize,
16982        n_head_kv: usize,
16983        b_n: usize,
16984        t_kv_max: usize,
16985        scale: f32,
16986        split_keys: usize,
16987        k_tok_bytes: usize,
16988        v_tok_bytes: usize,
16989    ) -> Result<(), Box<dyn std::error::Error>> {
16990        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
16991        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
16992        let o_len = b_n * n_head * n_splits_max * head_dim;
16993        let ml_len = b_n * n_head * n_splits_max;
16994        let mut part_guard = self.fa_part_pool.lock().unwrap();
16995        if part_guard
16996            .as_ref()
16997            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
16998            .unwrap_or(true)
16999        {
17000            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17001            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17002            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17003            // later live allocations land at those addresses, and the next graph REPLAY writes
17004            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17005            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17006            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17007            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17008            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17009            // (total retired < final size).
17010            let old = part_guard.take();
17011            let (co, cm) = old
17012                .as_ref()
17013                .map(|pp| (pp.0.len(), pp.1.len()))
17014                .unwrap_or((0, 0));
17015            if let Some(old) = old {
17016                self.fa_part_retired.lock().unwrap().push(old);
17017            }
17018            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17019                eprintln!(
17020                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17021                    co, o_len, cm, ml_len
17022                );
17023            }
17024            *part_guard = Some((
17025                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17026                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17027                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17028            ));
17029        }
17030        let pg = part_guard.as_mut().unwrap();
17031        self.gpu
17032            .stream()
17033            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17034        self.gpu
17035            .stream()
17036            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17037        self.gpu
17038            .stream()
17039            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17040        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17041        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17042        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
17043        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17044        let gqa = (n_head / n_head_kv).max(1) as u32;
17045        let f = self.func("fa_decode_vec_q_seqs_v4");
17046        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
17047        let shmem = (11520 + 32 * head_dim * 2) as u32;
17048        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17049        f.set_attribute(
17050            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17051            shmem as i32,
17052        )?;
17053        let cfg = LaunchConfig {
17054            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
17055            block_dim: (32, gqa, 1),
17056            shared_mem_bytes: shmem,
17057        };
17058        {
17059            let __s_b = self.gpu.stream();
17060            let mut b = __s_b.launch_builder(&f);
17061            b.arg(q)
17062                .arg(kv_ptrs)
17063                .arg(pos_seq)
17064                .arg(&mut *part_o)
17065                .arg(&mut *part_m)
17066                .arg(&mut *part_l)
17067                .arg(&hd)
17068                .arg(&nh)
17069                .arg(&nhkv)
17070                .arg(&scale)
17071                .arg(&nspm)
17072                .arg(&spk)
17073                .arg(&ktb)
17074                .arg(&vtb);
17075            unsafe {
17076                b.launch(cfg)?;
17077            }
17078        }
17079        let fc = self.func("fa_decode_combine_seqs");
17080        let cfg2 = LaunchConfig {
17081            grid_dim: (n_head as u32, b_n as u32, 1),
17082            block_dim: (head_dim as u32, 1, 1),
17083            shared_mem_bytes: 0,
17084        };
17085        let __s_b2 = self.gpu.stream();
17086        let mut b2 = __s_b2.launch_builder(&fc);
17087        b2.arg(&*part_o)
17088            .arg(&*part_m)
17089            .arg(&*part_l)
17090            .arg(o)
17091            .arg(&hd)
17092            .arg(&nh)
17093            .arg(pos_seq)
17094            .arg(&nspm)
17095            .arg(&spk);
17096        unsafe {
17097            b2.launch(cfg2)?;
17098        }
17099        Ok(())
17100    }
17101
17102    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
17103    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
17104    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
17105    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
17106    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
17107    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
17108    #[allow(clippy::too_many_arguments)]
17109    pub fn append_kv_quantized_seqs(
17110        &self,
17111        k_rows: &CudaSlice<f32>,
17112        v_rows: &CudaSlice<f32>,
17113        kv_ptrs: &cudarc::driver::CudaView<u64>,
17114        pos_seq: &CudaSlice<i32>,
17115        b_n: usize,
17116        kv_dim_k: usize,
17117        kv_dim_v: usize,
17118        k_tok_bytes: usize,
17119        v_tok_bytes: usize,
17120    ) -> Result<(), Box<dyn std::error::Error>> {
17121        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
17122        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
17123        let cfg = LaunchConfig {
17124            grid_dim: (nblk, b_n as u32, 1),
17125            block_dim: (32, 1, 1),
17126            shared_mem_bytes: 0,
17127        };
17128        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
17129        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17130        let __s_b = self.gpu.stream();
17131        let mut b = __s_b.launch_builder(&f);
17132        b.arg(k_rows)
17133            .arg(v_rows)
17134            .arg(kv_ptrs)
17135            .arg(pos_seq)
17136            .arg(&kdk)
17137            .arg(&kdv)
17138            .arg(&ktb)
17139            .arg(&vtb);
17140        unsafe {
17141            b.launch(cfg)?;
17142        }
17143        Ok(())
17144    }
17145
17146    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
17147    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
17148    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
17149    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
17150    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
17151    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
17152        std::env::var("MEMRA_NO_FA_VEC").is_err()
17153            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
17154            && base_len + 1 >= fa_vec_min_tkv()
17155            && head_dim <= 256
17156            && head_dim % 32 == 0
17157    }
17158
17159    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
17160    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
17161    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
17162    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
17163    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
17164    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
17165    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
17166    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
17167    #[allow(clippy::too_many_arguments)]
17168    pub fn fa_decode_rows(
17169        &self,
17170        q: &CudaSlice<f32>,
17171        k: &cudarc::driver::CudaView<u8>,
17172        v: &cudarc::driver::CudaView<u8>,
17173        o: &mut CudaSlice<f32>,
17174        head_dim: usize,
17175        n_head: usize,
17176        n_head_kv: usize,
17177        base_len: usize,
17178        t: usize,
17179        scale: f32,
17180        k_tok_bytes: usize,
17181        v_tok_bytes: usize,
17182        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
17183        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
17184        // keep the host arg. None is a bug for hd512 (asserted below).
17185        base_dev: Option<(&CudaSlice<i32>, i32)>,
17186        // K and V planes hold the same values (gemma globals, wv:=wk): pick
17187        // the _kv twin — V plane never read, value rides the q8_0 key dq.
17188        kv_shared: bool,
17189        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
17190        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
17191        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
17192        g: bool,
17193        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
17194        // (hd512 path) — the standalone quantize launch folds away.
17195        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17196    ) -> Result<(), Box<dyn std::error::Error>> {
17197        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
17198        let t_kv_max = base_len + t; // LAST row's key bound
17199        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
17200        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
17201        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
17202        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
17203        // (parity law), so the partition is freely tunable — verify and decode move together.
17204        if head_dim == 512 {
17205            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17206            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
17207            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
17208            let v = *SP512.get_or_init(|| {
17209                std::env::var("MEMRA_FA_SP512")
17210                    .ok()
17211                    .and_then(|x| x.parse().ok())
17212                    .unwrap_or(0)
17213            });
17214            sp = if v >= 8 {
17215                v
17216            } else {
17217                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17218            };
17219        }
17220        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17221        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17222        let gqa = (n_head / n_head_kv).max(1) as u32;
17223        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
17224        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
17225        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
17226        // the different partition changes the combine's FP order (greedy tie flips at depth;
17227        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
17228        // consecutive rows by their OWN ladder value and launch once per group — each row then
17229        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
17230        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
17231        // sp override is t_kv-independent by construction).
17232        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
17233        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
17234            groups.push((0, t, sp));
17235        } else {
17236            let mut r0 = 0usize;
17237            while r0 < t {
17238                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
17239                let mut r1 = r0 + 1;
17240                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
17241                    r1 += 1;
17242                }
17243                groups.push((r0, r1 - r0, sp_g));
17244                r0 = r1;
17245            }
17246        }
17247        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
17248        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
17249        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
17250        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17251        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
17252            std::env::var("MEMRA_FA_SMEM_TKV")
17253                .ok()
17254                .and_then(|v| v.parse().ok())
17255                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17256        });
17257        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
17258        let v3 = fa_v3_active(head_dim);
17259        let smem_rows =
17260            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
17261        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
17262        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
17263        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
17264        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
17265        let _ = kv_shared;
17266        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
17267        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
17268        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
17269        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
17270        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
17271        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
17272        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
17273        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
17274        // (kv_head, split) stages its tile once and loops the rows over it — kills the
17275        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
17276        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
17277        // shared by every hd512 caller through this wrapper (decode+verify flip together;
17278        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
17279        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
17280        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
17281        // not unpack-bound; jsonl 2026-07-14.
17282        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17283        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
17284        let tb512 = head_dim == 512
17285            && sp <= 32
17286            && n_head / n_head_kv.max(1) <= 16
17287            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
17288        let fname = if tb512 {
17289            "fa_decode_vec_q_rows_v4_512_tb"
17290        } else if i2 {
17291            "fa_decode_vec_q_rows_dpl16_i2"
17292        } else if head_dim == 512 {
17293            "fa_decode_vec_q_rows_dpl16"
17294        }
17295        // gemma globals (parity law)
17296        else if v4 {
17297            "fa_decode_vec_q_rows_v4"
17298        } else if v3 {
17299            "fa_decode_vec_q_rows_v3"
17300        } else if fa_v2_on() {
17301            "fa_decode_vec_q_rows_v2"
17302        } else if smem_rows {
17303            "fa_decode_vec_q_rows_smem"
17304        } else {
17305            "fa_decode_vec_q_rows"
17306        };
17307        let f = if head_dim == 512 {
17308            self.fa_func(fname, head_dim)
17309        } else if g {
17310            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
17311            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
17312            // g-module rows against decode's g-module v4 — different programs, short-VG
17313            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
17314            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
17315            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
17316            // dq macros are format-aware.
17317            self.func_g(if smem_rows {
17318                "fa_decode_vec_q_rows"
17319            } else {
17320                fname
17321            })
17322        } else {
17323            self.func(fname)
17324        };
17325        let shmem = if tb512 {
17326            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
17327            let gk = Self::gkv_on();
17328            let sh =
17329                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
17330            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17331            f.set_attribute(
17332                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17333                sh as i32,
17334            )?;
17335            sh
17336        } else if v4 || v3 || smem_rows || fa_v2_on() {
17337            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
17338            let sh = (if v4 {
17339                11520 + 32 * head_dim * if g { 1 } else { 2 }
17340            } else if v3 {
17341                32 * head_dim * 2
17342            } else {
17343                2 * 32 * head_dim * 2
17344            }) as u32;
17345            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17346            f.set_attribute(
17347                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17348                sh as i32,
17349            )?;
17350            sh
17351        } else {
17352            0
17353        };
17354        // Per-GROUP launches (single group in the common case — identical to the pre-fix
17355        // single launch there): each group gets its own partials (the rows kernel indexes
17356        // partials by its LOCAL grid.z row) and q/o row-offset views.
17357        for &(r0, t_g, sp_g) in &groups {
17358            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
17359            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
17360            let base_i = (base_len + r0) as i32;
17361            let o_len = t_g * n_head * n_splits_g * head_dim;
17362            let ml_len = t_g * n_head * n_splits_g;
17363            let mut part_guard = self.fa_part_pool.lock().unwrap();
17364            if part_guard
17365                .as_ref()
17366                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17367                .unwrap_or(true)
17368            {
17369                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17370                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17371                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17372                // later live allocations land at those addresses, and the next graph REPLAY writes
17373                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17374                // output corruption began the burst after the trunk's t_kv growth first realloc'd
17375                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17376                // the baked addresses alive (single-stream: eager writes the new buffers, replays
17377                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17378                // (total retired < final size).
17379                let old = part_guard.take();
17380                let (co, cm) = old
17381                    .as_ref()
17382                    .map(|pp| (pp.0.len(), pp.1.len()))
17383                    .unwrap_or((0, 0));
17384                if let Some(old) = old {
17385                    self.fa_part_retired.lock().unwrap().push(old);
17386                }
17387                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17388                    eprintln!(
17389                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17390                        co, o_len, cm, ml_len
17391                    );
17392                }
17393                *part_guard = Some((
17394                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17395                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17396                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17397                ));
17398            }
17399            let pg = part_guard.as_mut().unwrap();
17400            self.gpu
17401                .stream()
17402                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17403            self.gpu
17404                .stream()
17405                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17406            self.gpu
17407                .stream()
17408                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17409            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17410            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17411            let qv = self.view(q, t * n_head * head_dim);
17412            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17413            let cfg = LaunchConfig {
17414                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
17415                block_dim: (32, gqa, 1),
17416                shared_mem_bytes: shmem,
17417            };
17418            {
17419                let __s_b = self.gpu.stream();
17420                let mut b = __s_b.launch_builder(&f);
17421                if tb512 {
17422                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
17423                    let (bd, plus) =
17424                        base_dev.expect("hd512 rows twin requires a device base counter");
17425                    let plus_g = plus + r0 as i32;
17426                    let nr = t_g as i32;
17427                    if Self::pdl_on() && Self::pdl_wb_on() {
17428                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
17429                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17430                        let s = &self.gpu.stream();
17431                        let (pq, _b0) = q_g.device_ptr(s);
17432                        let (pk, _b1) = k.device_ptr(s);
17433                        let (pv, _b2) = v.device_ptr(s);
17434                        let (po, _b3) = part_o.device_ptr_mut(s);
17435                        let (pm, _b4) = part_m.device_ptr_mut(s);
17436                        let (pl, _b5) = part_l.device_ptr_mut(s);
17437                        let (pb, _b6) = bd.device_ptr(s);
17438                        let mut ps = [
17439                            &pq as *const _ as *mut std::ffi::c_void,
17440                            &pk as *const _ as *mut _,
17441                            &pv as *const _ as *mut _,
17442                            &po as *const _ as *mut _,
17443                            &pm as *const _ as *mut _,
17444                            &pl as *const _ as *mut _,
17445                            &hd as *const _ as *mut _,
17446                            &nh as *const _ as *mut _,
17447                            &nhkv as *const _ as *mut _,
17448                            &pb as *const _ as *mut _,
17449                            &plus_g as *const _ as *mut _,
17450                            &scale as *const _ as *mut _,
17451                            &nspm as *const _ as *mut _,
17452                            &spk as *const _ as *mut _,
17453                            &ktb as *const _ as *mut _,
17454                            &vtb as *const _ as *mut _,
17455                            &nr as *const _ as *mut _,
17456                        ];
17457                        unsafe {
17458                            self.launch_pdl_flash(
17459                                Self::gkv_on(),
17460                                "fa_decode_vec_q_rows_v4_512_tb",
17461                                (n_head_kv as u32, n_splits_g as u32, 1),
17462                                (32, gqa, 1),
17463                                shmem,
17464                                &mut ps,
17465                            )?;
17466                        }
17467                    } else {
17468                        let cfg_tb = LaunchConfig {
17469                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
17470                            block_dim: (32, gqa, 1),
17471                            shared_mem_bytes: shmem,
17472                        };
17473                        b.arg(&q_g)
17474                            .arg(k)
17475                            .arg(v)
17476                            .arg(&mut *part_o)
17477                            .arg(&mut *part_m)
17478                            .arg(&mut *part_l)
17479                            .arg(&hd)
17480                            .arg(&nh)
17481                            .arg(&nhkv)
17482                            .arg(bd)
17483                            .arg(&plus_g)
17484                            .arg(&scale)
17485                            .arg(&nspm)
17486                            .arg(&spk)
17487                            .arg(&ktb)
17488                            .arg(&vtb)
17489                            .arg(&nr);
17490                        unsafe {
17491                            b.launch(cfg_tb)?;
17492                        }
17493                    }
17494                } else if head_dim == 512 {
17495                    let (bd, plus) =
17496                        base_dev.expect("hd512 rows twin requires a device base counter");
17497                    let plus_g = plus + r0 as i32;
17498                    b.arg(&q_g)
17499                        .arg(k)
17500                        .arg(v)
17501                        .arg(&mut *part_o)
17502                        .arg(&mut *part_m)
17503                        .arg(&mut *part_l)
17504                        .arg(&hd)
17505                        .arg(&nh)
17506                        .arg(&nhkv)
17507                        .arg(bd)
17508                        .arg(&plus_g)
17509                        .arg(&scale)
17510                        .arg(&nspm)
17511                        .arg(&spk)
17512                        .arg(&ktb)
17513                        .arg(&vtb);
17514                    unsafe {
17515                        b.launch(cfg)?;
17516                    }
17517                } else {
17518                    b.arg(&q_g)
17519                        .arg(k)
17520                        .arg(v)
17521                        .arg(&mut *part_o)
17522                        .arg(&mut *part_m)
17523                        .arg(&mut *part_l)
17524                        .arg(&hd)
17525                        .arg(&nh)
17526                        .arg(&nhkv)
17527                        .arg(&base_i)
17528                        .arg(&scale)
17529                        .arg(&nspm)
17530                        .arg(&spk)
17531                        .arg(&ktb)
17532                        .arg(&vtb);
17533                    unsafe {
17534                        b.launch(cfg)?;
17535                    }
17536                }
17537            }
17538            let cfg2 = LaunchConfig {
17539                grid_dim: (n_head as u32, t_g as u32, 1),
17540                block_dim: (head_dim as u32, 1, 1),
17541                shared_mem_bytes: 0,
17542            };
17543            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17544            if head_dim == 512 {
17545                // device-len combine (shared by verify/eager/graph — parity by symbol): the
17546                // per-row n_splits derives from the SAME counter the rows kernel read.
17547                let (bd, plus) = base_dev.unwrap();
17548                let plus_g = plus + r0 as i32;
17549                if let Some((oq, od)) = q8_out.as_mut() {
17550                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
17551                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
17552                    if Self::pdl_on() && Self::pdl_wb_on() {
17553                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
17554                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17555                        let s = &self.gpu.stream();
17556                        let (po, _g0) = part_o.device_ptr(s);
17557                        let (pm, _g1) = part_m.device_ptr(s);
17558                        let (pl, _g2) = part_l.device_ptr(s);
17559                        let (pq, _g3) = oq.device_ptr_mut(s);
17560                        let (pd, _g4) = od.device_ptr_mut(s);
17561                        let (pb, _g5) = bd.device_ptr(s);
17562                        let mut ps = [
17563                            &po as *const _ as *mut std::ffi::c_void,
17564                            &pm as *const _ as *mut _,
17565                            &pl as *const _ as *mut _,
17566                            &pq as *const _ as *mut _,
17567                            &pd as *const _ as *mut _,
17568                            &hd as *const _ as *mut _,
17569                            &nh as *const _ as *mut _,
17570                            &pb as *const _ as *mut _,
17571                            &plus_g as *const _ as *mut _,
17572                            &nspm as *const _ as *mut _,
17573                            &spk as *const _ as *mut _,
17574                        ];
17575                        unsafe {
17576                            self.launch_pdl_flash(
17577                                Self::gkv_on(),
17578                                "fa_decode_combine_rows_dc_q8_1",
17579                                cfg2.grid_dim,
17580                                cfg2.block_dim,
17581                                0,
17582                                &mut ps,
17583                            )?;
17584                        }
17585                        continue;
17586                    }
17587                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
17588                    let __s_b2 = self.gpu.stream();
17589                    let mut b2 = __s_b2.launch_builder(&fc);
17590                    b2.arg(&*part_o)
17591                        .arg(&*part_m)
17592                        .arg(&*part_l)
17593                        .arg(&mut **oq)
17594                        .arg(&mut **od)
17595                        .arg(&hd)
17596                        .arg(&nh)
17597                        .arg(bd)
17598                        .arg(&plus_g)
17599                        .arg(&nspm)
17600                        .arg(&spk);
17601                    unsafe {
17602                        b2.launch(cfg2)?;
17603                    }
17604                    continue;
17605                }
17606                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
17607                let __s_b2 = self.gpu.stream();
17608                let mut b2 = __s_b2.launch_builder(&fc);
17609                b2.arg(&*part_o)
17610                    .arg(&*part_m)
17611                    .arg(&*part_l)
17612                    .arg(&mut o_g)
17613                    .arg(&hd)
17614                    .arg(&nh)
17615                    .arg(bd)
17616                    .arg(&plus_g)
17617                    .arg(&nspm)
17618                    .arg(&spk);
17619                unsafe {
17620                    b2.launch(cfg2)?;
17621                }
17622            } else {
17623                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
17624                // leave the caller's pair unwritten (consumer would read garbage).
17625                assert!(
17626                    q8_out.is_none(),
17627                    "rows q8 emit requires the hd512 dc combine"
17628                );
17629                let fc = self.func("fa_decode_combine_rows");
17630                let __s_b2 = self.gpu.stream();
17631                let mut b2 = __s_b2.launch_builder(&fc);
17632                b2.arg(&*part_o)
17633                    .arg(&*part_m)
17634                    .arg(&*part_l)
17635                    .arg(&mut o_g)
17636                    .arg(&hd)
17637                    .arg(&nh)
17638                    .arg(&base_i)
17639                    .arg(&nspm)
17640                    .arg(&spk);
17641                unsafe {
17642                    b2.launch(cfg2)?;
17643                }
17644            }
17645        }
17646        Ok(())
17647    }
17648
17649    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
17650    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
17651    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
17652    #[allow(clippy::too_many_arguments)]
17653    pub fn fa_decode_rows_w(
17654        &self,
17655        q: &CudaSlice<f32>,
17656        k: &cudarc::driver::CudaView<u8>,
17657        v: &cudarc::driver::CudaView<u8>,
17658        o: &mut CudaSlice<f32>,
17659        head_dim: usize,
17660        n_head: usize,
17661        n_head_kv: usize,
17662        base_dev: &CudaSlice<i32>,
17663        base_plus: i32,
17664        t: usize,
17665        scale: f32,
17666        window: usize,
17667        k_tok_bytes: usize,
17668        v_tok_bytes: usize,
17669        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17670    ) -> Result<(), Box<dyn std::error::Error>> {
17671        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
17672        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
17673        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
17674        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
17675        debug_assert!(head_dim == 256);
17676        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
17677        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
17678        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
17679        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
17680        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
17681        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
17682        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
17683        let sp = {
17684            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17685            let v = *SPW.get_or_init(|| {
17686                std::env::var("MEMRA_FA_SPW")
17687                    .ok()
17688                    .and_then(|x| x.parse().ok())
17689                    .unwrap_or(0)
17690            });
17691            if v >= 8 {
17692                v
17693            } else {
17694                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17695            }
17696        };
17697        let n_splits_max = (window + sp - 1) / sp;
17698        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17699        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
17700        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17701        let gqa = (n_head / n_head_kv).max(1) as u32;
17702        let o_len = t * n_head * n_splits_max * head_dim;
17703        let ml_len = t * n_head * n_splits_max;
17704        let mut part_guard = self.fa_part_pool.lock().unwrap();
17705        if part_guard
17706            .as_ref()
17707            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17708            .unwrap_or(true)
17709        {
17710            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17711            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17712            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17713            // later live allocations land at those addresses, and the next graph REPLAY writes
17714            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17715            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17716            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17717            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17718            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17719            // (total retired < final size).
17720            let old = part_guard.take();
17721            let (co, cm) = old
17722                .as_ref()
17723                .map(|pp| (pp.0.len(), pp.1.len()))
17724                .unwrap_or((0, 0));
17725            if let Some(old) = old {
17726                self.fa_part_retired.lock().unwrap().push(old);
17727            }
17728            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17729                eprintln!(
17730                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17731                    co, o_len, cm, ml_len
17732                );
17733            }
17734            *part_guard = Some((
17735                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17736                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17737                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17738            ));
17739        }
17740        let pg = part_guard.as_mut().unwrap();
17741        self.gpu
17742            .stream()
17743            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17744        self.gpu
17745            .stream()
17746            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17747        self.gpu
17748            .stream()
17749            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17750        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17751        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
17752        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
17753        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
17754        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
17755        // floor (deep-ctx broadcast win); register twin between.
17756        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17757        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
17758            std::env::var("MEMRA_FA_SMEM_TKV")
17759                .ok()
17760                .and_then(|v| v.parse().ok())
17761                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17762        });
17763        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
17764        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
17765        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
17766        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
17767        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
17768        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17769        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
17770        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
17771        // per (lane, format-module) keeps parity structural; the old register-i2 detour
17772        // (-33%) is retired.
17773        let wg = Self::wkv_on();
17774        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
17775        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
17776        let sp2 =
17777            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
17778        if sp2 {
17779            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
17780            if Self::pdl_on() && Self::pdl_wb_on() {
17781                // wave-B2b: flavor mirrors wg.
17782                use cudarc::driver::{DevicePtr, DevicePtrMut};
17783                let s = &self.gpu.stream();
17784                let (pq, _b0) = q.device_ptr(s);
17785                let (pk, _b1) = k.device_ptr(s);
17786                let (pv, _b2) = v.device_ptr(s);
17787                let (po, _b3) = part_o.device_ptr_mut(s);
17788                let (pm, _b4) = part_m.device_ptr_mut(s);
17789                let (pl, _b5) = part_l.device_ptr_mut(s);
17790                let (pb, _b6) = base_dev.device_ptr(s);
17791                let mut ps = [
17792                    &pq as *const _ as *mut std::ffi::c_void,
17793                    &pk as *const _ as *mut _,
17794                    &pv as *const _ as *mut _,
17795                    &po as *const _ as *mut _,
17796                    &pm as *const _ as *mut _,
17797                    &pl as *const _ as *mut _,
17798                    &hd as *const _ as *mut _,
17799                    &nh as *const _ as *mut _,
17800                    &nhkv as *const _ as *mut _,
17801                    &pb as *const _ as *mut _,
17802                    &base_plus as *const _ as *mut _,
17803                    &scale as *const _ as *mut _,
17804                    &nspm as *const _ as *mut _,
17805                    &spk as *const _ as *mut _,
17806                    &ktb as *const _ as *mut _,
17807                    &vtb as *const _ as *mut _,
17808                    &wini as *const _ as *mut _,
17809                ];
17810                unsafe {
17811                    self.launch_pdl_flash(
17812                        wg,
17813                        "fa_decode_vec_q_rows_v4_w_sp",
17814                        (n_head_kv as u32, n_splits_max as u32, t as u32),
17815                        (32, gqa + 1, 1),
17816                        sh,
17817                        &mut ps,
17818                    )?;
17819                }
17820            } else {
17821                let f = if wg {
17822                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
17823                } else {
17824                    self.func("fa_decode_vec_q_rows_v4_w_sp")
17825                };
17826                f.set_attribute(
17827                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17828                    sh as i32,
17829                )?;
17830                let cfg = LaunchConfig {
17831                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
17832                    block_dim: (32, gqa + 1, 1),
17833                    shared_mem_bytes: sh,
17834                };
17835                let __s_b = self.gpu.stream();
17836                let mut b = __s_b.launch_builder(&f);
17837                b.arg(q)
17838                    .arg(k)
17839                    .arg(v)
17840                    .arg(&mut *part_o)
17841                    .arg(&mut *part_m)
17842                    .arg(&mut *part_l)
17843                    .arg(&hd)
17844                    .arg(&nh)
17845                    .arg(&nhkv)
17846                    .arg(base_dev)
17847                    .arg(&base_plus)
17848                    .arg(&scale)
17849                    .arg(&nspm)
17850                    .arg(&spk)
17851                    .arg(&ktb)
17852                    .arg(&vtb)
17853                    .arg(&wini);
17854                unsafe {
17855                    b.launch(cfg)?;
17856                }
17857            }
17858        } else {
17859            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
17860                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
17861                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
17862                use cudarc::driver::{DevicePtr, DevicePtrMut};
17863                let s = &self.gpu.stream();
17864                let (pq, _b0) = q.device_ptr(s);
17865                let (pk, _b1) = k.device_ptr(s);
17866                let (pv, _b2) = v.device_ptr(s);
17867                let (po, _b3) = part_o.device_ptr_mut(s);
17868                let (pm, _b4) = part_m.device_ptr_mut(s);
17869                let (pl, _b5) = part_l.device_ptr_mut(s);
17870                let (pb, _b6) = base_dev.device_ptr(s);
17871                let mut ps = [
17872                    &pq as *const _ as *mut std::ffi::c_void,
17873                    &pk as *const _ as *mut _,
17874                    &pv as *const _ as *mut _,
17875                    &po as *const _ as *mut _,
17876                    &pm as *const _ as *mut _,
17877                    &pl as *const _ as *mut _,
17878                    &hd as *const _ as *mut _,
17879                    &nh as *const _ as *mut _,
17880                    &nhkv as *const _ as *mut _,
17881                    &pb as *const _ as *mut _,
17882                    &base_plus as *const _ as *mut _,
17883                    &scale as *const _ as *mut _,
17884                    &nspm as *const _ as *mut _,
17885                    &spk as *const _ as *mut _,
17886                    &ktb as *const _ as *mut _,
17887                    &vtb as *const _ as *mut _,
17888                    &wini as *const _ as *mut _,
17889                ];
17890                unsafe {
17891                    self.launch_pdl_flash(
17892                        wg,
17893                        "fa_decode_vec_q_rows_v4_w",
17894                        (n_head_kv as u32, n_splits_max as u32, t as u32),
17895                        (32, gqa, 1),
17896                        sh,
17897                        &mut ps,
17898                    )?;
17899                }
17900            } else {
17901                let pick = |name: &str| {
17902                    if wg {
17903                        self.func_g(name)
17904                    } else {
17905                        self.func(name)
17906                    }
17907                };
17908                let (f, sh) = if fa_v4_at(window) {
17909                    let f = pick("fa_decode_vec_q_rows_v4_w");
17910                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
17911                } else if smem_tkv > 0 && window >= smem_tkv {
17912                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
17913                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
17914                    (
17915                        pick("fa_decode_vec_q_rows_smem_w"),
17916                        (2 * 32 * head_dim * 2) as u32,
17917                    )
17918                } else {
17919                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
17920                };
17921                f.set_attribute(
17922                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17923                    sh as i32,
17924                )?;
17925                let cfg = LaunchConfig {
17926                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
17927                    block_dim: (32, gqa, 1),
17928                    shared_mem_bytes: sh,
17929                };
17930                let __s_b = self.gpu.stream();
17931                let mut b = __s_b.launch_builder(&f);
17932                b.arg(q)
17933                    .arg(k)
17934                    .arg(v)
17935                    .arg(&mut *part_o)
17936                    .arg(&mut *part_m)
17937                    .arg(&mut *part_l)
17938                    .arg(&hd)
17939                    .arg(&nh)
17940                    .arg(&nhkv)
17941                    .arg(base_dev)
17942                    .arg(&base_plus)
17943                    .arg(&scale)
17944                    .arg(&nspm)
17945                    .arg(&spk)
17946                    .arg(&ktb)
17947                    .arg(&vtb)
17948                    .arg(&wini);
17949                unsafe {
17950                    b.launch(cfg)?;
17951                }
17952            }
17953        }
17954        let cfg2 = LaunchConfig {
17955            grid_dim: (n_head as u32, t as u32, 1),
17956            block_dim: (head_dim as u32, 1, 1),
17957            shared_mem_bytes: 0,
17958        };
17959        if let Some((oq, od)) = q8_out {
17960            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
17961            // consumes the pair directly; the standalone quantize launch folds away.
17962            if Self::pdl_on() && Self::pdl_wb_on() {
17963                // wave-B2: flavor mirrors the builder's wg choice.
17964                use cudarc::driver::{DevicePtr, DevicePtrMut};
17965                let s = &self.gpu.stream();
17966                let (po, _g0) = part_o.device_ptr(s);
17967                let (pm, _g1) = part_m.device_ptr(s);
17968                let (pl, _g2) = part_l.device_ptr(s);
17969                let (pq, _g3) = oq.device_ptr_mut(s);
17970                let (pd, _g4) = od.device_ptr_mut(s);
17971                let mut ps = [
17972                    &po as *const _ as *mut std::ffi::c_void,
17973                    &pm as *const _ as *mut _,
17974                    &pl as *const _ as *mut _,
17975                    &pq as *const _ as *mut _,
17976                    &pd as *const _ as *mut _,
17977                    &hd as *const _ as *mut _,
17978                    &nh as *const _ as *mut _,
17979                    &nspm as *const _ as *mut _,
17980                    &spk as *const _ as *mut _,
17981                    &wini as *const _ as *mut _,
17982                ];
17983                unsafe {
17984                    self.launch_pdl_flash(
17985                        wg,
17986                        "fa_decode_combine_rows_w_q8_1",
17987                        cfg2.grid_dim,
17988                        cfg2.block_dim,
17989                        0,
17990                        &mut ps,
17991                    )?;
17992                }
17993                return Ok(());
17994            }
17995            let fc = if wg {
17996                self.func_g("fa_decode_combine_rows_w_q8_1")
17997            } else {
17998                self.func("fa_decode_combine_rows_w_q8_1")
17999            };
18000            let __s_b2 = self.gpu.stream();
18001            let mut b2 = __s_b2.launch_builder(&fc);
18002            b2.arg(&*part_o)
18003                .arg(&*part_m)
18004                .arg(&*part_l)
18005                .arg(oq)
18006                .arg(od)
18007                .arg(&hd)
18008                .arg(&nh)
18009                .arg(&nspm)
18010                .arg(&spk)
18011                .arg(&wini);
18012            unsafe {
18013                b2.launch(cfg2)?;
18014            }
18015            return Ok(());
18016        }
18017        let fc = if wg {
18018            self.func_g("fa_decode_combine_rows_w")
18019        } else {
18020            self.func("fa_decode_combine_rows_w")
18021        };
18022        let __s_b2 = self.gpu.stream();
18023        let mut b2 = __s_b2.launch_builder(&fc);
18024        b2.arg(&*part_o)
18025            .arg(&*part_m)
18026            .arg(&*part_l)
18027            .arg(o)
18028            .arg(&hd)
18029            .arg(&nh)
18030            .arg(&nspm)
18031            .arg(&spk)
18032            .arg(&wini);
18033        unsafe {
18034            b2.launch(cfg2)?;
18035        }
18036        Ok(())
18037    }
18038
18039    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
18040    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
18041    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
18042    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
18043    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
18044    #[allow(clippy::too_many_arguments)]
18045    pub fn fa_decode_rows_dc(
18046        &self,
18047        q: &CudaSlice<f32>,
18048        k: &cudarc::driver::CudaView<u8>,
18049        v: &cudarc::driver::CudaView<u8>,
18050        o: &mut CudaSlice<f32>,
18051        head_dim: usize,
18052        n_head: usize,
18053        n_head_kv: usize,
18054        base_dev: &CudaSlice<i32>,
18055        t_kv_upper: usize,
18056        t: usize,
18057        scale: f32,
18058        k_tok_bytes: usize,
18059        v_tok_bytes: usize,
18060        base_plus: i32,
18061        g: bool,
18062    ) -> Result<(), Box<dyn std::error::Error>> {
18063        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
18064        assert!(
18065            v4 || fa_v3_active(head_dim),
18066            "stream fa rows requires the v3 or v4 lane"
18067        );
18068        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
18069        if v4 {
18070            let sp = fa_split_keys(t_kv_upper, n_head_kv);
18071            let n_splits_max = (t_kv_upper + sp - 1) / sp;
18072            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18073            let (nspm, spk) = (n_splits_max as i32, sp as i32);
18074            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18075            let gqa = (n_head / n_head_kv).max(1) as u32;
18076            let o_len = t * n_head * n_splits_max * head_dim;
18077            let ml_len = t * n_head * n_splits_max;
18078            let mut part_guard = self.fa_part_pool.lock().unwrap();
18079            if part_guard
18080                .as_ref()
18081                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18082                .unwrap_or(true)
18083            {
18084                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18085                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18086                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18087                // later live allocations land at those addresses, and the next graph REPLAY writes
18088                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18089                // output corruption began the burst after the trunk's t_kv growth first realloc'd
18090                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18091                // the baked addresses alive (single-stream: eager writes the new buffers, replays
18092                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18093                // (total retired < final size).
18094                let old = part_guard.take();
18095                let (co, cm) = old
18096                    .as_ref()
18097                    .map(|pp| (pp.0.len(), pp.1.len()))
18098                    .unwrap_or((0, 0));
18099                if let Some(old) = old {
18100                    self.fa_part_retired.lock().unwrap().push(old);
18101                }
18102                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18103                    eprintln!(
18104                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18105                        co, o_len, cm, ml_len
18106                    );
18107                }
18108                *part_guard = Some((
18109                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18110                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18111                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18112                ));
18113            }
18114            let pg = part_guard.as_mut().unwrap();
18115            self.gpu
18116                .stream()
18117                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18118            self.gpu
18119                .stream()
18120                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18121            self.gpu
18122                .stream()
18123                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18124            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18125            let f = if g {
18126                self.func_g("fa_decode_vec_q_rows_v4_dc")
18127            } else {
18128                self.func("fa_decode_vec_q_rows_v4_dc")
18129            };
18130            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18131            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18132            f.set_attribute(
18133                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18134                sh as i32,
18135            )?;
18136            let cfg = LaunchConfig {
18137                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18138                block_dim: (32, gqa, 1),
18139                shared_mem_bytes: sh,
18140            };
18141            let __s_b = self.gpu.stream();
18142            let mut b = __s_b.launch_builder(&f);
18143            b.arg(q)
18144                .arg(k)
18145                .arg(v)
18146                .arg(&mut *part_o)
18147                .arg(&mut *part_m)
18148                .arg(&mut *part_l)
18149                .arg(&hd)
18150                .arg(&nh)
18151                .arg(&nhkv)
18152                .arg(base_dev)
18153                .arg(&base_plus)
18154                .arg(&scale)
18155                .arg(&nspm)
18156                .arg(&spk)
18157                .arg(&ktb)
18158                .arg(&vtb);
18159            unsafe {
18160                b.launch(cfg)?;
18161            }
18162            let fc = self.func("fa_decode_combine_rows_dc");
18163            let cfg2 = LaunchConfig {
18164                grid_dim: (n_head as u32, t as u32, 1),
18165                block_dim: (head_dim as u32, 1, 1),
18166                shared_mem_bytes: 0,
18167            };
18168            let __s_b2 = self.gpu.stream();
18169            let mut b2 = __s_b2.launch_builder(&fc);
18170            b2.arg(&*part_o)
18171                .arg(&*part_m)
18172                .arg(&*part_l)
18173                .arg(o)
18174                .arg(&hd)
18175                .arg(&nh)
18176                .arg(base_dev)
18177                .arg(&base_plus)
18178                .arg(&nspm)
18179                .arg(&spk);
18180            unsafe {
18181                b2.launch(cfg2)?;
18182            }
18183            return Ok(());
18184        }
18185        let sp = fa_split_keys(t_kv_upper, n_head_kv);
18186        let n_splits_max = (t_kv_upper + sp - 1) / sp;
18187        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18188        let (nspm, spk) = (n_splits_max as i32, sp as i32);
18189        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18190        let gqa = (n_head / n_head_kv).max(1) as u32;
18191        let o_len = t * n_head * n_splits_max * head_dim;
18192        let ml_len = t * n_head * n_splits_max;
18193        let mut part_guard = self.fa_part_pool.lock().unwrap();
18194        if part_guard
18195            .as_ref()
18196            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18197            .unwrap_or(true)
18198        {
18199            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18200            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18201            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18202            // later live allocations land at those addresses, and the next graph REPLAY writes
18203            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18204            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18205            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18206            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18207            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18208            // (total retired < final size).
18209            let old = part_guard.take();
18210            let (co, cm) = old
18211                .as_ref()
18212                .map(|pp| (pp.0.len(), pp.1.len()))
18213                .unwrap_or((0, 0));
18214            if let Some(old) = old {
18215                self.fa_part_retired.lock().unwrap().push(old);
18216            }
18217            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18218                eprintln!(
18219                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18220                    co, o_len, cm, ml_len
18221                );
18222            }
18223            *part_guard = Some((
18224                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18225                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18226                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18227            ));
18228        }
18229        let pg = part_guard.as_mut().unwrap();
18230        self.gpu
18231            .stream()
18232            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18233        self.gpu
18234            .stream()
18235            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18236        self.gpu
18237            .stream()
18238            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18239        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18240        let f = self.func("fa_decode_vec_q_rows_v3_dc");
18241        let sh = (32 * head_dim * 2) as u32;
18242        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18243        f.set_attribute(
18244            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18245            sh as i32,
18246        )?;
18247        let cfg = LaunchConfig {
18248            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18249            block_dim: (32, gqa, 1),
18250            shared_mem_bytes: sh,
18251        };
18252        let __s_b = self.gpu.stream();
18253        let mut b = __s_b.launch_builder(&f);
18254        b.arg(q)
18255            .arg(k)
18256            .arg(v)
18257            .arg(&mut *part_o)
18258            .arg(&mut *part_m)
18259            .arg(&mut *part_l)
18260            .arg(&hd)
18261            .arg(&nh)
18262            .arg(&nhkv)
18263            .arg(base_dev)
18264            .arg(&scale)
18265            .arg(&nspm)
18266            .arg(&spk)
18267            .arg(&ktb)
18268            .arg(&vtb);
18269        unsafe {
18270            b.launch(cfg)?;
18271        }
18272        let fc = self.func("fa_decode_combine_rows_dc");
18273        let cfg2 = LaunchConfig {
18274            grid_dim: (n_head as u32, t as u32, 1),
18275            block_dim: (head_dim as u32, 1, 1),
18276            shared_mem_bytes: 0,
18277        };
18278        let plus0 = 0i32;
18279        let __s_b2 = self.gpu.stream();
18280        let mut b2 = __s_b2.launch_builder(&fc);
18281        b2.arg(&*part_o)
18282            .arg(&*part_m)
18283            .arg(&*part_l)
18284            .arg(o)
18285            .arg(&hd)
18286            .arg(&nh)
18287            .arg(base_dev)
18288            .arg(&plus0)
18289            .arg(&nspm)
18290            .arg(&spk);
18291        unsafe {
18292            b2.launch(cfg2)?;
18293        }
18294        Ok(())
18295    }
18296
18297    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
18298    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
18299    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
18300    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
18301    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
18302    ///
18303    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
18304    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
18305    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
18306    /// grouping (different but mathematically-equal log-sum-exp merge).
18307    pub fn fa_decode_dc(
18308        &self,
18309        q: &CudaSlice<f32>,
18310        k: &cudarc::driver::CudaView<u8>,
18311        v: &cudarc::driver::CudaView<u8>,
18312        o: &mut CudaSlice<f32>,
18313        head_dim: usize,
18314        n_head: usize,
18315        n_head_kv: usize,
18316        t_kv_dev: &CudaSlice<i32>,
18317        bucket_max: usize,
18318        scale: f32,
18319        k_tok_bytes: usize,
18320        v_tok_bytes: usize,
18321        g: bool,
18322    ) -> Result<(), Box<dyn std::error::Error>> {
18323        self.fa_decode_dc_q8(
18324            q,
18325            k,
18326            v,
18327            o,
18328            head_dim,
18329            n_head,
18330            n_head_kv,
18331            t_kv_dev,
18332            bucket_max,
18333            scale,
18334            k_tok_bytes,
18335            v_tok_bytes,
18336            g,
18337            None,
18338        )
18339    }
18340
18341    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
18342    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
18343    #[allow(clippy::too_many_arguments)]
18344    pub fn fa_decode_dc_q8(
18345        &self,
18346        q: &CudaSlice<f32>,
18347        k: &cudarc::driver::CudaView<u8>,
18348        v: &cudarc::driver::CudaView<u8>,
18349        o: &mut CudaSlice<f32>,
18350        head_dim: usize,
18351        n_head: usize,
18352        n_head_kv: usize,
18353        t_kv_dev: &CudaSlice<i32>,
18354        bucket_max: usize,
18355        scale: f32,
18356        k_tok_bytes: usize,
18357        v_tok_bytes: usize,
18358        g: bool,
18359        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18360    ) -> Result<(), Box<dyn std::error::Error>> {
18361        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
18362        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
18363        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
18364        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
18365        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
18366        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
18367        // 2026-07-12).
18368        let mut fa_vec =
18369            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
18370        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
18371            fa_vec = false;
18372        } // mirror kvmod/geom
18373        let sp = fa_split_keys(bucket_max, n_head_kv);
18374        let n_splits = if fa_vec {
18375            ((bucket_max + sp - 1) / sp).max(1)
18376        } else {
18377            ((bucket_max + 255) / 256).max(1)
18378        };
18379        let o_len = n_head * n_splits * head_dim;
18380        let ml_len = n_head * n_splits;
18381        let mut part_guard = self.fa_part_pool.lock().unwrap();
18382        if part_guard
18383            .as_ref()
18384            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18385            .unwrap_or(true)
18386        {
18387            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18388            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18389            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18390            // later live allocations land at those addresses, and the next graph REPLAY writes
18391            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18392            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18393            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18394            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18395            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18396            // (total retired < final size).
18397            let old = part_guard.take();
18398            let (co, cm) = old
18399                .as_ref()
18400                .map(|pp| (pp.0.len(), pp.1.len()))
18401                .unwrap_or((0, 0));
18402            if let Some(old) = old {
18403                self.fa_part_retired.lock().unwrap().push(old);
18404            }
18405            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18406                eprintln!(
18407                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18408                    co, o_len, cm, ml_len
18409                );
18410            }
18411            *part_guard = Some((
18412                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18413                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18414                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18415            ));
18416        }
18417        let pg = part_guard.as_mut().unwrap();
18418        self.gpu
18419            .stream()
18420            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18421        self.gpu
18422            .stream()
18423            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18424        self.gpu
18425            .stream()
18426            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18427        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18428        let (hd, nh, nhkv, nsp) = (
18429            head_dim as i32,
18430            n_head as i32,
18431            n_head_kv as i32,
18432            n_splits as i32,
18433        );
18434        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18435        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
18436        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
18437        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
18438        let deep = fa_vec
18439            && head_dim == 256
18440            && fa_v4_at(bucket_max)
18441            && !g
18442            && fa_deep_at(bucket_max)
18443            && !matches!(fa_v4_mode(), "noB3" | "stage");
18444        let (f, cfg) = if fa_vec
18445            && head_dim == 512
18446            && bucket_max >= {
18447                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18448                *FA512_MIN_DC.get_or_init(|| {
18449                    std::env::var("MEMRA_FA512_MIN")
18450                        .ok()
18451                        .and_then(|v| v.parse().ok())
18452                        .unwrap_or(512)
18453                })
18454            } {
18455            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
18456            let gqa = (n_head / n_head_kv).max(1) as u32;
18457            (
18458                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
18459                LaunchConfig {
18460                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18461                    block_dim: (32, gqa, 1),
18462                    shared_mem_bytes: 0,
18463                },
18464            )
18465        } else if fa_vec && head_dim == 512 {
18466            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
18467            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
18468            let q_view = q.as_view();
18469            let mut o_view = o.as_view_mut();
18470            return self.fa_decode_scalar_unified(
18471                &q_view,
18472                k,
18473                v,
18474                &mut o_view,
18475                head_dim,
18476                n_head,
18477                n_head_kv,
18478                0,
18479                Some(t_kv_dev),
18480                scale,
18481                n_splits,
18482                sp,
18483                k_tok_bytes,
18484                v_tok_bytes,
18485                g,
18486                &mut *part_o,
18487                &mut *part_m,
18488                &mut *part_l,
18489                q8_out,
18490            );
18491        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
18492            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
18493            // incl the g-module route + raw-e4m3 sV sizing.
18494            let gqa = (n_head / n_head_kv).max(1) as u32;
18495            let fv = if g {
18496                self.func_g("fa_decode_vec_q_v4_dc")
18497            } else if deep {
18498                self.func("fa_decode_vec_q_v4_deep_dc")
18499            } else {
18500                self.func("fa_decode_vec_q_v4_dc")
18501            };
18502            let shmem =
18503                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18504            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18505            fv.set_attribute(
18506                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18507                shmem as i32,
18508            )?;
18509            (
18510                fv,
18511                LaunchConfig {
18512                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18513                    block_dim: (32, gqa, 1),
18514                    shared_mem_bytes: shmem,
18515                },
18516            )
18517        } else if fa_vec && fa_v3_active(head_dim) {
18518            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
18519            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
18520            let gqa = (n_head / n_head_kv).max(1) as u32;
18521            let fv = if g {
18522                self.func_g("fa_decode_vec_q_v3_dc")
18523            } else {
18524                self.func("fa_decode_vec_q_v3_dc")
18525            };
18526            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
18527            (
18528                fv,
18529                LaunchConfig {
18530                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18531                    block_dim: (32, gqa, 1),
18532                    shared_mem_bytes: shmem,
18533                },
18534            )
18535        } else if fa_vec && fa_v2_on() {
18536            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
18537            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
18538            // a numeric config; eager, rows-verify and graph all switch together).
18539            let gqa = (n_head / n_head_kv).max(1) as u32;
18540            let fv = if g {
18541                self.func_g("fa_decode_vec_q_v2_dc")
18542            } else {
18543                self.func("fa_decode_vec_q_v2_dc")
18544            };
18545            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
18546            (
18547                fv,
18548                LaunchConfig {
18549                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18550                    block_dim: (32, gqa, 1),
18551                    shared_mem_bytes: shmem,
18552                },
18553            )
18554        } else if fa_vec {
18555            let gqa = (n_head / n_head_kv).max(1) as u32;
18556            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
18557            let fv = if g {
18558                self.func_g("fa_decode_vec_q_dc")
18559            } else {
18560                self.func("fa_decode_vec_q_dc")
18561            };
18562            (
18563                fv,
18564                LaunchConfig {
18565                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18566                    block_dim: (32, gqa, 1),
18567                    shared_mem_bytes: 0,
18568                },
18569            )
18570        } else {
18571            let q_view = q.as_view();
18572            let mut o_view = o.as_view_mut();
18573            return self.fa_decode_scalar_unified(
18574                &q_view,
18575                k,
18576                v,
18577                &mut o_view,
18578                head_dim,
18579                n_head,
18580                n_head_kv,
18581                0,
18582                Some(t_kv_dev),
18583                scale,
18584                n_splits,
18585                if fa_vec { sp } else { 256 },
18586                k_tok_bytes,
18587                v_tok_bytes,
18588                g,
18589                &mut *part_o,
18590                &mut *part_m,
18591                &mut *part_l,
18592                q8_out,
18593            );
18594        };
18595        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
18596        let __s_b = self.gpu.stream();
18597        let mut b = __s_b.launch_builder(&f);
18598        b.arg(q)
18599            .arg(k)
18600            .arg(v)
18601            .arg(&mut *part_o)
18602            .arg(&mut *part_m)
18603            .arg(&mut *part_l)
18604            .arg(&hd)
18605            .arg(&nh)
18606            .arg(&nhkv)
18607            .arg(t_kv_dev)
18608            .arg(&scale)
18609            .arg(&nsp)
18610            .arg(&ski)
18611            .arg(&ktb)
18612            .arg(&vtb);
18613        unsafe {
18614            b.launch(cfg)?;
18615        }
18616        let cfg2 = LaunchConfig {
18617            grid_dim: (n_head as u32, 1, 1),
18618            block_dim: (head_dim as u32, 1, 1),
18619            shared_mem_bytes: 0,
18620        };
18621        if let Some((oq, od)) = q8_out {
18622            let fc = if g {
18623                self.func_g("fa_decode_combine_q8_1")
18624            } else {
18625                self.fa_func("fa_decode_combine_q8_1", head_dim)
18626            };
18627            let __s_b2 = self.gpu.stream();
18628            let mut b2 = __s_b2.launch_builder(&fc);
18629            b2.arg(&*part_o)
18630                .arg(&*part_m)
18631                .arg(&*part_l)
18632                .arg(oq)
18633                .arg(od)
18634                .arg(&hd)
18635                .arg(&nh)
18636                .arg(&nsp);
18637            unsafe {
18638                b2.launch(cfg2)?;
18639            }
18640            return Ok(());
18641        }
18642        let fc = if g {
18643            self.func_g("fa_decode_combine_f32")
18644        } else {
18645            self.fa_func("fa_decode_combine_f32", head_dim)
18646        };
18647        let __s_b2 = self.gpu.stream();
18648        let mut b2 = __s_b2.launch_builder(&fc);
18649        b2.arg(&*part_o)
18650            .arg(&*part_m)
18651            .arg(&*part_l)
18652            .arg(o)
18653            .arg(&hd)
18654            .arg(&nh)
18655            .arg(&nsp);
18656        unsafe {
18657            b2.launch(cfg2)?;
18658        }
18659        Ok(())
18660    }
18661
18662    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
18663    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
18664    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
18665    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
18666    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
18667    pub fn fa_geom_eager(
18668        &self,
18669        t_kv: usize,
18670        head_dim: usize,
18671        n_head_kv: usize,
18672        g: bool,
18673    ) -> (bool, usize) {
18674        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
18675        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
18676        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
18677        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
18678        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
18679        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
18680        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
18681        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
18682        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
18683        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
18684        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
18685        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
18686        // family; everything else falls to the g-module scalar.
18687        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
18688        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
18689        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
18690        if g && head_dim == 256 && !fa_v4_at(t_kv) {
18691            fa_vec = false;
18692        }
18693        let sp = fa_split_keys(t_kv, n_head_kv);
18694        let n_splits = if fa_vec {
18695            ((t_kv + sp - 1) / sp).max(1)
18696        } else {
18697            ((t_kv + 255) / 256).max(1)
18698        };
18699        (fa_vec, n_splits)
18700    }
18701
18702    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
18703    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
18704    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
18705    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
18706    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
18707    pub fn fa_bucket_key(
18708        &self,
18709        t_kv: usize,
18710        head_dim: usize,
18711        n_head_kv: usize,
18712        g: bool,
18713    ) -> (bool, usize) {
18714        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
18715    }
18716
18717    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
18718    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
18719    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
18720    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
18721    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
18722    /// device data) — every per-step varying scalar must come from a device counter. Returns the
18723    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
18724    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
18725    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
18726    /// replays (transients returning to the pool get reused by unrelated work and corrupt
18727    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
18728    pub fn capture_graph_retained<F>(
18729        &self,
18730        step: F,
18731    ) -> Result<
18732        (
18733            cudarc::driver::CudaGraph,
18734            Vec<Box<dyn std::any::Any + Send>>,
18735        ),
18736        Box<dyn std::error::Error>,
18737    >
18738    where
18739        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18740    {
18741        use cudarc::driver::sys::CUgraphInstantiate_flags;
18742        self.capture_graph_retained_flags(
18743            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
18744            step,
18745        )
18746    }
18747
18748    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
18749    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
18750    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
18751    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
18752    pub fn capture_graph_retained_flags<F>(
18753        &self,
18754        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
18755        mut step: F,
18756    ) -> Result<
18757        (
18758            cudarc::driver::CudaGraph,
18759            Vec<Box<dyn std::any::Any + Send>>,
18760        ),
18761        Box<dyn std::error::Error>,
18762    >
18763    where
18764        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18765    {
18766        use cudarc::driver::sys::CUstreamCaptureMode;
18767        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
18768        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
18769        // while the capture region is open become dead copy NODES replayed every launch
18770        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
18771        // warmup runs allocate the same transient sequence at the same pool addresses, so
18772        // retaining the warmup clones preserves the draft-graph fix without polluting the
18773        // captured graph.
18774        self.capture_keep.lock().unwrap().clear();
18775        let was_tracking = self.gpu.ctx.is_event_tracking();
18776        if was_tracking {
18777            unsafe {
18778                self.gpu.ctx.disable_event_tracking();
18779            }
18780        }
18781        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
18782            self.capture_keep_on
18783                .store(true, std::sync::atomic::Ordering::Relaxed);
18784            let w = (|| {
18785                step(self)?;
18786                step(self)
18787            })();
18788            self.capture_keep_on
18789                .store(false, std::sync::atomic::Ordering::Relaxed);
18790            w?;
18791            self.gpu.stream().synchronize()?;
18792            self.gpu
18793                .stream()
18794                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
18795            let r = step(self);
18796            let g = self.gpu.stream().end_capture(flags);
18797            r?;
18798            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
18799            graph.upload()?;
18800            Ok(graph)
18801        };
18802        let result = run();
18803        self.capture_keep_on
18804            .store(false, std::sync::atomic::Ordering::Relaxed);
18805        if was_tracking {
18806            unsafe {
18807                self.gpu.ctx.enable_event_tracking();
18808            }
18809        }
18810        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
18811        Ok((result?, keeper))
18812    }
18813
18814    pub fn capture_graph<F>(
18815        &self,
18816        mut step: F,
18817    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
18818    where
18819        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18820    {
18821        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
18822        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
18823        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
18824        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
18825        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
18826        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
18827        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
18828        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
18829        let was_tracking = self.gpu.ctx.is_event_tracking();
18830        if was_tracking {
18831            unsafe {
18832                self.gpu.ctx.disable_event_tracking();
18833            }
18834        }
18835        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
18836        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
18837        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
18838        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
18839        // measure that scan's real cost on the generic path. Diagnostic door only; the
18840        // default stays AUTO_FREE until a measured A/B justifies moving it.
18841        let iflag = {
18842            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
18843            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
18844                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
18845                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
18846                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
18847                Ok("priority") => {
18848                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
18849                }
18850                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
18851            })
18852        };
18853        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
18854        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
18855        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
18856        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
18857        // eager step executions and are node-count-invariant. Printing the split bounds the
18858        // refactor's ceiling instead of assuming it.
18859        let ct = {
18860            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18861            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
18862        };
18863        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
18864        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
18865        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
18866        // chased, and node-count-invariant, so no capture-body refactor could touch it.
18867        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
18868        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
18869        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
18870        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
18871        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
18872        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
18873        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
18874        // grow and never frees, resident counters/scratch, cache set in place), and the
18875        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
18876        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
18877        // settling and pool mapping. Arbitrated adversarially, not by taste:
18878        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
18879        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
18880        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
18881        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
18882        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
18883        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
18884        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
18885        let warmups = {
18886            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18887            *W.get_or_init(|| {
18888                std::env::var("MEMRA_GRAPH_WARMUPS")
18889                    .ok()
18890                    .and_then(|v| v.parse().ok())
18891                    .filter(|n| *n >= 1)
18892                    .unwrap_or(1)
18893            })
18894        };
18895        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
18896            let t_w = std::time::Instant::now();
18897            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
18898            for _ in 0..warmups {
18899                step(self)?;
18900            }
18901            self.gpu.stream().synchronize()?;
18902            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
18903            // capture the third run.
18904            let t_c = std::time::Instant::now();
18905            self.gpu
18906                .stream()
18907                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
18908            // If the body errors mid-capture, end the capture before propagating so the stream isn't
18909            // left in a capturing state.
18910            let r = step(self);
18911            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
18912            let t_i = std::time::Instant::now();
18913            let g = self.gpu.stream().end_capture(iflag);
18914            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
18915            r?;
18916            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
18917            let t_u = std::time::Instant::now();
18918            graph.upload()?;
18919            if ct {
18920                println!(
18921                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
18922                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
18923                    t_u.elapsed().as_secs_f64() * 1e3
18924                );
18925            }
18926            Ok(graph)
18927        };
18928        let result = run();
18929        if was_tracking {
18930            unsafe {
18931                self.gpu.ctx.enable_event_tracking();
18932            }
18933        }
18934        result
18935    }
18936
18937    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
18938    pub fn gdn_scan_s128_view(
18939        &self,
18940        q: &CudaSlice<f32>,
18941        k: &CudaSlice<f32>,
18942        v: &CudaSlice<f32>,
18943        g: &CudaSlice<f32>,
18944        beta: &CudaSlice<f32>,
18945        state_in: &cudarc::driver::CudaView<f32>,
18946        state_out: &mut cudarc::driver::CudaViewMut<f32>,
18947        o: &mut CudaSlice<f32>,
18948        n_head: usize,
18949        t: usize,
18950        scale: f32,
18951    ) -> Result<(), Box<dyn std::error::Error>> {
18952        let f = self.func("gdn_scan_s128");
18953        const S_V: u32 = 128;
18954        const WARP: u32 = 32;
18955        const COLS: u32 = 4;
18956        let cfg = LaunchConfig {
18957            grid_dim: (n_head as u32, 1, S_V / COLS),
18958            block_dim: (WARP, COLS, 1),
18959            shared_mem_bytes: 0,
18960        };
18961        let (h, ti) = (n_head as i32, t as i32);
18962        let __s_b = self.gpu.stream();
18963        let mut b = __s_b.launch_builder(&f);
18964        b.arg(q)
18965            .arg(k)
18966            .arg(v)
18967            .arg(g)
18968            .arg(beta)
18969            .arg(state_in)
18970            .arg(state_out)
18971            .arg(o)
18972            .arg(&h)
18973            .arg(&ti)
18974            .arg(&scale);
18975        unsafe {
18976            b.launch(cfg)?;
18977        }
18978        Ok(())
18979    }
18980
18981    /// conv1d where the input is a CudaView (resident conv state assembled in place).
18982    pub fn ssm_conv1d_view(
18983        &self,
18984        x: &cudarc::driver::CudaView<f32>,
18985        w: &CudaSlice<f32>,
18986        y: &mut CudaSlice<f32>,
18987        conv_dim: usize,
18988        t: usize,
18989        d_conv: usize,
18990        silu: bool,
18991    ) -> Result<(), Box<dyn std::error::Error>> {
18992        let f = self.func("ssm_conv1d_silu_f32");
18993        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
18994        let cfg = LaunchConfig {
18995            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
18996            block_dim: (256, 1, 1),
18997            shared_mem_bytes: 0,
18998        };
18999        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19000        let __s_b = self.gpu.stream();
19001        let mut b = __s_b.launch_builder(&f);
19002        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19003        unsafe {
19004            b.launch(cfg)?;
19005        }
19006        Ok(())
19007    }
19008
19009    /// Depthwise causal conv1d + optional SiLU.
19010    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
19011    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
19012    /// FUSED prefill conv (token-major input, zero left-state): replaces
19013    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
19014    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
19015    pub fn ssm_conv1d_tm(
19016        &self,
19017        qkv_tm: &CudaSlice<f32>,
19018        w: &CudaSlice<f32>,
19019        y: &mut CudaSlice<f32>,
19020        conv_dim: usize,
19021        t: usize,
19022        d_conv: usize,
19023    ) -> Result<(), Box<dyn std::error::Error>> {
19024        let f = self.func("ssm_conv1d_tm_f32");
19025        let cfg = LaunchConfig {
19026            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19027            block_dim: (256, 1, 1),
19028            shared_mem_bytes: 0,
19029        };
19030        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19031        let __s_b = self.gpu.stream();
19032        let mut b = __s_b.launch_builder(&f);
19033        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
19034        unsafe {
19035            b.launch(cfg)?;
19036        }
19037        Ok(())
19038    }
19039
19040    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
19041    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
19042    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
19043    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
19044    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
19045    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
19046    /// columns; the final ring == what T sequential decode ring rolls leave).
19047    pub fn ssm_conv1d_tm_state(
19048        &self,
19049        qkv_tm: &CudaSlice<f32>,
19050        conv_state: &mut CudaSlice<f32>,
19051        w: &CudaSlice<f32>,
19052        y: &mut CudaSlice<f32>,
19053        conv_dim: usize,
19054        t: usize,
19055        d_conv: usize,
19056    ) -> Result<(), Box<dyn std::error::Error>> {
19057        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
19058    }
19059
19060    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
19061    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
19062    #[allow(clippy::too_many_arguments)]
19063    pub fn ssm_conv1d_tm_state_pad(
19064        &self,
19065        qkv_tm: &CudaSlice<f32>,
19066        conv_state: &mut CudaSlice<f32>,
19067        w: &CudaSlice<f32>,
19068        y: &mut CudaSlice<f32>,
19069        conv_dim: usize,
19070        t: usize,
19071        d_conv: usize,
19072        pad_len: Option<&CudaSlice<i32>>,
19073    ) -> Result<(), Box<dyn std::error::Error>> {
19074        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19075        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19076        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19077        // cloning first keeps the ordering trivially correct under any future stream split.
19078        let ring_old = if t < d_conv - 1 {
19079            Some(self.clone_dtod(conv_state)?)
19080        } else {
19081            None
19082        };
19083        {
19084            let f = self.func("ssm_conv1d_tm_state_f32");
19085            let cfg = LaunchConfig {
19086                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19087                block_dim: (256, 1, 1),
19088                shared_mem_bytes: 0,
19089            };
19090            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19091            let __s_b = self.gpu.stream();
19092            let mut b = __s_b.launch_builder(&f);
19093            b.arg(qkv_tm)
19094                .arg(&*conv_state)
19095                .arg(w)
19096                .arg(y)
19097                .arg(&cd)
19098                .arg(&ti)
19099                .arg(&dc);
19100            unsafe {
19101                b.launch(cfg)?;
19102            }
19103        }
19104        match (ring_old, pad_len) {
19105            (None, Some(len_d)) => {
19106                let f = self.func("ssm_conv_ring_update_dev_f32");
19107                let n = conv_dim * (d_conv - 1);
19108                let cfg = LaunchConfig::for_num_elems(n as u32);
19109                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19110                let __s_b = self.gpu.stream();
19111                let mut b = __s_b.launch_builder(&f);
19112                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19113                unsafe {
19114                    b.launch(cfg)?;
19115                }
19116            }
19117            (None, None) => {
19118                let f = self.func("ssm_conv_ring_update_f32");
19119                let n = conv_dim * (d_conv - 1);
19120                let cfg = LaunchConfig::for_num_elems(n as u32);
19121                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19122                let __s_b = self.gpu.stream();
19123                let mut b = __s_b.launch_builder(&f);
19124                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19125                unsafe {
19126                    b.launch(cfg)?;
19127                }
19128            }
19129            (Some(old), _) => {
19130                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
19131            }
19132        }
19133        Ok(())
19134    }
19135
19136    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
19137    pub fn ssm_conv1d_tm_state_pad_v(
19138        &self,
19139        qkv_tm: &cudarc::driver::CudaView<f32>,
19140        conv_state: &mut CudaSlice<f32>,
19141        w: &CudaSlice<f32>,
19142        y: &mut CudaSlice<f32>,
19143        conv_dim: usize,
19144        t: usize,
19145        d_conv: usize,
19146        pad_len: Option<&CudaSlice<i32>>,
19147    ) -> Result<(), Box<dyn std::error::Error>> {
19148        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19149        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19150        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19151        // cloning first keeps the ordering trivially correct under any future stream split.
19152        let ring_old = if t < d_conv - 1 {
19153            Some(self.clone_dtod(conv_state)?)
19154        } else {
19155            None
19156        };
19157        {
19158            let f = self.func("ssm_conv1d_tm_state_f32");
19159            let cfg = LaunchConfig {
19160                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19161                block_dim: (256, 1, 1),
19162                shared_mem_bytes: 0,
19163            };
19164            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19165            let __s_b = self.gpu.stream();
19166            let mut b = __s_b.launch_builder(&f);
19167            b.arg(qkv_tm)
19168                .arg(&*conv_state)
19169                .arg(w)
19170                .arg(y)
19171                .arg(&cd)
19172                .arg(&ti)
19173                .arg(&dc);
19174            unsafe {
19175                b.launch(cfg)?;
19176            }
19177        }
19178        match (ring_old, pad_len) {
19179            (None, Some(len_d)) => {
19180                let f = self.func("ssm_conv_ring_update_dev_f32");
19181                let n = conv_dim * (d_conv - 1);
19182                let cfg = LaunchConfig::for_num_elems(n as u32);
19183                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19184                let __s_b = self.gpu.stream();
19185                let mut b = __s_b.launch_builder(&f);
19186                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19187                unsafe {
19188                    b.launch(cfg)?;
19189                }
19190            }
19191            (None, None) => {
19192                let f = self.func("ssm_conv_ring_update_f32");
19193                let n = conv_dim * (d_conv - 1);
19194                let cfg = LaunchConfig::for_num_elems(n as u32);
19195                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19196                let __s_b = self.gpu.stream();
19197                let mut b = __s_b.launch_builder(&f);
19198                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19199                unsafe {
19200                    b.launch(cfg)?;
19201                }
19202            }
19203            (Some(_), _) => unreachable!(
19204                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
19205            ),
19206        }
19207        Ok(())
19208    }
19209
19210    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
19211    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
19212    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
19213    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
19214    pub fn ssm_conv_ring_rebuild(
19215        &self,
19216        qkv_tm: &CudaSlice<f32>,
19217        ring_old: &CudaSlice<f32>,
19218        conv_state: &mut CudaSlice<f32>,
19219        conv_dim: usize,
19220        tc: usize,
19221        d_conv: usize,
19222    ) -> Result<(), Box<dyn std::error::Error>> {
19223        let f = self.func("ssm_conv_ring_rebuild_f32");
19224        let n = conv_dim * (d_conv - 1);
19225        let cfg = LaunchConfig::for_num_elems(n as u32);
19226        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
19227        let __s_b = self.gpu.stream();
19228        let mut b = __s_b.launch_builder(&f);
19229        b.arg(qkv_tm)
19230            .arg(ring_old)
19231            .arg(conv_state)
19232            .arg(&cd)
19233            .arg(&ti)
19234            .arg(&dc);
19235        unsafe {
19236            b.launch(cfg)?;
19237        }
19238        Ok(())
19239    }
19240
19241    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
19242    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
19243    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
19244    /// the argmax + run-spec gates are the authority.
19245    #[allow(clippy::too_many_arguments)]
19246    pub fn gdn_prep_decode(
19247        &self,
19248        conv_out: &CudaSlice<f32>,
19249        beta_raw: &CudaSlice<f32>,
19250        alpha: &CudaSlice<f32>,
19251        dt_bias: &CudaSlice<f32>,
19252        a: &CudaSlice<f32>,
19253        q_l2: &mut CudaSlice<f32>,
19254        k_l2: &mut CudaSlice<f32>,
19255        v_g: &mut CudaSlice<f32>,
19256        beta: &mut CudaSlice<f32>,
19257        g_log: &mut CudaSlice<f32>,
19258        d_state: usize,
19259        num_v: usize,
19260        num_k: usize,
19261        key_dim: usize,
19262        eps: f32,
19263    ) -> Result<(), Box<dyn std::error::Error>> {
19264        let f = self.func("gdn_prep_decode_f32");
19265        let cfg = LaunchConfig {
19266            grid_dim: (num_v as u32, 1, 1),
19267            block_dim: (32, 4, 1),
19268            shared_mem_bytes: 0,
19269        };
19270        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19271        let __s_b = self.gpu.stream();
19272        let mut b = __s_b.launch_builder(&f);
19273        b.arg(conv_out)
19274            .arg(beta_raw)
19275            .arg(alpha)
19276            .arg(dt_bias)
19277            .arg(a)
19278            .arg(q_l2)
19279            .arg(k_l2)
19280            .arg(v_g)
19281            .arg(beta)
19282            .arg(g_log)
19283            .arg(&ds)
19284            .arg(&nv)
19285            .arg(&nk)
19286            .arg(&kd)
19287            .arg(&eps);
19288        unsafe {
19289            b.launch(cfg)?;
19290        }
19291        Ok(())
19292    }
19293
19294    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
19295    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
19296    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
19297    #[allow(clippy::too_many_arguments)]
19298    pub fn ssm_conv1d_gdn(
19299        &self,
19300        qkv_tm: &CudaSlice<f32>,
19301        w: &CudaSlice<f32>,
19302        q_g: &mut CudaSlice<f32>,
19303        k_g: &mut CudaSlice<f32>,
19304        v_g: &mut CudaSlice<f32>,
19305        conv_dim: usize,
19306        t: usize,
19307        d_conv: usize,
19308        d_state: usize,
19309        num_v: usize,
19310        num_k: usize,
19311        key_dim: usize,
19312    ) -> Result<(), Box<dyn std::error::Error>> {
19313        let f = self.func("ssm_conv1d_gdn_f32");
19314        let cfg = LaunchConfig {
19315            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19316            block_dim: (256, 1, 1),
19317            shared_mem_bytes: 0,
19318        };
19319        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19320        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19321        let __s_b = self.gpu.stream();
19322        let mut b = __s_b.launch_builder(&f);
19323        b.arg(qkv_tm)
19324            .arg(w)
19325            .arg(q_g)
19326            .arg(k_g)
19327            .arg(v_g)
19328            .arg(&cd)
19329            .arg(&ti)
19330            .arg(&dc)
19331            .arg(&ds)
19332            .arg(&nv)
19333            .arg(&nk)
19334            .arg(&kd);
19335        unsafe {
19336            b.launch(cfg)?;
19337        }
19338        Ok(())
19339    }
19340
19341    pub fn ssm_conv1d(
19342        &self,
19343        x: &CudaSlice<f32>,
19344        w: &CudaSlice<f32>,
19345        y: &mut CudaSlice<f32>,
19346        conv_dim: usize,
19347        t: usize,
19348        d_conv: usize,
19349        silu: bool,
19350    ) -> Result<(), Box<dyn std::error::Error>> {
19351        let f = self.func("ssm_conv1d_silu_f32");
19352        let cfg = LaunchConfig {
19353            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19354            block_dim: (256, 1, 1),
19355            shared_mem_bytes: 0,
19356        };
19357        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19358        let __s_b = self.gpu.stream();
19359        let mut b = __s_b.launch_builder(&f);
19360        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19361        unsafe {
19362            b.launch(cfg)?;
19363        }
19364        Ok(())
19365    }
19366
19367    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
19368    /// o:[128,H,T]. Single sequence.
19369    pub fn gdn_scan_s128(
19370        &self,
19371        q: &CudaSlice<f32>,
19372        k: &CudaSlice<f32>,
19373        v: &CudaSlice<f32>,
19374        g: &CudaSlice<f32>,
19375        beta: &CudaSlice<f32>,
19376        state_in: &CudaSlice<f32>,
19377        state_out: &mut CudaSlice<f32>,
19378        o: &mut CudaSlice<f32>,
19379        n_head: usize,
19380        t: usize,
19381        scale: f32,
19382    ) -> Result<(), Box<dyn std::error::Error>> {
19383        let f = self.func("gdn_scan_s128");
19384        const S_V: u32 = 128;
19385        const WARP: u32 = 32;
19386        const COLS_PER_BLOCK: u32 = 4;
19387        let cfg = LaunchConfig {
19388            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
19389            block_dim: (WARP, COLS_PER_BLOCK, 1),
19390            shared_mem_bytes: 0,
19391        };
19392        let (h, ti) = (n_head as i32, t as i32);
19393        let __s_b = self.gpu.stream();
19394        let mut b = __s_b.launch_builder(&f);
19395        b.arg(q)
19396            .arg(k)
19397            .arg(v)
19398            .arg(g)
19399            .arg(beta)
19400            .arg(state_in)
19401            .arg(state_out)
19402            .arg(o)
19403            .arg(&h)
19404            .arg(&ti)
19405            .arg(&scale);
19406        unsafe {
19407            b.launch(cfg)?;
19408        }
19409        Ok(())
19410    }
19411
19412    // ==== B2' batched decode state ops (decode_batch.rs) ====
19413    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
19414    // Bodies are the single-seq kernels per sequence — bit-identical per row.
19415
19416    #[allow(clippy::too_many_arguments)]
19417    pub fn ssm_conv1d_fused_decode_b(
19418        &self,
19419        qkv_cols: &CudaSlice<f32>,
19420        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
19421        w: &CudaSlice<f32>,
19422        conv_outs: &mut CudaSlice<f32>,
19423        conv_dim: usize,
19424        d_conv: usize,
19425        b_n: usize,
19426    ) -> Result<(), Box<dyn std::error::Error>> {
19427        let f = self.func("ssm_conv1d_fused_decode_b_f32");
19428        let cfg = LaunchConfig {
19429            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
19430            block_dim: (256, 1, 1),
19431            shared_mem_bytes: 0,
19432        };
19433        let (cd, dc) = (conv_dim as i32, d_conv as i32);
19434        let __s_b = self.gpu.stream();
19435        let mut b = __s_b.launch_builder(&f);
19436        b.arg(qkv_cols)
19437            .arg(conv_state_ptrs)
19438            .arg(w)
19439            .arg(conv_outs)
19440            .arg(&cd)
19441            .arg(&dc);
19442        unsafe {
19443            b.launch(cfg)?;
19444        }
19445        Ok(())
19446    }
19447
19448    #[allow(clippy::too_many_arguments)]
19449    pub fn gdn_prep_decode_b(
19450        &self,
19451        conv_outs: &CudaSlice<f32>,
19452        beta_raws: &CudaSlice<f32>,
19453        alphas: &CudaSlice<f32>,
19454        dt_bias: &CudaSlice<f32>,
19455        a: &CudaSlice<f32>,
19456        q_l2: &mut CudaSlice<f32>,
19457        k_l2: &mut CudaSlice<f32>,
19458        v_g: &mut CudaSlice<f32>,
19459        beta: &mut CudaSlice<f32>,
19460        g_log: &mut CudaSlice<f32>,
19461        d_state: usize,
19462        num_v: usize,
19463        num_k: usize,
19464        key_dim: usize,
19465        eps: f32,
19466        conv_dim: usize,
19467        b_n: usize,
19468    ) -> Result<(), Box<dyn std::error::Error>> {
19469        let f = self.func("gdn_prep_decode_b_f32");
19470        let cfg = LaunchConfig {
19471            grid_dim: (num_v as u32, 1, b_n as u32),
19472            block_dim: (32, 4, 1),
19473            shared_mem_bytes: 0,
19474        };
19475        let (ds, nv, nk, kd, cd) = (
19476            d_state as i32,
19477            num_v as i32,
19478            num_k as i32,
19479            key_dim as i32,
19480            conv_dim as i32,
19481        );
19482        let __s_b = self.gpu.stream();
19483        let mut b = __s_b.launch_builder(&f);
19484        b.arg(conv_outs)
19485            .arg(beta_raws)
19486            .arg(alphas)
19487            .arg(dt_bias)
19488            .arg(a)
19489            .arg(q_l2)
19490            .arg(k_l2)
19491            .arg(v_g)
19492            .arg(beta)
19493            .arg(g_log)
19494            .arg(&ds)
19495            .arg(&nv)
19496            .arg(&nk)
19497            .arg(&kd)
19498            .arg(&eps)
19499            .arg(&cd);
19500        unsafe {
19501            b.launch(cfg)?;
19502        }
19503        Ok(())
19504    }
19505
19506    #[allow(clippy::too_many_arguments)]
19507    pub fn gdn_scan_s128_batched(
19508        &self,
19509        q: &CudaSlice<f32>,
19510        k: &CudaSlice<f32>,
19511        v: &CudaSlice<f32>,
19512        g: &CudaSlice<f32>,
19513        beta: &CudaSlice<f32>,
19514        state_in_ptrs: &cudarc::driver::CudaView<u64>,
19515        state_out_ptrs: &cudarc::driver::CudaView<u64>,
19516        o: &mut CudaSlice<f32>,
19517        n_head: usize,
19518        b_n: usize,
19519        scale: f32,
19520    ) -> Result<(), Box<dyn std::error::Error>> {
19521        let f = self.func("gdn_scan_s128_b");
19522        const S_V: u32 = 128;
19523        const WARP: u32 = 32;
19524        const COLS_PER_BLOCK: u32 = 4;
19525        let cfg = LaunchConfig {
19526            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
19527            block_dim: (WARP, COLS_PER_BLOCK, 1),
19528            shared_mem_bytes: 0,
19529        };
19530        let h = n_head as i32;
19531        let __s_b = self.gpu.stream();
19532        let mut b = __s_b.launch_builder(&f);
19533        b.arg(q)
19534            .arg(k)
19535            .arg(v)
19536            .arg(g)
19537            .arg(beta)
19538            .arg(state_in_ptrs)
19539            .arg(state_out_ptrs)
19540            .arg(o)
19541            .arg(&h)
19542            .arg(&scale);
19543        unsafe {
19544            b.launch(cfg)?;
19545        }
19546        Ok(())
19547    }
19548
19549    /// VIEW twins of the three GDN decode state wrappers (lane/verify-launchslim): identical
19550    /// launches, row args as views into the caller's packed [T, ...] buffers — the t-parallel
19551    /// verify's per-row loop passes slices instead of paying an arithmetic-free dtod per row
19552    /// per kernel (48 layers x T rows x 4 copies/round on the money path). Same kernels, same
19553    /// numeric class; only the pointer arithmetic moved host-side.
19554    #[allow(clippy::too_many_arguments)]
19555    pub fn ssm_conv1d_fused_decode_b_view(
19556        &self,
19557        qkv_cols: &cudarc::driver::CudaView<f32>,
19558        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
19559        w: &CudaSlice<f32>,
19560        conv_outs: &mut CudaSlice<f32>,
19561        conv_dim: usize,
19562        d_conv: usize,
19563        b_n: usize,
19564    ) -> Result<(), Box<dyn std::error::Error>> {
19565        let f = self.func("ssm_conv1d_fused_decode_b_f32");
19566        let cfg = LaunchConfig {
19567            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
19568            block_dim: (256, 1, 1),
19569            shared_mem_bytes: 0,
19570        };
19571        let (cd, dc) = (conv_dim as i32, d_conv as i32);
19572        let __s_b = self.gpu.stream();
19573        let mut b = __s_b.launch_builder(&f);
19574        b.arg(qkv_cols)
19575            .arg(conv_state_ptrs)
19576            .arg(w)
19577            .arg(conv_outs)
19578            .arg(&cd)
19579            .arg(&dc);
19580        unsafe {
19581            b.launch(cfg)?;
19582        }
19583        Ok(())
19584    }
19585
19586    #[allow(clippy::too_many_arguments)]
19587    pub fn gdn_prep_decode_b_view(
19588        &self,
19589        conv_outs: &CudaSlice<f32>,
19590        beta_raws: &cudarc::driver::CudaView<f32>,
19591        alphas: &cudarc::driver::CudaView<f32>,
19592        dt_bias: &CudaSlice<f32>,
19593        a: &CudaSlice<f32>,
19594        q_l2: &mut CudaSlice<f32>,
19595        k_l2: &mut CudaSlice<f32>,
19596        v_g: &mut CudaSlice<f32>,
19597        beta: &mut CudaSlice<f32>,
19598        g_log: &mut CudaSlice<f32>,
19599        d_state: usize,
19600        num_v: usize,
19601        num_k: usize,
19602        key_dim: usize,
19603        eps: f32,
19604        conv_dim: usize,
19605        b_n: usize,
19606    ) -> Result<(), Box<dyn std::error::Error>> {
19607        let f = self.func("gdn_prep_decode_b_f32");
19608        let cfg = LaunchConfig {
19609            grid_dim: (num_v as u32, 1, b_n as u32),
19610            block_dim: (32, 4, 1),
19611            shared_mem_bytes: 0,
19612        };
19613        let (ds, nv, nk, kd, cd) = (
19614            d_state as i32,
19615            num_v as i32,
19616            num_k as i32,
19617            key_dim as i32,
19618            conv_dim as i32,
19619        );
19620        let __s_b = self.gpu.stream();
19621        let mut b = __s_b.launch_builder(&f);
19622        b.arg(conv_outs)
19623            .arg(beta_raws)
19624            .arg(alphas)
19625            .arg(dt_bias)
19626            .arg(a)
19627            .arg(q_l2)
19628            .arg(k_l2)
19629            .arg(v_g)
19630            .arg(beta)
19631            .arg(g_log)
19632            .arg(&ds)
19633            .arg(&nv)
19634            .arg(&nk)
19635            .arg(&kd)
19636            .arg(&eps)
19637            .arg(&cd);
19638        unsafe {
19639            b.launch(cfg)?;
19640        }
19641        Ok(())
19642    }
19643
19644    #[allow(clippy::too_many_arguments)]
19645    pub fn gdn_scan_s128_batched_view(
19646        &self,
19647        q: &CudaSlice<f32>,
19648        k: &CudaSlice<f32>,
19649        v: &CudaSlice<f32>,
19650        g: &CudaSlice<f32>,
19651        beta: &CudaSlice<f32>,
19652        state_in_ptrs: &cudarc::driver::CudaView<u64>,
19653        state_out_ptrs: &cudarc::driver::CudaView<u64>,
19654        o: &mut cudarc::driver::CudaViewMut<f32>,
19655        n_head: usize,
19656        b_n: usize,
19657        scale: f32,
19658    ) -> Result<(), Box<dyn std::error::Error>> {
19659        let f = self.func("gdn_scan_s128_b");
19660        const S_V: u32 = 128;
19661        const WARP: u32 = 32;
19662        const COLS_PER_BLOCK: u32 = 4;
19663        let cfg = LaunchConfig {
19664            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
19665            block_dim: (WARP, COLS_PER_BLOCK, 1),
19666            shared_mem_bytes: 0,
19667        };
19668        let h = n_head as i32;
19669        let __s_b = self.gpu.stream();
19670        let mut b = __s_b.launch_builder(&f);
19671        b.arg(q)
19672            .arg(k)
19673            .arg(v)
19674            .arg(g)
19675            .arg(beta)
19676            .arg(state_in_ptrs)
19677            .arg(state_out_ptrs)
19678            .arg(o)
19679            .arg(&h)
19680            .arg(&scale);
19681        unsafe {
19682            b.launch(cfg)?;
19683        }
19684        Ok(())
19685    }
19686
19687    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
19688    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
19689    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
19690    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
19691    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
19692    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
19693    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
19694    /// identity law); prime_cache/forward/forward_last are the only callers.
19695    pub fn gdn_chunked_enabled() -> bool {
19696        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19697        *E.get_or_init(|| {
19698            std::env::var("MEMRA_GDN_CHUNKED")
19699                .map(|v| v != "0")
19700                .unwrap_or(true)
19701        })
19702    }
19703
19704    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
19705    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
19706    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
19707    /// of 32 in [32, 128] (kernel row mappings require it).
19708    pub fn gdn_chunk_size() -> usize {
19709        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19710        *C.get_or_init(|| {
19711            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
19712                .ok()
19713                .and_then(|v| v.parse().ok())
19714                .unwrap_or(32);
19715            c.clamp(32, 128) / 32 * 32
19716        })
19717    }
19718
19719    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
19720    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
19721    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
19722    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
19723    #[allow(clippy::too_many_arguments)]
19724    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
19725    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
19726    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
19727    #[allow(clippy::too_many_arguments)]
19728    pub fn gdn_chunk_k123(
19729        &self,
19730        q: &CudaSlice<f32>,
19731        k: &CudaSlice<f32>,
19732        v: &CudaSlice<f32>,
19733        g: &CudaSlice<f32>,
19734        beta: &CudaSlice<f32>,
19735        wb16: Option<&mut CudaSlice<u8>>,
19736        n_head: usize,
19737        t: usize,
19738        c: usize,
19739        hk: usize,
19740        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
19741    ) -> Result<
19742        (
19743            CudaSlice<f32>,
19744            CudaSlice<f32>,
19745            CudaSlice<f32>,
19746            CudaSlice<f32>,
19747        ),
19748        Box<dyn std::error::Error>,
19749    > {
19750        const D: usize = 128;
19751        let h = n_head;
19752        let nc = (t + c - 1) / c;
19753        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
19754        let mut gcum = self.uninit(t * h)?;
19755        let mut a = self.uninit(nc * h * c * c)?;
19756        let mut p = self.uninit(nc * h * c * c)?;
19757        let mut u = self.uninit(nc * h * c * D)?;
19758        let mut w = self.uninit(nc * h * c * D)?;
19759        {
19760            // K1
19761            let f = self.func("gdn_chunk_cumgate_f32");
19762            let cfg = LaunchConfig {
19763                grid_dim: (nc as u32, h as u32, 1),
19764                block_dim: (32, 1, 1),
19765                shared_mem_bytes: 0,
19766            };
19767            let __s_b = self.gpu.stream();
19768            let mut b = __s_b.launch_builder(&f);
19769            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
19770            unsafe {
19771                b.launch(cfg)?;
19772            }
19773        }
19774        if let Some((qb, kb, pb)) = k2w {
19775            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
19776            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
19777            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
19778            let f = self.func("gdn_k2_wgmma");
19779            let cfg = LaunchConfig {
19780                grid_dim: (nc as u32, h as u32, 1),
19781                block_dim: (128, 1, 1),
19782                shared_mem_bytes: 0,
19783            };
19784            let hki = hk as i32;
19785            let __s_b = self.gpu.stream();
19786            let mut b = __s_b.launch_builder(&f);
19787            b.arg(qb)
19788                .arg(kb)
19789                .arg(&gcum)
19790                .arg(beta)
19791                .arg(&mut a)
19792                .arg(&mut *pb)
19793                .arg(&hi)
19794                .arg(&ti)
19795                .arg(&ci)
19796                .arg(&hki);
19797            unsafe {
19798                b.launch(cfg)?;
19799            }
19800        } else if c <= 64 && !portable_mma_gated() {
19801            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
19802            let f = self.func("gdn_chunk_attn_f32");
19803            let jt = ((c + 31) / 32) as u32;
19804            let cfg = LaunchConfig {
19805                grid_dim: (nc as u32, h as u32, jt),
19806                block_dim: (256, 1, 1),
19807                shared_mem_bytes: 0,
19808            };
19809            let hki = hk as i32;
19810            let __s_b = self.gpu.stream();
19811            let mut b = __s_b.launch_builder(&f);
19812            b.arg(q)
19813                .arg(k)
19814                .arg(&gcum)
19815                .arg(beta)
19816                .arg(&mut a)
19817                .arg(&mut p)
19818                .arg(&hi)
19819                .arg(&ti)
19820                .arg(&ci)
19821                .arg(&hki);
19822            unsafe {
19823                b.launch(cfg)?;
19824            }
19825        } else {
19826            // K2 generic (C = 128, or the portable target's low-smem fallback)
19827            assert!(
19828                hk == h,
19829                "generic K2 is broadcast-only (de-broadcast rides C==32)"
19830            );
19831            let f = self.func("gdn_chunk_attn_g_f32");
19832            let cfg = LaunchConfig {
19833                grid_dim: (nc as u32, h as u32, 1),
19834                block_dim: (32, 8, 1),
19835                shared_mem_bytes: 0,
19836            };
19837            let __s_b = self.gpu.stream();
19838            let mut b = __s_b.launch_builder(&f);
19839            b.arg(q)
19840                .arg(k)
19841                .arg(&gcum)
19842                .arg(beta)
19843                .arg(&mut a)
19844                .arg(&mut p)
19845                .arg(&hi)
19846                .arg(&ti)
19847                .arg(&ci);
19848            unsafe {
19849                b.launch(cfg)?;
19850            }
19851        }
19852        {
19853            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
19854            let cfg = LaunchConfig {
19855                grid_dim: (nc as u32, h as u32, 1),
19856                block_dim: (256, 1, 1),
19857                shared_mem_bytes: 0,
19858            };
19859            match c {
19860                32 | 64 => {
19861                    let f = self.func(if c == 32 {
19862                        "gdn_chunk_solve32_f32"
19863                    } else {
19864                        "gdn_chunk_solve64_f32"
19865                    });
19866                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
19867                    let wb: u64 = match wb16 {
19868                        Some(d) => self.addr_u8(d),
19869                        None => 0,
19870                    };
19871                    let hki = hk as i32;
19872                    let __s_b = self.gpu.stream();
19873                    let mut b = __s_b.launch_builder(&f);
19874                    b.arg(v)
19875                        .arg(k)
19876                        .arg(&a)
19877                        .arg(&gcum)
19878                        .arg(&mut u)
19879                        .arg(&mut w)
19880                        .arg(&wb)
19881                        .arg(&hi)
19882                        .arg(&ti)
19883                        .arg(&hki);
19884                    unsafe {
19885                        b.launch(cfg)?;
19886                    }
19887                }
19888                _ => {
19889                    assert!(hk == h, "generic K3 is broadcast-only");
19890                    let f = self.func("gdn_chunk_solve_f32");
19891                    let __s_b = self.gpu.stream();
19892                    let mut b = __s_b.launch_builder(&f);
19893                    b.arg(v)
19894                        .arg(k)
19895                        .arg(&a)
19896                        .arg(&gcum)
19897                        .arg(&mut u)
19898                        .arg(&mut w)
19899                        .arg(&hi)
19900                        .arg(&ti)
19901                        .arg(&ci);
19902                    unsafe {
19903                        b.launch(cfg)?;
19904                    }
19905                }
19906            }
19907        }
19908        Ok((gcum, p, u, w))
19909    }
19910
19911    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
19912    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
19913    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
19914    pub fn gdn_db_on() -> bool {
19915        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
19916    }
19917
19918    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
19919    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
19920    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
19921        !portable_mma_gated()
19922            && c == 32
19923            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
19924                Ok("1") => true,
19925                Ok("0") => false,
19926                _ => cfg!(memra_hopper_mma),
19927            }
19928    }
19929
19930    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
19931    /// mma config; same per-call env read discipline).
19932    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
19933        self.gdn_mma_enabled(c)
19934            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
19935                Ok("0") => false,
19936                Ok("1") => true,
19937                _ => cfg!(memra_hopper_mma),
19938            }
19939    }
19940
19941    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
19942    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
19943    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
19944    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
19945    #[allow(clippy::too_many_arguments)]
19946    pub fn ssm_conv1d_gdn_state_pad(
19947        &self,
19948        qkv_tm: &cudarc::driver::CudaView<f32>,
19949        conv_state: &mut CudaSlice<f32>,
19950        w: &CudaSlice<f32>,
19951        q_g: &mut CudaSlice<f32>,
19952        k_g: &mut CudaSlice<f32>,
19953        v_g: &mut CudaSlice<f32>,
19954        conv_dim: usize,
19955        t: usize,
19956        d_conv: usize,
19957        d_state: usize,
19958        num_v: usize,
19959        num_k: usize,
19960        key_dim: usize,
19961        hk: usize,
19962        pad_len: Option<&CudaSlice<i32>>,
19963    ) -> Result<(), Box<dyn std::error::Error>> {
19964        assert!(
19965            t >= d_conv - 1,
19966            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
19967        );
19968        {
19969            let f = self.func("ssm_conv1d_gdn_state_f32");
19970            let cfg = LaunchConfig {
19971                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19972                block_dim: (256, 1, 1),
19973                shared_mem_bytes: 0,
19974            };
19975            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19976            let (ds, nv, nk, kd, hki) = (
19977                d_state as i32,
19978                num_v as i32,
19979                num_k as i32,
19980                key_dim as i32,
19981                hk as i32,
19982            );
19983            let __s_b = self.gpu.stream();
19984            let mut b = __s_b.launch_builder(&f);
19985            b.arg(qkv_tm)
19986                .arg(&*conv_state)
19987                .arg(w)
19988                .arg(q_g)
19989                .arg(k_g)
19990                .arg(v_g)
19991                .arg(&cd)
19992                .arg(&ti)
19993                .arg(&dc)
19994                .arg(&ds)
19995                .arg(&nv)
19996                .arg(&nk)
19997                .arg(&kd)
19998                .arg(&hki);
19999            unsafe {
20000                b.launch(cfg)?;
20001            }
20002        }
20003        match pad_len {
20004            Some(len_d) => {
20005                let f = self.func("ssm_conv_ring_update_dev_f32");
20006                let n = conv_dim * (d_conv - 1);
20007                let cfg = LaunchConfig::for_num_elems(n as u32);
20008                let (cd, dc) = (conv_dim as i32, d_conv as i32);
20009                let __s_b = self.gpu.stream();
20010                let mut b = __s_b.launch_builder(&f);
20011                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
20012                unsafe {
20013                    b.launch(cfg)?;
20014                }
20015            }
20016            None => {
20017                let f = self.func("ssm_conv_ring_update_f32");
20018                let n = conv_dim * (d_conv - 1);
20019                let cfg = LaunchConfig::for_num_elems(n as u32);
20020                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
20021                let __s_b = self.gpu.stream();
20022                let mut b = __s_b.launch_builder(&f);
20023                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
20024                unsafe {
20025                    b.launch(cfg)?;
20026                }
20027            }
20028        }
20029        Ok(())
20030    }
20031
20032    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
20033    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
20034    /// K2/K3 can write them.
20035    pub fn gdn_chunk_alloc(
20036        &self,
20037        n_head: usize,
20038        t: usize,
20039        c: usize,
20040        hk: usize,
20041    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
20042        const D: usize = 128;
20043        assert!(
20044            c == 32,
20045            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
20046        );
20047        let h = n_head;
20048        let nc = (t + c - 1) / c;
20049        Ok(GdnChunkBufs {
20050            gcum: self.uninit(t * h)?,
20051            a: self.uninit(nc * h * c * c)?,
20052            p: self.uninit(nc * h * c * c)?,
20053            u: self.uninit(nc * h * c * D)?,
20054            w: self.uninit(nc * h * c * D)?,
20055            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20056            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20057            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
20058            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
20059            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
20060            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
20061            o: self.uninit(D * h * t)?,
20062            t,
20063            nc,
20064        })
20065    }
20066
20067    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
20068    pub fn f32_to_bf16_v(
20069        &self,
20070        x: &cudarc::driver::CudaView<f32>,
20071        dst: &mut CudaSlice<u8>,
20072        n: usize,
20073    ) -> Result<(), Box<dyn std::error::Error>> {
20074        let f = self.func("f32_to_bf16_bulk");
20075        let ni = n as i64;
20076        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20077        let __s_b = self.gpu.stream();
20078        let mut b = __s_b.launch_builder(&f);
20079        b.arg(x).arg(dst).arg(&ni);
20080        unsafe {
20081            b.launch(cfg)?;
20082        }
20083        Ok(())
20084    }
20085
20086    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
20087    pub fn f32_to_bf16_into(
20088        &self,
20089        x: &CudaSlice<f32>,
20090        dst: &mut CudaSlice<u8>,
20091        n: usize,
20092    ) -> Result<(), Box<dyn std::error::Error>> {
20093        let f = self.func("f32_to_bf16_bulk");
20094        let ni = n as i64;
20095        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
20096        let __s_b = self.gpu.stream();
20097        let mut b = __s_b.launch_builder(&f);
20098        b.arg(x).arg(dst).arg(&ni);
20099        unsafe {
20100            b.launch(cfg)?;
20101        }
20102        Ok(())
20103    }
20104
20105    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
20106    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
20107    pub fn gdn_chunk_k123_vl8(
20108        &self,
20109        seqs: &[GdnSeqVl],
20110        n_head: usize,
20111        hk: usize,
20112        wq: Option<&GdnWVl8>,
20113    ) -> Result<(), Box<dyn std::error::Error>> {
20114        let b = seqs.len();
20115        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
20116        let mut packed = [GdnSeqVl::default(); 8];
20117        packed[..b].copy_from_slice(seqs);
20118        let v = GdnVl8(packed);
20119        let (hi, ci) = (n_head as i32, 32i32);
20120        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20121        {
20122            let f = self.func("gdn_chunk_cumgate_vl");
20123            let cfg = LaunchConfig {
20124                grid_dim: (max_nc, n_head as u32, b as u32),
20125                block_dim: (32, 1, 1),
20126                shared_mem_bytes: 0,
20127            };
20128            let __s_lb = self.gpu.stream();
20129            let mut lb = __s_lb.launch_builder(&f);
20130            lb.arg(&v).arg(&hi).arg(&ci);
20131            unsafe {
20132                lb.launch(cfg)?;
20133            }
20134        }
20135        let hki = hk as i32;
20136        if let Some(w) = wq {
20137            // K2-wgmma vl twin (writes A + pre-masked Pb16)
20138            let f = self.func("gdn_k2_wgmma_vl");
20139            let cfg = LaunchConfig {
20140                grid_dim: (max_nc, n_head as u32, b as u32),
20141                block_dim: (128, 1, 1),
20142                shared_mem_bytes: 0,
20143            };
20144            let __s_lb = self.gpu.stream();
20145            let mut lb = __s_lb.launch_builder(&f);
20146            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
20147            unsafe {
20148                lb.launch(cfg)?;
20149            }
20150        } else {
20151            let f = self.func("gdn_chunk_attn_vl");
20152            let cfg = LaunchConfig {
20153                grid_dim: (max_nc, n_head as u32, b as u32),
20154                block_dim: (256, 1, 1),
20155                shared_mem_bytes: 0,
20156            };
20157            let __s_lb = self.gpu.stream();
20158            let mut lb = __s_lb.launch_builder(&f);
20159            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20160            unsafe {
20161                lb.launch(cfg)?;
20162            }
20163        }
20164        {
20165            let f = self.func("gdn_chunk_solve32_vl");
20166            let cfg = LaunchConfig {
20167                grid_dim: (max_nc, n_head as u32, b as u32),
20168                block_dim: (256, 1, 1),
20169                shared_mem_bytes: 0,
20170            };
20171            let __s_lb = self.gpu.stream();
20172            let mut lb = __s_lb.launch_builder(&f);
20173            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20174            unsafe {
20175                lb.launch(cfg)?;
20176            }
20177        }
20178        Ok(())
20179    }
20180
20181    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
20182    /// fused gate-prep, 5 launches for every sequence (per-element math identical
20183    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
20184    #[allow(clippy::too_many_arguments)]
20185    pub fn gdn_prep_vl8(
20186        &self,
20187        seqs: &[GdnPrepVl],
20188        conv_w: &CudaSlice<f32>,
20189        dt_bias: &CudaSlice<f32>,
20190        a: &CudaSlice<f32>,
20191        conv_dim: usize,
20192        d_conv: usize,
20193        d_state: usize,
20194        num_v: usize,
20195        num_k: usize,
20196        key_dim: usize,
20197        hk: usize,
20198        eps: f32,
20199    ) -> Result<(), Box<dyn std::error::Error>> {
20200        let b = seqs.len();
20201        assert!(b >= 1 && b <= 8);
20202        let mut packed = [GdnPrepVl::default(); 8];
20203        packed[..b].copy_from_slice(seqs);
20204        let v = GdnPrepVl8(packed);
20205        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20206        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
20207        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
20208        assert!(
20209            conv_fuse || hk == num_v,
20210            "de-broadcast requires the fused conv"
20211        );
20212        if conv_fuse {
20213            let f = self.func("ssm_conv1d_gdn_state_vl");
20214            let cfg = LaunchConfig {
20215                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
20216                block_dim: (256, 1, 1),
20217                shared_mem_bytes: 0,
20218            };
20219            let (dsi, nvi, nki, kdi, hki) = (
20220                d_state as i32,
20221                num_v as i32,
20222                num_k as i32,
20223                key_dim as i32,
20224                hk as i32,
20225            );
20226            let __s_lb = self.gpu.stream();
20227            let mut lb = __s_lb.launch_builder(&f);
20228            lb.arg(&v)
20229                .arg(conv_w)
20230                .arg(&cdi)
20231                .arg(&dci)
20232                .arg(&dsi)
20233                .arg(&nvi)
20234                .arg(&nki)
20235                .arg(&kdi)
20236                .arg(&hki);
20237            unsafe {
20238                lb.launch(cfg)?;
20239            }
20240        } else {
20241            let f = self.func("ssm_conv1d_tm_state_vl");
20242            let cfg = LaunchConfig {
20243                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
20244                block_dim: (256, 1, 1),
20245                shared_mem_bytes: 0,
20246            };
20247            let __s_lb = self.gpu.stream();
20248            let mut lb = __s_lb.launch_builder(&f);
20249            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
20250            unsafe {
20251                lb.launch(cfg)?;
20252            }
20253        }
20254        {
20255            let f = self.func("ssm_conv_ring_update_vl");
20256            let n = (conv_dim * (d_conv - 1)) as u32;
20257            let cfg = LaunchConfig {
20258                grid_dim: (n.div_ceil(256), 1, b as u32),
20259                block_dim: (256, 1, 1),
20260                shared_mem_bytes: 0,
20261            };
20262            let __s_lb = self.gpu.stream();
20263            let mut lb = __s_lb.launch_builder(&f);
20264            lb.arg(&v).arg(&cdi).arg(&dci);
20265            unsafe {
20266                lb.launch(cfg)?;
20267            }
20268        }
20269        if !conv_fuse {
20270            let f = self.func("qkv_to_gdn_repack_vl");
20271            let n = max_t * (num_v * d_state) as u32;
20272            let cfg = LaunchConfig {
20273                grid_dim: (n.div_ceil(256), 1, b as u32),
20274                block_dim: (256, 1, 1),
20275                shared_mem_bytes: 0,
20276            };
20277            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20278            let __s_lb = self.gpu.stream();
20279            let mut lb = __s_lb.launch_builder(&f);
20280            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
20281            unsafe {
20282                lb.launch(cfg)?;
20283            }
20284        }
20285        if Self::l2_v2_on(d_state) {
20286            let f = self.func("gdn_l2_v2_vl");
20287            let cfg = LaunchConfig {
20288                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
20289                block_dim: (256, 1, 1),
20290                shared_mem_bytes: 0,
20291            };
20292            let (dsi, nvi) = (d_state as i32, hk as i32);
20293            let __s_lb = self.gpu.stream();
20294            let mut lb = __s_lb.launch_builder(&f);
20295            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20296            unsafe {
20297                lb.launch(cfg)?;
20298            }
20299        } else {
20300            let f = self.func("gdn_l2_vl");
20301            let cfg = LaunchConfig {
20302                grid_dim: (max_t * hk as u32, 2, b as u32),
20303                block_dim: (256, 1, 1),
20304                shared_mem_bytes: 0,
20305            };
20306            let (dsi, nvi) = (d_state as i32, hk as i32);
20307            let __s_lb = self.gpu.stream();
20308            let mut lb = __s_lb.launch_builder(&f);
20309            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20310            unsafe {
20311                lb.launch(cfg)?;
20312            }
20313        }
20314        {
20315            let f = self.func("gdn_gate_prep_vl");
20316            let n = max_t * num_v as u32;
20317            let cfg = LaunchConfig {
20318                grid_dim: (n.div_ceil(256), 1, b as u32),
20319                block_dim: (256, 1, 1),
20320                shared_mem_bytes: 0,
20321            };
20322            let nvi = num_v as i32;
20323            let __s_lb = self.gpu.stream();
20324            let mut lb = __s_lb.launch_builder(&f);
20325            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
20326            unsafe {
20327                lb.launch(cfg)?;
20328            }
20329        }
20330        Ok(())
20331    }
20332
20333    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
20334    pub fn gdn_mirror_vl8(
20335        &self,
20336        seqs: &[GdnSeqVl],
20337        n_head: usize,
20338        which: i32,
20339        hk: usize,
20340    ) -> Result<(), Box<dyn std::error::Error>> {
20341        let b = seqs.len();
20342        assert!(b >= 1 && b <= 8);
20343        let mut packed = [GdnSeqVl::default(); 8];
20344        packed[..b].copy_from_slice(seqs);
20345        let v = GdnVl8(packed);
20346        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
20347        let max_n = seqs
20348            .iter()
20349            .map(|s| {
20350                if which == 0 {
20351                    s.t as i64 * ept as i64
20352                } else {
20353                    s.nc as i64 * ept as i64 * 32
20354                }
20355            })
20356            .max()
20357            .unwrap();
20358        let f = self.func("gdn_mirror_vl");
20359        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
20360        let cfg = LaunchConfig {
20361            grid_dim: (blocks, 1, b as u32),
20362            block_dim: (256, 1, 1),
20363            shared_mem_bytes: 0,
20364        };
20365        let __s_lb = self.gpu.stream();
20366        let mut lb = __s_lb.launch_builder(&f);
20367        lb.arg(&v).arg(&ept).arg(&which);
20368        unsafe {
20369            lb.launch(cfg)?;
20370        }
20371        Ok(())
20372    }
20373
20374    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
20375    pub fn gdn_tail_vl8(
20376        &self,
20377        seqs: &[GdnPrepVl],
20378        norm_w: &CudaSlice<f32>,
20379        d_state: usize,
20380        num_v: usize,
20381        eps: f32,
20382    ) -> Result<(), Box<dyn std::error::Error>> {
20383        let b = seqs.len();
20384        assert!(b >= 1 && b <= 8);
20385        let mut packed = [GdnPrepVl::default(); 8];
20386        packed[..b].copy_from_slice(seqs);
20387        let v = GdnPrepVl8(packed);
20388        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20389        let f = self.func("gated_rmsnorm_f16out_vl");
20390        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
20391        let cfg = LaunchConfig {
20392            grid_dim: (max_t * num_v as u32, 1, b as u32),
20393            block_dim: (128, 1, 1),
20394            shared_mem_bytes: 0,
20395        };
20396        let (dsi, nvi) = (d_state as i32, num_v as i32);
20397        let __s_lb = self.gpu.stream();
20398        let mut lb = __s_lb.launch_builder(&f);
20399        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
20400        unsafe {
20401            lb.launch(cfg)?;
20402        }
20403        Ok(())
20404    }
20405
20406    /// Raw device address helpers for the varlen by-value arg struct (single-stream
20407    /// launches; every buffer outlives the call — the f16 FFI discipline).
20408    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
20409        use cudarc::driver::DevicePtr;
20410        let s = self.gpu.stream();
20411        let (p, _g) = x.device_ptr(&s);
20412        p as u64
20413    }
20414    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
20415        use cudarc::driver::DevicePtrMut;
20416        let s = self.gpu.stream();
20417        let (p, _g) = x.device_ptr_mut(&s);
20418        p as u64
20419    }
20420    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
20421        use cudarc::driver::DevicePtr;
20422        let s = self.gpu.stream();
20423        let (p, _g) = x.device_ptr(&s);
20424        p as u64
20425    }
20426    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
20427        use cudarc::driver::DevicePtr;
20428        let s = self.gpu.stream();
20429        let (p, _g) = x.device_ptr(&s);
20430        p as u64
20431    }
20432
20433    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
20434    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
20435    /// launches, so this is strictly bit-gateable against them).
20436    pub fn gdn_chunk_vl8(
20437        &self,
20438        seqs: &[GdnSeqVl],
20439        n_head: usize,
20440        scale: f32,
20441        hk: usize,
20442        wq: Option<&GdnWVl8>,
20443    ) -> Result<(), Box<dyn std::error::Error>> {
20444        const NSPLIT: u32 = 4;
20445        let b = seqs.len();
20446        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
20447        let mut packed = [GdnSeqVl::default(); 8];
20448        packed[..b].copy_from_slice(seqs);
20449        let v = GdnVl8(packed);
20450        let (hi, ci) = (n_head as i32, 32i32);
20451        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20452        let hki = hk as i32;
20453        if let Some(w) = wq {
20454            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
20455            let f = self.func("gdn_k45_wgmma_vl");
20456            let cfg = LaunchConfig {
20457                grid_dim: (n_head as u32, NSPLIT, b as u32),
20458                block_dim: (256, 1, 1),
20459                shared_mem_bytes: 0,
20460            };
20461            let __s_lb = self.gpu.stream();
20462            let mut lb = __s_lb.launch_builder(&f);
20463            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
20464            unsafe {
20465                lb.launch(cfg)?;
20466            }
20467            let _ = max_nc;
20468            return Ok(());
20469        }
20470        {
20471            let f = self.func("gdn_chunk_state_mma_vl");
20472            let cfg = LaunchConfig {
20473                grid_dim: (n_head as u32, NSPLIT, b as u32),
20474                block_dim: (256, 1, 1),
20475                shared_mem_bytes: 0,
20476            };
20477            let __s_lb = self.gpu.stream();
20478            let mut lb = __s_lb.launch_builder(&f);
20479            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20480            unsafe {
20481                lb.launch(cfg)?;
20482            }
20483        }
20484        {
20485            let f = self.func("gdn_chunk_output_mma_vl");
20486            let cfg = LaunchConfig {
20487                grid_dim: (max_nc, n_head as u32, b as u32),
20488                block_dim: (256, 1, 1),
20489                shared_mem_bytes: 0,
20490            };
20491            let __s_lb = self.gpu.stream();
20492            let mut lb = __s_lb.launch_builder(&f);
20493            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
20494            unsafe {
20495                lb.launch(cfg)?;
20496            }
20497        }
20498        Ok(())
20499    }
20500    pub fn gdn_scan_chunked(
20501        &self,
20502        q: &CudaSlice<f32>,
20503        k: &CudaSlice<f32>,
20504        v: &CudaSlice<f32>,
20505        g: &CudaSlice<f32>,
20506        beta: &CudaSlice<f32>,
20507        kb16_pre: Option<&CudaSlice<u8>>,
20508        qb16_pre: Option<&CudaSlice<u8>>,
20509        state_in: &CudaSlice<f32>,
20510        state_out: &mut CudaSlice<f32>,
20511        o: &mut CudaSlice<f32>,
20512        n_head: usize,
20513        t: usize,
20514        scale: f32,
20515        c: usize,
20516        hk: usize,
20517    ) -> Result<(), Box<dyn std::error::Error>> {
20518        const D: usize = 128;
20519        const NSPLIT: u32 = 4;
20520        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
20521        let h = n_head;
20522        let nc = (t + c - 1) / c;
20523        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20524        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
20525        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
20526        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
20527        let gdn_mma_pre = !portable_mma_gated()
20528            && c == 32
20529            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20530                Ok("1") => true,
20531                Ok("0") => false,
20532                _ => cfg!(memra_hopper_mma),
20533            };
20534        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
20535            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
20536        } else {
20537            None
20538        };
20539        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
20540        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
20541        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
20542        let gdn_wgmma_pre = gdn_mma_pre
20543            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20544                Ok("0") => false,
20545                Ok("1") => true,
20546                _ => cfg!(memra_hopper_mma),
20547            };
20548        let nk = t * hk * D;
20549        let mut kb16_local: Option<CudaSlice<u8>> = None;
20550        if gdn_mma_pre && kb16_pre.is_none() {
20551            let mut kb = self.alloc_u8_uninit(nk * 2)?;
20552            let f = self.func("f32_to_bf16_bulk");
20553            let n2 = nk as i64;
20554            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20555            let __s_b = self.gpu.stream();
20556            let mut b = __s_b.launch_builder(&f);
20557            b.arg(k).arg(&mut kb).arg(&n2);
20558            unsafe {
20559                b.launch(cfg2)?;
20560            }
20561            kb16_local = Some(kb);
20562        }
20563        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
20564        if let Some(kb) = kb16_pre {
20565            assert!(kb.len() >= nk * 2, "kb16_pre too small");
20566        }
20567        let mut qb16: Option<CudaSlice<u8>> = None;
20568        let mut pb16: Option<CudaSlice<u8>> = None;
20569        if gdn_wgmma_pre {
20570            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
20571            // the standalone bulk cvt only serves callers without the prep mirror.
20572            if qb16_pre.is_none() {
20573                let mut qb = self.alloc_u8_uninit(nk * 2)?;
20574                let f = self.func("f32_to_bf16_bulk");
20575                let n2 = nk as i64;
20576                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20577                let __s_b = self.gpu.stream();
20578                let mut b = __s_b.launch_builder(&f);
20579                b.arg(q).arg(&mut qb).arg(&n2);
20580                unsafe {
20581                    b.launch(cfg2)?;
20582                }
20583                qb16 = Some(qb);
20584            } else if let Some(qb) = qb16_pre {
20585                assert!(qb.len() >= nk * 2, "qb16_pre too small");
20586            }
20587            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
20588        }
20589        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
20590        let k2w = if gdn_wgmma_pre {
20591            Some((
20592                *qb16_ref0.as_ref().unwrap(),
20593                *kb16_ref0.as_ref().unwrap(),
20594                pb16.as_mut().unwrap(),
20595            ))
20596        } else {
20597            None
20598        };
20599        let (gcum, p, u, w) =
20600            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
20601        let _ = &w;
20602        let mut y = self.uninit(nc * h * c * D)?;
20603        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
20604        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
20605        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
20606        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
20607        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
20608        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
20609        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
20610        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
20611        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
20612        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
20613        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
20614        let gdn_mma = !portable_mma_gated()
20615            && c == 32
20616            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20617                Ok("1") => true,
20618                Ok("0") => false,
20619                _ => cfg!(memra_hopper_mma),
20620            };
20621        if gdn_mma {
20622            let wb16 = wb16_pre
20623                .take()
20624                .expect("mma path pre-allocates wb16 (K3 store fold)");
20625            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
20626            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
20627            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
20628            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
20629            // pass runs inside the persistent-M kernel; Y and Ssnap are never
20630            // materialized. New numeric class (gk folds into k^T instead of ys) —
20631            // explicit opt-in until the state-carry battery promotes it. Env read per
20632            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
20633            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
20634            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
20635            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
20636            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
20637            if gdn_wgmma_pre {
20638                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
20639                let qb16 = qb16_ref0.unwrap();
20640                let pb16 = pb16.as_ref().unwrap();
20641                {
20642                    let f = self.func("gdn_k45_wgmma");
20643                    let cfg = LaunchConfig {
20644                        grid_dim: (h as u32, 4, 1),
20645                        block_dim: (256, 1, 1),
20646                        shared_mem_bytes: 0,
20647                    };
20648                    let hki = hk as i32;
20649                    let __s_b = self.gpu.stream();
20650                    let mut b = __s_b.launch_builder(&f);
20651                    b.arg(kb16_ref)
20652                        .arg(&gcum)
20653                        .arg(beta)
20654                        .arg(&u)
20655                        .arg(&wb16)
20656                        .arg(qb16)
20657                        .arg(pb16)
20658                        .arg(o)
20659                        .arg(&scale)
20660                        .arg(state_in)
20661                        .arg(&mut *state_out)
20662                        .arg(&hi)
20663                        .arg(&ti)
20664                        .arg(&ci)
20665                        .arg(&hki);
20666                    unsafe {
20667                        b.launch(cfg)?;
20668                    }
20669                }
20670                return Ok(());
20671            }
20672            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
20673            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
20674            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
20675            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
20676            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
20677            {
20678                let f = self.func("gdn_chunk_state_mma");
20679                let cfg = LaunchConfig {
20680                    grid_dim: (h as u32, NSPLIT, 1),
20681                    block_dim: (256, 1, 1),
20682                    shared_mem_bytes: 0,
20683                };
20684                let hki = hk as i32;
20685                let __s_b = self.gpu.stream();
20686                let mut b = __s_b.launch_builder(&f);
20687                b.arg(kb16_ref)
20688                    .arg(&gcum)
20689                    .arg(beta)
20690                    .arg(&u)
20691                    .arg(&wb16)
20692                    .arg(&mut y16)
20693                    .arg(&mut ssnap16)
20694                    .arg(state_in)
20695                    .arg(&mut *state_out)
20696                    .arg(&hi)
20697                    .arg(&ti)
20698                    .arg(&ci)
20699                    .arg(&hki);
20700                unsafe {
20701                    b.launch(cfg)?;
20702                }
20703            }
20704            {
20705                // K5-mma (bf16 St/Y consumers)
20706                let f = self.func("gdn_chunk_output_mma");
20707                let jt = ((c + 31) / 32) as u32;
20708                let cfg = LaunchConfig {
20709                    grid_dim: (nc as u32, h as u32, jt),
20710                    block_dim: (256, 1, 1),
20711                    shared_mem_bytes: 0,
20712                };
20713                let hki = hk as i32;
20714                let __s_b = self.gpu.stream();
20715                let mut b = __s_b.launch_builder(&f);
20716                b.arg(q)
20717                    .arg(&gcum)
20718                    .arg(&p)
20719                    .arg(&y16)
20720                    .arg(&ssnap16)
20721                    .arg(o)
20722                    .arg(&hi)
20723                    .arg(&ti)
20724                    .arg(&ci)
20725                    .arg(&scale)
20726                    .arg(&hki);
20727                unsafe {
20728                    b.launch(cfg)?;
20729                }
20730            }
20731            return Ok(());
20732        }
20733        {
20734            // K4 (sequential over chunks inside; blocks col-partition the state)
20735            let f = self.func("gdn_chunk_state_f32");
20736            let cfg = LaunchConfig {
20737                grid_dim: (h as u32, NSPLIT, 1),
20738                block_dim: (256, 1, 1),
20739                shared_mem_bytes: 0,
20740            };
20741            let __s_b = self.gpu.stream();
20742            let mut b = __s_b.launch_builder(&f);
20743            b.arg(k)
20744                .arg(&gcum)
20745                .arg(beta)
20746                .arg(&u)
20747                .arg(&w)
20748                .arg(&mut y)
20749                .arg(&mut ssnap)
20750                .arg(state_in)
20751                .arg(&mut *state_out)
20752                .arg(&hi)
20753                .arg(&ti)
20754                .arg(&ci);
20755            unsafe {
20756                b.launch(cfg)?;
20757            }
20758        }
20759        {
20760            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
20761            let f = self.func("gdn_chunk_output_f32");
20762            let jt = ((c + 31) / 32) as u32;
20763            let cfg = LaunchConfig {
20764                grid_dim: (nc as u32, h as u32, jt),
20765                block_dim: (256, 1, 1),
20766                shared_mem_bytes: 0,
20767            };
20768            let __s_b = self.gpu.stream();
20769            let mut b = __s_b.launch_builder(&f);
20770            b.arg(q)
20771                .arg(&gcum)
20772                .arg(&p)
20773                .arg(&y)
20774                .arg(&ssnap)
20775                .arg(o)
20776                .arg(&hi)
20777                .arg(&ti)
20778                .arg(&ci)
20779                .arg(&scale);
20780            unsafe {
20781                b.launch(cfg)?;
20782            }
20783        }
20784        Ok(())
20785    }
20786
20787    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
20788    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
20789    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
20790    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
20791    ///
20792    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
20793    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
20794    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
20795    #[allow(clippy::too_many_arguments)]
20796    #[allow(clippy::too_many_arguments)]
20797    pub fn gdn_scan_prefill(
20798        &self,
20799        q: &CudaSlice<f32>,
20800        k: &CudaSlice<f32>,
20801        v: &CudaSlice<f32>,
20802        g: &CudaSlice<f32>,
20803        beta: &CudaSlice<f32>,
20804        kb16_pre: Option<&CudaSlice<u8>>,
20805        qb16_pre: Option<&CudaSlice<u8>>,
20806        state_in: &CudaSlice<f32>,
20807        state_out: &mut CudaSlice<f32>,
20808        o: &mut CudaSlice<f32>,
20809        n_head: usize,
20810        t: usize,
20811        scale: f32,
20812        hk: usize,
20813    ) -> Result<(), Box<dyn std::error::Error>> {
20814        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
20815            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
20816            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
20817        }
20818        if Self::gdn_chunked_enabled() && t >= 16 {
20819            self.gdn_scan_chunked(
20820                q,
20821                k,
20822                v,
20823                g,
20824                beta,
20825                kb16_pre,
20826                qb16_pre,
20827                state_in,
20828                state_out,
20829                o,
20830                n_head,
20831                t,
20832                scale,
20833                Self::gdn_chunk_size(),
20834                hk,
20835            )
20836        } else {
20837            assert!(
20838                hk == n_head,
20839                "s128 scan is broadcast-only (prep guarantees by predicate)"
20840            );
20841            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
20842        }
20843    }
20844
20845    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
20846    #[allow(clippy::too_many_arguments)]
20847    fn gdn_scan_diff(
20848        &self,
20849        q: &CudaSlice<f32>,
20850        k: &CudaSlice<f32>,
20851        v: &CudaSlice<f32>,
20852        g: &CudaSlice<f32>,
20853        beta: &CudaSlice<f32>,
20854        state_in: &CudaSlice<f32>,
20855        state_out: &mut CudaSlice<f32>,
20856        o: &mut CudaSlice<f32>,
20857        n_head: usize,
20858        t: usize,
20859        scale: f32,
20860    ) -> Result<(), Box<dyn std::error::Error>> {
20861        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
20862        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
20863        let mut o_c = self.uninit(o.len())?;
20864        let mut st_c = self.uninit(state_out.len())?;
20865        self.gdn_scan_chunked(
20866            q,
20867            k,
20868            v,
20869            g,
20870            beta,
20871            None,
20872            None,
20873            state_in,
20874            &mut st_c,
20875            &mut o_c,
20876            n_head,
20877            t,
20878            scale,
20879            Self::gdn_chunk_size(),
20880            n_head,
20881        )?;
20882        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
20883        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
20884        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
20885        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
20886            let mut max_abs = 0f32;
20887            let mut max_rel = 0f32;
20888            let mut sum_rel = 0f64;
20889            for (x, y) in a.iter().zip(b) {
20890                let ad = (x - y).abs();
20891                let rel = ad / x.abs().max(y.abs()).max(1e-3);
20892                if ad > max_abs {
20893                    max_abs = ad;
20894                }
20895                if rel > max_rel {
20896                    max_rel = rel;
20897                }
20898                sum_rel += rel as f64;
20899            }
20900            (max_abs, max_rel, sum_rel / a.len() as f64)
20901        };
20902        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
20903        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
20904        println!(
20905            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
20906                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
20907            Self::gdn_chunk_size()
20908        );
20909        Ok(())
20910    }
20911
20912    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
20913    pub fn gdn_glog(
20914        &self,
20915        alpha: &CudaSlice<f32>,
20916        dt_bias: &CudaSlice<f32>,
20917        a: &CudaSlice<f32>,
20918        g_log: &mut CudaSlice<f32>,
20919        n_head: usize,
20920        t: usize,
20921    ) -> Result<(), Box<dyn std::error::Error>> {
20922        let f = self.func("gdn_glog_f32");
20923        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
20924        let (h, ti) = (n_head as i32, t as i32);
20925        let __s_b = self.gpu.stream();
20926        let mut b = __s_b.launch_builder(&f);
20927        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
20928        unsafe {
20929            b.launch(cfg)?;
20930        }
20931        Ok(())
20932    }
20933
20934    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
20935    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
20936    pub fn sigmoid_v(
20937        &self,
20938        x: &cudarc::driver::CudaView<f32>,
20939        y: &mut CudaSlice<f32>,
20940        n: usize,
20941    ) -> Result<(), Box<dyn std::error::Error>> {
20942        let f = self.func("sigmoid_f32");
20943        let cfg = LaunchConfig::for_num_elems(n as u32);
20944        let ni = n as i32;
20945        let __s_b = self.gpu.stream();
20946        let mut b = __s_b.launch_builder(&f);
20947        b.arg(x).arg(y).arg(&ni);
20948        unsafe {
20949            b.launch(cfg)?;
20950        }
20951        Ok(())
20952    }
20953
20954    pub fn gdn_glog_v(
20955        &self,
20956        alpha: &cudarc::driver::CudaView<f32>,
20957        dt_bias: &CudaSlice<f32>,
20958        a: &CudaSlice<f32>,
20959        g_log: &mut CudaSlice<f32>,
20960        n_head: usize,
20961        t: usize,
20962    ) -> Result<(), Box<dyn std::error::Error>> {
20963        let f = self.func("gdn_glog_f32");
20964        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
20965        let (h, ti) = (n_head as i32, t as i32);
20966        let __s_b = self.gpu.stream();
20967        let mut b = __s_b.launch_builder(&f);
20968        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
20969        unsafe {
20970            b.launch(cfg)?;
20971        }
20972        Ok(())
20973    }
20974
20975    pub fn sigmoid(
20976        &self,
20977        x: &CudaSlice<f32>,
20978        y: &mut CudaSlice<f32>,
20979        n: usize,
20980    ) -> Result<(), Box<dyn std::error::Error>> {
20981        let f = self.func("sigmoid_f32");
20982        let cfg = LaunchConfig::for_num_elems(n as u32);
20983        let ni = n as i32;
20984        let __s_b = self.gpu.stream();
20985        let mut b = __s_b.launch_builder(&f);
20986        b.arg(x).arg(y).arg(&ni);
20987        unsafe {
20988            b.launch(cfg)?;
20989        }
20990        Ok(())
20991    }
20992
20993    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
20994    /// (replaces sigmoid + mul + convert). Bit-identical class.
20995    pub fn sig_mul_f16out(
20996        &self,
20997        a: &CudaSlice<f32>,
20998        g: &CudaSlice<f32>,
20999        dst: &mut CudaSlice<f32>,
21000        dst16: &mut CudaSlice<u8>,
21001        n: usize,
21002    ) -> Result<(), Box<dyn std::error::Error>> {
21003        let f = self.func("sig_mul_f16out_f32");
21004        let cfg = LaunchConfig::for_num_elems(n as u32);
21005        let ni = n as i32;
21006        let __s_b = self.gpu.stream();
21007        let mut b = __s_b.launch_builder(&f);
21008        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
21009        unsafe {
21010            b.launch(cfg)?;
21011        }
21012        Ok(())
21013    }
21014
21015    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
21016    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
21017    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
21018    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
21019    ///
21020    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
21021    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
21022    /// applies the wrong number of distinct gate values.
21023    #[allow(clippy::too_many_arguments)]
21024    pub fn attn_head_gate(
21025        &self,
21026        a: &CudaSlice<f32>,
21027        g: &CudaSlice<f32>,
21028        dst: &mut CudaSlice<f32>,
21029        dst16: Option<&mut CudaSlice<u8>>,
21030        head_dim: usize,
21031        n_head: usize,
21032        t: usize,
21033    ) -> Result<(), Box<dyn std::error::Error>> {
21034        let f = self.func("attn_head_gate_f32");
21035        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21036        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21037        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
21038        let d16: u64 = match dst16 {
21039            Some(d) => self.addr_u8(d),
21040            None => 0,
21041        };
21042        let __s_b = self.gpu.stream();
21043        let mut b = __s_b.launch_builder(&f);
21044        b.arg(a)
21045            .arg(g)
21046            .arg(dst)
21047            .arg(&d16)
21048            .arg(&hd)
21049            .arg(&nh)
21050            .arg(&ti);
21051        unsafe {
21052            b.launch(cfg)?;
21053        }
21054        Ok(())
21055    }
21056
21057    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
21058    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
21059    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
21060    ///
21061    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
21062    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
21063    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
21064    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
21065    #[allow(clippy::too_many_arguments)]
21066    pub fn swiglu_clamped_mul_scaled(
21067        &self,
21068        gate: &CudaSlice<f32>,
21069        up: &CudaSlice<f32>,
21070        gs: f32,
21071        us: f32,
21072        limit: f32,
21073        dst: &mut CudaSlice<f32>,
21074        n: usize,
21075    ) -> Result<(), Box<dyn std::error::Error>> {
21076        debug_assert!(
21077            limit > 1e-6,
21078            "swiglu_clamped needs a live limit; use silu_mul_scaled"
21079        );
21080        let f = self.func("swiglu_clamped_mul_scaled_f32");
21081        let cfg = LaunchConfig::for_num_elems(n as u32);
21082        let ni = n as i32;
21083        let __s_b = self.gpu.stream();
21084        let mut b = __s_b.launch_builder(&f);
21085        b.arg(gate)
21086            .arg(up)
21087            .arg(&gs)
21088            .arg(&us)
21089            .arg(&limit)
21090            .arg(dst)
21091            .arg(&ni);
21092        unsafe {
21093            b.launch(cfg)?;
21094        }
21095        Ok(())
21096    }
21097
21098    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
21099    pub fn gated_rmsnorm(
21100        &self,
21101        o: &CudaSlice<f32>,
21102        w: &CudaSlice<f32>,
21103        z: &CudaSlice<f32>,
21104        dst: &mut CudaSlice<f32>,
21105        ncols: usize,
21106        nrows: usize,
21107        eps: f32,
21108    ) -> Result<(), Box<dyn std::error::Error>> {
21109        let f = self.func("gated_rmsnorm_f32");
21110        let cfg = LaunchConfig {
21111            grid_dim: (nrows as u32, 1, 1),
21112            block_dim: (128, 1, 1),
21113            shared_mem_bytes: 0,
21114        };
21115        let (nc, e) = (ncols as i32, eps);
21116        let __s_b = self.gpu.stream();
21117        let mut b = __s_b.launch_builder(&f);
21118        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
21119        unsafe {
21120            b.launch(cfg)?;
21121        }
21122        Ok(())
21123    }
21124
21125    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
21126    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
21127    pub fn gated_rmsnorm_f16out(
21128        &self,
21129        o: &CudaSlice<f32>,
21130        w: &CudaSlice<f32>,
21131        z: &CudaSlice<f32>,
21132        dst: &mut CudaSlice<f32>,
21133        dst16: &mut CudaSlice<u8>,
21134        ncols: usize,
21135        nrows: usize,
21136        eps: f32,
21137    ) -> Result<(), Box<dyn std::error::Error>> {
21138        let f = self.func("gated_rmsnorm_f16out_f32");
21139        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21140        let cfg = LaunchConfig {
21141            grid_dim: (nrows as u32, 1, 1),
21142            block_dim: (128, 1, 1),
21143            shared_mem_bytes: 0,
21144        };
21145        let (nc, e) = (ncols as i32, eps);
21146        let __s_b = self.gpu.stream();
21147        let mut b = __s_b.launch_builder(&f);
21148        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
21149        unsafe {
21150            b.launch(cfg)?;
21151        }
21152        Ok(())
21153    }
21154
21155    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
21156    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
21157    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
21158    #[allow(clippy::too_many_arguments)]
21159    pub fn add_rms_norm_zq8(
21160        &self,
21161        a: &CudaSlice<f32>,
21162        b_in: &CudaSlice<f32>,
21163        w: &CudaSlice<f32>,
21164        res: &mut CudaSlice<f32>,
21165        z: &mut CudaSlice<f32>,
21166        ncols: usize,
21167        nrows: usize,
21168        eps: f32,
21169    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
21170        assert!(ncols % 32 == 0);
21171        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
21172        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
21173        let f = self.func("add_rms_norm_zq8");
21174        let cfg = LaunchConfig {
21175            grid_dim: (nrows as u32, 1, 1),
21176            block_dim: (1024, 1, 1),
21177            shared_mem_bytes: 0,
21178        };
21179        let (nc, ep) = (ncols as i32, eps);
21180        let __s_b = self.gpu.stream();
21181        let mut b = __s_b.launch_builder(&f);
21182        b.arg(a)
21183            .arg(b_in)
21184            .arg(w)
21185            .arg(res)
21186            .arg(z)
21187            .arg(&mut q)
21188            .arg(&mut d)
21189            .arg(&nc)
21190            .arg(&ep);
21191        unsafe {
21192            b.launch(cfg)?;
21193        }
21194        Ok((q, d))
21195    }
21196
21197    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
21198    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
21199    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
21200    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
21201    pub fn gated_rmsnorm_zv(
21202        &self,
21203        o: &CudaSlice<f32>,
21204        w: &CudaSlice<f32>,
21205        z: &cudarc::driver::CudaView<f32>,
21206        dst: &mut CudaSlice<f32>,
21207        ncols: usize,
21208        nrows: usize,
21209        eps: f32,
21210    ) -> Result<(), Box<dyn std::error::Error>> {
21211        let f = self.func("gated_rmsnorm_f32");
21212        let cfg = LaunchConfig {
21213            grid_dim: (nrows as u32, 1, 1),
21214            block_dim: (128, 1, 1),
21215            shared_mem_bytes: 0,
21216        };
21217        let (nc, e) = (ncols as i32, eps);
21218        let __s_b = self.gpu.stream();
21219        let mut b = __s_b.launch_builder(&f);
21220        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
21221        unsafe {
21222            b.launch(cfg)?;
21223        }
21224        Ok(())
21225    }
21226
21227    pub fn gated_rmsnorm_f16out_zv(
21228        &self,
21229        o: &CudaSlice<f32>,
21230        w: &CudaSlice<f32>,
21231        z: &cudarc::driver::CudaView<f32>,
21232        dst: &mut CudaSlice<f32>,
21233        dst16: &mut CudaSlice<u8>,
21234        ncols: usize,
21235        nrows: usize,
21236        eps: f32,
21237    ) -> Result<(), Box<dyn std::error::Error>> {
21238        let f = self.func("gated_rmsnorm_f16out_f32");
21239        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
21240        let cfg = LaunchConfig {
21241            grid_dim: (nrows as u32, 1, 1),
21242            block_dim: (128, 1, 1),
21243            shared_mem_bytes: 0,
21244        };
21245        let (nc, e) = (ncols as i32, eps);
21246        let __s_b = self.gpu.stream();
21247        let mut b = __s_b.launch_builder(&f);
21248        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
21249        unsafe {
21250            b.launch(cfg)?;
21251        }
21252        Ok(())
21253    }
21254
21255    pub fn gated_rmsnorm_q8_1(
21256        &self,
21257        o: &CudaSlice<f32>,
21258        w: &CudaSlice<f32>,
21259        z: &CudaSlice<f32>,
21260        ncols: usize,
21261        nrows: usize,
21262        eps: f32,
21263    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
21264        assert!(ncols % 32 == 0);
21265        let f = self.func("gated_rmsnorm_q8_1");
21266        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
21267        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
21268        let cfg = LaunchConfig {
21269            grid_dim: (nrows as u32, 1, 1),
21270            block_dim: (128, 1, 1),
21271            shared_mem_bytes: 0,
21272        };
21273        let (nc, ep) = (ncols as i32, eps);
21274        let __s_b = self.gpu.stream();
21275        let mut b = __s_b.launch_builder(&f);
21276        b.arg(o)
21277            .arg(w)
21278            .arg(z)
21279            .arg(&mut out_q)
21280            .arg(&mut out_d)
21281            .arg(&nc)
21282            .arg(&ep);
21283        unsafe {
21284            b.launch(cfg)?;
21285        }
21286        Ok((out_q, out_d))
21287    }
21288
21289    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
21290    pub fn transpose(
21291        &self,
21292        inp: &CudaSlice<f32>,
21293        rows: usize,
21294        cols: usize,
21295    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21296        let f = self.func("transpose_f32");
21297        let mut out = self.zeros(rows * cols)?;
21298        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
21299        let (r, c) = (rows as i32, cols as i32);
21300        let __s_b = self.gpu.stream();
21301        let mut b = __s_b.launch_builder(&f);
21302        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
21303        unsafe {
21304            b.launch(cfg)?;
21305        }
21306        Ok(out)
21307    }
21308
21309    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
21310    pub fn repeat_heads(
21311        &self,
21312        inp: &CudaSlice<f32>,
21313        out: &mut CudaSlice<f32>,
21314        head_dim: usize,
21315        n_in: usize,
21316        n_out: usize,
21317        t: usize,
21318    ) -> Result<(), Box<dyn std::error::Error>> {
21319        let f = self.func("repeat_heads_f32");
21320        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
21321        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
21322        let __s_b = self.gpu.stream();
21323        let mut b = __s_b.launch_builder(&f);
21324        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
21325        unsafe {
21326            b.launch(cfg)?;
21327        }
21328        Ok(())
21329    }
21330
21331    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
21332    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
21333    pub fn q_gate_split(
21334        &self,
21335        qf: &CudaSlice<f32>,
21336        q_out: &mut CudaSlice<f32>,
21337        gate_out: &mut CudaSlice<f32>,
21338        head_dim: usize,
21339        n_head: usize,
21340        t: usize,
21341    ) -> Result<(), Box<dyn std::error::Error>> {
21342        let f = self.func("q_gate_split_f32");
21343        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21344        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21345        let __s_b = self.gpu.stream();
21346        let mut b = __s_b.launch_builder(&f);
21347        b.arg(qf)
21348            .arg(q_out)
21349            .arg(gate_out)
21350            .arg(&hd)
21351            .arg(&nh)
21352            .arg(&ti);
21353        unsafe {
21354            b.launch(cfg)?;
21355        }
21356        Ok(())
21357    }
21358
21359    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
21360    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
21361    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
21362    pub fn qkv_to_gdn_repack(
21363        &self,
21364        conv_out: &CudaSlice<f32>,
21365        q_g: &mut CudaSlice<f32>,
21366        k_g: &mut CudaSlice<f32>,
21367        v_g: &mut CudaSlice<f32>,
21368        d_state: usize,
21369        num_v: usize,
21370        num_k: usize,
21371        key_dim: usize,
21372        t: usize,
21373    ) -> Result<(), Box<dyn std::error::Error>> {
21374        let f = self.func("qkv_to_gdn_repack_f32");
21375        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
21376        let (ds, nv, nk, kd, ti) = (
21377            d_state as i32,
21378            num_v as i32,
21379            num_k as i32,
21380            key_dim as i32,
21381            t as i32,
21382        );
21383        let __s_b = self.gpu.stream();
21384        let mut b = __s_b.launch_builder(&f);
21385        b.arg(conv_out)
21386            .arg(q_g)
21387            .arg(k_g)
21388            .arg(v_g)
21389            .arg(&ds)
21390            .arg(&nv)
21391            .arg(&nk)
21392            .arg(&kd)
21393            .arg(&ti);
21394        unsafe {
21395            b.launch(cfg)?;
21396        }
21397        Ok(())
21398    }
21399
21400    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
21401    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
21402    pub fn conv_left_pad(
21403        &self,
21404        src: &CudaSlice<f32>,
21405        dst: &mut CudaSlice<f32>,
21406        conv_dim: usize,
21407        t: usize,
21408        pad: usize,
21409    ) -> Result<(), Box<dyn std::error::Error>> {
21410        let f = self.func("conv_left_pad_f32");
21411        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
21412        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
21413        let __s_b = self.gpu.stream();
21414        let mut b = __s_b.launch_builder(&f);
21415        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
21416        unsafe {
21417            b.launch(cfg)?;
21418        }
21419        Ok(())
21420    }
21421
21422    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
21423    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
21424    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
21425    pub fn conv_assemble_and_roll(
21426        &self,
21427        qkv_col: &CudaSlice<f32>,
21428        conv_state: &mut CudaSlice<f32>,
21429        conv_in: &mut CudaSlice<f32>,
21430        conv_dim: usize,
21431        pad: usize,
21432    ) -> Result<(), Box<dyn std::error::Error>> {
21433        let f = self.func("conv_assemble_and_roll_f32");
21434        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21435        let (cd, p) = (conv_dim as i32, pad as i32);
21436        let __s_b = self.gpu.stream();
21437        let mut b = __s_b.launch_builder(&f);
21438        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
21439        unsafe {
21440            b.launch(cfg)?;
21441        }
21442        Ok(())
21443    }
21444
21445    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
21446    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
21447    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
21448    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
21449    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
21450    pub fn ssm_conv1d_fused_decode(
21451        &self,
21452        qkv_col: &CudaSlice<f32>,
21453        conv_state: &mut CudaSlice<f32>,
21454        w: &CudaSlice<f32>,
21455        conv_out: &mut CudaSlice<f32>,
21456        conv_dim: usize,
21457        d_conv: usize,
21458    ) -> Result<(), Box<dyn std::error::Error>> {
21459        let f = self.func("ssm_conv1d_fused_decode_f32");
21460        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21461        let (cd, dc) = (conv_dim as i32, d_conv as i32);
21462        let __s_b = self.gpu.stream();
21463        let mut b = __s_b.launch_builder(&f);
21464        b.arg(qkv_col)
21465            .arg(conv_state)
21466            .arg(w)
21467            .arg(conv_out)
21468            .arg(&cd)
21469            .arg(&dc);
21470        unsafe {
21471            b.launch(cfg)?;
21472        }
21473        Ok(())
21474    }
21475
21476    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
21477    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
21478    pub fn slice_range(
21479        &self,
21480        src: &CudaSlice<f32>,
21481        start: usize,
21482        len: usize,
21483    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21484        let host = self.gpu.stream().clone_dtoh(src)?;
21485        self.gpu.stream().synchronize()?;
21486        Ok(self.htod(&host[start..start + len])?)
21487    }
21488}
21489
21490#[cfg(test)]
21491mod target_dispatch_tests {
21492    use super::legacy_quant_gemm_allowed;
21493
21494    #[test]
21495    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
21496        // sm_120a native lane
21497        assert!(legacy_quant_gemm_allowed(false, false, false));
21498        assert!(!legacy_quant_gemm_allowed(false, false, true));
21499        // pure portable lane (sm_89): gated
21500        assert!(!legacy_quant_gemm_allowed(true, false, false));
21501        assert!(!legacy_quant_gemm_allowed(true, false, true));
21502        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
21503        assert!(legacy_quant_gemm_allowed(true, true, false));
21504        assert!(!legacy_quant_gemm_allowed(true, true, true));
21505    }
21506
21507    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
21508    #[test]
21509    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
21510        assert!(!legacy_quant_gemm_allowed(
21511            cfg!(memra_portable_cuda),
21512            cfg!(memra_hopper_mma),
21513            false
21514        ));
21515    }
21516
21517    #[cfg(memra_hopper_mma)]
21518    #[test]
21519    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
21520        assert!(legacy_quant_gemm_allowed(
21521            cfg!(memra_portable_cuda),
21522            cfg!(memra_hopper_mma),
21523            false
21524        ));
21525        assert!(super::portable_mma_gated() == false);
21526    }
21527}
21528
21529/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
21530/// inherent methods (inherent methods win name resolution, so no recursion).
21531impl memra_kv::KvDev for Engine {
21532    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21533        Engine::zeros(self, n)
21534    }
21535    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21536        Engine::uninit(self, n)
21537    }
21538    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
21539        Engine::alloc_u8(self, n)
21540    }
21541    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
21542        Engine::htod_i32(self, v)
21543    }
21544    fn clone_dtod(
21545        &self,
21546        src: &CudaSlice<f32>,
21547    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21548        Engine::clone_dtod(self, src)
21549    }
21550    fn copy_into(
21551        &self,
21552        dst: &mut CudaSlice<f32>,
21553        off: usize,
21554        src: &CudaSlice<f32>,
21555        len: usize,
21556    ) -> Result<(), Box<dyn std::error::Error>> {
21557        Engine::copy_into(self, dst, off, src, len)
21558    }
21559    fn set_i32_one(
21560        &self,
21561        d: &mut CudaSlice<i32>,
21562        v: i32,
21563    ) -> Result<(), Box<dyn std::error::Error>> {
21564        Engine::set_i32_one(self, d, v)
21565    }
21566}