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    /// In-graph sampling-event counter bump (spec_sample.cu kernel 5): ctr[0] += 1. The sampled
2943    /// graph-draft chain replays with FIXED kernel args, so the Philox event counter must be
2944    /// DEVICE data — the host seeds it once per round; every replay bumps it before the perturb
2945    /// reads it (counter is data, not state — graph-replay-safe).
2946    pub fn sctr_inc(&self, ctr: &mut CudaSlice<u32>) -> Result<(), Box<dyn std::error::Error>> {
2947        let f = self.func("memra_sctr_inc");
2948        let cfg = LaunchConfig {
2949            grid_dim: (1, 1, 1),
2950            block_dim: (1, 1, 1),
2951            shared_mem_bytes: 0,
2952        };
2953        let __s_b = self.gpu.stream();
2954        let mut b = __s_b.launch_builder(&f);
2955        b.arg(&mut *ctr);
2956        unsafe {
2957            b.launch(cfg)?;
2958        }
2959        Ok(())
2960    }
2961
2962    /// Graph-capturable `gumbel_perturb`: the sampling-event counter comes from DEVICE memory
2963    /// (`ctr[0]`) instead of a host scalar. Identical math to `gumbel_perturb` at
2964    /// stream_pos == ctr[0] (same Philox call, same lane mapping) — the eager and graph sampled
2965    /// chains produce bit-identical perturbations for the same (seed, counter, temp).
2966    pub fn gumbel_perturb_ctr(
2967        &self,
2968        x: &CudaSlice<f32>,
2969        y: &mut CudaSlice<f32>,
2970        n: usize,
2971        seed: u64,
2972        ctr: &CudaSlice<u32>,
2973        temp: f32,
2974    ) -> Result<(), Box<dyn std::error::Error>> {
2975        let f = self.func("gumbel_perturb_ctr_f32");
2976        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
2977        let cfg = LaunchConfig {
2978            grid_dim: (n.div_ceil(256) as u32, 1, 1),
2979            block_dim: (256, 1, 1),
2980            shared_mem_bytes: 0,
2981        };
2982        let __s_b = self.gpu.stream();
2983        let mut b = __s_b.launch_builder(&f);
2984        b.arg(x)
2985            .arg(&mut *y)
2986            .arg(&ni)
2987            .arg(&slo)
2988            .arg(&shi)
2989            .arg(ctr)
2990            .arg(&temp);
2991        unsafe {
2992            b.launch(cfg)?;
2993        }
2994        Ok(())
2995    }
2996
2997    /// out[pair] = softmax_temp(x[rows[pair]])[ids[pair]] for npair (row, id) pairs; rows index
2998    /// into x with `row_stride` f32s per row. temp<=0: out = 1.0 iff id is the row argmax
2999    /// (smallest-index tie-break — matches the argmax-gate contract).
3000    pub fn softmax_gather(
3001        &self,
3002        x: &CudaSlice<f32>,
3003        row_stride: usize,
3004        ids: &CudaSlice<u32>,
3005        rows: &CudaSlice<i32>,
3006        out: &mut CudaSlice<f32>,
3007        n: usize,
3008        npair: usize,
3009        temp: f32,
3010    ) -> Result<(), Box<dyn std::error::Error>> {
3011        let f = self.func("softmax_gather_f32");
3012        let (ni, rs) = (n as i32, row_stride as i64);
3013        let np = npair as i32;
3014        let cfg = LaunchConfig {
3015            grid_dim: (npair as u32, 1, 1),
3016            block_dim: (256, 1, 1),
3017            shared_mem_bytes: 0,
3018        };
3019        let __s_b = self.gpu.stream();
3020        let mut b = __s_b.launch_builder(&f);
3021        b.arg(x)
3022            .arg(&rs)
3023            .arg(ids)
3024            .arg(rows)
3025            .arg(&mut *out)
3026            .arg(&ni)
3027            .arg(&np)
3028            .arg(&temp);
3029        unsafe {
3030            b.launch(cfg)?;
3031        }
3032        Ok(())
3033    }
3034
3035    /// Sample token from norm(max(0, softmax_temp(p) - softmax_temp(q))) (q = None -> plain
3036    /// categorical from softmax_temp(p)). Row stats (max, sumexp at temp) must be precomputed
3037    /// (softmax_gather's pass-1 values; see spec.rs caller). Deterministic fixed-order CDF walk.
3038    pub fn residual_sample(
3039        &self,
3040        p: &CudaSlice<f32>,
3041        q: Option<&CudaSlice<f32>>,
3042        n: usize,
3043        temp: f32,
3044        seed: u64,
3045        stream_pos: u32,
3046        out_tok: &mut CudaSlice<u32>,
3047    ) -> Result<(), Box<dyn std::error::Error>> {
3048        let f = self.func("residual_sample_f32");
3049        let (ni, slo, shi) = (n as i32, (seed & 0xFFFF_FFFF) as u32, (seed >> 32) as u32);
3050        let nth = 1024u32;
3051        let cfg = LaunchConfig {
3052            grid_dim: (1, 1, 1),
3053            block_dim: (nth, 1, 1),
3054            shared_mem_bytes: 0,
3055        };
3056        let has_q: i32 = q.is_some() as i32;
3057        let qbuf = q.unwrap_or(p); // dummy when absent; kernel gates on has_q
3058        let __s_b = self.gpu.stream();
3059        let mut b = __s_b.launch_builder(&f);
3060        b.arg(p)
3061            .arg(qbuf)
3062            .arg(&has_q)
3063            .arg(&ni)
3064            .arg(&temp)
3065            .arg(&slo)
3066            .arg(&shi)
3067            .arg(&stream_pos)
3068            .arg(&mut *out_tok);
3069        unsafe {
3070            b.launch(cfg)?;
3071        }
3072        Ok(())
3073    }
3074
3075    /// Access the shared MoE residency cache (EDGE-1 §B), building it on first use under
3076    /// MEMRA_MOE_CACHE. The closure runs while the lock is held — keep it to lookup/admit/issue, not
3077    /// the GEMM. `max_block_bytes` sizes the slots (largest of gate/up/down). Returns the closure's
3078    /// result. If MEMRA_MOE_CACHE is unset this is never called (the caller checks the env first).
3079    pub fn with_moe_cache<R>(
3080        &self,
3081        max_block_bytes: usize,
3082        f: impl FnOnce(
3083            &mut crate::moe_cache::MoeSlotCache,
3084            &Engine,
3085        ) -> Result<R, Box<dyn std::error::Error>>,
3086    ) -> Result<R, Box<dyn std::error::Error>> {
3087        let mut guard = self.moe_cache.lock().unwrap();
3088        if guard.is_none() {
3089            *guard = Some(crate::moe_cache::MoeSlotCache::new(self, max_block_bytes)?);
3090        }
3091        let cache = guard.as_mut().unwrap();
3092        f(cache, self)
3093    }
3094
3095    /// Freeze the already-built MoE residency set. This never constructs a cache: callers use it
3096    /// only after a real prefill has populated the machine-specific CPU/GPU working set.
3097    pub fn freeze_moe_cache(&self) {
3098        if let Some(cache) = self.moe_cache.lock().unwrap().as_mut() {
3099            cache.freeze();
3100        }
3101    }
3102
3103    /// The current residency set as (layer, proj, ex) triples, or None if no cache was built.
3104    /// Never constructs a cache.
3105    pub fn export_moe_residency(&self) -> Option<Vec<(u16, u8, u16)>> {
3106        self.moe_cache
3107            .lock()
3108            .unwrap()
3109            .as_ref()
3110            .map(crate::moe_cache::MoeSlotCache::export_residency)
3111    }
3112
3113    pub(crate) fn moe_cache_frozen(&self) -> bool {
3114        self.moe_cache
3115            .lock()
3116            .unwrap()
3117            .as_ref()
3118            .is_some_and(crate::moe_cache::MoeSlotCache::is_frozen)
3119    }
3120
3121    /// A frozen heterogeneous CPU/GPU expert split cannot use Hy3's ordinary batched prefill
3122    /// efficiently: T>=PRIME_MIN_T bypasses the CPU backend and transiently rereads every missing
3123    /// expert through the GPU spill path. Replay the short prompt through decode after freezing,
3124    /// while leaving the profiling warmup's established batched behavior untouched.
3125    /// (`pub`: run-gen's #46 batched-prime gate skips itself when generation will take the
3126    /// tokenwise arm anyway.)
3127    pub fn frozen_cpu_experts_prefer_tokenwise_prime(&self) -> bool {
3128        crate::cpu_experts::configured()
3129            && self.moe_cache_frozen()
3130            && std::env::var("MEMRA_CPU_EXPERT_BATCHED_PRIME").as_deref() != Ok("1")
3131    }
3132
3133    /// Install the loaded model's exact retained expert-block inventory before lazy cache build.
3134    pub(crate) fn configure_moe_cache_layout(&self, block_bytes: Vec<usize>) {
3135        assert!(
3136            self.moe_cache.lock().unwrap().is_none(),
3137            "MoE cache layout configured after cache construction"
3138        );
3139        *self.moe_cache_layout.lock().unwrap() = Some(block_bytes);
3140    }
3141
3142    pub(crate) fn moe_cache_layout(&self) -> Option<Vec<usize>> {
3143        self.moe_cache_layout.lock().unwrap().clone()
3144    }
3145
3146    /// True if the MoE residency cache is enabled (MEMRA_MOE_CACHE set).
3147    pub fn moe_cache_enabled() -> bool {
3148        std::env::var("MEMRA_MOE_CACHE").as_deref() != Ok("0")
3149    }
3150
3151    /// Snapshot the MoE cache counters (hits, misses, staged_bytes, n_slots) for the §D.4 PCIe gate.
3152    /// Returns None if the cache was never built (disabled or no MoE forward ran).
3153    pub fn moe_cache_stats(&self) -> Option<(u64, u64, u64, usize)> {
3154        let guard = self.moe_cache.lock().unwrap();
3155        guard
3156            .as_ref()
3157            .map(|c| (c.hits, c.misses, c.staged_bytes, c.n_slots()))
3158    }
3159
3160    /// Experimental CPU expert backend counters: completed layer calls, experts served, and the
3161    /// sum of backend wall nanoseconds. The timer includes explicit disk->RAM fills on cache misses;
3162    /// callers compare a before/after snapshot around a decode window.
3163    pub fn cpu_expert_stats(
3164        &self,
3165    ) -> Option<(u64, u64, u64, u64, u64, u64, u64, u64, u64, u64, u64)> {
3166        crate::cpu_experts::configured().then(crate::cpu_experts::stats)
3167    }
3168
3169    /// Caller-blocked nanoseconds at CPU expert joins. Compare before/after snapshots to measure
3170    /// the backend tail that resident-GPU expert work did not hide.
3171    pub fn cpu_expert_predictor_stats(&self) -> (u64, u64) {
3172        crate::cpu_experts::predictor_stats()
3173    }
3174
3175    pub fn cpu_expert_exposed_wait_ns(&self) -> Option<u64> {
3176        crate::cpu_experts::configured().then(crate::cpu_experts::exposed_wait_ns)
3177    }
3178
3179    /// CPU-routed expert selections grouped by how many of their three projections were already
3180    /// resident in HBM. This makes otherwise-stranded partial residency visible to tuning runs.
3181    pub fn cpu_expert_gpu_residency_stats(&self) -> Option<(u64, u64, u64)> {
3182        crate::cpu_experts::configured().then(crate::cpu_experts::incomplete_gpu_residency_stats)
3183    }
3184
3185    /// Positioned-read proof-backend counters:
3186    /// `(reads, bytes, read_errors, short_reads, mmap_fallbacks, buffer_waits, ring_full)`.
3187    pub fn moe_pread_stats(&self) -> Option<(u64, u64, u64, u64, u64, u64, u64)> {
3188        let guard = self.moe_cache.lock().unwrap();
3189        guard
3190            .as_ref()
3191            .and_then(|cache| cache.pread_stats())
3192            .map(|stats| {
3193                (
3194                    stats.reads,
3195                    stats.bytes,
3196                    stats.read_errors,
3197                    stats.short_reads,
3198                    stats.fallbacks,
3199                    stats.buffer_waits,
3200                    stats.ring_full,
3201                )
3202            })
3203    }
3204
3205    /// Spill configuration values that warned and substituted their documented defaults.
3206    pub fn spill_config_fallbacks(&self) -> u64 {
3207        crate::spill_pread::config_fallbacks()
3208    }
3209
3210    /// Reset the MoE cache perf counters (to separate warmup from steady-state windows).
3211    pub fn moe_cache_reset_counters(&self) {
3212        if let Some(c) = self.moe_cache.lock().unwrap().as_mut() {
3213            c.reset_counters();
3214        }
3215    }
3216
3217    pub fn htod_bytes(&self, v: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3218        Ok(self.gpu.stream().clone_htod(v)?)
3219    }
3220
3221    /// `htod_bytes` with a mapped (uninit) tail pad: the wide-load expert dots read up to 6B
3222    /// past the final q4_0 block through their aligned window — the bytes never reach a
3223    /// result (funnelshift discards them) but must be mapped memory.
3224    pub fn htod_bytes_padded(
3225        &self,
3226        v: &[u8],
3227        pad: usize,
3228    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3229        let mut d = self.alloc_u8_uninit(v.len() + pad)?;
3230        {
3231            let mut view = d.slice_mut(0..v.len());
3232            self.gpu.stream().memcpy_htod(v, &mut view)?;
3233        }
3234        Ok(d)
3235    }
3236
3237    /// Device-to-device copy of `src` into `dst[off..off+len]` (f32). For in-place KV append.
3238    pub fn copy_into(
3239        &self,
3240        dst: &mut CudaSlice<f32>,
3241        off: usize,
3242        src: &CudaSlice<f32>,
3243        len: usize,
3244    ) -> Result<(), Box<dyn std::error::Error>> {
3245        let mut view = dst.slice_mut(off..off + len);
3246        self.gpu
3247            .stream()
3248            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3249        Ok(())
3250    }
3251
3252    /// View a sub-range of a device buffer (for attending over [0..len) of a KV cache).
3253    /// u8 twin of copy_into (D2D byte-range copy at an offset).
3254    pub fn copy_u8_into(
3255        &self,
3256        dst: &mut CudaSlice<u8>,
3257        off: usize,
3258        src: &CudaSlice<u8>,
3259        len: usize,
3260    ) -> Result<(), Box<dyn std::error::Error>> {
3261        let mut view = dst.slice_mut(off..off + len);
3262        self.gpu
3263            .stream()
3264            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3265        Ok(())
3266    }
3267
3268    /// D2D byte-range copy with explicit source and destination offsets.
3269    pub fn copy_u8_range_into(
3270        &self,
3271        dst: &mut CudaSlice<u8>,
3272        dst_off: usize,
3273        src: &CudaSlice<u8>,
3274        src_off: usize,
3275        len: usize,
3276    ) -> Result<(), Box<dyn std::error::Error>> {
3277        let mut dst_view = dst.slice_mut(dst_off..dst_off + len);
3278        self.gpu
3279            .stream()
3280            .memcpy_dtod(&src.slice(src_off..src_off + len), &mut dst_view)?;
3281        Ok(())
3282    }
3283
3284    /// Resolve an absolute append slot to the Step35 SWA layer's physical rows. At wrap, copy
3285    /// only the aligned live prefix through temporary device storage and rebase it at row zero,
3286    /// keeping the audited attention range contiguous without changing its absolute start.
3287    pub fn prepare_kv_append(
3288        &self,
3289        kv: &mut crate::cache::KvLayer,
3290        retain_from: usize,
3291        append_rows: usize,
3292    ) -> Result<usize, Box<dyn std::error::Error>> {
3293        let Some(plan) = kv
3294            .ring
3295            .as_ref()
3296            .map(|ring| ring.append_plan(kv.len, retain_from, append_rows))
3297            .transpose()?
3298        else {
3299            return Ok(kv.len);
3300        };
3301        match plan {
3302            crate::cache::KvRingAppend::Contiguous { write_row } => Ok(write_row),
3303            crate::cache::KvRingAppend::Rebase {
3304                src_row,
3305                keep_rows,
3306                new_base,
3307                write_row,
3308            } => {
3309                if keep_rows > 0 {
3310                    let k_len = keep_rows * kv.k_tok_bytes;
3311                    let v_len = keep_rows * kv.v_tok_bytes;
3312                    let mut k_tmp = self.alloc_u8_uninit(k_len)?;
3313                    let mut v_tmp = self.alloc_u8_uninit(v_len)?;
3314                    self.copy_u8_range_into(&mut k_tmp, 0, &kv.k, src_row * kv.k_tok_bytes, k_len)?;
3315                    self.copy_u8_range_into(&mut v_tmp, 0, &kv.v, src_row * kv.v_tok_bytes, v_len)?;
3316                    self.copy_u8_into(&mut kv.k, 0, &k_tmp, k_len)?;
3317                    self.copy_u8_into(&mut kv.v, 0, &v_tmp, v_len)?;
3318                }
3319                kv.ring.as_mut().unwrap().apply_rebase(new_base);
3320                Ok(write_row)
3321            }
3322        }
3323    }
3324
3325    /// H2D write of `src` into `dst[off..off+src.len()]` (u8). In-place row updates for the
3326    /// adaptive trim head: no realloc, so captured graphs keep their baked addresses.
3327    pub fn htod_u8_into(
3328        &self,
3329        dst: &mut CudaSlice<u8>,
3330        off: usize,
3331        src: &[u8],
3332    ) -> Result<(), Box<dyn std::error::Error>> {
3333        let mut view = dst.slice_mut(off..off + src.len());
3334        self.gpu.stream().memcpy_htod(src, &mut view)?;
3335        Ok(())
3336    }
3337
3338    pub fn view<'a>(&self, b: &'a CudaSlice<f32>, len: usize) -> cudarc::driver::CudaView<'a, f32> {
3339        b.slice(0..len)
3340    }
3341
3342    /// View the first `len` BYTES of a u8 device buffer (quantized KV cache: [0..t_kv*tok_bytes)).
3343    /// Byte-range view (gemma4 R6 window offset into the quantized KV stream).
3344    pub fn view_u8_range<'a>(
3345        &self,
3346        b: &'a CudaSlice<u8>,
3347        start: usize,
3348        end: usize,
3349    ) -> cudarc::driver::CudaView<'a, u8> {
3350        b.slice(start..end)
3351    }
3352    pub fn view_u8<'a>(
3353        &self,
3354        b: &'a CudaSlice<u8>,
3355        len: usize,
3356    ) -> cudarc::driver::CudaView<'a, u8> {
3357        b.slice(0..len)
3358    }
3359
3360    /// Append-quantize ONE token's post-RoPE K (q8_0) and V (q5_1) into the resident byte caches at
3361    /// token index `t` (KVQUANT-PLAN §C). One CTA (one warp) per 32-element block; the kernel writes
3362    /// the f16 scale(s) + packed quants for K and V. k_row/v_row are f32 [kv_dim_k]/[kv_dim_v].
3363    pub fn append_kv_quantized(
3364        &self,
3365        k_row: &CudaSlice<f32>,
3366        v_row: &CudaSlice<f32>,
3367        kc: &mut CudaSlice<u8>,
3368        vc: &mut CudaSlice<u8>,
3369        t: usize,
3370        kv_dim_k: usize,
3371        kv_dim_v: usize,
3372        k_tok_bytes: usize,
3373        v_tok_bytes: usize,
3374        g: bool,
3375    ) -> Result<(), Box<dyn std::error::Error>> {
3376        let f = if g {
3377            self.func_g("append_quantize_kv_q8_0_q5_1")
3378        } else {
3379            self.func("append_quantize_kv_q8_0_q5_1")
3380        };
3381        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3382        let cfg = LaunchConfig {
3383            grid_dim: (nblk, 1, 1),
3384            block_dim: (32, 1, 1),
3385            shared_mem_bytes: 0,
3386        };
3387        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3388        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3389        let __s_b = self.gpu.stream();
3390        let mut b = __s_b.launch_builder(&f);
3391        b.arg(k_row)
3392            .arg(v_row)
3393            .arg(kc)
3394            .arg(vc)
3395            .arg(&ti)
3396            .arg(&kdk)
3397            .arg(&kdv)
3398            .arg(&ktb)
3399            .arg(&vtb);
3400        unsafe {
3401            b.launch(cfg)?;
3402        }
3403        Ok(())
3404    }
3405
3406    /// Device-counter variant of `append_kv_quantized` (CUDA-GRAPH-PLAN Phase 2): the write slot
3407    /// `t` is read from `t_dev[0]` (a resident device i32[1]) instead of a host int arg, so the
3408    /// launch args are FIXED across decode steps (graph-capturable). Identical quant math.
3409    pub fn append_kv_quantized_dc(
3410        &self,
3411        k_row: &CudaSlice<f32>,
3412        v_row: &CudaSlice<f32>,
3413        kc: &mut CudaSlice<u8>,
3414        vc: &mut CudaSlice<u8>,
3415        t_dev: &CudaSlice<i32>,
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 nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3423        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
3424        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3425        // PDL wave-B2: flash-module flavor mirrors the builder path's g flag exactly.
3426        if Self::pdl_on() && Self::pdl_wb_on() {
3427            use cudarc::driver::{DevicePtr, DevicePtrMut};
3428            let s = &self.gpu.stream();
3429            let (pk, _g0) = k_row.device_ptr(s);
3430            let (pv, _g1) = v_row.device_ptr(s);
3431            let (pkc, _g2) = kc.device_ptr_mut(s);
3432            let (pvc, _g3) = vc.device_ptr_mut(s);
3433            let (pt, _g4) = t_dev.device_ptr(s);
3434            let mut ps = [
3435                &pk as *const _ as *mut std::ffi::c_void,
3436                &pv as *const _ as *mut _,
3437                &pkc as *const _ as *mut _,
3438                &pvc as *const _ as *mut _,
3439                &pt as *const _ as *mut _,
3440                &kdk as *const _ as *mut _,
3441                &kdv as *const _ as *mut _,
3442                &ktb as *const _ as *mut _,
3443                &vtb as *const _ as *mut _,
3444            ];
3445            unsafe {
3446                self.launch_pdl_flash(
3447                    g,
3448                    "append_quantize_kv_q8_0_q5_1_dc",
3449                    (nblk, 1, 1),
3450                    (32, 1, 1),
3451                    0,
3452                    &mut ps,
3453                )?;
3454            }
3455            return Ok(());
3456        }
3457        let f = if g {
3458            self.func_g("append_quantize_kv_q8_0_q5_1_dc")
3459        } else {
3460            self.func("append_quantize_kv_q8_0_q5_1_dc")
3461        };
3462        let cfg = LaunchConfig {
3463            grid_dim: (nblk, 1, 1),
3464            block_dim: (32, 1, 1),
3465            shared_mem_bytes: 0,
3466        };
3467        let __s_b = self.gpu.stream();
3468        let mut b = __s_b.launch_builder(&f);
3469        b.arg(k_row)
3470            .arg(v_row)
3471            .arg(kc)
3472            .arg(vc)
3473            .arg(t_dev)
3474            .arg(&kdk)
3475            .arg(&kdv)
3476            .arg(&ktb)
3477            .arg(&vtb);
3478        unsafe {
3479            b.launch(cfg)?;
3480        }
3481        Ok(())
3482    }
3483
3484    /// Append-quantize T token rows in one shot (BATCHED PROMPT PRIME). k_rows/v_rows are
3485    /// token-major [T, kv_dim] post-RoPE f32; rows land at cache slots t0..t0+T. Default = the
3486    /// batched `_rows` kernel: one (nblk, T) launch whose per-(block,token) warp program is the
3487    /// per-token append kernel verbatim -> every written row is BIT-IDENTICAL to T sequential
3488    /// `append_kv_quantized_view` calls (kernel_check pins the bytes). MEMRA_PRIME_APPEND_LOOP=1
3489    /// forces the T-launch per-row loop (the A/B seam that measured the launch overhead).
3490    #[allow(clippy::too_many_arguments)]
3491    pub fn append_kv_quantized_rows(
3492        &self,
3493        k_rows: &CudaSlice<f32>,
3494        v_rows: &CudaSlice<f32>,
3495        kc: &mut CudaSlice<u8>,
3496        vc: &mut CudaSlice<u8>,
3497        t0: usize,
3498        t: usize,
3499        kv_dim_k: usize,
3500        kv_dim_v: usize,
3501        k_tok_bytes: usize,
3502        v_tok_bytes: usize,
3503        g: bool,
3504    ) -> Result<(), Box<dyn std::error::Error>> {
3505        if std::env::var("MEMRA_PRIME_APPEND_LOOP").is_ok() {
3506            for i in 0..t {
3507                let k_row = k_rows.slice(i * kv_dim_k..(i + 1) * kv_dim_k);
3508                let v_row = v_rows.slice(i * kv_dim_v..(i + 1) * kv_dim_v);
3509                self.append_kv_quantized_view(
3510                    &k_row,
3511                    &v_row,
3512                    kc,
3513                    vc,
3514                    t0 + i,
3515                    kv_dim_k,
3516                    kv_dim_v,
3517                    k_tok_bytes,
3518                    v_tok_bytes,
3519                    g,
3520                )?;
3521            }
3522            return Ok(());
3523        }
3524        let f = if g {
3525            self.func_g("append_quantize_kv_q8_0_q5_1_rows")
3526        } else {
3527            self.func("append_quantize_kv_q8_0_q5_1_rows")
3528        };
3529        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3530        let cfg = LaunchConfig {
3531            grid_dim: (nblk, t as u32, 1),
3532            block_dim: (32, 1, 1),
3533            shared_mem_bytes: 0,
3534        };
3535        let (t0i, kdk, kdv) = (t0 as i32, kv_dim_k as i32, kv_dim_v as i32);
3536        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3537        let __s_b = self.gpu.stream();
3538        let mut b = __s_b.launch_builder(&f);
3539        b.arg(k_rows)
3540            .arg(v_rows)
3541            .arg(kc)
3542            .arg(vc)
3543            .arg(&t0i)
3544            .arg(&kdk)
3545            .arg(&kdv)
3546            .arg(&ktb)
3547            .arg(&vtb);
3548        unsafe {
3549            b.launch(cfg)?;
3550        }
3551        Ok(())
3552    }
3553
3554    /// Increment a device i32[1] counter in place (p[0] += 1) via the resident `inc_i32` kernel.
3555    /// Used to advance the device-resident seqlen/pos counters inside the decode-dc path (and,
3556    /// later, inside a captured graph) without a host round-trip.
3557    pub fn inc_seqlen(&self, p: &mut CudaSlice<i32>) -> Result<(), Box<dyn std::error::Error>> {
3558        let f = self.func("inc_i32");
3559        let cfg = LaunchConfig {
3560            grid_dim: (1, 1, 1),
3561            block_dim: (1, 1, 1),
3562            shared_mem_bytes: 0,
3563        };
3564        let __s_b = self.gpu.stream();
3565        let mut b = __s_b.launch_builder(&f);
3566        b.arg(p);
3567        unsafe {
3568            b.launch(cfg)?;
3569        }
3570        Ok(())
3571    }
3572
3573    /// Like `append_kv_quantized` but k_row/v_row are CudaViews (one token's row sliced out of a
3574    /// token-major [T, kv_dim] activation buffer — the MTP verify path appends T tokens).
3575    pub fn append_kv_quantized_view(
3576        &self,
3577        k_row: &cudarc::driver::CudaView<f32>,
3578        v_row: &cudarc::driver::CudaView<f32>,
3579        kc: &mut CudaSlice<u8>,
3580        vc: &mut CudaSlice<u8>,
3581        t: usize,
3582        kv_dim_k: usize,
3583        kv_dim_v: usize,
3584        k_tok_bytes: usize,
3585        v_tok_bytes: usize,
3586        g: bool,
3587    ) -> Result<(), Box<dyn std::error::Error>> {
3588        let f = if g {
3589            self.func_g("append_quantize_kv_q8_0_q5_1")
3590        } else {
3591            self.func("append_quantize_kv_q8_0_q5_1")
3592        };
3593        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
3594        let cfg = LaunchConfig {
3595            grid_dim: (nblk, 1, 1),
3596            block_dim: (32, 1, 1),
3597            shared_mem_bytes: 0,
3598        };
3599        let (ti, kdk, kdv) = (t as i32, kv_dim_k as i32, kv_dim_v as i32);
3600        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
3601        let __s_b = self.gpu.stream();
3602        let mut b = __s_b.launch_builder(&f);
3603        b.arg(k_row)
3604            .arg(v_row)
3605            .arg(kc)
3606            .arg(vc)
3607            .arg(&ti)
3608            .arg(&kdk)
3609            .arg(&kdv)
3610            .arg(&ktb)
3611            .arg(&vtb);
3612        unsafe {
3613            b.launch(cfg)?;
3614        }
3615        Ok(())
3616    }
3617
3618    /// Device-to-device copy of a CudaView `src` into `dst[off..off+len]` (f32). Like `copy_into`
3619    /// but the source is a sub-view (e.g. one column of a token-major activation buffer).
3620    pub fn copy_view_into(
3621        &self,
3622        dst: &mut CudaSlice<f32>,
3623        off: usize,
3624        src: &cudarc::driver::CudaView<f32>,
3625        len: usize,
3626    ) -> Result<(), Box<dyn std::error::Error>> {
3627        let mut view = dst.slice_mut(off..off + len);
3628        self.gpu
3629            .stream()
3630            .memcpy_dtod(&src.slice(0..len), &mut view)?;
3631        Ok(())
3632    }
3633
3634    /// Real device-to-device COPY of `src` into a freshly allocated buffer (NOT an Arc clone).
3635    /// Used for cache snapshots (MTP-PLAN §D.4): `CudaSlice::clone()` only bumps a refcount and
3636    /// would alias the live buffer; this allocs new device memory and memcpy_dtod's the contents.
3637    pub fn clone_dtod(
3638        &self,
3639        src: &CudaSlice<f32>,
3640    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3641        let mut dst = self.gpu.stream().alloc_zeros::<f32>(src.len())?;
3642        self.gpu.stream().memcpy_dtod(src, &mut dst)?;
3643        Ok(dst)
3644    }
3645
3646    /// D2D row extraction: copy a view (e.g. one row of a [B, n] batch buffer) into `dst`.
3647    /// Stream-ordered, async — decode_batch's per-sequence row plumbing.
3648    pub fn dtod_copy_view(
3649        &self,
3650        src: &cudarc::driver::CudaView<f32>,
3651        dst: &mut CudaSlice<f32>,
3652    ) -> Result<(), Box<dyn std::error::Error>> {
3653        self.gpu.stream().memcpy_dtod(src, dst)?;
3654        Ok(())
3655    }
3656
3657    /// D2D i8 twin of `dtod_copy_view` (q8_1 activation rows).
3658    pub fn dtod_copy_view_i8(
3659        &self,
3660        src: &cudarc::driver::CudaView<i8>,
3661        dst: &mut CudaSlice<i8>,
3662    ) -> Result<(), Box<dyn std::error::Error>> {
3663        self.gpu.stream().memcpy_dtod(src, dst)?;
3664        Ok(())
3665    }
3666
3667    /// D2D row placement: copy `src` into `dst[offset .. offset+src.len()]`.
3668    pub fn dtod_copy_into(
3669        &self,
3670        src: &CudaSlice<f32>,
3671        dst: &mut CudaSlice<f32>,
3672        offset: usize,
3673    ) -> Result<(), Box<dyn std::error::Error>> {
3674        let n = src.len();
3675        let mut dv = dst.slice_mut(offset..offset + n);
3676        self.gpu.stream().memcpy_dtod(src, &mut dv)?;
3677        Ok(())
3678    }
3679
3680    /// Uninitialized i8 device buffer (decode_batch q8_1 row scratch).
3681    pub fn uninit_i8(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
3682        self.alloc_uninit::<i8>(n)
3683    }
3684
3685    /// Resident-quantized linear (Stage-A: f32 dequant-in-kernel). y[m,out]=x[m,in]@W[out,in]^T.
3686    pub fn qmatvec(
3687        &self,
3688        w: &CudaSlice<u8>,
3689        x: &CudaSlice<f32>,
3690        m: usize,
3691        in_f: usize,
3692        out_f: usize,
3693        qtype: i32,
3694        row_bytes: usize,
3695    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
3696        let f = self.func("qmatvec_f32");
3697        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
3698        let cfg = LaunchConfig {
3699            grid_dim: (out_f as u32, m as u32, 1),
3700            block_dim: (256, 1, 1),
3701            shared_mem_bytes: 0,
3702        };
3703        let (inf, outf, mi, qt, rb) =
3704            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
3705        let __s_b = self.gpu.stream();
3706        let mut b = __s_b.launch_builder(&f);
3707        b.arg(w)
3708            .arg(x)
3709            .arg(&mut y)
3710            .arg(&inf)
3711            .arg(&outf)
3712            .arg(&mi)
3713            .arg(&qt)
3714            .arg(&rb);
3715        unsafe {
3716            b.launch(cfg)?;
3717        }
3718        Ok(y)
3719    }
3720
3721    /// Allocate a reusable u8 GPU scratch buffer (for staged expert weights).
3722    pub fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3723        let s = self.gpu.stream().alloc_zeros::<u8>(n)?;
3724        self.keep_if_capturing(&s);
3725        Ok(s)
3726    }
3727
3728    /// Uninitialized u8 scratch — skips alloc_zeros' memset. ONLY for staging buffers whose read
3729    /// range is fully overwritten by a stage_expert H2D before any kernel reads it (LAUNCH-STRUCTURE
3730    /// STAGE 2: the per-layer MoE scratch trio was 3 dead ~1MB memsets per layer per decode token).
3731    pub fn alloc_u8_uninit(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
3732        let s = unsafe { self.gpu.stream().alloc::<u8>(n)? };
3733        self.keep_if_capturing(&s);
3734        Ok(s)
3735    }
3736
3737    /// Zero a SUB-RANGE of an f32 buffer (CudaViewMut) — the row-sized memset the moe_out
3738    /// memset-elision uses for tokens that fall off the gdec fast path (LAUNCH-STRUCTURE STAGE 2).
3739    pub fn memset_zeros_view(
3740        &self,
3741        dst: &mut cudarc::driver::CudaViewMut<f32>,
3742    ) -> Result<(), Box<dyn std::error::Error>> {
3743        self.gpu.stream().memset_zeros(dst)?;
3744        Ok(())
3745    }
3746
3747    /// EDGE-1 staging: copy `host_bytes` (a sub-slice of a HostExps buffer) into `scratch`
3748    /// at byte offset `off` (async H2D on the default stream). Length is host_bytes.len().
3749    /// The qmatvec_view that reads `scratch[off..]` is enqueued on the SAME stream after this,
3750    /// so ordering is guaranteed without an explicit sync (Stage-1; Stage-2 prefetch on a 2nd
3751    /// stream would require an event).
3752    pub fn stage_expert(
3753        &self,
3754        host_bytes: &[u8],
3755        scratch: &mut CudaSlice<u8>,
3756        off: usize,
3757    ) -> Result<(), Box<dyn std::error::Error>> {
3758        let mut dst = scratch.slice_mut(off..off + host_bytes.len()); // CudaViewMut<u8>
3759        self.gpu.stream().memcpy_htod(host_bytes, &mut dst)?; // accepts &[u8] HostSlice src
3760        Ok(())
3761    }
3762
3763    /// EDGE-1 §A: fused MoE router. `logits` is the router output [t, n_expert] (device, f32, the
3764    /// `gate_inp @ z` result). Returns (sel_idx [t, n_used] i32, sel_w [t, n_used] f32): the top-k
3765    /// expert ids (DESC by prob, ascending-index tiebreak) and renormalized weights. Replaces the
3766    /// host dtoh + softmax-256 + stable DESC top-8 sort + renorm (hybrid_forward.rs ~281-298).
3767    /// One CTA per token row, 256 threads (one per expert).
3768    pub fn moe_router_topk(
3769        &self,
3770        logits: &CudaSlice<f32>,
3771        t: usize,
3772        n_expert: usize,
3773        n_used: usize,
3774    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3775        let f = self.func("moe_router_topk_f32");
3776        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?; // kernel fully overwrites
3777        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?; // kernel fully overwrites
3778        let cfg = LaunchConfig {
3779            grid_dim: (t as u32, 1, 1),
3780            block_dim: (n_expert as u32, 1, 1),
3781            shared_mem_bytes: 0,
3782        };
3783        let (ne, nu) = (n_expert as i32, n_used as i32);
3784        let __s_b = self.gpu.stream();
3785        let mut b = __s_b.launch_builder(&f);
3786        b.arg(logits)
3787            .arg(&mut sel_idx)
3788            .arg(&mut sel_w)
3789            .arg(&ne)
3790            .arg(&nu);
3791        unsafe {
3792            b.launch(cfg)?;
3793        }
3794        Ok((sel_idx, sel_w))
3795    }
3796
3797    /// gemma4 twin: per-expert output scale folded into the topk renorm write (replaces the
3798    /// separate moe_w_exscale launch; value chain identical: (w/ws) * s[sel]).
3799    pub fn moe_router_topk_scaled(
3800        &self,
3801        logits: &CudaSlice<f32>,
3802        t: usize,
3803        n_expert: usize,
3804        n_used: usize,
3805        ex_scale: &CudaSlice<f32>,
3806    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3807        // barrier-lean v2 twin (per-warp top-k + one-warp merge) FALSIFIED 2026-07-14:
3808        // bit-identical streams but −1.4% (26B plain N=3 interleaved) — at t=1 the grid is
3809        // ONE block, so the 6.6us is launch/dependency overhead, not the barrier chain;
3810        // fewer barriers bought nothing and the merge structure cost. jsonl is the record.
3811        let f = self.func("moe_router_topk_scaled_f32");
3812        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3813        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3814        let cfg = LaunchConfig {
3815            grid_dim: (t as u32, 1, 1),
3816            block_dim: (n_expert as u32, 1, 1),
3817            shared_mem_bytes: 0,
3818        };
3819        let (ne, nu) = (n_expert as i32, n_used as i32);
3820        let __s_b = self.gpu.stream();
3821        let mut b = __s_b.launch_builder(&f);
3822        b.arg(logits)
3823            .arg(&mut sel_idx)
3824            .arg(&mut sel_w)
3825            .arg(&ne)
3826            .arg(&nu)
3827            .arg(ex_scale);
3828        unsafe {
3829            b.launch(cfg)?;
3830        }
3831        Ok((sel_idx, sel_w))
3832    }
3833
3834    /// LAUNCH-STRUCTURE STAGE 1 (2026-07-05): fused router + SINGLE-SYNC host readback. The old
3835    /// MEMRA_FUSED_ROUTER path lost 2% at t=1 because it paid TWO full stream syncs (dtoh_i32 then
3836    /// dtoh, each = clone_dtoh + synchronize) + two alloc_zeros memsets per MoE layer, where the
3837    /// host route pays ONE sync on the 1KB logits dtoh. This variant: uninit outputs (kernel fully
3838    /// overwrites), both DtoH copies issued ASYNC into a persistent PINNED host staging buffer
3839    /// (flags=0 — cacheable, NOT cudarc's WRITECOMBINED default, so the host-side reads of sel/w
3840    /// stay cached), then ONE synchronize. Numerics identical to `moe_router_topk` (same kernel).
3841    pub fn moe_router_topk_host(
3842        &self,
3843        logits: &CudaSlice<f32>,
3844        t: usize,
3845        n_expert: usize,
3846        n_used: usize,
3847    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3848        let f = self.func("moe_router_topk_f32");
3849        let n = t * n_used;
3850        let mut sel_idx = self.alloc_uninit::<i32>(n)?;
3851        let mut sel_w = self.alloc_uninit::<f32>(n)?;
3852        let cfg = LaunchConfig {
3853            grid_dim: (t as u32, 1, 1),
3854            block_dim: (n_expert as u32, 1, 1),
3855            shared_mem_bytes: 0,
3856        };
3857        let (ne, nu) = (n_expert as i32, n_used as i32);
3858        let __s_b = self.gpu.stream();
3859        let mut b = __s_b.launch_builder(&f);
3860        b.arg(logits)
3861            .arg(&mut sel_idx)
3862            .arg(&mut sel_w)
3863            .arg(&ne)
3864            .arg(&nu);
3865        unsafe {
3866            b.launch(cfg)?;
3867        }
3868        // single-sync readback: sel (i32) at offset 0, w (f32) at offset n*4 of the pinned stage.
3869        let bytes = n * 8;
3870        let mut guard = self.router_stage.lock().unwrap();
3871        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
3872            *guard = Some(PinnedStage::new(bytes.max(4096))?);
3873        }
3874        let stage = guard.as_mut().unwrap();
3875        let (si, sw) = unsafe {
3876            (
3877                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
3878                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
3879            )
3880        };
3881        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?; // async (pinned dst)
3882        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?; // async (pinned dst)
3883        self.gpu.stream().synchronize()?; // ONE sync for both
3884        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
3885    }
3886
3887    /// Device sigmoid router for Step-3.7 / DeepSeek-V3-class MoEs. `correction_bias` is added
3888    /// only to the top-k key; returned weights use the un-biased sigmoid score. `active` masks
3889    /// original expert ids before top-k. Exact key ties choose the smaller original id.
3890    #[allow(clippy::too_many_arguments)]
3891    pub fn moe_router_sigmoid_topk(
3892        &self,
3893        logits: &CudaSlice<f32>,
3894        t: usize,
3895        n_expert: usize,
3896        n_used: usize,
3897        active_count: usize,
3898        correction_bias: &CudaSlice<f32>,
3899        active: &CudaSlice<u8>,
3900        scaling_factor: f32,
3901        route_norm: bool,
3902    ) -> Result<(CudaSlice<i32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
3903        crate::sigrouter_contract::validate_active_count(n_used, active_count)?;
3904        if n_expert == 0 || n_expert > 1024 || n_used == 0 || n_used > n_expert {
3905            return Err(format!(
3906                "sigmoid router shape unsupported: n_expert={n_expert}, n_used={n_used}",
3907            )
3908            .into());
3909        }
3910        if logits.len() < t * n_expert
3911            || correction_bias.len() != n_expert
3912            || active.len() != n_expert
3913        {
3914            return Err(format!(
3915                "sigmoid router buffer mismatch: logits={} bias={} active={} expected logits>={} row={}",
3916                logits.len(), correction_bias.len(), active.len(), t * n_expert, n_expert,
3917            ).into());
3918        }
3919        let f = self.func("moe_router_sigmoid_topk_f32");
3920        let mut sel_idx = self.alloc_uninit::<i32>(t * n_used)?;
3921        let mut sel_w = self.alloc_uninit::<f32>(t * n_used)?;
3922        let threads = n_expert.div_ceil(32) * 32;
3923        let cfg = LaunchConfig {
3924            grid_dim: (t as u32, 1, 1),
3925            block_dim: (threads as u32, 1, 1),
3926            shared_mem_bytes: 0,
3927        };
3928        let (ne, nu, rn) = (n_expert as i32, n_used as i32, i32::from(route_norm));
3929        let __s_b = self.gpu.stream();
3930        let mut b = __s_b.launch_builder(&f);
3931        b.arg(logits)
3932            .arg(correction_bias)
3933            .arg(active)
3934            .arg(&mut sel_idx)
3935            .arg(&mut sel_w)
3936            .arg(&ne)
3937            .arg(&nu)
3938            .arg(&scaling_factor)
3939            .arg(&rn);
3940        unsafe {
3941            b.launch(cfg)?;
3942        }
3943        Ok((sel_idx, sel_w))
3944    }
3945
3946    /// Single-sync pinned readback twin of `moe_router_sigmoid_topk`. This preserves the existing
3947    /// grouped/staged dispatch contract while replacing the full-logit DtoH plus host sigmoid/sort.
3948    #[allow(clippy::too_many_arguments)]
3949    pub fn moe_router_sigmoid_topk_host(
3950        &self,
3951        logits: &CudaSlice<f32>,
3952        t: usize,
3953        n_expert: usize,
3954        n_used: usize,
3955        active_count: usize,
3956        correction_bias: &CudaSlice<f32>,
3957        active: &CudaSlice<u8>,
3958        scaling_factor: f32,
3959        route_norm: bool,
3960    ) -> Result<(Vec<u32>, Vec<f32>), Box<dyn std::error::Error>> {
3961        let (sel_idx, sel_w) = self.moe_router_sigmoid_topk(
3962            logits,
3963            t,
3964            n_expert,
3965            n_used,
3966            active_count,
3967            correction_bias,
3968            active,
3969            scaling_factor,
3970            route_norm,
3971        )?;
3972        let n = t * n_used;
3973        let bytes = n * 8;
3974        let mut guard = self.router_stage.lock().unwrap();
3975        if guard.as_ref().map(|p| p.cap < bytes).unwrap_or(true) {
3976            *guard = Some(PinnedStage::new(bytes.max(4096))?);
3977        }
3978        let stage = guard.as_mut().unwrap();
3979        let (si, sw) = unsafe {
3980            (
3981                std::slice::from_raw_parts_mut(stage.ptr as *mut i32, n),
3982                std::slice::from_raw_parts_mut(stage.ptr.add(n * 4) as *mut f32, n),
3983            )
3984        };
3985        self.gpu.stream().memcpy_dtoh(&sel_idx, si)?;
3986        self.gpu.stream().memcpy_dtoh(&sel_w, sw)?;
3987        self.gpu.stream().synchronize()?;
3988        Ok((si.iter().map(|&i| i as u32).collect(), sw.to_vec()))
3989    }
3990
3991    /// EDGE-1 §C.2: async H2D of `host_bytes` into `scratch[off..]` on the COPY stream, returning a
3992    /// recorded event the compute stream can `wait` on before the dependent GEMM. Used for in-token
3993    /// expert prefetch (pipeline by one). `host_bytes` should be pinned for a true DMA (§C.1).
3994    pub fn stage_expert_async(
3995        &self,
3996        host_bytes: &[u8],
3997        scratch: &mut CudaSlice<u8>,
3998        off: usize,
3999    ) -> Result<cudarc::driver::CudaEvent, Box<dyn std::error::Error>> {
4000        let mut dst = scratch.slice_mut(off..off + host_bytes.len());
4001        self.copy_stream.memcpy_htod(host_bytes, &mut dst)?;
4002        Ok(self.copy_stream.record_event(None)?)
4003    }
4004
4005    /// Make the compute stream wait for an async copy event (the consumer side of `stage_expert_async`).
4006    pub fn compute_wait(
4007        &self,
4008        ev: &cudarc::driver::CudaEvent,
4009    ) -> Result<(), Box<dyn std::error::Error>> {
4010        self.gpu.stream().wait(ev)?;
4011        Ok(())
4012    }
4013
4014    /// qmatvec over a byte sub-range of a (resident/scratch) CudaSlice<u8> holding ONE expert
4015    /// matrix. x is a CudaView<f32> (a sliced row of z, or a sliced activation). Reuses the
4016    /// validated qmatvec_f32 dequant path (NOT a fast path — the correctness gate). The
4017    /// CudaView base+offset pointer is honored by the launch arg.
4018    pub fn qmatvec_view(
4019        &self,
4020        w: &CudaSlice<u8>,
4021        range: std::ops::Range<usize>,
4022        x: &cudarc::driver::CudaView<f32>,
4023        m: usize,
4024        in_f: usize,
4025        out_f: usize,
4026        qtype: i32,
4027        row_bytes: usize,
4028    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4029        let f = self.func("qmatvec_f32");
4030        let wv = w.slice(range); // CudaView<u8>, offset honored
4031        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
4032        let cfg = LaunchConfig {
4033            grid_dim: (out_f as u32, m as u32, 1),
4034            block_dim: (256, 1, 1),
4035            shared_mem_bytes: 0,
4036        };
4037        let (inf, outf, mi, qt, rb) =
4038            (in_f as i32, out_f as i32, m as i32, qtype, row_bytes as i64);
4039        let __s_b = self.gpu.stream();
4040        let mut b = __s_b.launch_builder(&f);
4041        b.arg(&wv)
4042            .arg(x)
4043            .arg(&mut y)
4044            .arg(&inf)
4045            .arg(&outf)
4046            .arg(&mi)
4047            .arg(&qt)
4048            .arg(&rb);
4049        unsafe {
4050            b.launch(cfg)?;
4051        }
4052        Ok(y)
4053    }
4054
4055    /// STAGE-2 GROUPED DECODE (2026-07-04): one MoE layer's gate+up+SiLU for all `n_used` routed
4056    /// experts of ONE token in ONE launch (replaces 8x qmatvec(gate) + 8x qmatvec(up) + 8x
4057    /// silu_mul = 24 launches). `gp`/`up` are the 8 expert weight-block device pointers (SLRU
4058    /// cache slots — fixed-address, stable for the launch). Returns act [n_used, n_ff].
4059    /// BIT-IDENTICAL to the sequential chain: each dot reproduces qmatvec_f32's exact 256-thread
4060    /// reduction; the SiLU epilogue is silu_mul_f32's exact expression (see kernel header).
4061    #[allow(clippy::too_many_arguments)]
4062    /// dp4a q8 twins (MoE expert dp4a arc, 2026-07-06): same contract as the _f32 versions but
4063    /// consume a PRE-QUANTIZED q8_1 activation. FP-order differs from _f32 (int dot + warp tree)
4064    /// — the argmax/stream-identity battery arbitrates; MEMRA_MOE_Q8=0 restores f32.
4065    pub fn moe_gate_up_silu8_q8(
4066        &self,
4067        gp: WPtr8,
4068        up: WPtr8,
4069        aq: &CudaSlice<i8>,
4070        ad: &CudaSlice<f32>,
4071        in_f: usize,
4072        n_ff: usize,
4073        n_used: usize,
4074        qt_g: i32,
4075        qt_u: i32,
4076        rb_g: usize,
4077        rb_u: usize,
4078    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4079        let f = self.func("moe_gate_up_silu8_q8");
4080        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4081        let cfg = LaunchConfig {
4082            grid_dim: (n_ff as u32, n_used as u32, 1),
4083            block_dim: (32, 1, 1),
4084            shared_mem_bytes: 0,
4085        };
4086        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4087        let __s_b = self.gpu.stream();
4088        let mut b = __s_b.launch_builder(&f);
4089        b.arg(&gp)
4090            .arg(&up)
4091            .arg(aq)
4092            .arg(ad)
4093            .arg(&mut act)
4094            .arg(&inf)
4095            .arg(&nff)
4096            .arg(&qt_g)
4097            .arg(&qt_u)
4098            .arg(&rbg)
4099            .arg(&rbu);
4100        unsafe {
4101            b.launch(cfg)?;
4102        }
4103        Ok(act)
4104    }
4105
4106    #[allow(clippy::too_many_arguments)]
4107    pub fn moe_down8_fma_q8(
4108        &self,
4109        dp: WPtr8,
4110        w: F32x8,
4111        aq2: &CudaSlice<i8>,
4112        ad2: &CudaSlice<f32>,
4113        dst: &mut cudarc::driver::CudaViewMut<f32>,
4114        in_f: usize,
4115        out_f: usize,
4116        n_used: usize,
4117        qt: i32,
4118        rb: usize,
4119    ) -> Result<(), Box<dyn std::error::Error>> {
4120        let f = self.func("moe_down8_fma_q8");
4121        let cfg = LaunchConfig {
4122            grid_dim: (out_f as u32, 1, 1),
4123            block_dim: (32, 1, 1),
4124            shared_mem_bytes: 0,
4125        };
4126        let (inf, outf, nu, rbi) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4127        let __s_b = self.gpu.stream();
4128        let mut b = __s_b.launch_builder(&f);
4129        b.arg(&dp)
4130            .arg(&w)
4131            .arg(aq2)
4132            .arg(ad2)
4133            .arg(dst)
4134            .arg(&inf)
4135            .arg(&outf)
4136            .arg(&nu)
4137            .arg(&qt)
4138            .arg(&rbi);
4139        unsafe {
4140            b.launch(cfg)?;
4141        }
4142        Ok(())
4143    }
4144
4145    /// q8 sequential expert matvec (staged path twin of qmatvec_view for IQ3_S/IQ4_XS).
4146    pub fn qmatvec_expert_q8(
4147        &self,
4148        w: &CudaSlice<u8>,
4149        range: std::ops::Range<usize>,
4150        aq: &CudaSlice<i8>,
4151        ad: &CudaSlice<f32>,
4152        m: usize,
4153        in_f: usize,
4154        out_f: usize,
4155        qtype: i32,
4156        row_bytes: usize,
4157    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4158        let f = self.func("qmatvec_expert_q8");
4159        let wv = w.slice(range);
4160        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
4161        const ROWS: u32 = 4; // MEMRA_MMVQ_ROWS
4162        let cfg = LaunchConfig {
4163            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, m as u32, 1),
4164            block_dim: (32, ROWS, 1),
4165            shared_mem_bytes: 0,
4166        };
4167        let (inf, outf, mi, rbi) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
4168        let __s_b = self.gpu.stream();
4169        let mut b = __s_b.launch_builder(&f);
4170        b.arg(&wv)
4171            .arg(aq)
4172            .arg(ad)
4173            .arg(&mut y)
4174            .arg(&inf)
4175            .arg(&outf)
4176            .arg(&mi)
4177            .arg(&qtype)
4178            .arg(&rbi);
4179        unsafe {
4180            b.launch(cfg)?;
4181        }
4182        Ok(y)
4183    }
4184
4185    pub fn moe_gate_up_silu8(
4186        &self,
4187        gp: WPtr8,
4188        up: WPtr8,
4189        x: &cudarc::driver::CudaView<f32>,
4190        in_f: usize,
4191        n_ff: usize,
4192        n_used: usize,
4193        qt_g: i32,
4194        qt_u: i32,
4195        rb_g: usize,
4196        rb_u: usize,
4197    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4198        let f = self.func("moe_gate_up_silu8_f32");
4199        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
4200        let cfg = LaunchConfig {
4201            grid_dim: (n_ff as u32, n_used as u32, 1),
4202            block_dim: (256, 1, 1),
4203            shared_mem_bytes: 0,
4204        };
4205        let (inf, nff, rbg, rbu) = (in_f as i32, n_ff as i32, rb_g as i64, rb_u as i64);
4206        let __s_b = self.gpu.stream();
4207        let mut b = __s_b.launch_builder(&f);
4208        b.arg(&gp)
4209            .arg(&up)
4210            .arg(x)
4211            .arg(&mut act)
4212            .arg(&inf)
4213            .arg(&nff)
4214            .arg(&qt_g)
4215            .arg(&qt_u)
4216            .arg(&rbg)
4217            .arg(&rbu);
4218        unsafe {
4219            b.launch(cfg)?;
4220        }
4221        Ok(act)
4222    }
4223
4224    /// STAGE-2 GROUPED DECODE: one MoE layer's down-proj + weighted accumulation for all `n_used`
4225    /// routed experts in ONE launch (replaces 8x qmatvec(down) + 8x axpy = 16 launches), writing
4226    /// the token's moe_out row DIRECTLY (`dst` is the zeroed row; the in-kernel slot-ordered
4227    /// __fmaf_rn chain starting at 0.0f reproduces the sequential axpy_f32 accumulation into the
4228    /// zeroed row bit-for-bit — the A2 byte-identity scheme at m=1).
4229    #[allow(clippy::too_many_arguments)]
4230    pub fn moe_down8_fma_into(
4231        &self,
4232        dp: WPtr8,
4233        w: F32x8,
4234        act: &CudaSlice<f32>,
4235        dst: &mut cudarc::driver::CudaViewMut<f32>,
4236        in_f: usize,
4237        out_f: usize,
4238        n_used: usize,
4239        qt: i32,
4240        rb: usize,
4241    ) -> Result<(), Box<dyn std::error::Error>> {
4242        let f = self.func("moe_down8_fma_f32");
4243        let cfg = LaunchConfig {
4244            grid_dim: (out_f as u32, 1, 1),
4245            block_dim: (256, 1, 1),
4246            shared_mem_bytes: 0,
4247        };
4248        let (inf, outf, nu, rbv) = (in_f as i32, out_f as i32, n_used as i32, rb as i64);
4249        let __s_b = self.gpu.stream();
4250        let mut b = __s_b.launch_builder(&f);
4251        b.arg(&dp)
4252            .arg(&w)
4253            .arg(act)
4254            .arg(dst)
4255            .arg(&inf)
4256            .arg(&outf)
4257            .arg(&nu)
4258            .arg(&qt)
4259            .arg(&rbv);
4260        unsafe {
4261            b.launch(cfg)?;
4262        }
4263        Ok(())
4264    }
4265
4266    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_gate_up_silu8` for FULLY-RESIDENT
4267    /// layers. The expert ids come from the router kernel's DEVICE `sel` output (no DtoH) and the
4268    /// weight pointers from the per-layer device table `[3, n_expert]` of slot base addresses.
4269    /// BIT-IDENTICAL math (same grid/block/reduction; only the pointer/id source differs).
4270    #[allow(clippy::too_many_arguments)]
4271    /// dp4a q8 twin of the _dev pair (resident-experts arc).
4272    ///
4273    /// GEOMETRY VARIANTS (multirow/occupancy arc 2026-07-05): all outputs are BIT-IDENTICAL to
4274    /// the base one-warp-per-(row,slot) kernel (same expert_dot_g g-order + warp tree per row;
4275    /// down's FMA chain stays slot-ordered serial). Seams:
4276    ///   MEMRA_MOE_DEVQ8_GU   = 0(base) | 1 | 2 | 4 -> _r{1,2,4} multirow twin (RPW rows/warp)
4277    ///                       | s2 (gate/up warp split) | s2z (s2 + WPB rows packed per block)
4278    ///                       | gs4 (gate/up x low/high-group 4-warp split, nsb==64 only)
4279    ///                       | u64 (nsb==64 unrolled ILP twin, geometry unchanged)
4280    ///   MEMRA_MOE_DEVQ8_WPB  = warps per block for _r twins / z-rows for s2z (default 4)
4281    ///   MEMRA_MOE_DEVQ8_DOWN = auto(default: w8h2 when in_f==512 & n_used<=8 — measured +3.8%
4282    ///                       decode on 35B/G7e) | 0 (base one-warp serial-slot) | 1 | 2 | 4 ->
4283    ///                       _w8r{1,2,4} slot-parallel twin | h2 (half-warp dual-row, nsb==16
4284    ///                       only) | w8h2 (h2 x slot-parallel)
4285    #[allow(clippy::too_many_arguments)]
4286    /// MoE PREFILL pair-batch matvec: one launch covers all (token,expert) pairs for one proj.
4287    #[allow(clippy::too_many_arguments)]
4288    pub fn moe_pairs_matvec_q8(
4289        &self,
4290        table: &CudaSlice<u64>,
4291        proj: i32,
4292        pair_tok: &CudaSlice<i32>,
4293        pair_ex: &CudaSlice<i32>,
4294        aq: &CudaSlice<i8>,
4295        ad: &CudaSlice<f32>,
4296        in_f: usize,
4297        out_f: usize,
4298        n_expert: usize,
4299        n_pairs: usize,
4300        qtype: i32,
4301        row_bytes: usize,
4302    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4303        let f = self.func("moe_pairs_matvec_q8");
4304        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4305        const ROWS: u32 = 4;
4306        let cfg = LaunchConfig {
4307            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_pairs as u32, 1),
4308            block_dim: (32, ROWS, 1),
4309            shared_mem_bytes: 0,
4310        };
4311        let (inf, outf, ne, np, rbi) = (
4312            in_f as i32,
4313            out_f as i32,
4314            n_expert as i32,
4315            n_pairs as i32,
4316            row_bytes as i64,
4317        );
4318        let __s_b = self.gpu.stream();
4319        let mut b = __s_b.launch_builder(&f);
4320        b.arg(table)
4321            .arg(&proj)
4322            .arg(pair_tok)
4323            .arg(pair_ex)
4324            .arg(aq)
4325            .arg(ad)
4326            .arg(&mut y)
4327            .arg(&inf)
4328            .arg(&outf)
4329            .arg(&ne)
4330            .arg(&np)
4331            .arg(&qtype)
4332            .arg(&rbi);
4333        unsafe {
4334            b.launch(cfg)?;
4335        }
4336        Ok(y)
4337    }
4338
4339    /// Expert-major pair matvec (weight-reuse across each expert's token group).
4340    #[allow(clippy::too_many_arguments)]
4341    pub fn moe_pairs_matvec_q8_em(
4342        &self,
4343        table: &CudaSlice<u64>,
4344        proj: i32,
4345        ex_ids: &CudaSlice<i32>,
4346        ex_off: &CudaSlice<i32>,
4347        ex_pairs: &CudaSlice<i32>,
4348        pair_tok: &CudaSlice<i32>,
4349        aq: &CudaSlice<i8>,
4350        ad: &CudaSlice<f32>,
4351        in_f: usize,
4352        out_f: usize,
4353        n_expert: usize,
4354        n_active: usize,
4355        n_pairs: usize,
4356        qtype: i32,
4357        row_bytes: usize,
4358    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4359        let f = self.func("moe_pairs_matvec_q8_em");
4360        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4361        const ROWS: u32 = 4;
4362        let cfg = LaunchConfig {
4363            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4364            block_dim: (32, ROWS, 1),
4365            shared_mem_bytes: 0,
4366        };
4367        let (inf, outf, ne, na, rbi) = (
4368            in_f as i32,
4369            out_f as i32,
4370            n_expert as i32,
4371            n_active as i32,
4372            row_bytes as i64,
4373        );
4374        let __s_b = self.gpu.stream();
4375        let mut b = __s_b.launch_builder(&f);
4376        b.arg(table)
4377            .arg(&proj)
4378            .arg(ex_ids)
4379            .arg(ex_off)
4380            .arg(ex_pairs)
4381            .arg(pair_tok)
4382            .arg(aq)
4383            .arg(ad)
4384            .arg(&mut y)
4385            .arg(&inf)
4386            .arg(&outf)
4387            .arg(&ne)
4388            .arg(&na)
4389            .arg(&qtype)
4390            .arg(&rbi);
4391        unsafe {
4392            b.launch(cfg)?;
4393        }
4394        Ok(y)
4395    }
4396
4397    // Decode-once expert-major MMQ (rung 3). Same CSR inputs/geometry as _em; kernel dequants each
4398    // weight group once per (row,group) then dp4a's across the expert's token group.
4399    #[allow(clippy::too_many_arguments)]
4400    pub fn moe_pairs_matvec_q8_dec(
4401        &self,
4402        table: &CudaSlice<u64>,
4403        proj: i32,
4404        ex_ids: &CudaSlice<i32>,
4405        ex_off: &CudaSlice<i32>,
4406        ex_pairs: &CudaSlice<i32>,
4407        pair_tok: &CudaSlice<i32>,
4408        aq: &CudaSlice<i8>,
4409        ad: &CudaSlice<f32>,
4410        in_f: usize,
4411        out_f: usize,
4412        n_expert: usize,
4413        n_active: usize,
4414        n_pairs: usize,
4415        qtype: i32,
4416        row_bytes: usize,
4417    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4418        let f = self.func("moe_pairs_matvec_q8_dec");
4419        let mut y = self.alloc_uninit::<f32>(n_pairs * out_f)?;
4420        const ROWS: u32 = 4;
4421        let cfg = LaunchConfig {
4422            grid_dim: ((out_f as u32 + ROWS - 1) / ROWS, n_active as u32, 1),
4423            block_dim: (32, ROWS, 1),
4424            shared_mem_bytes: 0,
4425        };
4426        let (inf, outf, ne, na, rbi) = (
4427            in_f as i32,
4428            out_f as i32,
4429            n_expert as i32,
4430            n_active as i32,
4431            row_bytes as i64,
4432        );
4433        let __s_b = self.gpu.stream();
4434        let mut b = __s_b.launch_builder(&f);
4435        b.arg(table)
4436            .arg(&proj)
4437            .arg(ex_ids)
4438            .arg(ex_off)
4439            .arg(ex_pairs)
4440            .arg(pair_tok)
4441            .arg(aq)
4442            .arg(ad)
4443            .arg(&mut y)
4444            .arg(&inf)
4445            .arg(&outf)
4446            .arg(&ne)
4447            .arg(&na)
4448            .arg(&qtype)
4449            .arg(&rbi);
4450        unsafe {
4451            b.launch(cfg)?;
4452        }
4453        Ok(y)
4454    }
4455
4456    pub fn moe_pairs_gelu_mul(
4457        &self,
4458        gate: &CudaSlice<f32>,
4459        up: &CudaSlice<f32>,
4460        n: usize,
4461    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4462        let f = self.func("moe_pairs_gelu_mul");
4463        let mut act = self.alloc_uninit::<f32>(n)?;
4464        let cfg = LaunchConfig::for_num_elems(n as u32);
4465        let nl = n as i64;
4466        let __s_b = self.gpu.stream();
4467        let mut b = __s_b.launch_builder(&f);
4468        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4469        unsafe {
4470            b.launch(cfg)?;
4471        }
4472        Ok(act)
4473    }
4474
4475    pub fn moe_pairs_silu_mul(
4476        &self,
4477        gate: &CudaSlice<f32>,
4478        up: &CudaSlice<f32>,
4479        n: usize,
4480    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4481        let f = self.func("moe_pairs_silu_mul");
4482        let mut act = self.alloc_uninit::<f32>(n)?;
4483        let cfg = LaunchConfig::for_num_elems(n as u32);
4484        let nl = n as i64;
4485        let __s_b = self.gpu.stream();
4486        let mut b = __s_b.launch_builder(&f);
4487        b.arg(gate).arg(up).arg(&mut act).arg(&nl);
4488        unsafe {
4489            b.launch(cfg)?;
4490        }
4491        Ok(act)
4492    }
4493
4494    #[allow(clippy::too_many_arguments)]
4495    pub fn moe_pairs_scatter(
4496        &self,
4497        y_down: &CudaSlice<f32>,
4498        pair_w: &CudaSlice<f32>,
4499        tok_pair_off: &CudaSlice<i32>,
4500        tok_pair_ids: &CudaSlice<i32>,
4501        moe_out: &mut CudaSlice<f32>,
4502        t: usize,
4503        n_embd: usize,
4504    ) -> Result<(), Box<dyn std::error::Error>> {
4505        let f = self.func("moe_pairs_scatter");
4506        let cfg = LaunchConfig {
4507            grid_dim: (((n_embd + 255) / 256) as u32, t as u32, 1),
4508            block_dim: (256, 1, 1),
4509            shared_mem_bytes: 0,
4510        };
4511        let ne = n_embd as i32;
4512        let __s_b = self.gpu.stream();
4513        let mut b = __s_b.launch_builder(&f);
4514        b.arg(y_down)
4515            .arg(pair_w)
4516            .arg(tok_pair_off)
4517            .arg(tok_pair_ids)
4518            .arg(moe_out)
4519            .arg(&ne);
4520        unsafe {
4521            b.launch(cfg)?;
4522        }
4523        Ok(())
4524    }
4525
4526    /// gemma4 GELU twin of moe_gate_up_silu8_dev_q8 (base geometry — slot-packed j8/j8r2
4527    /// twins probed 2026-08-01 g26 decode dig: bit-identical rows, -2.5%/-2.9% whole-model
4528    /// decode x3 interleaved -> refuted and killed; research/g26-decode-20260801/receipts.md).
4529    #[allow(clippy::too_many_arguments)]
4530    pub fn moe_gate_up_gelu8_dev_q8(
4531        &self,
4532        table: &CudaSlice<u64>,
4533        sel: &cudarc::driver::CudaView<i32>,
4534        aq: &CudaSlice<i8>,
4535        ad: &CudaSlice<f32>,
4536        in_f: usize,
4537        n_ff: usize,
4538        n_used: usize,
4539        n_expert: usize,
4540        qt_g: i32,
4541        qt_u: i32,
4542        rb_g: usize,
4543        rb_u: usize,
4544    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4545        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
4546        let (inf, nff, ne, rbg, rbu) = (
4547            in_f as i32,
4548            n_ff as i32,
4549            n_expert as i32,
4550            rb_g as i64,
4551            rb_u as i64,
4552        );
4553        let f = self.func("moe_gate_up_gelu8_dev_q8");
4554        let cfg = LaunchConfig {
4555            grid_dim: (n_ff as u32, n_used as u32, 1),
4556            block_dim: (32, 1, 1),
4557            shared_mem_bytes: 0,
4558        };
4559        let __s_b = self.gpu.stream();
4560        let mut b = __s_b.launch_builder(&f);
4561        b.arg(table)
4562            .arg(sel)
4563            .arg(aq)
4564            .arg(ad)
4565            .arg(&mut act)
4566            .arg(&inf)
4567            .arg(&nff)
4568            .arg(&ne)
4569            .arg(&qt_g)
4570            .arg(&qt_u)
4571            .arg(&rbg)
4572            .arg(&rbu);
4573        unsafe {
4574            b.launch(cfg)?;
4575        }
4576        Ok(act)
4577    }
4578
4579    /// gemma4 GELU rows twin (verify): one launch over (n_ff, n_used, t).
4580    #[allow(clippy::too_many_arguments)]
4581    pub fn moe_gate_up_gelu8_dev_q8_rows(
4582        &self,
4583        table: &CudaSlice<u64>,
4584        sel: &CudaSlice<i32>,
4585        aq: &CudaSlice<i8>,
4586        ad: &CudaSlice<f32>,
4587        t: usize,
4588        in_f: usize,
4589        n_ff: usize,
4590        n_used: usize,
4591        n_expert: usize,
4592        qt_g: i32,
4593        qt_u: i32,
4594        rb_g: usize,
4595        rb_u: usize,
4596    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4597        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
4598        let (inf, nff, ne, rbg, rbu, nu) = (
4599            in_f as i32,
4600            n_ff as i32,
4601            n_expert as i32,
4602            rb_g as i64,
4603            rb_u as i64,
4604            n_used as i32,
4605        );
4606        let f = self.func("moe_gate_up_gelu8_dev_q8_rows");
4607        let cfg = LaunchConfig {
4608            grid_dim: (n_ff as u32, n_used as u32, t as u32),
4609            block_dim: (32, 1, 1),
4610            shared_mem_bytes: 0,
4611        };
4612        let __s_b = self.gpu.stream();
4613        let mut b = __s_b.launch_builder(&f);
4614        b.arg(table)
4615            .arg(sel)
4616            .arg(aq)
4617            .arg(ad)
4618            .arg(&mut act)
4619            .arg(&inf)
4620            .arg(&nff)
4621            .arg(&ne)
4622            .arg(&qt_g)
4623            .arg(&qt_u)
4624            .arg(&rbg)
4625            .arg(&rbu)
4626            .arg(&nu);
4627        unsafe {
4628            b.launch(cfg)?;
4629        }
4630        Ok(act)
4631    }
4632
4633    /// gemma4 GELU CSR twin (verify dedup: owner block serves every pair of its expert).
4634    #[allow(clippy::too_many_arguments)]
4635    pub fn moe_gate_up_gelu8_dev_q8_csr(
4636        &self,
4637        table: &CudaSlice<u64>,
4638        sel: &CudaSlice<i32>,
4639        aq: &CudaSlice<i8>,
4640        ad: &CudaSlice<f32>,
4641        n_pairs: usize,
4642        in_f: usize,
4643        n_ff: usize,
4644        n_used: usize,
4645        n_expert: usize,
4646        qt_g: i32,
4647        qt_u: i32,
4648        rb_g: usize,
4649        rb_u: usize,
4650    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
4651        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
4652        let (inf, nff, ne, rbg, rbu, nu, npi) = (
4653            in_f as i32,
4654            n_ff as i32,
4655            n_expert as i32,
4656            rb_g as i64,
4657            rb_u as i64,
4658            n_used as i32,
4659            n_pairs as i32,
4660        );
4661        let f = self.func("moe_gate_up_gelu8_dev_q8_csr");
4662        let cfg = LaunchConfig {
4663            grid_dim: (n_ff as u32, n_pairs as u32, 1),
4664            block_dim: (32, 1, 1),
4665            shared_mem_bytes: 0,
4666        };
4667        let __s_b = self.gpu.stream();
4668        let mut b = __s_b.launch_builder(&f);
4669        b.arg(table)
4670            .arg(sel)
4671            .arg(aq)
4672            .arg(ad)
4673            .arg(&mut act)
4674            .arg(&inf)
4675            .arg(&nff)
4676            .arg(&ne)
4677            .arg(&qt_g)
4678            .arg(&qt_u)
4679            .arg(&rbg)
4680            .arg(&rbu)
4681            .arg(&nu)
4682            .arg(&npi);
4683        unsafe {
4684            b.launch(cfg)?;
4685        }
4686        Ok(act)
4687    }
4688
4689    /// gemma4 generic down rows twin (verify): one launch over (out_f, 1, t).
4690    #[allow(clippy::too_many_arguments)]
4691    pub fn moe_down8_fma_dev_q8_rows_g(
4692        &self,
4693        table: &CudaSlice<u64>,
4694        sel: &CudaSlice<i32>,
4695        w: &CudaSlice<f32>,
4696        aq2: &CudaSlice<i8>,
4697        ad2: &CudaSlice<f32>,
4698        dst: &mut CudaSlice<f32>,
4699        t: usize,
4700        in_f: usize,
4701        out_f: usize,
4702        n_used: usize,
4703        n_expert: usize,
4704        qt: i32,
4705        rb: usize,
4706    ) -> Result<(), Box<dyn std::error::Error>> {
4707        let (inf, outf, nu, ne, rbi) = (
4708            in_f as i32,
4709            out_f as i32,
4710            n_used as i32,
4711            n_expert as i32,
4712            rb as i64,
4713        );
4714        // Exact Step-3.7 B=1 shape: expose the eight independent slot dots as
4715        // eight warps, then replay the original slot-ordered FMA chain. Every
4716        // other shape retains the generic one-warp rows kernel.
4717        let step_b1_w8 = t == 1 && in_f == 1280 && out_f == 4096 && n_used == 8 && qt == QT_IQ4_XS;
4718        let f = self.func(if step_b1_w8 {
4719            "moe_down8_fma_dev_q8_rows_w8"
4720        } else {
4721            "moe_down8_fma_dev_q8_rows_g"
4722        });
4723        let cfg = LaunchConfig {
4724            grid_dim: (out_f as u32, 1, t as u32),
4725            block_dim: (32, if step_b1_w8 { 8 } else { 1 }, 1),
4726            shared_mem_bytes: 0,
4727        };
4728        let __s_b = self.gpu.stream();
4729        let mut b = __s_b.launch_builder(&f);
4730        b.arg(table)
4731            .arg(sel)
4732            .arg(w)
4733            .arg(aq2)
4734            .arg(ad2)
4735            .arg(dst)
4736            .arg(&inf)
4737            .arg(&outf)
4738            .arg(&nu)
4739            .arg(&ne)
4740            .arg(&qt)
4741            .arg(&rbi);
4742        unsafe {
4743            b.launch(cfg)?;
4744        }
4745        Ok(())
4746    }
4747
4748    /// rp_q4 microprobe (2026-07-10 verify-trunk lever): b4 GGUF-block layout vs the Q4_0
4749    /// split-plane twin on the wq-class shape. Returns (blk_us, rp_us) after asserting bitwise
4750    /// identity. Bench-only surface (rp_q4_probe bin); no production dispatch reads this.
4751    pub fn rp_probe_q4(&self, m: usize) -> Result<(f64, f64), Box<dyn std::error::Error>> {
4752        let (out_f, in_f) = (2048usize, 2816usize);
4753        let nblk = in_f / 32;
4754        let mut seed = 0x9E3779B97F4A7C15u64;
4755        let mut rng = move || {
4756            seed = seed
4757                .wrapping_mul(6364136223846793005)
4758                .wrapping_add(1442695040888963407);
4759            (seed >> 33) as u8
4760        };
4761        let mut w = vec![0u8; out_f * nblk * 18];
4762        for b in w.iter_mut() {
4763            *b = rng();
4764        }
4765        for r in 0..out_f {
4766            for g in 0..nblk {
4767                let off = (r * nblk + g) * 18;
4768                w[off] = 0x00;
4769                w[off + 1] = 0x2C; // sane half d
4770            }
4771        }
4772        let qplane = out_f * nblk * 16;
4773        let mut wrp = vec![0u8; w.len()];
4774        for r in 0..out_f {
4775            for g in 0..nblk {
4776                let src = &w[(r * nblk + g) * 18..(r * nblk + g) * 18 + 18];
4777                wrp[qplane + (r * nblk + g) * 2..qplane + (r * nblk + g) * 2 + 2]
4778                    .copy_from_slice(&src[0..2]);
4779                wrp[(r * nblk + g) * 16..(r * nblk + g) * 16 + 16].copy_from_slice(&src[2..18]);
4780            }
4781        }
4782        let w_d = self.htod_bytes(&w)?;
4783        let wrp_d = self.htod_bytes(&wrp)?;
4784        let mut aq = vec![0i8; m * in_f];
4785        for v in aq.iter_mut() {
4786            *v = rng() as i8;
4787        }
4788        let aq_d = self.htod_i8(&aq)?;
4789        let ad_d = self.htod(&vec![0.03125f32; m * nblk])?;
4790        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
4791        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
4792        const RPB: u32 = 4;
4793        let cfg = LaunchConfig {
4794            grid_dim: ((out_f as u32).div_ceil(RPB), 1, 1),
4795            block_dim: (32, RPB, 1),
4796            shared_mem_bytes: 0,
4797        };
4798        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
4799        let (rb, qp) = ((nblk * 18) as i64, qplane as i64);
4800        let fb = self.func("qmatvec_q4_0_mmvq_b4");
4801        let fr = self.func("qmatvec_q4_0_mmvq_b4_rp");
4802        {
4803            let __s_b = self.gpu.stream();
4804            let mut b = __s_b.launch_builder(&fb);
4805            b.arg(&w_d)
4806                .arg(&aq_d)
4807                .arg(&ad_d)
4808                .arg(&mut y0)
4809                .arg(&inf)
4810                .arg(&outf)
4811                .arg(&mi)
4812                .arg(&rb);
4813            unsafe {
4814                b.launch(cfg)?;
4815            }
4816            let __s_b = self.gpu.stream();
4817            let mut b = __s_b.launch_builder(&fr);
4818            b.arg(&wrp_d)
4819                .arg(&aq_d)
4820                .arg(&ad_d)
4821                .arg(&mut y1)
4822                .arg(&inf)
4823                .arg(&outf)
4824                .arg(&mi)
4825                .arg(&qp);
4826            unsafe {
4827                b.launch(cfg)?;
4828            }
4829        }
4830        self.gpu.stream().synchronize()?;
4831        let (h0, h1) = (self.dtoh(&y0)?, self.dtoh(&y1)?);
4832        let nd = h0
4833            .iter()
4834            .zip(&h1)
4835            .filter(|(a, b)| a.to_bits() != b.to_bits())
4836            .count();
4837        if nd != 0 {
4838            return Err(format!("rp twin not bitwise: {nd}/{} diffs", h0.len()).into());
4839        }
4840        let mut time = |rp: bool| -> Result<f64, Box<dyn std::error::Error>> {
4841            self.gpu.stream().synchronize()?;
4842            let t0 = std::time::Instant::now();
4843            for _ in 0..500 {
4844                if rp {
4845                    let __s_b = self.gpu.stream();
4846                    let mut b = __s_b.launch_builder(&fr);
4847                    b.arg(&wrp_d)
4848                        .arg(&aq_d)
4849                        .arg(&ad_d)
4850                        .arg(&mut y1)
4851                        .arg(&inf)
4852                        .arg(&outf)
4853                        .arg(&mi)
4854                        .arg(&qp);
4855                    unsafe {
4856                        b.launch(cfg)?;
4857                    }
4858                } else {
4859                    let __s_b = self.gpu.stream();
4860                    let mut b = __s_b.launch_builder(&fb);
4861                    b.arg(&w_d)
4862                        .arg(&aq_d)
4863                        .arg(&ad_d)
4864                        .arg(&mut y0)
4865                        .arg(&inf)
4866                        .arg(&outf)
4867                        .arg(&mi)
4868                        .arg(&rb);
4869                    unsafe {
4870                        b.launch(cfg)?;
4871                    }
4872                }
4873            }
4874            self.gpu.stream().synchronize()?;
4875            Ok(t0.elapsed().as_secs_f64() * 1e6 / 500.0)
4876        };
4877        let _ = time(false)?;
4878        let _ = time(true)?; // warm
4879        Ok((time(false)?, time(true)?))
4880    }
4881
4882    /// Build the Q4_0 split-plane decode mirror for a 2D Quant tensor (device-side permutation,
4883    /// q4_0_split_rp_build). Raw bytes stay resident (prefill/gemm/Stage-A); the m<=8 decode
4884    /// dispatch prefers the mirror (_rp twins). No-op unless (Q4_0, 2D, mirror absent).
4885    /// VRAM cost == the tensor's weight size. MEMRA_Q4RP=0 disables at the call sites.
4886    pub fn build_q4_rp4(
4887        &self,
4888        t: &mut crate::model::GpuTensor,
4889    ) -> Result<(), Box<dyn std::error::Error>> {
4890        use crate::model::GpuTensor;
4891        let GpuTensor::Quant {
4892            bytes,
4893            qtype,
4894            row_bytes,
4895            ne,
4896            rp4,
4897            ..
4898        } = t
4899        else {
4900            return Ok(());
4901        };
4902        if *qtype != QT_Q4_0 || rp4.is_some() || ne.len() != 2 {
4903            return Ok(());
4904        }
4905        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
4906        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 18 {
4907            return Ok(());
4908        }
4909        let nblk = in_f / 32;
4910        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 18)?;
4911        let f = self.func("q4_0_split_rp_build");
4912        let n = (out_f * nblk) as i32;
4913        let cfg = LaunchConfig {
4914            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
4915            block_dim: (256, 1, 1),
4916            shared_mem_bytes: 0,
4917        };
4918        let (of, nb) = (out_f as i32, nblk as i32);
4919        let _ = n;
4920        let __s_b = self.gpu.stream();
4921        let mut b = __s_b.launch_builder(&f);
4922        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
4923        unsafe {
4924            b.launch(cfg)?;
4925        }
4926        *rp4 = Some(dst);
4927        Ok(())
4928    }
4929
4930    /// Q8_0 twin of `build_q4_rp4` (H100 coalescing fix, 2026-07-26 ncu: GGUF 34B-stride
4931    /// weight loads hold Max Bandwidth at 41-46%; the split mirror makes them aligned 16B
4932    /// ldcs). Raw bytes stay resident (prefill GEMM/MMQ/fused m=1 launches read GGUF layout);
4933    /// the mmvq/batched decode arms prefer the mirror via `rp4`. Bit-identical outputs.
4934    pub fn build_q8_rp4(
4935        &self,
4936        t: &mut crate::model::GpuTensor,
4937    ) -> Result<(), Box<dyn std::error::Error>> {
4938        use crate::model::GpuTensor;
4939        let GpuTensor::Quant {
4940            bytes,
4941            qtype,
4942            row_bytes,
4943            ne,
4944            rp4,
4945            ..
4946        } = t
4947        else {
4948            return Ok(());
4949        };
4950        if *qtype != QT_Q8_0 || rp4.is_some() || ne.len() != 2 {
4951            return Ok(());
4952        }
4953        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
4954        if in_f % 32 != 0 || *row_bytes != (in_f / 32) * 34 {
4955            return Ok(());
4956        }
4957        *rp4 = Some(self.build_q8_rp4_raw(bytes, in_f, out_f)?);
4958        Ok(())
4959    }
4960
4961    /// Raw rp-mirror build for gates/benches: split GGUF Q8_0 bytes into the qplane+dplane
4962    /// mirror without a GpuTensor (same kernel the loader path above uses).
4963    pub fn build_q8_rp4_raw(
4964        &self,
4965        bytes: &CudaSlice<u8>,
4966        in_f: usize,
4967        out_f: usize,
4968    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
4969        assert!(in_f % 32 == 0);
4970        let nblk = in_f / 32;
4971        let mut dst = self.alloc_uninit::<u8>(out_f * nblk * 34)?;
4972        let f = self.func("q8_0_split_rp_build");
4973        let cfg = LaunchConfig {
4974            grid_dim: (((out_f * nblk) as u32).div_ceil(256), 1, 1),
4975            block_dim: (256, 1, 1),
4976            shared_mem_bytes: 0,
4977        };
4978        let (of, nb) = (out_f as i32, nblk as i32);
4979        let __s_b = self.gpu.stream();
4980        let mut b = __s_b.launch_builder(&f);
4981        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
4982        unsafe {
4983            b.launch(cfg)?;
4984        }
4985        Ok(dst)
4986    }
4987
4988    /// K-quant twins of `build_q8_rp4` (H100 K-quant coalescing fix, 2026-08-01 ncu on the
4989    /// q27 Q4_K_M decode: q4_K mmvq DRAM 41-54% with 65% excessive sectors, q6_K 40% with
4990    /// 78% — the 144B/210B superblock strides land every 4B weight load off-sector). The
4991    /// mirror re-packs each tensor into planes (q4_K: qs ++ 16B meta; q6_K: ql ++ qh ++
4992    /// scales ++ d — same total bytes) so every quant fetch is an aligned 16B ldcs. Raw
4993    /// bytes stay resident (prefill GEMM/dequant/Stage-A read GGUF layout); the mmvq/batched
4994    /// decode arms prefer the mirror via `rp4`. Bit-identical outputs.
4995    pub fn build_q4k_rp4(
4996        &self,
4997        t: &mut crate::model::GpuTensor,
4998    ) -> Result<(), Box<dyn std::error::Error>> {
4999        use crate::model::GpuTensor;
5000        let GpuTensor::Quant {
5001            bytes,
5002            qtype,
5003            row_bytes,
5004            ne,
5005            rp4,
5006            ..
5007        } = t
5008        else {
5009            return Ok(());
5010        };
5011        if *qtype != QT_Q4_K || rp4.is_some() || ne.len() != 2 {
5012            return Ok(());
5013        }
5014        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5015        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 144 {
5016            return Ok(());
5017        }
5018        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q4_K)?);
5019        Ok(())
5020    }
5021
5022    pub fn build_q6k_rp4(
5023        &self,
5024        t: &mut crate::model::GpuTensor,
5025    ) -> Result<(), Box<dyn std::error::Error>> {
5026        use crate::model::GpuTensor;
5027        let GpuTensor::Quant {
5028            bytes,
5029            qtype,
5030            row_bytes,
5031            ne,
5032            rp4,
5033            ..
5034        } = t
5035        else {
5036            return Ok(());
5037        };
5038        if *qtype != QT_Q6_K || rp4.is_some() || ne.len() != 2 {
5039            return Ok(());
5040        }
5041        let (in_f, out_f) = (ne[0] as usize, ne[1] as usize);
5042        if in_f % 256 != 0 || *row_bytes != (in_f / 256) * 210 {
5043            return Ok(());
5044        }
5045        *rp4 = Some(self.build_kq_rp4_raw(bytes, in_f, out_f, QT_Q6_K)?);
5046        Ok(())
5047    }
5048
5049    /// Raw K-quant rp-mirror build for gates/benches (same kernels the loader path uses).
5050    pub fn build_kq_rp4_raw(
5051        &self,
5052        bytes: &CudaSlice<u8>,
5053        in_f: usize,
5054        out_f: usize,
5055        qtype: i32,
5056    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
5057        assert!(in_f % 256 == 0);
5058        let nsbk = in_f / 256;
5059        let (sb_bytes, kname) = match qtype {
5060            QT_Q4_K => (144usize, "q4_K_split_rp_build"),
5061            QT_Q6_K => (210usize, "q6_K_split_rp_build"),
5062            _ => return Err(format!("build_kq_rp4_raw: qtype {qtype} has no rp mirror").into()),
5063        };
5064        let mut dst = self.alloc_uninit::<u8>(out_f * nsbk * sb_bytes)?;
5065        let f = self.func(kname);
5066        let cfg = LaunchConfig {
5067            grid_dim: (((out_f * nsbk) as u32).div_ceil(256), 1, 1),
5068            block_dim: (256, 1, 1),
5069            shared_mem_bytes: 0,
5070        };
5071        let (of, nb) = (out_f as i32, nsbk as i32);
5072        let __s_b = self.gpu.stream();
5073        let mut b = __s_b.launch_builder(&f);
5074        b.arg(&*bytes).arg(&mut dst).arg(&of).arg(&nb);
5075        unsafe {
5076            b.launch(cfg)?;
5077        }
5078        Ok(dst)
5079    }
5080
5081    /// MEMRA_KQRP seam: the K-quant (q4_K/q6_K) split-plane decode mirrors at model load.
5082    /// Default follows the Q8RP convention — ON on the Hopper lane (80GB pays the mirror
5083    /// VRAM), OFF elsewhere (a 24GB card cannot hold model + mirror + KV for the big trunks).
5084    pub fn kqrp_enabled() -> bool {
5085        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5086        *ON.get_or_init(|| match std::env::var("MEMRA_KQRP").as_deref() {
5087            Ok("0") => false,
5088            Ok(_) => true,
5089            Err(_) => cfg!(memra_hopper_mma),
5090        })
5091    }
5092
5093    /// IN-PLACE split-plane swap (the 31B dense arc): build the split layout and REPLACE the
5094    /// GGUF bytes (zero extra steady-state VRAM — the transient peak is one tensor's size).
5095    /// The tensor's `rp` flag then routes every consumer (mmvq/batched `_rp` twins, the
5096    /// `qmatvec_gemm_q4_0_rp` prefill kernel). Callers gate on the fast path being active —
5097    /// the Stage-A f32 oracle (`MEMRA_FAST=0`) reads GGUF layout and must never see a swap.
5098    pub fn build_q4_rp_swap(
5099        &self,
5100        t: &mut crate::model::GpuTensor,
5101    ) -> Result<bool, Box<dyn std::error::Error>> {
5102        self.build_q4_rp4(t)?;
5103        self.gpu.stream().synchronize()?; // build kernel reads the GGUF bytes — drain BEFORE dropping them
5104        use crate::model::GpuTensor;
5105        let GpuTensor::Quant { bytes, rp4, rp, .. } = t else {
5106            return Ok(false);
5107        };
5108        match rp4.take() {
5109            Some(split) => {
5110                *bytes = split; // the GGUF-layout buffer drops here
5111                *rp = true;
5112                Ok(true)
5113            }
5114            None => Ok(false),
5115        }
5116    }
5117
5118    /// MEMRA_Q4RP seam (default ON): the Q4_0 split-plane decode mirror at model load.
5119    pub fn q4rp_enabled() -> bool {
5120        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
5121        *ON.get_or_init(|| {
5122            std::env::var("MEMRA_Q4RP")
5123                .map(|v| v != "0")
5124                .unwrap_or(true)
5125        })
5126    }
5127
5128    /// gemma4-E4B: dense [t][row_elems] gather of layer il's rows from the strided prologue
5129    /// buffer ([t][n_layer][n_epl]; off = il*n_epl, stride = n_layer*n_epl).
5130    pub fn copy_rows_strided(
5131        &self,
5132        src: &CudaSlice<f32>,
5133        dst: &mut CudaSlice<f32>,
5134        row_elems: usize,
5135        n_rows: usize,
5136        src_stride: usize,
5137        src_off: usize,
5138    ) -> Result<(), Box<dyn std::error::Error>> {
5139        let f = self.func("copy_rows_strided_f32");
5140        let cfg = LaunchConfig {
5141            grid_dim: (((row_elems as u32 + 255) / 256).max(1), n_rows as u32, 1),
5142            block_dim: (256, 1, 1),
5143            shared_mem_bytes: 0,
5144        };
5145        let (re, nr) = (row_elems as i32, n_rows as i32);
5146        let (st, off) = (src_stride as i64, src_off as i64);
5147        let __s_b = self.gpu.stream();
5148        let mut b = __s_b.launch_builder(&f);
5149        b.arg(src)
5150            .arg(&mut *dst)
5151            .arg(&re)
5152            .arg(&nr)
5153            .arg(&st)
5154            .arg(&off);
5155        unsafe {
5156            b.launch(cfg)?;
5157        }
5158        Ok(())
5159    }
5160
5161    /// Async device u32 store (value rides the kernel ARG — no host-memory transfer/sync).
5162    pub fn u32_set_k(
5163        &self,
5164        dst: &mut CudaSlice<u32>,
5165        v: u32,
5166        idx: usize,
5167    ) -> Result<(), Box<dyn std::error::Error>> {
5168        let f = self.func("u32_set_k");
5169        let cfg = LaunchConfig {
5170            grid_dim: (1, 1, 1),
5171            block_dim: (1, 1, 1),
5172            shared_mem_bytes: 0,
5173        };
5174        let ii = idx as i32;
5175        let __s_b = self.gpu.stream();
5176        let mut b = __s_b.launch_builder(&f);
5177        b.arg(dst).arg(&v).arg(&ii);
5178        unsafe {
5179            b.launch(cfg)?;
5180        }
5181        Ok(())
5182    }
5183
5184    /// counter += v (device-slot append advance; the +1 twin is `inc_seqlen`).
5185    pub fn i32_add_k(
5186        &self,
5187        d: &mut CudaSlice<i32>,
5188        v: i32,
5189    ) -> Result<(), Box<dyn std::error::Error>> {
5190        let f = self.func("i32_add_k");
5191        let cfg = LaunchConfig {
5192            grid_dim: (1, 1, 1),
5193            block_dim: (32, 1, 1),
5194            shared_mem_bytes: 0,
5195        };
5196        let __s_b = self.gpu.stream();
5197        let mut b = __s_b.launch_builder(&f);
5198        b.arg(d).arg(&v);
5199        unsafe {
5200            b.launch(cfg)?;
5201        }
5202        Ok(())
5203    }
5204
5205    /// pos rows from a device counter: dst[i] = ctr[0] + i (verify-stream rope positions).
5206    pub fn i32_iota_from(
5207        &self,
5208        ctr: &CudaSlice<i32>,
5209        dst: &mut CudaSlice<i32>,
5210        n: usize,
5211    ) -> Result<(), Box<dyn std::error::Error>> {
5212        let f = self.func("i32_iota_from");
5213        let cfg = LaunchConfig::for_num_elems(n as u32);
5214        let ni = n as i32;
5215        let __s_b = self.gpu.stream();
5216        let mut b = __s_b.launch_builder(&f);
5217        b.arg(ctr).arg(dst).arg(&ni);
5218        unsafe {
5219            b.launch(cfg)?;
5220        }
5221        Ok(())
5222    }
5223
5224    /// In-place trim-id translate: buf[idx] = map[buf[idx]] (FR-Spec d2t, async single-slot).
5225    pub fn u32_map_k(
5226        &self,
5227        buf: &mut CudaSlice<u32>,
5228        map: &CudaSlice<u32>,
5229        idx: usize,
5230    ) -> Result<(), Box<dyn std::error::Error>> {
5231        let f = self.func("u32_map_k");
5232        let cfg = LaunchConfig {
5233            grid_dim: (1, 1, 1),
5234            block_dim: (1, 1, 1),
5235            shared_mem_bytes: 0,
5236        };
5237        let ii = idx as i32;
5238        let __s_b = self.gpu.stream();
5239        let mut b = __s_b.launch_builder(&f);
5240        b.arg(buf).arg(map).arg(&ii);
5241        unsafe {
5242            b.launch(cfg)?;
5243        }
5244        Ok(())
5245    }
5246
5247    /// Pack a[off..off+n1] ++ b[0..n2] into one buffer (single dtoh follows).
5248    #[allow(clippy::too_many_arguments)]
5249    pub fn u32_pack2(
5250        &self,
5251        a: &CudaSlice<u32>,
5252        off_a: usize,
5253        n1: usize,
5254        b_in: &CudaSlice<u32>,
5255        n2: usize,
5256        out: &mut CudaSlice<u32>,
5257    ) -> Result<(), Box<dyn std::error::Error>> {
5258        let f = self.func("u32_pack2");
5259        let cfg = LaunchConfig::for_num_elems((n1 + n2) as u32);
5260        let (oa, i1, i2) = (off_a as i32, n1 as i32, n2 as i32);
5261        let __s_b = self.gpu.stream();
5262        let mut b = __s_b.launch_builder(&f);
5263        b.arg(a).arg(&oa).arg(&i1).arg(b_in).arg(&i2).arg(out);
5264        unsafe {
5265            b.launch(cfg)?;
5266        }
5267        Ok(())
5268    }
5269
5270    /// gemma4 R3 device fold: w[i] *= s[sel[i]] over the router's [n] (sel, w) pair.
5271    pub fn moe_w_exscale(
5272        &self,
5273        w: &mut CudaSlice<f32>,
5274        sel: &CudaSlice<i32>,
5275        s: &CudaSlice<f32>,
5276        n: usize,
5277    ) -> Result<(), Box<dyn std::error::Error>> {
5278        let f = self.func("moe_w_exscale");
5279        let cfg = LaunchConfig::for_num_elems(n as u32);
5280        let ni = n as i32;
5281        let __s_b = self.gpu.stream();
5282        let mut b = __s_b.launch_builder(&f);
5283        b.arg(w).arg(sel).arg(s).arg(&ni);
5284        unsafe {
5285            b.launch(cfg)?;
5286        }
5287        Ok(())
5288    }
5289
5290    /// Down-projection macro fold: w[i] *= macros[2*n_expert + sel[i]] on the device router
5291    /// weights (one launch per MoE layer, only for macro-carrying artifacts — see MoeWeights).
5292    pub fn moe_w_scale_by_expert(
5293        &self,
5294        w: &mut CudaSlice<f32>,
5295        sel: &CudaSlice<i32>,
5296        macros: &CudaSlice<f32>,
5297        n_expert: usize,
5298        n: usize,
5299    ) -> Result<(), Box<dyn std::error::Error>> {
5300        let f = self.func("moe_w_scale_by_expert");
5301        let cfg = LaunchConfig {
5302            grid_dim: (n.div_ceil(64) as u32, 1, 1),
5303            block_dim: (64, 1, 1),
5304            shared_mem_bytes: 0,
5305        };
5306        let (ne, nn) = (n_expert as i32, n as i32);
5307        let __s_b = self.gpu.stream();
5308        let mut b = __s_b.launch_builder(&f);
5309        b.arg(w).arg(sel).arg(macros).arg(&ne).arg(&nn);
5310        unsafe {
5311            b.launch(cfg)?;
5312        }
5313        Ok(())
5314    }
5315
5316    pub fn moe_gate_up_silu8_dev_q8(
5317        &self,
5318        table: &CudaSlice<u64>,
5319        sel: &cudarc::driver::CudaView<i32>,
5320        aq: &CudaSlice<i8>,
5321        ad: &CudaSlice<f32>,
5322        in_f: usize,
5323        n_ff: usize,
5324        n_used: usize,
5325        n_expert: usize,
5326        qt_g: i32,
5327        qt_u: i32,
5328        rb_g: usize,
5329        rb_u: usize,
5330        macros: &CudaSlice<f32>,
5331    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5332        static GU: std::sync::OnceLock<(String, u32)> = std::sync::OnceLock::new();
5333        let (mode, wpb) = GU.get_or_init(|| {
5334            let mode = std::env::var("MEMRA_MOE_DEVQ8_GU").unwrap_or_default();
5335            let wpb = std::env::var("MEMRA_MOE_DEVQ8_WPB")
5336                .ok()
5337                .and_then(|v| v.parse().ok())
5338                .unwrap_or(4u32)
5339                .clamp(1, 16);
5340            (mode, wpb)
5341        });
5342        let (mode, wpb) = (mode.as_str(), *wpb);
5343        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5344        let (inf, nff, ne, rbg, rbu) = (
5345            in_f as i32,
5346            n_ff as i32,
5347            n_expert as i32,
5348            rb_g as i64,
5349            rb_u as i64,
5350        );
5351        let (f, cfg) = match mode {
5352            "1" | "2" | "4" => {
5353                let rpw: u32 = mode.parse().unwrap();
5354                let f = self.func(match rpw {
5355                    1 => "moe_gate_up_silu8_dev_q8_r1",
5356                    2 => "moe_gate_up_silu8_dev_q8_r2",
5357                    _ => "moe_gate_up_silu8_dev_q8_r4",
5358                });
5359                let rows_per_block = (rpw * wpb) as usize;
5360                let gx = n_ff.div_ceil(rows_per_block) as u32;
5361                (
5362                    f,
5363                    LaunchConfig {
5364                        grid_dim: (gx, n_used as u32, 1),
5365                        block_dim: (32, wpb, 1),
5366                        shared_mem_bytes: 0,
5367                    },
5368                )
5369            }
5370            "j8" if n_used <= 32 => (
5371                self.func("moe_gate_up_silu8_dev_q8_j8"),
5372                LaunchConfig {
5373                    grid_dim: (n_ff as u32, 1, 1),
5374                    block_dim: (32, n_used as u32, 1),
5375                    shared_mem_bytes: 0,
5376                },
5377            ),
5378            // SMEM-GRID twins (IQ3_S 2KB grid copied to shared, static smem — bit-identical dots)
5379            "vsm2" => {
5380                let f = self.func("moe_gate_up_silu8_dev_q8_vsm2");
5381                let sh = (rb_g + rb_u) as u32;
5382                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5383                f.set_attribute(
5384                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5385                    sh as i32,
5386                )?;
5387                (
5388                    f,
5389                    LaunchConfig {
5390                        grid_dim: (n_ff as u32, n_used as u32, 1),
5391                        block_dim: (32, 1, 1),
5392                        shared_mem_bytes: sh,
5393                    },
5394                )
5395            }
5396            "vsm" => {
5397                let f = self.func("moe_gate_up_silu8_dev_q8_vsm");
5398                let sh = (rb_g + rb_u) as u32;
5399                use cudarc::driver::sys::CUfunction_attribute_enum as A;
5400                f.set_attribute(
5401                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
5402                    sh as i32,
5403                )?;
5404                (
5405                    f,
5406                    LaunchConfig {
5407                        grid_dim: (n_ff as u32, n_used as u32, 1),
5408                        block_dim: (32, 1, 1),
5409                        shared_mem_bytes: sh,
5410                    },
5411                )
5412            }
5413            "sg" => (
5414                self.func("moe_gate_up_silu8_dev_q8_sg"),
5415                LaunchConfig {
5416                    grid_dim: (n_ff as u32, n_used as u32, 1),
5417                    block_dim: (32, 1, 1),
5418                    shared_mem_bytes: 0,
5419                },
5420            ),
5421            "j8sg" if n_used <= 32 => (
5422                self.func("moe_gate_up_silu8_dev_q8_j8sg"),
5423                LaunchConfig {
5424                    grid_dim: (n_ff as u32, 1, 1),
5425                    block_dim: (32, n_used as u32, 1),
5426                    shared_mem_bytes: 0,
5427                },
5428            ),
5429            "u64" if in_f == 2048 => (
5430                self.func("moe_gate_up_silu8_dev_q8_u64"),
5431                LaunchConfig {
5432                    grid_dim: (n_ff as u32, n_used as u32, 1),
5433                    block_dim: (32, 1, 1),
5434                    shared_mem_bytes: 0,
5435                },
5436            ),
5437            "gs4" if in_f == 2048 => (
5438                self.func("moe_gate_up_silu8_dev_q8_gs4"),
5439                LaunchConfig {
5440                    grid_dim: (n_ff as u32, n_used as u32, 1),
5441                    block_dim: (32, 4, 1),
5442                    shared_mem_bytes: 0,
5443                },
5444            ),
5445            // _v twin (down8 lane 2026-07-08): wide-load IQ4_XS dot, base geometry, bit-identical.
5446            "v" | "" => (
5447                self.func("moe_gate_up_silu8_dev_q8_v"),
5448                LaunchConfig {
5449                    grid_dim: (n_ff as u32, n_used as u32, 1),
5450                    block_dim: (32, 1, 1),
5451                    shared_mem_bytes: 0,
5452                },
5453            ),
5454            "s2" => (
5455                self.func("moe_gate_up_silu8_dev_q8_s2"),
5456                LaunchConfig {
5457                    grid_dim: (n_ff as u32, n_used as u32, 1),
5458                    block_dim: (32, 2, 1),
5459                    shared_mem_bytes: 0,
5460                },
5461            ),
5462            "s2z" => {
5463                let rz = wpb.min(16); // s2z smem tile is [16][2]
5464                (
5465                    self.func("moe_gate_up_silu8_dev_q8_s2z"),
5466                    LaunchConfig {
5467                        grid_dim: (n_ff.div_ceil(rz as usize) as u32, n_used as u32, 1),
5468                        block_dim: (32, 2, rz),
5469                        shared_mem_bytes: 0,
5470                    },
5471                )
5472            }
5473            _ => (
5474                self.func("moe_gate_up_silu8_dev_q8"),
5475                LaunchConfig {
5476                    grid_dim: (n_ff as u32, n_used as u32, 1),
5477                    block_dim: (32, 1, 1),
5478                    shared_mem_bytes: 0,
5479                },
5480            ),
5481        };
5482        let __s_b = self.gpu.stream();
5483        let mut b = __s_b.launch_builder(&f);
5484        b.arg(table)
5485            .arg(sel)
5486            .arg(aq)
5487            .arg(ad)
5488            .arg(&mut act)
5489            .arg(&inf)
5490            .arg(&nff)
5491            .arg(&ne)
5492            .arg(&qt_g)
5493            .arg(&qt_u)
5494            .arg(&rbg)
5495            .arg(&rbu)
5496            .arg(macros);
5497        unsafe {
5498            b.launch(cfg)?;
5499        }
5500        Ok(act)
5501    }
5502
5503    #[allow(clippy::too_many_arguments)]
5504    pub fn moe_down8_fma_dev_q8(
5505        &self,
5506        table: &CudaSlice<u64>,
5507        sel: &cudarc::driver::CudaView<i32>,
5508        w: &cudarc::driver::CudaView<f32>,
5509        aq2: &CudaSlice<i8>,
5510        ad2: &CudaSlice<f32>,
5511        dst: &mut cudarc::driver::CudaViewMut<f32>,
5512        in_f: usize,
5513        out_f: usize,
5514        n_used: usize,
5515        n_expert: usize,
5516        qt: i32,
5517        rb: usize,
5518    ) -> Result<(), Box<dyn std::error::Error>> {
5519        static DOWN: std::sync::OnceLock<String> = std::sync::OnceLock::new();
5520        let mode = DOWN.get_or_init(|| std::env::var("MEMRA_MOE_DEVQ8_DOWN").unwrap_or_default());
5521        let (inf, outf, nu, ne, rbi) = (
5522            in_f as i32,
5523            out_f as i32,
5524            n_used as i32,
5525            n_expert as i32,
5526            rb as i64,
5527        );
5528        // the w8 twins' smem tile is [RPW][8] — n_used must fit the 8-slot tile;
5529        // the h2 twins are nsb==16 (in_f==512) shape-gated.
5530        let (f, cfg) = match mode.as_str() {
5531            m @ ("1" | "2" | "4") if n_used <= 8 => {
5532                let rpw: usize = m.parse().unwrap();
5533                let f = self.func(match rpw {
5534                    1 => "moe_down8_fma_dev_q8_w8r1",
5535                    2 => "moe_down8_fma_dev_q8_w8r2",
5536                    _ => "moe_down8_fma_dev_q8_w8r4",
5537                });
5538                (
5539                    f,
5540                    LaunchConfig {
5541                        grid_dim: (out_f.div_ceil(rpw) as u32, 1, 1),
5542                        block_dim: (32, n_used as u32, 1),
5543                        shared_mem_bytes: 0,
5544                    },
5545                )
5546            }
5547            "h2" if in_f == 512 => (
5548                self.func("moe_down8_fma_dev_q8_h2"),
5549                LaunchConfig {
5550                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5551                    block_dim: (32, 1, 1),
5552                    shared_mem_bytes: 0,
5553                },
5554            ),
5555            // "" = AUTO gemma shape (in_f==704): w8r2 measured +1 tok/s vs base (sweep
5556            // 1/2/4 -> 133.6/134.2/133.6, 2026-07-10); slot-ordered chain preserved.
5557            "" if in_f == 704 && n_used <= 8 => (
5558                self.func("moe_down8_fma_dev_q8_w8r2"),
5559                LaunchConfig {
5560                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5561                    block_dim: (32, n_used as u32, 1),
5562                    shared_mem_bytes: 0,
5563                },
5564            ),
5565            // "" = AUTO: the measured winner for the 35B expert shape (arc 2026-07-05, +3.8%);
5566            // any shape the h2 kernels can't take (nsb!=16 / n_used>8) falls to base via `_`.
5567            // _v twins (down8 lane 2026-07-08): wide-load IQ4_XS dot, bit-identical outputs.
5568            "w8h2v" | "" if in_f == 512 && n_used <= 8 => (
5569                self.func("moe_down8_fma_dev_q8_w8h2v"),
5570                LaunchConfig {
5571                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5572                    block_dim: (32, n_used as u32, 1),
5573                    shared_mem_bytes: 0,
5574                },
5575            ),
5576            "w8h2r2v" if in_f == 512 && n_used <= 8 => (
5577                self.func("moe_down8_fma_dev_q8_w8h2r2v"),
5578                LaunchConfig {
5579                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5580                    block_dim: (32, n_used as u32, 1),
5581                    shared_mem_bytes: 0,
5582                },
5583            ),
5584            "w8h2r2" if in_f == 512 && n_used <= 8 => (
5585                self.func("moe_down8_fma_dev_q8_w8h2r2"),
5586                LaunchConfig {
5587                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5588                    block_dim: (32, n_used as u32, 1),
5589                    shared_mem_bytes: 0,
5590                },
5591            ),
5592            "w8h2" if in_f == 512 && n_used <= 8 => (
5593                self.func("moe_down8_fma_dev_q8_w8h2"),
5594                LaunchConfig {
5595                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5596                    block_dim: (32, n_used as u32, 1),
5597                    shared_mem_bytes: 0,
5598                },
5599            ),
5600            _ => (
5601                self.func("moe_down8_fma_dev_q8"),
5602                LaunchConfig {
5603                    grid_dim: (out_f as u32, 1, 1),
5604                    block_dim: (32, 1, 1),
5605                    shared_mem_bytes: 0,
5606                },
5607            ),
5608        };
5609        let __s_b = self.gpu.stream();
5610        let mut b = __s_b.launch_builder(&f);
5611        b.arg(table)
5612            .arg(sel)
5613            .arg(w)
5614            .arg(aq2)
5615            .arg(ad2)
5616            .arg(dst)
5617            .arg(&inf)
5618            .arg(&outf)
5619            .arg(&nu)
5620            .arg(&ne)
5621            .arg(&qt)
5622            .arg(&rbi);
5623        unsafe {
5624            b.launch(cfg)?;
5625        }
5626        Ok(())
5627    }
5628
5629    /// SMALL-M VERIFY rows twin (MEMRA_SPEC_M2, lane/spec-m2): ONE launch covers all `t` tokens
5630    /// of the spec verify's MoE dev gate/up (grid.z = token) — the _v geometry per token, with
5631    /// tok-offset sel/aq/ad/act pointers matching the serial loop's slices. BIT-IDENTICAL per
5632    /// token (see the kernel header). aq/ad are the BATCHED z-quantize ([t, in_f] rows —
5633    /// quantize_q8_1's per-32-block program is row-independent, so batched rows == the serial
5634    /// loop's per-token quantize_q8_1_view bytes). Returns act [t, n_used, n_ff].
5635    #[allow(clippy::too_many_arguments)]
5636    pub fn moe_gate_up_silu8_dev_q8_rows(
5637        &self,
5638        table: &CudaSlice<u64>,
5639        sel: &CudaSlice<i32>,
5640        aq: &CudaSlice<i8>,
5641        ad: &CudaSlice<f32>,
5642        t: usize,
5643        in_f: usize,
5644        n_ff: usize,
5645        n_used: usize,
5646        n_expert: usize,
5647        qt_g: i32,
5648        qt_u: i32,
5649        rb_g: usize,
5650        rb_u: usize,
5651        macros: &CudaSlice<f32>,
5652    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5653        let f = self.func("moe_gate_up_silu8_dev_q8_v_rows");
5654        let mut act = self.alloc_uninit::<f32>(t * n_used * n_ff)?;
5655        let cfg = LaunchConfig {
5656            grid_dim: (n_ff as u32, n_used as u32, t as u32),
5657            block_dim: (32, 1, 1),
5658            shared_mem_bytes: 0,
5659        };
5660        let (inf, nff, ne, nu, rbg, rbu) = (
5661            in_f as i32,
5662            n_ff as i32,
5663            n_expert as i32,
5664            n_used as i32,
5665            rb_g as i64,
5666            rb_u as i64,
5667        );
5668        let __s_b = self.gpu.stream();
5669        let mut b = __s_b.launch_builder(&f);
5670        b.arg(table)
5671            .arg(sel)
5672            .arg(aq)
5673            .arg(ad)
5674            .arg(&mut act)
5675            .arg(&inf)
5676            .arg(&nff)
5677            .arg(&ne)
5678            .arg(&qt_g)
5679            .arg(&qt_u)
5680            .arg(&rbg)
5681            .arg(&rbu)
5682            .arg(&nu)
5683            .arg(macros);
5684        unsafe {
5685            b.launch(cfg)?;
5686        }
5687        Ok(act)
5688    }
5689
5690    /// SMALL-M VERIFY rows twin of the down proj: w8h2v geometry per token on a grid.z token
5691    /// axis. Caller gates the w8h2v shape contract (in_f == 512, n_used <= 8) — same gate as
5692    /// the AUTO dispatch in `moe_down8_fma_dev_q8`. aq2/ad2 = batched act quantize
5693    /// ([t*n_used, in_f] rows). dst rows are FULLY overwritten per token.
5694    #[allow(clippy::too_many_arguments)]
5695    pub fn moe_down8_fma_dev_q8_rows(
5696        &self,
5697        table: &CudaSlice<u64>,
5698        sel: &CudaSlice<i32>,
5699        w: &CudaSlice<f32>,
5700        aq2: &CudaSlice<i8>,
5701        ad2: &CudaSlice<f32>,
5702        dst: &mut CudaSlice<f32>,
5703        t: usize,
5704        in_f: usize,
5705        out_f: usize,
5706        n_used: usize,
5707        n_expert: usize,
5708        qt: i32,
5709        rb: usize,
5710    ) -> Result<(), Box<dyn std::error::Error>> {
5711        assert!(
5712            in_f == 512 && n_used <= 8,
5713            "down rows twin is w8h2v shape-gated"
5714        );
5715        let f = self.func("moe_down8_fma_dev_q8_w8h2v_rows");
5716        let cfg = LaunchConfig {
5717            grid_dim: (out_f.div_ceil(2) as u32, 1, t as u32),
5718            block_dim: (32, n_used as u32, 1),
5719            shared_mem_bytes: 0,
5720        };
5721        let (inf, outf, nu, ne, rbi) = (
5722            in_f as i32,
5723            out_f as i32,
5724            n_used as i32,
5725            n_expert as i32,
5726            rb as i64,
5727        );
5728        let __s_b = self.gpu.stream();
5729        let mut b = __s_b.launch_builder(&f);
5730        b.arg(table)
5731            .arg(sel)
5732            .arg(w)
5733            .arg(aq2)
5734            .arg(ad2)
5735            .arg(dst)
5736            .arg(&inf)
5737            .arg(&outf)
5738            .arg(&nu)
5739            .arg(&ne)
5740            .arg(&qt)
5741            .arg(&rbi);
5742        unsafe {
5743            b.launch(cfg)?;
5744        }
5745        Ok(())
5746    }
5747
5748    /// CSR gate/up v3 (owner-scan dedup, no build kernel): qtypes {IQ4_XS, IQ3_S} (caller
5749    /// gates), grid.y = pair index; the first pair of each expert serves all its pairs.
5750    /// Bit-identical to moe_gate_up_silu8_dev_q8_v_rows (explicit-intrinsic accumulate).
5751    #[allow(clippy::too_many_arguments)]
5752    pub fn moe_gate_up_silu8_dev_q8_csr(
5753        &self,
5754        table: &CudaSlice<u64>,
5755        sel: &CudaSlice<i32>,
5756        aq: &CudaSlice<i8>,
5757        ad: &CudaSlice<f32>,
5758        n_pairs: usize,
5759        in_f: usize,
5760        n_ff: usize,
5761        n_used: usize,
5762        n_expert: usize,
5763        qt_g: i32,
5764        qt_u: i32,
5765        rb_g: usize,
5766        rb_u: usize,
5767    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5768        let f = self.func("moe_gate_up_silu8_dev_q8_csr_iq4");
5769        let mut act = self.alloc_uninit::<f32>(n_pairs * n_ff)?;
5770        let cfg = LaunchConfig {
5771            grid_dim: (n_ff as u32, n_pairs as u32, 1),
5772            block_dim: (32, 1, 1),
5773            shared_mem_bytes: 0,
5774        };
5775        let (inf, nff, ne, nu, npi, rbg, rbu) = (
5776            in_f as i32,
5777            n_ff as i32,
5778            n_expert as i32,
5779            n_used as i32,
5780            n_pairs as i32,
5781            rb_g as i64,
5782            rb_u as i64,
5783        );
5784        let __s_b = self.gpu.stream();
5785        let mut b = __s_b.launch_builder(&f);
5786        b.arg(table)
5787            .arg(sel)
5788            .arg(aq)
5789            .arg(ad)
5790            .arg(&mut act)
5791            .arg(&inf)
5792            .arg(&nff)
5793            .arg(&ne)
5794            .arg(&qt_g)
5795            .arg(&qt_u)
5796            .arg(&rbg)
5797            .arg(&rbu)
5798            .arg(&nu)
5799            .arg(&npi);
5800        unsafe {
5801            b.launch(cfg)?;
5802        }
5803        Ok(act)
5804    }
5805
5806    /// TEST SEAM (down8 lane 2026-07-08): launch a down dev_q8 variant BY NAME with its
5807    /// canonical geometry, bypassing the env-cached dispatch so moe-devq8-check can byte-
5808    /// compare variants in one process. Variants: "base", "w8h2", "w8h2r2", "w8h2v", "w8h2r2v".
5809    #[allow(clippy::too_many_arguments)]
5810    pub fn moe_down8_fma_dev_q8_variant(
5811        &self,
5812        variant: &str,
5813        table: &CudaSlice<u64>,
5814        sel: &cudarc::driver::CudaView<i32>,
5815        w: &cudarc::driver::CudaView<f32>,
5816        aq2: &CudaSlice<i8>,
5817        ad2: &CudaSlice<f32>,
5818        dst: &mut cudarc::driver::CudaViewMut<f32>,
5819        in_f: usize,
5820        out_f: usize,
5821        n_used: usize,
5822        n_expert: usize,
5823        qt: i32,
5824        rb: usize,
5825    ) -> Result<(), Box<dyn std::error::Error>> {
5826        let (inf, outf, nu, ne, rbi) = (
5827            in_f as i32,
5828            out_f as i32,
5829            n_used as i32,
5830            n_expert as i32,
5831            rb as i64,
5832        );
5833        let (f, cfg) = match variant {
5834            "w8h2" | "w8h2v" => (
5835                self.func(if variant == "w8h2" {
5836                    "moe_down8_fma_dev_q8_w8h2"
5837                } else {
5838                    "moe_down8_fma_dev_q8_w8h2v"
5839                }),
5840                LaunchConfig {
5841                    grid_dim: (out_f.div_ceil(2) as u32, 1, 1),
5842                    block_dim: (32, n_used as u32, 1),
5843                    shared_mem_bytes: 0,
5844                },
5845            ),
5846            "w8h2r2" | "w8h2r2v" => (
5847                self.func(if variant == "w8h2r2" {
5848                    "moe_down8_fma_dev_q8_w8h2r2"
5849                } else {
5850                    "moe_down8_fma_dev_q8_w8h2r2v"
5851                }),
5852                LaunchConfig {
5853                    grid_dim: (out_f.div_ceil(4) as u32, 1, 1),
5854                    block_dim: (32, n_used as u32, 1),
5855                    shared_mem_bytes: 0,
5856                },
5857            ),
5858            _ => (
5859                self.func("moe_down8_fma_dev_q8"),
5860                LaunchConfig {
5861                    grid_dim: (out_f as u32, 1, 1),
5862                    block_dim: (32, 1, 1),
5863                    shared_mem_bytes: 0,
5864                },
5865            ),
5866        };
5867        let __s_b = self.gpu.stream();
5868        let mut b = __s_b.launch_builder(&f);
5869        b.arg(table)
5870            .arg(sel)
5871            .arg(w)
5872            .arg(aq2)
5873            .arg(ad2)
5874            .arg(dst)
5875            .arg(&inf)
5876            .arg(&outf)
5877            .arg(&nu)
5878            .arg(&ne)
5879            .arg(&qt)
5880            .arg(&rbi);
5881        unsafe {
5882            b.launch(cfg)?;
5883        }
5884        Ok(())
5885    }
5886
5887    /// TEST SEAM (down8 lane): gate_up twin of the above. Variants: "base", "v".
5888    #[allow(clippy::too_many_arguments)]
5889    pub fn moe_gate_up_silu8_dev_q8_variant(
5890        &self,
5891        variant: &str,
5892        table: &CudaSlice<u64>,
5893        sel: &cudarc::driver::CudaView<i32>,
5894        aq: &CudaSlice<i8>,
5895        ad: &CudaSlice<f32>,
5896        in_f: usize,
5897        n_ff: usize,
5898        n_used: usize,
5899        n_expert: usize,
5900        qt_g: i32,
5901        qt_u: i32,
5902        rb_g: usize,
5903        rb_u: usize,
5904    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5905        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?;
5906        let (inf, nff, ne, rbg, rbu) = (
5907            in_f as i32,
5908            n_ff as i32,
5909            n_expert as i32,
5910            rb_g as i64,
5911            rb_u as i64,
5912        );
5913        let f = self.func(if variant == "v" {
5914            "moe_gate_up_silu8_dev_q8_v"
5915        } else {
5916            "moe_gate_up_silu8_dev_q8"
5917        });
5918        let cfg = LaunchConfig {
5919            grid_dim: (n_ff as u32, n_used as u32, 1),
5920            block_dim: (32, 1, 1),
5921            shared_mem_bytes: 0,
5922        };
5923        let __s_b = self.gpu.stream();
5924        let mut b = __s_b.launch_builder(&f);
5925        b.arg(table)
5926            .arg(sel)
5927            .arg(aq)
5928            .arg(ad)
5929            .arg(&mut act)
5930            .arg(&inf)
5931            .arg(&nff)
5932            .arg(&ne)
5933            .arg(&qt_g)
5934            .arg(&qt_u)
5935            .arg(&rbg)
5936            .arg(&rbu);
5937        unsafe {
5938            b.launch(cfg)?;
5939        }
5940        Ok(act)
5941    }
5942
5943    pub fn moe_gate_up_silu8_dev(
5944        &self,
5945        table: &CudaSlice<u64>,
5946        sel: &cudarc::driver::CudaView<i32>,
5947        x: &cudarc::driver::CudaView<f32>,
5948        in_f: usize,
5949        n_ff: usize,
5950        n_used: usize,
5951        n_expert: usize,
5952        qt_g: i32,
5953        qt_u: i32,
5954        rb_g: usize,
5955        rb_u: usize,
5956        macros: &CudaSlice<f32>,
5957    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
5958        let f = self.func("moe_gate_up_silu8_dev");
5959        let mut act = self.alloc_uninit::<f32>(n_used * n_ff)?; // fully overwritten
5960        let cfg = LaunchConfig {
5961            grid_dim: (n_ff as u32, n_used as u32, 1),
5962            block_dim: (256, 1, 1),
5963            shared_mem_bytes: 0,
5964        };
5965        let (inf, nff, ne, rbg, rbu) = (
5966            in_f as i32,
5967            n_ff as i32,
5968            n_expert as i32,
5969            rb_g as i64,
5970            rb_u as i64,
5971        );
5972        let __s_b = self.gpu.stream();
5973        let mut b = __s_b.launch_builder(&f);
5974        b.arg(table)
5975            .arg(sel)
5976            .arg(x)
5977            .arg(&mut act)
5978            .arg(&inf)
5979            .arg(&nff)
5980            .arg(&ne)
5981            .arg(&qt_g)
5982            .arg(&qt_u)
5983            .arg(&rbg)
5984            .arg(&rbu)
5985            .arg(macros);
5986        unsafe {
5987            b.launch(cfg)?;
5988        }
5989        Ok(act)
5990    }
5991
5992    /// LAUNCH-STRUCTURE STAGE 3: device-dispatch twin of `moe_down8_fma_into` — expert ids AND
5993    /// renormalized weights read from the router kernel's device output. BIT-IDENTICAL chain.
5994    #[allow(clippy::too_many_arguments)]
5995    pub fn moe_down8_fma_dev(
5996        &self,
5997        table: &CudaSlice<u64>,
5998        sel: &cudarc::driver::CudaView<i32>,
5999        w: &cudarc::driver::CudaView<f32>,
6000        act: &CudaSlice<f32>,
6001        dst: &mut cudarc::driver::CudaViewMut<f32>,
6002        in_f: usize,
6003        out_f: usize,
6004        n_used: usize,
6005        n_expert: usize,
6006        qt: i32,
6007        rb: usize,
6008    ) -> Result<(), Box<dyn std::error::Error>> {
6009        let f = self.func("moe_down8_fma_dev");
6010        let cfg = LaunchConfig {
6011            grid_dim: (out_f as u32, 1, 1),
6012            block_dim: (256, 1, 1),
6013            shared_mem_bytes: 0,
6014        };
6015        let (inf, outf, nu, ne, rbv) = (
6016            in_f as i32,
6017            out_f as i32,
6018            n_used as i32,
6019            n_expert as i32,
6020            rb as i64,
6021        );
6022        let __s_b = self.gpu.stream();
6023        let mut b = __s_b.launch_builder(&f);
6024        b.arg(table)
6025            .arg(sel)
6026            .arg(w)
6027            .arg(act)
6028            .arg(dst)
6029            .arg(&inf)
6030            .arg(&outf)
6031            .arg(&nu)
6032            .arg(&ne)
6033            .arg(&qt)
6034            .arg(&rbv);
6035        unsafe {
6036            b.launch(cfg)?;
6037        }
6038        Ok(())
6039    }
6040
6041    /// dst[i] += alpha * src[i], i in 0..n. dst is a CudaViewMut (a row of moe_out).
6042    pub fn axpy_into(
6043        &self,
6044        src: &CudaSlice<f32>,
6045        alpha: f32,
6046        dst: &mut cudarc::driver::CudaViewMut<f32>,
6047        n: usize,
6048    ) -> Result<(), Box<dyn std::error::Error>> {
6049        let f = self.func("axpy_f32");
6050        let cfg = LaunchConfig::for_num_elems(n as u32);
6051        let (a, ni) = (alpha, n as i32);
6052        let __s_b = self.gpu.stream();
6053        let mut b = __s_b.launch_builder(&f);
6054        b.arg(src).arg(dst).arg(&a).arg(&ni);
6055        unsafe {
6056            b.launch(cfg)?;
6057        }
6058        Ok(())
6059    }
6060
6061    /// dst[r*ncols + c] += src[r*ncols + c] * scale[r]. Per-row scalar accumulate (shared expert).
6062    pub fn add_scaled_rows(
6063        &self,
6064        src: &CudaSlice<f32>,
6065        scale: &CudaSlice<f32>,
6066        dst: &mut CudaSlice<f32>,
6067        ncols: usize,
6068        nrows: usize,
6069    ) -> Result<(), Box<dyn std::error::Error>> {
6070        let f = self.func("add_scaled_rows_f32");
6071        let cfg = LaunchConfig::for_num_elems((ncols * nrows) as u32);
6072        let (nc, nr) = (ncols as i32, nrows as i32);
6073        let __s_b = self.gpu.stream();
6074        let mut b = __s_b.launch_builder(&f);
6075        b.arg(src).arg(scale).arg(dst).arg(&nc).arg(&nr);
6076        unsafe {
6077            b.launch(cfg)?;
6078        }
6079        Ok(())
6080    }
6081
6082    // ======== A2 GROUPED MoE PREFILL KERNELS ========
6083
6084    /// Gather m_e rows from src[T, ncols] into dst[m_e, ncols] using index array idx[m_e].
6085    pub fn gather_rows(
6086        &self,
6087        src: &CudaSlice<f32>,
6088        idx: &CudaSlice<i32>,
6089        dst: &mut CudaSlice<f32>,
6090        ncols: usize,
6091        m_e: usize,
6092    ) -> Result<(), Box<dyn std::error::Error>> {
6093        let f = self.func("gather_rows_f32");
6094        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6095        let (nc, me) = (ncols as i32, m_e as i32);
6096        let __s_b = self.gpu.stream();
6097        let mut b = __s_b.launch_builder(&f);
6098        b.arg(src).arg(idx).arg(dst).arg(&nc).arg(&me);
6099        unsafe {
6100            b.launch(cfg)?;
6101        }
6102        Ok(())
6103    }
6104
6105    /// Scatter expert outputs into per-token slots: dst[tok_idx[r], slot_idx[r], :] = src[r, :] * weight[r].
6106    /// dst is [T, n_used, ncols], zero-initialized. Each (expert, token) pair maps to a unique slot.
6107    /// Scatter expert outputs into per-token slots (raw copy, no weight multiply).
6108    /// Weight stored into wbuf[tok*n_used + slot] for FMA in reduce step.
6109    pub fn scatter_slot(
6110        &self,
6111        src: &CudaSlice<f32>,
6112        tok_idx: &CudaSlice<i32>,
6113        slot_idx: &CudaSlice<i32>,
6114        weight: &CudaSlice<f32>,
6115        dst: &mut CudaSlice<f32>,
6116        wbuf: &mut CudaSlice<f32>,
6117        ncols: usize,
6118        n_used: usize,
6119        m_e: usize,
6120    ) -> Result<(), Box<dyn std::error::Error>> {
6121        let f = self.func("scatter_add_slot_f32");
6122        let cfg = LaunchConfig::for_num_elems((m_e * ncols) as u32);
6123        let (nc, nu, me) = (ncols as i32, n_used as i32, m_e as i32);
6124        let __s_b = self.gpu.stream();
6125        let mut b = __s_b.launch_builder(&f);
6126        b.arg(src)
6127            .arg(tok_idx)
6128            .arg(slot_idx)
6129            .arg(weight)
6130            .arg(dst)
6131            .arg(wbuf)
6132            .arg(&nc)
6133            .arg(&nu)
6134            .arg(&me);
6135        unsafe {
6136            b.launch(cfg)?;
6137        }
6138        Ok(())
6139    }
6140
6141    /// Reduce n_used slots per token: dst[t, col] = sum_s slots[t, s, col].
6142    /// Reduce n_used slots per token: dst[t, col] = sum_s FMA(wbuf[t,s], slots[t,s,col], acc).
6143    /// Uses FMA for bit-identity with the sequential axpy path.
6144    pub fn reduce_slots(
6145        &self,
6146        slots: &CudaSlice<f32>,
6147        wbuf: &CudaSlice<f32>,
6148        dst: &mut CudaSlice<f32>,
6149        ncols: usize,
6150        n_used: usize,
6151        t: usize,
6152    ) -> Result<(), Box<dyn std::error::Error>> {
6153        let f = self.func("reduce_slots_f32");
6154        let cfg = LaunchConfig::for_num_elems((t * ncols) as u32);
6155        let (nc, nu, ti) = (ncols as i32, n_used as i32, t as i32);
6156        let __s_b = self.gpu.stream();
6157        let mut b = __s_b.launch_builder(&f);
6158        b.arg(slots).arg(wbuf).arg(dst).arg(&nc).arg(&nu).arg(&ti);
6159        unsafe {
6160            b.launch(cfg)?;
6161        }
6162        Ok(())
6163    }
6164
6165    /// Stage-B: quantize activation [m,in] f32 -> q8_1 (int8 qs + per-block f32 scale).
6166    /// Quantize an activation [m, in_f] to q8_1 (int8 qs + per-32 f32 scale). Public so the
6167    /// forward can quantize a SHARED activation ONCE and feed it to several matmuls (gate+up
6168    /// share `z`; q/k/v and wqkv/gate/beta/alpha share `h`) — quantize_q8_1 was 13.5% of decode
6169    /// GPU time, ~half of it redundant re-quantization of the same row.
6170    /// quantize_q8_1 over a CudaView (a sliced z-row) — same kernel, offset-honoring arg.
6171    pub fn quantize_q8_1_view(
6172        &self,
6173        x: &cudarc::driver::CudaView<f32>,
6174        m: usize,
6175        in_f: usize,
6176    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6177        let f = self.func("quantize_q8_1");
6178        let nblk = in_f / 32;
6179        let mut q = self.alloc_uninit::<i8>(m * in_f)?;
6180        let mut d = self.alloc_uninit::<f32>(m * nblk)?;
6181        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6182        let (inf, mi) = (in_f as i32, m as i32);
6183        let __s_b = self.gpu.stream();
6184        let mut b = __s_b.launch_builder(&f);
6185        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6186        unsafe {
6187            b.launch(cfg)?;
6188        }
6189        Ok((q, d))
6190    }
6191
6192    pub fn quantize_q8_1(
6193        &self,
6194        x: &CudaSlice<f32>,
6195        m: usize,
6196        in_f: usize,
6197    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
6198        let nblk = in_f / 32;
6199        let mut q = self.alloc_uninit::<i8>(m * in_f)?; // full-overwrite output: skip memset
6200        let mut d = self.alloc_uninit::<f32>(m * nblk)?; // full-overwrite output: skip memset
6201        // WARP-PER-BLOCK kernel: one warp per 32-block -> m*in_f threads total.
6202        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
6203        let (inf, mi) = (in_f as i32, m as i32);
6204        if Self::pdl_on() && Self::pdl_wb_on() {
6205            {
6206                use cudarc::driver::{DevicePtr, DevicePtrMut};
6207                let s = &self.gpu.stream();
6208                let (px, _g0) = x.device_ptr(s);
6209                let (pq, _g1) = q.device_ptr_mut(s);
6210                let (pd, _g2) = d.device_ptr_mut(s);
6211                let mut ps = [
6212                    &px as *const _ as *mut std::ffi::c_void,
6213                    &pq as *const _ as *mut _,
6214                    &pd as *const _ as *mut _,
6215                    &inf as *const _ as *mut _,
6216                    &mi as *const _ as *mut _,
6217                ];
6218                unsafe {
6219                    self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
6220                }
6221            }
6222            return Ok((q, d));
6223        }
6224        let f = self.func("quantize_q8_1");
6225        let __s_b = self.gpu.stream();
6226        let mut b = __s_b.launch_builder(&f);
6227        b.arg(x).arg(&mut q).arg(&mut d).arg(&inf).arg(&mi);
6228        unsafe {
6229            b.launch(cfg)?;
6230        }
6231        Ok((q, d))
6232    }
6233
6234    /// Stage-C FP4: quantize activation [m,in] f32 -> e2m1 nibbles (aq4: u32 [m, in/8]) + per-16
6235    /// UE4M3 scale (ad4: u8 [m, in/16]), the layout the mxf4nvf4 block-scale GEMM B-operand wants.
6236    /// in_f must be a multiple of 64 (one NVFP4 K-block). One thread per (token, 16-block).
6237    pub fn quantize_fp4_act(
6238        &self,
6239        x: &CudaSlice<f32>,
6240        m: usize,
6241        in_f: usize,
6242    ) -> Result<(CudaSlice<u32>, CudaSlice<u8>), Box<dyn std::error::Error>> {
6243        let f = self.func("quantize_fp4_act");
6244        let nb16 = in_f / 16;
6245        let mut aq4 = self.alloc_uninit::<u32>(m * (in_f / 8))?; // full-overwrite output: skip memset
6246        let mut ad4 = self.alloc_uninit::<u8>(m * nb16)?; // full-overwrite output: skip memset
6247        let cfg = LaunchConfig::for_num_elems((m * nb16) as u32);
6248        let (inf, mi) = (in_f as i32, m as i32);
6249        let __s_b = self.gpu.stream();
6250        let mut b = __s_b.launch_builder(&f);
6251        b.arg(x).arg(&mut aq4).arg(&mut ad4).arg(&inf).arg(&mi);
6252        unsafe {
6253            b.launch(cfg)?;
6254        }
6255        Ok((aq4, ad4))
6256    }
6257
6258    /// Stage-C FP4 GEMM (NVFP4 weights): native mxf4nvf4 block-scale tensor-core matmul. Feeds raw
6259    /// e2m1 weight nibbles + raw UE4M3 micro-scales directly to mma.sync.m16n8k64 (762 TFLOP/s peak,
6260    /// 3.5x int8). Activation `x` is quantized to FP4 e2m1 here. NVFP4 per-tensor macro-scale applied
6261    /// post (scale==1.0 -> no-op). `bytes` = raw NVFP4 weight rows. Used by the MEMRA_FP4 prefill path.
6262    pub fn qmatvec_gemm_nvfp4_fp4(
6263        &self,
6264        bytes: &CudaSlice<u8>,
6265        x: &CudaSlice<f32>,
6266        m: usize,
6267        in_f: usize,
6268        out_f: usize,
6269        row_bytes: usize,
6270        scale: f32,
6271    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6272        assert!(
6273            in_f % 64 == 0,
6274            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6275        );
6276        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6277        let mut y = self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)?;
6278        if scale != 1.0 {
6279            self.scale_inplace(&mut y, scale, m * out_f)?;
6280        }
6281        Ok(y)
6282    }
6283
6284    /// Shared mxf4 GEMM launch (pre-quantized FP4 activation aq4/ad4). Same CTA tile as the int8 GEMM
6285    /// (BM=64 rows x BN=128 tokens, 4 warps). No macro-scale applied here.
6286    fn fp4_gemm_launch(
6287        &self,
6288        bytes: &CudaSlice<u8>,
6289        aq4: &CudaSlice<u32>,
6290        ad4: &CudaSlice<u8>,
6291        m: usize,
6292        in_f: usize,
6293        out_f: usize,
6294        row_bytes: usize,
6295    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6296        let f = self.func("qmatvec_gemm_nvfp4_fp4");
6297        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6298        const BM: u32 = 64;
6299        const BN: u32 = 256;
6300        let cfg = LaunchConfig {
6301            grid_dim: ((out_f as u32 + BM - 1) / BM, (m as u32 + BN - 1) / BN, 1),
6302            block_dim: (32, 4, 1),
6303            shared_mem_bytes: 0,
6304        };
6305        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6306        let __s_b = self.gpu.stream();
6307        let mut b = __s_b.launch_builder(&f);
6308        b.arg(bytes)
6309            .arg(aq4)
6310            .arg(ad4)
6311            .arg(&mut y)
6312            .arg(&inf)
6313            .arg(&outf)
6314            .arg(&mi)
6315            .arg(&rb);
6316        unsafe {
6317            b.launch(cfg)?;
6318        }
6319        Ok(y)
6320    }
6321
6322    /// Test entry (kernel_check): run the FP4 GEMM from raw bytes; NO macro-scale (caller compares bare).
6323    pub fn qmatvec_gemm_nvfp4_fp4_raw(
6324        &self,
6325        bytes: &CudaSlice<u8>,
6326        x: &CudaSlice<f32>,
6327        m: usize,
6328        in_f: usize,
6329        out_f: usize,
6330        row_bytes: usize,
6331    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6332        assert!(
6333            in_f % 64 == 0,
6334            "FP4 GEMM requires in_f % 64 == 0, got {in_f}"
6335        );
6336        let (aq4, ad4) = self.quantize_fp4_act(x, m, in_f)?;
6337        self.fp4_gemm_launch(bytes, &aq4, &ad4, m, in_f, out_f, row_bytes)
6338    }
6339
6340    /// Stage-B: Q8_0 weight x q8_1 activation int8 dp4a matmul. y[m,out]=x@W^T.
6341    pub fn qmatvec_q8_0_fast(
6342        &self,
6343        w: &CudaSlice<u8>,
6344        x: &CudaSlice<f32>,
6345        m: usize,
6346        in_f: usize,
6347        out_f: usize,
6348        row_bytes: usize,
6349    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6350        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6351        let f = self.func("qmatvec_q8_0_dp4a");
6352        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6353        let cfg = LaunchConfig {
6354            grid_dim: (out_f as u32, m as u32, 1),
6355            block_dim: (128, 1, 1),
6356            shared_mem_bytes: 0,
6357        };
6358        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6359        let __s_b = self.gpu.stream();
6360        let mut b = __s_b.launch_builder(&f);
6361        b.arg(w)
6362            .arg(&aq)
6363            .arg(&ad)
6364            .arg(&mut y)
6365            .arg(&inf)
6366            .arg(&outf)
6367            .arg(&mi)
6368            .arg(&rb);
6369        unsafe {
6370            b.launch(cfg)?;
6371        }
6372        Ok(y)
6373    }
6374
6375    /// Stage-B: Q4_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6376    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6377    pub fn qmatvec_q4_K_fast(
6378        &self,
6379        w: &CudaSlice<u8>,
6380        x: &CudaSlice<f32>,
6381        m: usize,
6382        in_f: usize,
6383        out_f: usize,
6384        row_bytes: usize,
6385    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6386        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6387        let f = self.func("qmatvec_q4_K_dp4a");
6388        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6389        let cfg = LaunchConfig {
6390            grid_dim: (out_f as u32, m as u32, 1),
6391            block_dim: (128, 1, 1),
6392            shared_mem_bytes: 0,
6393        };
6394        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6395        let __s_b = self.gpu.stream();
6396        let mut b = __s_b.launch_builder(&f);
6397        b.arg(w)
6398            .arg(&aq)
6399            .arg(&ad)
6400            .arg(&mut y)
6401            .arg(&inf)
6402            .arg(&outf)
6403            .arg(&mi)
6404            .arg(&rb);
6405        unsafe {
6406            b.launch(cfg)?;
6407        }
6408        Ok(y)
6409    }
6410
6411    /// Stage-B: Q6_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6412    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6413    pub fn qmatvec_q6_K_fast(
6414        &self,
6415        w: &CudaSlice<u8>,
6416        x: &CudaSlice<f32>,
6417        m: usize,
6418        in_f: usize,
6419        out_f: usize,
6420        row_bytes: usize,
6421    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6422        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6423        let f = self.func("qmatvec_q6_K_dp4a");
6424        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6425        let cfg = LaunchConfig {
6426            grid_dim: (out_f as u32, m as u32, 1),
6427            block_dim: (128, 1, 1),
6428            shared_mem_bytes: 0,
6429        };
6430        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6431        let __s_b = self.gpu.stream();
6432        let mut b = __s_b.launch_builder(&f);
6433        b.arg(w)
6434            .arg(&aq)
6435            .arg(&ad)
6436            .arg(&mut y)
6437            .arg(&inf)
6438            .arg(&outf)
6439            .arg(&mi)
6440            .arg(&rb);
6441        unsafe {
6442            b.launch(cfg)?;
6443        }
6444        Ok(y)
6445    }
6446
6447    /// Stage-B: Q5_K weight x q8_1 activation int8 dp4a (decode). Min-offset via q8_1 sum term.
6448    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6449    pub fn qmatvec_q5_K_fast(
6450        &self,
6451        w: &CudaSlice<u8>,
6452        x: &CudaSlice<f32>,
6453        m: usize,
6454        in_f: usize,
6455        out_f: usize,
6456        row_bytes: usize,
6457    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6458        self.qmatvec_dp4a_named("qmatvec_q5_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6459    }
6460    /// Stage-B: Q3_K weight x q8_1 activation int8 dp4a (decode, symmetric).
6461    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6462    pub fn qmatvec_q3_K_fast(
6463        &self,
6464        w: &CudaSlice<u8>,
6465        x: &CudaSlice<f32>,
6466        m: usize,
6467        in_f: usize,
6468        out_f: usize,
6469        row_bytes: usize,
6470    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6471        self.qmatvec_dp4a_named("qmatvec_q3_K_dp4a", w, x, m, in_f, out_f, row_bytes)
6472    }
6473    /// A6 split-plane twin of `qmatvec_nvfp4_fast` (weights repacked; used by the rp gates).
6474    pub fn qmatvec_nvfp4_fast_rp(
6475        &self,
6476        w: &CudaSlice<u8>,
6477        x: &CudaSlice<f32>,
6478        m: usize,
6479        in_f: usize,
6480        out_f: usize,
6481        row_bytes: usize,
6482    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6483        assert!(
6484            in_f % 64 == 0,
6485            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6486        );
6487        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a_rp", w, x, m, in_f, out_f, row_bytes)
6488    }
6489    /// Stage-B: NVFP4 weight x q8_1 activation int8 dp4a (decode, symmetric, codebook lookup).
6490    pub fn qmatvec_nvfp4_fast(
6491        &self,
6492        w: &CudaSlice<u8>,
6493        x: &CudaSlice<f32>,
6494        m: usize,
6495        in_f: usize,
6496        out_f: usize,
6497        row_bytes: usize,
6498    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6499        // B1: the NVFP4 dp4a kernel maps two 32-elem q8_1 blocks onto one 64-elem block_nvfp4
6500        // (sblk = g >> 1). in_f must be a multiple of 64 or the last block reads a partial superblock.
6501        assert!(
6502            in_f % 64 == 0,
6503            "NVFP4 dp4a requires in_f % 64 == 0, got {in_f}"
6504        );
6505        self.qmatvec_dp4a_named("qmatvec_nvfp4_dp4a", w, x, m, in_f, out_f, row_bytes)
6506    }
6507    /// Stage-B (optional perf): IQ4_XS codebook int8 dp4a.
6508    #[allow(non_snake_case)] // Keep GGUF qtype spelling visible at the public kernel boundary.
6509    pub fn qmatvec_iq4_XS_fast(
6510        &self,
6511        w: &CudaSlice<u8>,
6512        x: &CudaSlice<f32>,
6513        m: usize,
6514        in_f: usize,
6515        out_f: usize,
6516        row_bytes: usize,
6517    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6518        self.qmatvec_dp4a_named("qmatvec_iq4_XS_dp4a", w, x, m, in_f, out_f, row_bytes)
6519    }
6520
6521    /// Shared dp4a launcher: quantize_q8_1 then call the named kernel (grid (out,m), block 64).
6522    fn qmatvec_dp4a_named(
6523        &self,
6524        name: &str,
6525        w: &CudaSlice<u8>,
6526        x: &CudaSlice<f32>,
6527        m: usize,
6528        in_f: usize,
6529        out_f: usize,
6530        row_bytes: usize,
6531    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6532        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
6533        let f = self.func(name);
6534        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
6535        let cfg = LaunchConfig {
6536            grid_dim: (out_f as u32, m as u32, 1),
6537            block_dim: (128, 1, 1),
6538            shared_mem_bytes: 0,
6539        };
6540        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
6541        let __s_b = self.gpu.stream();
6542        let mut b = __s_b.launch_builder(&f);
6543        b.arg(w)
6544            .arg(&aq)
6545            .arg(&ad)
6546            .arg(&mut y)
6547            .arg(&inf)
6548            .arg(&outf)
6549            .arg(&mi)
6550            .arg(&rb);
6551        unsafe {
6552            b.launch(cfg)?;
6553        }
6554        Ok(y)
6555    }
6556
6557    pub fn htod(&self, v: &[f32]) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6558        Ok(self.gpu.stream().clone_htod(v)?)
6559    }
6560    pub fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
6561        Ok(self.gpu.stream().clone_htod(v)?)
6562    }
6563    /// i8 upload (moe-devq8-check: synthetic q8_1 activation bytes).
6564    pub fn htod_i8(&self, v: &[i8]) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
6565        Ok(self.gpu.stream().clone_htod(v)?)
6566    }
6567    pub fn htod_u64(&self, v: &[u64]) -> Result<CudaSlice<u64>, Box<dyn std::error::Error>> {
6568        Ok(self.gpu.stream().clone_htod(v)?)
6569    }
6570    /// View twin of `dtoh` (lean-logits component 3: D2H one row of a [B, n_vocab] stack).
6571    pub fn dtoh_view(
6572        &self,
6573        d: &cudarc::driver::CudaView<f32>,
6574    ) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6575        let v = self.gpu.stream().clone_dtoh(d)?;
6576        self.gpu.stream().synchronize()?;
6577        Ok(v)
6578    }
6579    pub fn dtoh(&self, d: &CudaSlice<f32>) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
6580        let v = self.gpu.stream().clone_dtoh(d)?;
6581        self.gpu.stream().synchronize()?;
6582        Ok(v)
6583    }
6584    /// Queue two f32 device-to-host copies on the compute stream, then establish one host
6585    /// boundary for both. Hy3's CPU/GPU expert split needs the router logits and the MoE input;
6586    /// issuing them together avoids a second stream synchronization in every trunk layer.
6587    pub fn dtoh_pair(
6588        &self,
6589        a: &CudaSlice<f32>,
6590        b: &CudaSlice<f32>,
6591    ) -> Result<(Vec<f32>, Vec<f32>), Box<dyn std::error::Error>> {
6592        let av = self.gpu.stream().clone_dtoh(a)?;
6593        let bv = self.gpu.stream().clone_dtoh(b)?;
6594        self.gpu.stream().synchronize()?;
6595        Ok((av, bv))
6596    }
6597    /// Device-to-host copy of an i32 buffer (fused-router sel_idx readback).
6598    pub fn dtoh_i32(&self, d: &CudaSlice<i32>) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
6599        let v = self.gpu.stream().clone_dtoh(d)?;
6600        self.gpu.stream().synchronize()?;
6601        Ok(v)
6602    }
6603    /// Device-to-host copy of a u8 buffer (used to read back the quantized KV cache for validation).
6604    pub fn dtoh_u8(&self, d: &CudaSlice<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6605        let v = self.gpu.stream().clone_dtoh(d)?;
6606        self.gpu.stream().synchronize()?;
6607        Ok(v)
6608    }
6609    pub fn dtoh_u8_view(
6610        &self,
6611        d: &cudarc::driver::CudaView<u8>,
6612    ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
6613        let v = self.gpu.stream().clone_dtoh(d)?;
6614        self.gpu.stream().synchronize()?;
6615        Ok(v)
6616    }
6617    pub fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6618        let s = self.gpu.stream().alloc_zeros::<f32>(n)?;
6619        self.keep_if_capturing(&s);
6620        Ok(s)
6621    }
6622
6623    /// GPU-resident greedy argmax (CUDA-GRAPH-PLAN Phase 1): logits[n_vocab] -> token id in a
6624    /// resident device u32 [1]. PARALLEL 2-pass (RANK1 LEVER): the old single-CTA scan (one 256-thread
6625    /// block on one SM over 248K logits) was memory-starved at ~426us/token. Now pass 1 fans NB=256
6626    /// blocks across the SMs to saturate HBM, pass 2 reduces the NB partials. Bit-identical to host
6627    /// `argmax` (smallest index on tie). The whole point is NOT to dtoh logits — only a [1] u32 is read
6628    /// back (or kept resident for graph replay). Returns the device token buffer.
6629    /// Softmax probability of the (already-argmaxed) token `tok` under `logits` — the spec-decode
6630    /// p-min confidence signal. 2-pass like the parallel argmax; returns a device [1] f32.
6631    pub fn prob_of_token_device(
6632        &self,
6633        logits: &CudaSlice<f32>,
6634        tok: &CudaSlice<u32>,
6635        n_vocab: usize,
6636    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
6637        let nb = ARGMAX_NB;
6638        let mut part = self.alloc_uninit::<f32>(nb)?;
6639        let mut p = self.alloc_uninit::<f32>(1)?;
6640        let f1 = self.func("prob_of_token_partial_f32");
6641        let cfg1 = LaunchConfig {
6642            grid_dim: (nb as u32, 1, 1),
6643            block_dim: (256, 1, 1),
6644            shared_mem_bytes: 0,
6645        };
6646        let nv = n_vocab as i32;
6647        let __s_b1 = self.gpu.stream();
6648        let mut b1 = __s_b1.launch_builder(&f1);
6649        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6650        unsafe {
6651            b1.launch(cfg1)?;
6652        }
6653        let f2 = self.func("prob_of_token_final_f32");
6654        let cfg2 = LaunchConfig {
6655            grid_dim: (1, 1, 1),
6656            block_dim: (256, 1, 1),
6657            shared_mem_bytes: 0,
6658        };
6659        let nbi = nb as i32;
6660        let __s_b2 = self.gpu.stream();
6661        let mut b2 = __s_b2.launch_builder(&f2);
6662        b2.arg(&part).arg(&mut p).arg(&nbi);
6663        unsafe {
6664            b2.launch(cfg2)?;
6665        }
6666        Ok(p)
6667    }
6668
6669    /// Like `prob_of_token_device` but writes into a PERSISTENT `p_out` buffer (stable pointer).
6670    /// Required for CUDA-graph capture of the draft chain: the captured prob kernels must write
6671    /// where the host reads the p-min confidence between replays. Same kernels, same math.
6672    /// Slot-addressed twin of `prob_of_token_device_into`: token read from `tok_all[tok_idx]`
6673    /// (a view at the slot), probability written to `p_out[p_idx]` — same two kernels, the
6674    /// pointers just land mid-buffer. Zero-sync (gemma confidence-adaptive draft depth).
6675    pub fn prob_of_token_device_col(
6676        &self,
6677        logits: &CudaSlice<f32>,
6678        tok_all: &CudaSlice<u32>,
6679        tok_idx: usize,
6680        p_out: &mut CudaSlice<f32>,
6681        p_idx: usize,
6682        n_vocab: usize,
6683    ) -> Result<(), Box<dyn std::error::Error>> {
6684        let tok_v = tok_all.slice(tok_idx..tok_idx + 1);
6685        let mut p_v = p_out.slice_mut(p_idx..p_idx + 1);
6686        let nb = ARGMAX_NB;
6687        let mut part = self.alloc_uninit::<f32>(nb)?;
6688        let f1 = self.func("prob_of_token_partial_f32");
6689        let cfg1 = LaunchConfig {
6690            grid_dim: (nb as u32, 1, 1),
6691            block_dim: (256, 1, 1),
6692            shared_mem_bytes: 0,
6693        };
6694        let nv = n_vocab as i32;
6695        let __s_b1 = self.gpu.stream();
6696        let mut b1 = __s_b1.launch_builder(&f1);
6697        b1.arg(logits).arg(&tok_v).arg(&mut part).arg(&nv);
6698        unsafe {
6699            b1.launch(cfg1)?;
6700        }
6701        let f2 = self.func("prob_of_token_final_f32");
6702        let cfg2 = LaunchConfig {
6703            grid_dim: (1, 1, 1),
6704            block_dim: (256, 1, 1),
6705            shared_mem_bytes: 0,
6706        };
6707        let nbi = nb as i32;
6708        let __s_b2 = self.gpu.stream();
6709        let mut b2 = __s_b2.launch_builder(&f2);
6710        b2.arg(&part).arg(&mut p_v).arg(&nbi);
6711        unsafe {
6712            b2.launch(cfg2)?;
6713        }
6714        Ok(())
6715    }
6716
6717    pub fn prob_of_token_device_into(
6718        &self,
6719        logits: &CudaSlice<f32>,
6720        tok: &CudaSlice<u32>,
6721        p_out: &mut CudaSlice<f32>,
6722        n_vocab: usize,
6723    ) -> Result<(), Box<dyn std::error::Error>> {
6724        let nb = ARGMAX_NB;
6725        let mut part = self.alloc_uninit::<f32>(nb)?;
6726        let f1 = self.func("prob_of_token_partial_f32");
6727        let cfg1 = LaunchConfig {
6728            grid_dim: (nb as u32, 1, 1),
6729            block_dim: (256, 1, 1),
6730            shared_mem_bytes: 0,
6731        };
6732        let nv = n_vocab as i32;
6733        let __s_b1 = self.gpu.stream();
6734        let mut b1 = __s_b1.launch_builder(&f1);
6735        b1.arg(logits).arg(tok).arg(&mut part).arg(&nv);
6736        unsafe {
6737            b1.launch(cfg1)?;
6738        }
6739        let f2 = self.func("prob_of_token_final_f32");
6740        let cfg2 = LaunchConfig {
6741            grid_dim: (1, 1, 1),
6742            block_dim: (256, 1, 1),
6743            shared_mem_bytes: 0,
6744        };
6745        let nbi = nb as i32;
6746        let __s_b2 = self.gpu.stream();
6747        let mut b2 = __s_b2.launch_builder(&f2);
6748        b2.arg(&part).arg(p_out).arg(&nbi);
6749        unsafe {
6750            b2.launch(cfg2)?;
6751        }
6752        Ok(())
6753    }
6754
6755    pub fn argmax_token_device(
6756        &self,
6757        logits: &CudaSlice<f32>,
6758        n_vocab: usize,
6759    ) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6760        let mut tok = unsafe { self.gpu.stream().alloc::<u32>(1)? };
6761        self.argmax_token_device_into(logits, &mut tok, n_vocab)?;
6762        Ok(tok)
6763    }
6764    /// Like `argmax_token_device` but writes into a PERSISTENT `tok` buffer (stable pointer) instead
6765    /// of allocating a fresh one. Required for CUDA-graph capture: the captured argmax must write the
6766    /// next token into the SAME device buffer the next replay's embed_gather reads, so the buffer
6767    /// pointer is baked once and the token id never round-trips to host inside steady state. The
6768    /// pass-1 partials scratch (`argmax_partials`) is also a resident stable-pointer buffer so both
6769    /// captured passes bake fixed addresses.
6770    pub fn argmax_token_device_into(
6771        &self,
6772        logits: &CudaSlice<f32>,
6773        tok: &mut CudaSlice<u32>,
6774        n_vocab: usize,
6775    ) -> Result<(), Box<dyn std::error::Error>> {
6776        let nb = ARGMAX_NB;
6777        let f1 = self.func("argmax_partial_f32");
6778        let f2 = self.func("argmax_final_f32");
6779        let mut guard = self.argmax_partials.lock().unwrap();
6780        if guard.is_none() {
6781            // allocate ONCE; under generate_graph this runs in the tracking-off prime window so the
6782            // buffers carry no cudarc events (illegal inside capture).
6783            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6784            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6785            *guard = Some((pv, pi));
6786        }
6787        let (part_v, part_i) = guard.as_mut().unwrap();
6788        let nv = n_vocab as i32;
6789        let nbi = nb as i32;
6790        // pass 1: NB blocks x 256 threads grid-stride scan -> per-block (val, idx) partials.
6791        let cfg1 = LaunchConfig {
6792            grid_dim: (nb as u32, 1, 1),
6793            block_dim: (256, 1, 1),
6794            shared_mem_bytes: 0,
6795        };
6796        let __s_b1 = self.gpu.stream();
6797        let mut b1 = __s_b1.launch_builder(&f1);
6798        b1.arg(logits).arg(&mut *part_v).arg(&mut *part_i).arg(&nv);
6799        unsafe {
6800            b1.launch(cfg1)?;
6801        }
6802        // pass 2: one block reduces NB partials -> token_out[0].
6803        let cfg2 = LaunchConfig {
6804            grid_dim: (1, 1, 1),
6805            block_dim: (256, 1, 1),
6806            shared_mem_bytes: 0,
6807        };
6808        let __s_b2 = self.gpu.stream();
6809        let mut b2 = __s_b2.launch_builder(&f2);
6810        b2.arg(&*part_v).arg(&*part_i).arg(tok).arg(&nbi);
6811        unsafe {
6812            b2.launch(cfg2)?;
6813        }
6814        Ok(())
6815    }
6816    /// Column-`col` device argmax over a stacked verify-logits buffer [t, n_vocab] (spec accept
6817    /// walk): toks[out_idx] = argmax(logits[col*n_vocab .. (col+1)*n_vocab]). SAME 2-pass kernels
6818    /// and tie-break contract as `argmax_token_device_into` (bit-identical to host argmax,
6819    /// argmax_gate-validated) — only the input pointer (a column view) and the output slot differ.
6820    /// Lets the accept walk read ONE [t] u32 instead of dtoh'ing the full [t, n_vocab] logits.
6821    pub fn argmax_token_device_col(
6822        &self,
6823        logits: &CudaSlice<f32>,
6824        col: usize,
6825        n_vocab: usize,
6826        toks: &mut CudaSlice<u32>,
6827        out_idx: usize,
6828    ) -> Result<(), Box<dyn std::error::Error>> {
6829        let nb = ARGMAX_NB;
6830        let f1 = self.func("argmax_partial_f32");
6831        let f2 = self.func("argmax_final_f32");
6832        let mut guard = self.argmax_partials.lock().unwrap();
6833        if guard.is_none() {
6834            let pv = self.gpu.stream().alloc_zeros::<f32>(nb)?;
6835            let pi = self.gpu.stream().alloc_zeros::<i32>(nb)?;
6836            *guard = Some((pv, pi));
6837        }
6838        let (part_v, part_i) = guard.as_mut().unwrap();
6839        let col_view = logits.slice(col * n_vocab..(col + 1) * n_vocab);
6840        let nv = n_vocab as i32;
6841        let nbi = nb as i32;
6842        let cfg1 = LaunchConfig {
6843            grid_dim: (nb as u32, 1, 1),
6844            block_dim: (256, 1, 1),
6845            shared_mem_bytes: 0,
6846        };
6847        let __s_b1 = self.gpu.stream();
6848        let mut b1 = __s_b1.launch_builder(&f1);
6849        b1.arg(&col_view)
6850            .arg(&mut *part_v)
6851            .arg(&mut *part_i)
6852            .arg(&nv);
6853        unsafe {
6854            b1.launch(cfg1)?;
6855        }
6856        let mut tok_view = toks.slice_mut(out_idx..out_idx + 1);
6857        let cfg2 = LaunchConfig {
6858            grid_dim: (1, 1, 1),
6859            block_dim: (256, 1, 1),
6860            shared_mem_bytes: 0,
6861        };
6862        let __s_b2 = self.gpu.stream();
6863        let mut b2 = __s_b2.launch_builder(&f2);
6864        b2.arg(&*part_v).arg(&*part_i).arg(&mut tok_view).arg(&nbi);
6865        unsafe {
6866            b2.launch(cfg2)?;
6867        }
6868        Ok(())
6869    }
6870    /// Read back a device u32 buffer (the spec accept walk's [t] per-column argmax tokens).
6871    pub fn htod_u32_v(&self, v: &[u32]) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6872        Ok(self.gpu.stream().clone_htod(v)?)
6873    }
6874    pub fn dtoh_u32(&self, d: &CudaSlice<u32>) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
6875        let v = self.gpu.stream().clone_dtoh(d)?;
6876        self.gpu.stream().synchronize()?;
6877        Ok(v)
6878    }
6879    /// Allocate a zeroed device u32 buffer (persistent spec-loop prediction slots).
6880    /// H2D into an EXISTING u32 buffer (stable pointer — the per-step grammar-mask upload:
6881    /// contents change every step, the address must not, so a captured graph can read it).
6882    pub fn htod_u32_into(
6883        &self,
6884        dst: &mut CudaSlice<u32>,
6885        src: &[u32],
6886    ) -> Result<(), Box<dyn std::error::Error>> {
6887        let mut view = dst.slice_mut(0..src.len());
6888        self.gpu.stream().memcpy_htod(src, &mut view)?;
6889        Ok(())
6890    }
6891
6892    /// H2D into an existing i32 buffer. OPTIPIPE uses this to refresh a stage-local saved-len
6893    /// table without changing the device address its reconcile kernel consumes.
6894    pub fn htod_i32_into(
6895        &self,
6896        dst: &mut CudaSlice<i32>,
6897        src: &[i32],
6898    ) -> Result<(), Box<dyn std::error::Error>> {
6899        let mut view = dst.slice_mut(0..src.len());
6900        self.gpu.stream().memcpy_htod(src, &mut view)?;
6901        Ok(())
6902    }
6903
6904    pub fn alloc_u32_zeroed(&self, n: usize) -> Result<CudaSlice<u32>, Box<dyn std::error::Error>> {
6905        let s = self.gpu.stream().alloc_zeros::<u32>(n)?;
6906        self.keep_if_capturing(&s);
6907        Ok(s)
6908    }
6909    /// embed_gather into a PERSISTENT `x_out` buffer (stable pointer) for CUDA-graph capture (the
6910    /// embed output starts the per-step kernel chain and must be at a fixed address across replays).
6911    pub fn embed_gather_device_into(
6912        &self,
6913        embd: &CudaSlice<u8>,
6914        token_d: &CudaSlice<u32>,
6915        x_out: &mut CudaSlice<f32>,
6916        n_embd: usize,
6917        qtype: i32,
6918        row_bytes: usize,
6919    ) -> Result<(), Box<dyn std::error::Error>> {
6920        let f = self.func("embed_gather_u32");
6921        let cfg = LaunchConfig {
6922            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
6923            block_dim: (256, 1, 1),
6924            shared_mem_bytes: 0,
6925        };
6926        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
6927        let __s_b = self.gpu.stream();
6928        let mut b = __s_b.launch_builder(&f);
6929        b.arg(embd)
6930            .arg(token_d)
6931            .arg(x_out)
6932            .arg(&ne)
6933            .arg(&qt)
6934            .arg(&rb);
6935        unsafe {
6936            b.launch(cfg)?;
6937        }
6938        Ok(())
6939    }
6940    /// Read a [1] i32 device counter (pos / seqlen) back to host. Tiny D2H + sync.
6941    pub fn dtoh_i32_one(&self, d: &CudaSlice<i32>) -> Result<i32, Box<dyn std::error::Error>> {
6942        let v = self.gpu.stream().clone_dtoh(d)?;
6943        self.gpu.stream().synchronize()?;
6944        Ok(v[0])
6945    }
6946    /// Set a [1] i32 device counter IN PLACE (keeps the buffer pointer stable — required for the
6947    /// graph-resident pos/seqlen counters whose addresses are baked into captured graphs). Restores
6948    /// the counter value after the throwaway capture warmups corrupt it.
6949    /// ASYNC i32 single-slot store (value rides the kernel arg — no host-memory transfer/sync).
6950    /// The graph-arc device-len counters use this; set_i32_one below is the SYNCING pageable
6951    /// copy (fine at stream-idle boundaries, poison mid-round).
6952    pub fn i32_set_k(
6953        &self,
6954        dst: &mut CudaSlice<i32>,
6955        v: i32,
6956    ) -> Result<(), Box<dyn std::error::Error>> {
6957        let f = self.func("i32_set_k");
6958        let cfg = LaunchConfig {
6959            grid_dim: (1, 1, 1),
6960            block_dim: (1, 1, 1),
6961            shared_mem_bytes: 0,
6962        };
6963        let idx = 0i32;
6964        let __s_b = self.gpu.stream();
6965        let mut b = __s_b.launch_builder(&f);
6966        b.arg(dst).arg(&v).arg(&idx);
6967        unsafe {
6968            b.launch(cfg)?;
6969        }
6970        Ok(())
6971    }
6972
6973    pub fn set_i32_one(
6974        &self,
6975        d: &mut CudaSlice<i32>,
6976        v: i32,
6977    ) -> Result<(), Box<dyn std::error::Error>> {
6978        self.gpu.stream().memcpy_htod(&[v], d)?;
6979        Ok(())
6980    }
6981    /// Set a [1] u32 device buffer IN PLACE (stable pointer) — for the resident `token_d` counter
6982    /// during priming / capture-state restore.
6983    pub fn set_u32_one(
6984        &self,
6985        d: &mut CudaSlice<u32>,
6986        v: u32,
6987    ) -> Result<(), Box<dyn std::error::Error>> {
6988        self.gpu.stream().memcpy_htod(&[v], d)?;
6989        Ok(())
6990    }
6991    /// Read back a [1] u32 device buffer (the argmax token). One tiny D2H + sync.
6992    pub fn dtoh_u32_one(&self, d: &CudaSlice<u32>) -> Result<u32, Box<dyn std::error::Error>> {
6993        let v = self.gpu.stream().clone_dtoh(d)?;
6994        self.gpu.stream().synchronize()?;
6995        Ok(v[0])
6996    }
6997    /// Upload raw bytes to a resident device u8 buffer (e.g. the embed table for device gather).
6998    pub fn upload_u8(&self, bytes: &[u8]) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
6999        Ok(self.gpu.stream().clone_htod(bytes)?)
7000    }
7001    /// Embed-from-device (CUDA-GRAPH-PLAN Phase 1): gather+dequant the row for the token id in
7002    /// `token_d[0]` from the resident embed table `embd` -> x_out[n_embd]. Bit-identical to host
7003    /// EmbedHost::gather (same per-dtype `deq`). No host round-trip of the token id.
7004    pub fn embed_gather_device(
7005        &self,
7006        embd: &CudaSlice<u8>,
7007        token_d: &CudaSlice<u32>,
7008        n_embd: usize,
7009        qtype: i32,
7010        row_bytes: usize,
7011    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7012        let f = self.func("embed_gather_u32");
7013        let mut x = self.alloc_uninit::<f32>(n_embd)?;
7014        let cfg = LaunchConfig {
7015            grid_dim: (((n_embd as u32 + 255) / 256).max(1), 1, 1),
7016            block_dim: (256, 1, 1),
7017            shared_mem_bytes: 0,
7018        };
7019        let (ne, qt, rb) = (n_embd as i32, qtype, row_bytes as i64);
7020        let __s_b = self.gpu.stream();
7021        let mut b = __s_b.launch_builder(&f);
7022        b.arg(embd)
7023            .arg(token_d)
7024            .arg(&mut x)
7025            .arg(&ne)
7026            .arg(&qt)
7027            .arg(&rb);
7028        unsafe {
7029            b.launch(cfg)?;
7030        }
7031        Ok(x)
7032    }
7033
7034    /// T-token device embed gather (spec verify/replay): tokens uploaded as a tiny [T] u32 htod,
7035    /// rows dequanted on-device -> x[T, n_embd]. Replaces host per-row dequant + T*n_embd*4B htod
7036    /// (nsys: 84% of spec API time was HtoD). Bit-identical rows (same per-dtype deq).
7037    pub fn embed_gather_device_t(
7038        &self,
7039        embd: &CudaSlice<u8>,
7040        tokens: &[u32],
7041        n_embd: usize,
7042        qtype: i32,
7043        row_bytes: usize,
7044    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7045        let t = tokens.len();
7046        let tok_d = self.gpu.stream().clone_htod(tokens)?;
7047        let f = self.func("embed_gather_u32_t");
7048        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7049        let cfg = LaunchConfig {
7050            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7051            block_dim: (256, 1, 1),
7052            shared_mem_bytes: 0,
7053        };
7054        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7055        let __s_b = self.gpu.stream();
7056        let mut b = __s_b.launch_builder(&f);
7057        b.arg(embd)
7058            .arg(&tok_d)
7059            .arg(&mut x)
7060            .arg(&ne)
7061            .arg(&qt)
7062            .arg(&rb)
7063            .arg(&ti);
7064        unsafe {
7065            b.launch(cfg)?;
7066        }
7067        Ok(x)
7068    }
7069
7070    /// T-token embed gather from a DEVICE token buffer (round-stream stage c: the verify tokens
7071    /// are assembled on-device from the draft-chain pack slots; no host round trip). Same kernel
7072    /// as embed_gather_device_t — bit-identical rows.
7073    /// embed_gather over a token VIEW (spec round: tokens live in the round's batch buffer).
7074    pub fn embed_gather_device_tv(
7075        &self,
7076        embd: &CudaSlice<u8>,
7077        tok_v: &cudarc::driver::CudaView<u32>,
7078        t: usize,
7079        n_embd: usize,
7080        qtype: i32,
7081        row_bytes: usize,
7082    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7083        let f = self.func("embed_gather_u32_t");
7084        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7085        let cfg = LaunchConfig {
7086            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7087            block_dim: (256, 1, 1),
7088            shared_mem_bytes: 0,
7089        };
7090        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7091        let __s_b = self.gpu.stream();
7092        let mut b = __s_b.launch_builder(&f);
7093        b.arg(embd)
7094            .arg(tok_v)
7095            .arg(&mut x)
7096            .arg(&ne)
7097            .arg(&qt)
7098            .arg(&rb)
7099            .arg(&ti);
7100        unsafe {
7101            b.launch(cfg)?;
7102        }
7103        Ok(x)
7104    }
7105
7106    pub fn embed_gather_device_td(
7107        &self,
7108        embd: &CudaSlice<u8>,
7109        tok_d: &CudaSlice<u32>,
7110        t: usize,
7111        n_embd: usize,
7112        qtype: i32,
7113        row_bytes: usize,
7114    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7115        let f = self.func("embed_gather_u32_t");
7116        let mut x = self.alloc_uninit::<f32>(t * n_embd)?;
7117        let cfg = LaunchConfig {
7118            grid_dim: (((n_embd as u32 + 255) / 256).max(1), t as u32, 1),
7119            block_dim: (256, 1, 1),
7120            shared_mem_bytes: 0,
7121        };
7122        let (ne, qt, rb, ti) = (n_embd as i32, qtype, row_bytes as i64, t as i32);
7123        let __s_b = self.gpu.stream();
7124        let mut b = __s_b.launch_builder(&f);
7125        b.arg(embd)
7126            .arg(tok_d)
7127            .arg(&mut x)
7128            .arg(&ne)
7129            .arg(&qt)
7130            .arg(&rb)
7131            .arg(&ti);
7132        unsafe {
7133            b.launch(cfg)?;
7134        }
7135        Ok(x)
7136    }
7137
7138    /// Uninitialized device buffer — SKIPS the memset that `alloc_zeros` always issues. Decode
7139    /// profile (nsys): ~1050 memsets/token = 6.5% of decode GPU time + ~half the launch count, the
7140    /// dominant contributor to the 19% inter-kernel idle gap and a blocker for clean CUDA-graph
7141    /// capture. Use ONLY for buffers a kernel FULLY overwrites (every element written, no `+=`).
7142    /// SAFETY: caller guarantees the producing kernel writes every element before any read.
7143    #[inline]
7144    /// Keep an allocation alive for the current capture (no-op when retain mode is off).
7145    fn keep_if_capturing<T: cudarc::driver::DeviceRepr + Send + 'static>(&self, s: &CudaSlice<T>) {
7146        if self
7147            .capture_keep_on
7148            .load(std::sync::atomic::Ordering::Relaxed)
7149        {
7150            self.capture_keep.lock().unwrap().push(Box::new(s.clone()));
7151        }
7152    }
7153
7154    fn alloc_uninit<T: cudarc::driver::DeviceRepr + Send + 'static>(
7155        &self,
7156        n: usize,
7157    ) -> Result<CudaSlice<T>, Box<dyn std::error::Error>> {
7158        let mut s = unsafe { self.gpu.stream().alloc::<T>(n)? };
7159        // MEMRA_DEBUG_ZERO_ALLOCS=1 (task #14 defect hunt): memset EVERY engine allocation —
7160        // the global uninit-read discriminator (the prime-fn-scoped zeroing experiment could
7161        // not cover engine-internal buffers). Debug-only: massive launch overhead.
7162        {
7163            static Z: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7164            if *Z.get_or_init(|| std::env::var("MEMRA_DEBUG_ZERO_ALLOCS").as_deref() == Ok("1")) {
7165                // raw D8 memset (T lacks ValidAsZeroBits in the generic bound)
7166                use cudarc::driver::DevicePtrMut;
7167                let n_bytes = s.len() * std::mem::size_of::<T>();
7168                let stream = self.gpu.stream();
7169                let (p_, _g) = s.device_ptr_mut(&stream);
7170                unsafe {
7171                    cudarc::driver::sys::cuMemsetD8Async(p_, 0, n_bytes, stream.cu_stream())
7172                        .result()?;
7173                }
7174            }
7175        }
7176        self.keep_if_capturing(&s);
7177        Ok(s)
7178    }
7179
7180    /// Public f32 uninitialized scratch (see `alloc_uninit`). For decode/forward scratch a kernel
7181    /// fully overwrites. SAFETY: producing kernel must write every element before any read.
7182    /// Uninitialized q8_1 activation pair (int8 + per-32 scales) — the fa combine q8-emit
7183    /// consumers alloc through this (m=1 decode arms).
7184    pub fn uninit_q8_pair(
7185        &self,
7186        n: usize,
7187    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7188        Ok((
7189            self.alloc_uninit::<i8>(n)?,
7190            self.alloc_uninit::<f32>(n / 32)?,
7191        ))
7192    }
7193
7194    pub fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
7195        self.alloc_uninit::<f32>(n)
7196    }
7197
7198    /// i8 uninitialized scratch (same contract as `uninit`).
7199    pub fn alloc_i8_uninit(&self, n: usize) -> Result<CudaSlice<i8>, Box<dyn std::error::Error>> {
7200        self.alloc_uninit::<i8>(n)
7201    }
7202
7203    /// RMSNorm: x[ncols,nrows] row-major, weight[ncols] -> dst. One block/row, 256 threads.
7204    /// gemma4: 3 rms_norms of the SAME input in one launch (one reduction, three weights).
7205    /// Per-output bit-identical to three rms_norm calls (verbatim reduction/scale chain).
7206    #[allow(clippy::too_many_arguments)]
7207    pub fn rms_norm3(
7208        &self,
7209        x: &CudaSlice<f32>,
7210        w0: &CudaSlice<f32>,
7211        w1: &CudaSlice<f32>,
7212        w2: &CudaSlice<f32>,
7213        d0: &mut CudaSlice<f32>,
7214        d1: &mut CudaSlice<f32>,
7215        d2: &mut CudaSlice<f32>,
7216        ncols: usize,
7217        nrows: usize,
7218        eps: f32,
7219    ) -> Result<(), Box<dyn std::error::Error>> {
7220        let f = self.func("rms_norm3_f32");
7221        let cfg = LaunchConfig {
7222            grid_dim: (nrows as u32, 1, 1),
7223            block_dim: (rms_block(), 1, 1),
7224            shared_mem_bytes: 0,
7225        };
7226        let (nc, e) = (ncols as i32, eps);
7227        let __s_b = self.gpu.stream();
7228        let mut b = __s_b.launch_builder(&f);
7229        b.arg(x)
7230            .arg(w0)
7231            .arg(w1)
7232            .arg(w2)
7233            .arg(d0)
7234            .arg(d1)
7235            .arg(d2)
7236            .arg(&nc)
7237            .arg(&e);
7238        unsafe {
7239            b.launch(cfg)?;
7240        }
7241        Ok(())
7242    }
7243
7244    /// gemma4 fused q/k/v head norms (one launch, per-row rms_norm_f32-verbatim).
7245    #[allow(clippy::too_many_arguments)]
7246    /// True when the warp-per-row qkv norm would engage for (rows, ncols) — the emit lane
7247    /// piggybacks on the same conditions.
7248    pub fn qkvnorm_w_on_prefill(rows: usize, ncols: usize) -> bool {
7249        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7250        *WARP_ON.get_or_init(|| {
7251            std::env::var("MEMRA_QKVNORM_W")
7252                .map(|v| v != "0")
7253                .unwrap_or(true)
7254        }) && ncols % 4 == 0
7255            && rows >= 64
7256    }
7257
7258    /// w4 norm with bf16 V EMIT (31B glue lane): the v segment also writes its normed rows as
7259    /// bf16 (the FA V operand — bit-identical to a post-hoc f32_to_bf16). Prefill-depth only.
7260    #[allow(clippy::too_many_arguments)]
7261    pub fn rms_norm_qkv_w4b(
7262        &self,
7263        q: &CudaSlice<f32>,
7264        k: &CudaSlice<f32>,
7265        v: &CudaSlice<f32>,
7266        wq: &CudaSlice<f32>,
7267        wk: &CudaSlice<f32>,
7268        wv: &CudaSlice<f32>,
7269        dq: &mut CudaSlice<f32>,
7270        dk: &mut CudaSlice<f32>,
7271        dv: &mut CudaSlice<f32>,
7272        dvb: &mut CudaSlice<u8>,
7273        ncols: usize,
7274        rq: usize,
7275        rk: usize,
7276        eps: f32,
7277        vf16: bool,
7278    ) -> Result<(), Box<dyn std::error::Error>> {
7279        assert!(ncols % 4 == 0 && rq + 2 * rk >= 64);
7280        let f = self.func("rms_norm_qkv_w4b_f32");
7281        let rows = (rq + 2 * rk) as u32;
7282        let cfg = LaunchConfig {
7283            grid_dim: (rows.div_ceil(8), 1, 1),
7284            block_dim: (256, 1, 1),
7285            shared_mem_bytes: 0,
7286        };
7287        let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7288        let vf = vf16 as i32;
7289        let __s_b = self.gpu.stream();
7290        let mut b = __s_b.launch_builder(&f);
7291        b.arg(q)
7292            .arg(k)
7293            .arg(v)
7294            .arg(wq)
7295            .arg(wk)
7296            .arg(wv)
7297            .arg(dq)
7298            .arg(dk)
7299            .arg(dv)
7300            .arg(&mut *dvb)
7301            .arg(&nc)
7302            .arg(&rqi)
7303            .arg(&rki)
7304            .arg(&rvi)
7305            .arg(&e)
7306            .arg(&vf);
7307        unsafe {
7308            b.launch(cfg)?;
7309        }
7310        Ok(())
7311    }
7312
7313    pub fn rms_norm_qkv(
7314        &self,
7315        q: &CudaSlice<f32>,
7316        k: &CudaSlice<f32>,
7317        v: &CudaSlice<f32>,
7318        wq: &CudaSlice<f32>,
7319        wk: &CudaSlice<f32>,
7320        wv: &CudaSlice<f32>,
7321        dq: &mut CudaSlice<f32>,
7322        dk: &mut CudaSlice<f32>,
7323        dv: &mut CudaSlice<f32>,
7324        ncols: usize,
7325        rq: usize,
7326        rk: usize,
7327        eps: f32,
7328    ) -> Result<(), Box<dyn std::error::Error>> {
7329        // Warp-per-row float4 twin (default; MEMRA_QKVNORM_W=0 reverts): the block-per-row form
7330        // spends 767us/launch on 17k+ 2KB rows at prefill depth (launch/reduce latency-bound,
7331        // ~92GB/s). Own numeric config (reduce order differs) — battery-gated.
7332        static WARP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
7333        let warp_on = *WARP_ON.get_or_init(|| {
7334            std::env::var("MEMRA_QKVNORM_W")
7335                .map(|v| v != "0")
7336                .unwrap_or(true)
7337        });
7338        // rows >= 64 keeps decode (nh + 2*nkv rows) on the block-tree kernel — decode/verify/
7339        // replay numerics are untouched on every model; only prefill depth takes the new config.
7340        if warp_on && ncols % 4 == 0 && rq + 2 * rk >= 64 {
7341            let f = self.func("rms_norm_qkv_w4_f32");
7342            let rows = (rq + 2 * rk) as u32;
7343            let cfg = LaunchConfig {
7344                grid_dim: (rows.div_ceil(8), 1, 1),
7345                block_dim: (256, 1, 1),
7346                shared_mem_bytes: 0,
7347            };
7348            let (nc, rqi, rki, rvi, e) = (ncols as i32, rq as i32, rk as i32, rk as i32, eps);
7349            let __s_b = self.gpu.stream();
7350            let mut b = __s_b.launch_builder(&f);
7351            b.arg(q)
7352                .arg(k)
7353                .arg(v)
7354                .arg(wq)
7355                .arg(wk)
7356                .arg(wv)
7357                .arg(dq)
7358                .arg(dk)
7359                .arg(dv)
7360                .arg(&nc)
7361                .arg(&rqi)
7362                .arg(&rki)
7363                .arg(&rvi)
7364                .arg(&e);
7365            unsafe {
7366                b.launch(cfg)?;
7367            }
7368            return Ok(());
7369        }
7370        let f = self.func("rms_norm_qkv_f32");
7371        let grid = (rq + 2 * rk) as u32;
7372        let cfg = LaunchConfig {
7373            grid_dim: (grid, 1, 1),
7374            block_dim: (rms_block(), 1, 1),
7375            shared_mem_bytes: 0,
7376        };
7377        let (nc, rqi, rki, e) = (ncols as i32, rq as i32, rk as i32, eps);
7378        let __s_b = self.gpu.stream();
7379        let mut b = __s_b.launch_builder(&f);
7380        b.arg(q)
7381            .arg(k)
7382            .arg(v)
7383            .arg(wq)
7384            .arg(wk)
7385            .arg(wv)
7386            .arg(dq)
7387            .arg(dk)
7388            .arg(dv)
7389            .arg(&nc)
7390            .arg(&rqi)
7391            .arg(&rki)
7392            .arg(&e);
7393        unsafe {
7394            b.launch(cfg)?;
7395        }
7396        Ok(())
7397    }
7398
7399    /// gemma4 fused pair of rms_norms over two different inputs (same width).
7400    #[allow(clippy::too_many_arguments)]
7401    pub fn rms_norm2x(
7402        &self,
7403        a: &CudaSlice<f32>,
7404        bb: &CudaSlice<f32>,
7405        wa: &CudaSlice<f32>,
7406        wb: &CudaSlice<f32>,
7407        da: &mut CudaSlice<f32>,
7408        db: &mut CudaSlice<f32>,
7409        ncols: usize,
7410        nrows: usize,
7411        eps: f32,
7412    ) -> Result<(), Box<dyn std::error::Error>> {
7413        let f = self.func("rms_norm2x_f32");
7414        let cfg = LaunchConfig {
7415            grid_dim: (2 * nrows as u32, 1, 1),
7416            block_dim: (rms_block(), 1, 1),
7417            shared_mem_bytes: 0,
7418        };
7419        let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
7420        let __s_b = self.gpu.stream();
7421        let mut b = __s_b.launch_builder(&f);
7422        b.arg(a)
7423            .arg(bb)
7424            .arg(wa)
7425            .arg(wb)
7426            .arg(da)
7427            .arg(db)
7428            .arg(&nc)
7429            .arg(&nr)
7430            .arg(&e);
7431        unsafe {
7432            b.launch(cfg)?;
7433        }
7434        Ok(())
7435    }
7436
7437    /// gemma4 R4: in-place final-logit softcap y = cap*tanh(y/cap).
7438    pub fn softcap(
7439        &self,
7440        y: &mut CudaSlice<f32>,
7441        cap: f32,
7442        n: usize,
7443    ) -> Result<(), Box<dyn std::error::Error>> {
7444        let f = self.func("softcap_f32");
7445        let cfg = LaunchConfig::for_num_elems(n as u32);
7446        let ni = n as i32;
7447        let __s_b = self.gpu.stream();
7448        let mut b = __s_b.launch_builder(&f);
7449        b.arg(y).arg(&cap).arg(&ni);
7450        unsafe {
7451            b.launch(cfg)?;
7452        }
7453        Ok(())
7454    }
7455
7456    /// gemma4 suppress-token mask: y[row][ids[j]] = -inf over t logits rows (fixed-arg launch —
7457    /// graph-capture safe; NOT monotonic like softcap, so it must run before any argmax).
7458    pub fn mask_ids_rows(
7459        &self,
7460        y: &mut CudaSlice<f32>,
7461        ids: &CudaSlice<i32>,
7462        n_ids: usize,
7463        n_vocab: usize,
7464        t: usize,
7465    ) -> Result<(), Box<dyn std::error::Error>> {
7466        let f = self.func("mask_ids_rows_f32");
7467        let cfg = LaunchConfig::for_num_elems((n_ids * t) as u32);
7468        let (ni, nv, ti) = (n_ids as i32, n_vocab as i32, t as i32);
7469        let __s_b = self.gpu.stream();
7470        let mut b = __s_b.launch_builder(&f);
7471        b.arg(y).arg(ids).arg(&ni).arg(&nv).arg(&ti);
7472        unsafe {
7473            b.launch(cfg)?;
7474        }
7475        Ok(())
7476    }
7477
7478    /// gemma4: res = (a+b)*c AND dst = rms_norm(res, w) in one launch.
7479    #[allow(clippy::too_many_arguments)]
7480    pub fn add_scale_rms_norm(
7481        &self,
7482        a: &CudaSlice<f32>,
7483        b_in: &CudaSlice<f32>,
7484        c: f32,
7485        w: &CudaSlice<f32>,
7486        res: &mut CudaSlice<f32>,
7487        dst: &mut CudaSlice<f32>,
7488        ncols: usize,
7489        nrows: usize,
7490        eps: f32,
7491    ) -> Result<(), Box<dyn std::error::Error>> {
7492        let f = self.func("add_scale_rms_norm_f32");
7493        let cfg = LaunchConfig {
7494            grid_dim: (nrows as u32, 1, 1),
7495            block_dim: (rms_block(), 1, 1),
7496            shared_mem_bytes: 0,
7497        };
7498        let (nc, e2) = (ncols as i32, eps);
7499        let __s_b = self.gpu.stream();
7500        let mut b = __s_b.launch_builder(&f);
7501        b.arg(a)
7502            .arg(b_in)
7503            .arg(&c)
7504            .arg(w)
7505            .arg(res)
7506            .arg(dst)
7507            .arg(&nc)
7508            .arg(&e2);
7509        unsafe {
7510            b.launch(cfg)?;
7511        }
7512        Ok(())
7513    }
7514
7515    /// gemma4: res = (a+b)*c AND the next layer's attn_norm EMITTED q8_1 in one launch.
7516    /// Quantize epilogue bit-identical to quantize_q8_1 (the rms_norm_q8_1 form).
7517    #[allow(clippy::too_many_arguments)]
7518    pub fn add_scale_rms_norm_q8_1(
7519        &self,
7520        a: &CudaSlice<f32>,
7521        b_in: &CudaSlice<f32>,
7522        c: f32,
7523        w: &CudaSlice<f32>,
7524        res: &mut CudaSlice<f32>,
7525        ncols: usize,
7526        nrows: usize,
7527        eps: f32,
7528    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7529        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7530        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7531        let (nc, e2) = (ncols as i32, eps);
7532        if Self::pdl_on() && Self::pdl_wb_on() {
7533            {
7534                use cudarc::driver::{DevicePtr, DevicePtrMut};
7535                let s = &self.gpu.stream();
7536                let (pa, _g0) = a.device_ptr(s);
7537                let (pb, _g1) = b_in.device_ptr(s);
7538                let (pw, _g2) = w.device_ptr(s);
7539                let (pr, _g3) = res.device_ptr_mut(s);
7540                let (pq, _g4) = out_q.device_ptr_mut(s);
7541                let (pd, _g5) = out_d.device_ptr_mut(s);
7542                let mut ps = [
7543                    &pa as *const _ as *mut std::ffi::c_void,
7544                    &pb as *const _ as *mut _,
7545                    &c as *const _ as *mut _,
7546                    &pw as *const _ as *mut _,
7547                    &pr as *const _ as *mut _,
7548                    &pq as *const _ as *mut _,
7549                    &pd as *const _ as *mut _,
7550                    &nc as *const _ as *mut _,
7551                    &e2 as *const _ as *mut _,
7552                ];
7553                unsafe {
7554                    self.launch_pdl(
7555                        "add_scale_rms_norm_q8_1",
7556                        (nrows as u32, 1, 1),
7557                        (rms_block(), 1, 1),
7558                        &mut ps,
7559                    )?;
7560                }
7561            }
7562            return Ok((out_q, out_d));
7563        }
7564        let f = self.func("add_scale_rms_norm_q8_1");
7565        let cfg = LaunchConfig {
7566            grid_dim: (nrows as u32, 1, 1),
7567            block_dim: (rms_block(), 1, 1),
7568            shared_mem_bytes: 0,
7569        };
7570        let __s_b = self.gpu.stream();
7571        let mut b = __s_b.launch_builder(&f);
7572        b.arg(a)
7573            .arg(b_in)
7574            .arg(&c)
7575            .arg(w)
7576            .arg(res)
7577            .arg(&mut out_q)
7578            .arg(&mut out_d)
7579            .arg(&nc)
7580            .arg(&e2);
7581        unsafe {
7582            b.launch(cfg)?;
7583        }
7584        Ok((out_q, out_d))
7585    }
7586
7587    /// Slot-fed add_scale_rms_norm_q8_1 twin (alloc-free capture lane).
7588    #[allow(clippy::too_many_arguments)]
7589    pub fn add_scale_rms_norm_q8_1_into(
7590        &self,
7591        a: &CudaSlice<f32>,
7592        b_in: &CudaSlice<f32>,
7593        c: f32,
7594        w: &CudaSlice<f32>,
7595        res: &mut CudaSlice<f32>,
7596        ncols: usize,
7597        nrows: usize,
7598        eps: f32,
7599        out_q: &mut CudaSlice<i8>,
7600        out_d: &mut CudaSlice<f32>,
7601    ) -> Result<(), Box<dyn std::error::Error>> {
7602        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7603        let (nc, e2) = (ncols as i32, eps);
7604        if Self::pdl_on() && Self::pdl_wb_on() {
7605            use cudarc::driver::{DevicePtr, DevicePtrMut};
7606            let s = &self.gpu.stream();
7607            let (pa, _g0) = a.device_ptr(s);
7608            let (pb, _g1) = b_in.device_ptr(s);
7609            let (pw, _g2) = w.device_ptr(s);
7610            let (pr, _g3) = res.device_ptr_mut(s);
7611            let (pq, _g4) = out_q.device_ptr_mut(s);
7612            let (pd, _g5) = out_d.device_ptr_mut(s);
7613            let mut ps = [
7614                &pa as *const _ as *mut std::ffi::c_void,
7615                &pb as *const _ as *mut _,
7616                &c as *const _ as *mut _,
7617                &pw as *const _ as *mut _,
7618                &pr as *const _ as *mut _,
7619                &pq as *const _ as *mut _,
7620                &pd as *const _ as *mut _,
7621                &nc as *const _ as *mut _,
7622                &e2 as *const _ as *mut _,
7623            ];
7624            unsafe {
7625                self.launch_pdl(
7626                    "add_scale_rms_norm_q8_1",
7627                    (nrows as u32, 1, 1),
7628                    (rms_block(), 1, 1),
7629                    &mut ps,
7630                )?;
7631            }
7632            return Ok(());
7633        }
7634        let f = self.func("add_scale_rms_norm_q8_1");
7635        let cfg = LaunchConfig {
7636            grid_dim: (nrows as u32, 1, 1),
7637            block_dim: (rms_block(), 1, 1),
7638            shared_mem_bytes: 0,
7639        };
7640        let __s_b = self.gpu.stream();
7641        let mut b = __s_b.launch_builder(&f);
7642        b.arg(a)
7643            .arg(b_in)
7644            .arg(&c)
7645            .arg(w)
7646            .arg(res)
7647            .arg(&mut *out_q)
7648            .arg(&mut *out_d)
7649            .arg(&nc)
7650            .arg(&e2);
7651        unsafe {
7652            b.launch(cfg)?;
7653        }
7654        Ok(())
7655    }
7656
7657    /// E4B glue fusion: rms(a, wa) prologue + the add_scale_rms_norm_q8_1 program — one launch
7658    /// replaces the per-layer rms_norm_f32(y) + emit pair in the PLE tail.
7659    #[allow(clippy::too_many_arguments)]
7660    pub fn rms_pre_add_scale_rms_norm_q8_1(
7661        &self,
7662        a: &CudaSlice<f32>,
7663        wa: &CudaSlice<f32>,
7664        b_in: &CudaSlice<f32>,
7665        c: f32,
7666        w: &CudaSlice<f32>,
7667        res: &mut CudaSlice<f32>,
7668        ncols: usize,
7669        nrows: usize,
7670        eps: f32,
7671    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7672        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7673        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7674        let (nc, e2) = (ncols as i32, eps);
7675        if Self::pdl_on() {
7676            {
7677                use cudarc::driver::{DevicePtr, DevicePtrMut};
7678                let s = &self.gpu.stream();
7679                let (pa, _g0) = a.device_ptr(s);
7680                let (pwa, _g1) = wa.device_ptr(s);
7681                let (pb, _g2) = b_in.device_ptr(s);
7682                let (pw, _g3) = w.device_ptr(s);
7683                let (pr, _g4) = res.device_ptr_mut(s);
7684                let (pq, _g5) = out_q.device_ptr_mut(s);
7685                let (pd, _g6) = out_d.device_ptr_mut(s);
7686                let mut ps = [
7687                    &pa as *const _ as *mut std::ffi::c_void,
7688                    &pwa as *const _ as *mut _,
7689                    &pb as *const _ as *mut _,
7690                    &c as *const _ as *mut _,
7691                    &pw as *const _ as *mut _,
7692                    &pr as *const _ as *mut _,
7693                    &pq as *const _ as *mut _,
7694                    &pd as *const _ as *mut _,
7695                    &nc as *const _ as *mut _,
7696                    &e2 as *const _ as *mut _,
7697                ];
7698                unsafe {
7699                    self.launch_pdl(
7700                        "rms_pre_add_scale_rms_norm_q8_1",
7701                        (nrows as u32, 1, 1),
7702                        (rms_block(), 1, 1),
7703                        &mut ps,
7704                    )?;
7705                }
7706            }
7707            return Ok((out_q, out_d));
7708        }
7709        let f = self.func("rms_pre_add_scale_rms_norm_q8_1");
7710        let cfg = LaunchConfig {
7711            grid_dim: (nrows as u32, 1, 1),
7712            block_dim: (rms_block(), 1, 1),
7713            shared_mem_bytes: 0,
7714        };
7715        let __s_b = self.gpu.stream();
7716        let mut b = __s_b.launch_builder(&f);
7717        b.arg(a)
7718            .arg(wa)
7719            .arg(b_in)
7720            .arg(&c)
7721            .arg(w)
7722            .arg(res)
7723            .arg(&mut out_q)
7724            .arg(&mut out_d)
7725            .arg(&nc)
7726            .arg(&e2);
7727        unsafe {
7728            b.launch(cfg)?;
7729        }
7730        Ok((out_q, out_d))
7731    }
7732
7733    /// GELU(tanh)*up with the activation emitted q8_1 alongside f32 (glue-fusion lane): the
7734    /// consumer matmul rides matmul_pre, killing its standalone quantize_q8_1 launch.
7735    pub fn gelu_tanh_mul_q8_1(
7736        &self,
7737        gate: &CudaSlice<f32>,
7738        up: &cudarc::driver::CudaView<f32>,
7739        act: &mut CudaSlice<f32>,
7740        ncols: usize,
7741        nrows: usize,
7742    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
7743        debug_assert!(ncols % 128 == 0);
7744        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
7745        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7746        let nc = ncols as i32;
7747        if Self::pdl_on() {
7748            {
7749                use cudarc::driver::{DevicePtr, DevicePtrMut};
7750                let s = &self.gpu.stream();
7751                let (pg, _g0) = gate.device_ptr(s);
7752                let (pu, _g1) = up.device_ptr(s);
7753                let (pact, _g2) = act.device_ptr_mut(s);
7754                let (pq, _g3) = out_q.device_ptr_mut(s);
7755                let (pd, _g4) = out_d.device_ptr_mut(s);
7756                let mut ps = [
7757                    &pg as *const _ as *mut std::ffi::c_void,
7758                    &pu as *const _ as *mut _,
7759                    &pact as *const _ as *mut _,
7760                    &pq as *const _ as *mut _,
7761                    &pd as *const _ as *mut _,
7762                    &nc as *const _ as *mut _,
7763                ];
7764                unsafe {
7765                    self.launch_pdl(
7766                        "gelu_tanh_mul_q8_1",
7767                        (nrows as u32, 1, 1),
7768                        (rms_block(), 1, 1),
7769                        &mut ps,
7770                    )?;
7771                }
7772            }
7773            return Ok((out_q, out_d));
7774        }
7775        let f = self.func("gelu_tanh_mul_q8_1");
7776        let cfg = LaunchConfig {
7777            grid_dim: (nrows as u32, 1, 1),
7778            block_dim: (rms_block(), 1, 1),
7779            shared_mem_bytes: 0,
7780        };
7781        let __s_b = self.gpu.stream();
7782        let mut b = __s_b.launch_builder(&f);
7783        b.arg(gate)
7784            .arg(up)
7785            .arg(act)
7786            .arg(&mut out_q)
7787            .arg(&mut out_d)
7788            .arg(&nc);
7789        unsafe {
7790            b.launch(cfg)?;
7791        }
7792        Ok((out_q, out_d))
7793    }
7794
7795    /// Slot-fed gelu_tanh_mul_q8_1 twin (alloc-free capture lane; incl. the PDL arm).
7796    #[allow(clippy::too_many_arguments)]
7797    pub fn gelu_tanh_mul_q8_1_into(
7798        &self,
7799        gate: &CudaSlice<f32>,
7800        up: &cudarc::driver::CudaView<f32>,
7801        act: &mut CudaSlice<f32>,
7802        ncols: usize,
7803        nrows: usize,
7804        out_q: &mut CudaSlice<i8>,
7805        out_d: &mut CudaSlice<f32>,
7806    ) -> Result<(), Box<dyn std::error::Error>> {
7807        debug_assert!(ncols % 128 == 0);
7808        debug_assert!(out_q.len() >= nrows * ncols && out_d.len() >= nrows * (ncols / 32));
7809        let nc = ncols as i32;
7810        if Self::pdl_on() {
7811            use cudarc::driver::{DevicePtr, DevicePtrMut};
7812            let s = &self.gpu.stream();
7813            let (pg, _g0) = gate.device_ptr(s);
7814            let (pu, _g1) = up.device_ptr(s);
7815            let (pact, _g2) = act.device_ptr_mut(s);
7816            let (pq, _g3) = out_q.device_ptr_mut(s);
7817            let (pd, _g4) = out_d.device_ptr_mut(s);
7818            let mut ps = [
7819                &pg as *const _ as *mut std::ffi::c_void,
7820                &pu as *const _ as *mut _,
7821                &pact as *const _ as *mut _,
7822                &pq as *const _ as *mut _,
7823                &pd as *const _ as *mut _,
7824                &nc as *const _ as *mut _,
7825            ];
7826            unsafe {
7827                self.launch_pdl(
7828                    "gelu_tanh_mul_q8_1",
7829                    (nrows as u32, 1, 1),
7830                    (rms_block(), 1, 1),
7831                    &mut ps,
7832                )?;
7833            }
7834            return Ok(());
7835        }
7836        let f = self.func("gelu_tanh_mul_q8_1");
7837        let cfg = LaunchConfig {
7838            grid_dim: (nrows as u32, 1, 1),
7839            block_dim: (rms_block(), 1, 1),
7840            shared_mem_bytes: 0,
7841        };
7842        let __s_b = self.gpu.stream();
7843        let mut b = __s_b.launch_builder(&f);
7844        b.arg(gate)
7845            .arg(up)
7846            .arg(&mut *act)
7847            .arg(&mut *out_q)
7848            .arg(&mut *out_d)
7849            .arg(&nc);
7850        unsafe {
7851            b.launch(cfg)?;
7852        }
7853        Ok(())
7854    }
7855
7856    /// gemma4: add + rms_norm3 with outputs 0/2 emitted q8_1 (zsh + moe_in) and 1 f32 (router).
7857    #[allow(clippy::too_many_arguments)]
7858    pub fn add_rms_norm3_q8z(
7859        &self,
7860        a: &CudaSlice<f32>,
7861        b_in: &CudaSlice<f32>,
7862        w0: &CudaSlice<f32>,
7863        w1: &CudaSlice<f32>,
7864        w2: &CudaSlice<f32>,
7865        res: &mut CudaSlice<f32>,
7866        out1: &mut CudaSlice<f32>,
7867        ncols: usize,
7868        nrows: usize,
7869        eps: f32,
7870    ) -> Result<
7871        (
7872            (CudaSlice<i8>, CudaSlice<f32>),
7873            (CudaSlice<i8>, CudaSlice<f32>),
7874        ),
7875        Box<dyn std::error::Error>,
7876    > {
7877        let mut q0 = self.alloc_uninit::<i8>(nrows * ncols)?;
7878        let mut d0 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7879        let mut q2 = self.alloc_uninit::<i8>(nrows * ncols)?;
7880        let mut d2 = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
7881        let f = self.func("add_rms_norm3_q8z_f32");
7882        let cfg = LaunchConfig {
7883            grid_dim: (nrows as u32, 1, 1),
7884            block_dim: (rms_block(), 1, 1),
7885            shared_mem_bytes: 0,
7886        };
7887        let (nc, e2) = (ncols as i32, eps);
7888        let __s_b = self.gpu.stream();
7889        let mut b = __s_b.launch_builder(&f);
7890        b.arg(a)
7891            .arg(b_in)
7892            .arg(w0)
7893            .arg(w1)
7894            .arg(w2)
7895            .arg(res)
7896            .arg(&mut q0)
7897            .arg(&mut d0)
7898            .arg(out1)
7899            .arg(&mut q2)
7900            .arg(&mut d2)
7901            .arg(&nc)
7902            .arg(&e2);
7903        unsafe {
7904            b.launch(cfg)?;
7905        }
7906        Ok(((q0, d0), (q2, d2)))
7907    }
7908
7909    /// gemma4: res = a+b AND the three rms_norms of res in one launch.
7910    #[allow(clippy::too_many_arguments)]
7911    pub fn add_rms_norm3(
7912        &self,
7913        a: &CudaSlice<f32>,
7914        b_in: &CudaSlice<f32>,
7915        w0: &CudaSlice<f32>,
7916        w1: &CudaSlice<f32>,
7917        w2: &CudaSlice<f32>,
7918        res: &mut CudaSlice<f32>,
7919        d0: &mut CudaSlice<f32>,
7920        d1: &mut CudaSlice<f32>,
7921        d2: &mut CudaSlice<f32>,
7922        ncols: usize,
7923        nrows: usize,
7924        eps: f32,
7925    ) -> Result<(), Box<dyn std::error::Error>> {
7926        let f = self.func("add_rms_norm3_f32");
7927        let cfg = LaunchConfig {
7928            grid_dim: (nrows as u32, 1, 1),
7929            block_dim: (rms_block(), 1, 1),
7930            shared_mem_bytes: 0,
7931        };
7932        let (nc, e2) = (ncols as i32, eps);
7933        let __s_b = self.gpu.stream();
7934        let mut b = __s_b.launch_builder(&f);
7935        b.arg(a)
7936            .arg(b_in)
7937            .arg(w0)
7938            .arg(w1)
7939            .arg(w2)
7940            .arg(res)
7941            .arg(d0)
7942            .arg(d1)
7943            .arg(d2)
7944            .arg(&nc)
7945            .arg(&e2);
7946        unsafe {
7947            b.launch(cfg)?;
7948        }
7949        Ok(())
7950    }
7951
7952    /// dst = (a + b) * c (residual add + layer scale, one launch).
7953    pub fn add_scale(
7954        &self,
7955        a: &CudaSlice<f32>,
7956        b_in: &CudaSlice<f32>,
7957        c: f32,
7958        dst: &mut CudaSlice<f32>,
7959        n: usize,
7960    ) -> Result<(), Box<dyn std::error::Error>> {
7961        let f = self.func("add_scale_f32");
7962        let cfg = LaunchConfig::for_num_elems(n as u32);
7963        let ni = n as i32;
7964        let __s_b = self.gpu.stream();
7965        let mut b = __s_b.launch_builder(&f);
7966        b.arg(a).arg(b_in).arg(&c).arg(dst).arg(&ni);
7967        unsafe {
7968            b.launch(cfg)?;
7969        }
7970        Ok(())
7971    }
7972
7973    pub fn rms_norm(
7974        &self,
7975        x: &CudaSlice<f32>,
7976        w: &CudaSlice<f32>,
7977        dst: &mut CudaSlice<f32>,
7978        ncols: usize,
7979        nrows: usize,
7980        eps: f32,
7981    ) -> Result<(), Box<dyn std::error::Error>> {
7982        let (nc, e) = (ncols as i32, eps);
7983        if Self::pdl_on() && Self::pdl_wb_on() {
7984            use cudarc::driver::{DevicePtr, DevicePtrMut};
7985            let s = &self.gpu.stream();
7986            let (px, _g0) = x.device_ptr(s);
7987            let (pw, _g1) = w.device_ptr(s);
7988            let (pd, _g2) = dst.device_ptr_mut(s);
7989            let mut ps = [
7990                &px as *const _ as *mut std::ffi::c_void,
7991                &pw as *const _ as *mut _,
7992                &pd as *const _ as *mut _,
7993                &nc as *const _ as *mut _,
7994                &e as *const _ as *mut _,
7995            ];
7996            unsafe {
7997                self.launch_pdl(
7998                    "rms_norm_f32",
7999                    (nrows as u32, 1, 1),
8000                    (rms_block(), 1, 1),
8001                    &mut ps,
8002                )?;
8003            }
8004            return Ok(());
8005        }
8006        let f = self.func("rms_norm_f32");
8007        let cfg = LaunchConfig {
8008            grid_dim: (nrows as u32, 1, 1),
8009            block_dim: (rms_block(), 1, 1),
8010            shared_mem_bytes: 0,
8011        };
8012        let __s_b = self.gpu.stream();
8013        let mut b = __s_b.launch_builder(&f);
8014        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8015        unsafe {
8016            b.launch(cfg)?;
8017        }
8018        Ok(())
8019    }
8020
8021    /// RMS-norm with blockDim=1024 — BIT-IDENTICAL to the fused `rms_norm_q8_1` and
8022    /// `add_rms_norm_q8_1` kernels' sum-of-squares reduction. The spec verify path MUST use this
8023    /// to match decode's FP accumulation order: the standard `rms_norm` at blockDim=256 has a
8024    /// different per-thread stride (ncols/256 partials vs ncols/1024 partials) and therefore a
8025    /// different shfl-tree reduction that can shift `scale = rsqrt(sum/n + eps)` by ULPs, causing
8026    /// divergence through the GDN scan and argmax flips on the 9B text prompt. The underlying
8027    /// `rms_norm_f32` kernel supports any blockDim (generic reduce with shared[32]).
8028    pub fn rms_norm_decode(
8029        &self,
8030        x: &CudaSlice<f32>,
8031        w: &CudaSlice<f32>,
8032        dst: &mut CudaSlice<f32>,
8033        ncols: usize,
8034        nrows: usize,
8035        eps: f32,
8036    ) -> Result<(), Box<dyn std::error::Error>> {
8037        let f = self.func("rms_norm_f32");
8038        let cfg = LaunchConfig {
8039            grid_dim: (nrows as u32, 1, 1),
8040            block_dim: (1024, 1, 1),
8041            shared_mem_bytes: 0,
8042        };
8043        let (nc, e) = (ncols as i32, eps);
8044        let __s_b = self.gpu.stream();
8045        let mut b = __s_b.launch_builder(&f);
8046        b.arg(x).arg(w).arg(dst).arg(&nc).arg(&e);
8047        unsafe {
8048            b.launch(cfg)?;
8049        }
8050        Ok(())
8051    }
8052
8053    /// DECODE GLUE-FUSION LEVER: `z = rms_norm(x)*w` emitted DIRECTLY as q8_1 (no f32 `z` materialized,
8054    /// no standalone quantize_q8_1 launch). Returns (out_q [nrows*ncols i8], out_d [nrows*nblk f32])
8055    /// ready to feed matmul_pre. BIT-IDENTICAL to rms_norm + quantize_q8_1. ncols % 32 == 0.
8056    pub fn rms_norm_q8_1(
8057        &self,
8058        x: &CudaSlice<f32>,
8059        w: &CudaSlice<f32>,
8060        ncols: usize,
8061        nrows: usize,
8062        eps: f32,
8063    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8064        let nblk = ncols / 32;
8065        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8066        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8067        let (nc, e) = (ncols as i32, eps);
8068        if Self::pdl_on() {
8069            {
8070                use cudarc::driver::{DevicePtr, DevicePtrMut};
8071                let s = &self.gpu.stream();
8072                let (px, _g0) = x.device_ptr(s);
8073                let (pw, _g1) = w.device_ptr(s);
8074                let (pq, _g2) = q.device_ptr_mut(s);
8075                let (pd, _g3) = d.device_ptr_mut(s);
8076                let mut ps = [
8077                    &px as *const _ as *mut std::ffi::c_void,
8078                    &pw as *const _ as *mut _,
8079                    &pq as *const _ as *mut _,
8080                    &pd as *const _ as *mut _,
8081                    &nc as *const _ as *mut _,
8082                    &e as *const _ as *mut _,
8083                ];
8084                unsafe {
8085                    self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8086                }
8087            }
8088            return Ok((q, d));
8089        }
8090        let f = self.func("rms_norm_q8_1");
8091        // 1024 threads: decode is nrows=1 -> ONE CTA; 32 warps hide the pass1->pass2 latency
8092        // (s[32] reduce already sized for 32 warps). Same shape math at any blockDim.
8093        let cfg = LaunchConfig {
8094            grid_dim: (nrows as u32, 1, 1),
8095            block_dim: (1024, 1, 1),
8096            shared_mem_bytes: 0,
8097        };
8098        let __s_b = self.gpu.stream();
8099        let mut b = __s_b.launch_builder(&f);
8100        b.arg(x).arg(w).arg(&mut q).arg(&mut d).arg(&nc).arg(&e);
8101        unsafe {
8102            b.launch(cfg)?;
8103        }
8104        Ok((q, d))
8105    }
8106
8107    /// Slot-fed rms_norm_q8_1 twin (alloc-free capture lane): identical launch (incl. the
8108    /// PDL arm), caller-owned outputs.
8109    pub fn rms_norm_q8_1_into(
8110        &self,
8111        x: &CudaSlice<f32>,
8112        w: &CudaSlice<f32>,
8113        ncols: usize,
8114        nrows: usize,
8115        eps: f32,
8116        q: &mut CudaSlice<i8>,
8117        d: &mut CudaSlice<f32>,
8118    ) -> Result<(), Box<dyn std::error::Error>> {
8119        let nblk = ncols / 32;
8120        debug_assert!(q.len() >= nrows * ncols && d.len() >= nrows * nblk);
8121        let (nc, e) = (ncols as i32, eps);
8122        if Self::pdl_on() {
8123            use cudarc::driver::{DevicePtr, DevicePtrMut};
8124            let s = &self.gpu.stream();
8125            let (px, _g0) = x.device_ptr(s);
8126            let (pw, _g1) = w.device_ptr(s);
8127            let (pq, _g2) = q.device_ptr_mut(s);
8128            let (pd, _g3) = d.device_ptr_mut(s);
8129            let mut ps = [
8130                &px as *const _ as *mut std::ffi::c_void,
8131                &pw as *const _ as *mut _,
8132                &pq as *const _ as *mut _,
8133                &pd as *const _ as *mut _,
8134                &nc as *const _ as *mut _,
8135                &e as *const _ as *mut _,
8136            ];
8137            unsafe {
8138                self.launch_pdl("rms_norm_q8_1", (nrows as u32, 1, 1), (1024, 1, 1), &mut ps)?;
8139            }
8140            return Ok(());
8141        }
8142        let f = self.func("rms_norm_q8_1");
8143        let cfg = LaunchConfig {
8144            grid_dim: (nrows as u32, 1, 1),
8145            block_dim: (1024, 1, 1),
8146            shared_mem_bytes: 0,
8147        };
8148        let __s_b = self.gpu.stream();
8149        let mut b = __s_b.launch_builder(&f);
8150        b.arg(x).arg(w).arg(&mut *q).arg(&mut *d).arg(&nc).arg(&e);
8151        unsafe {
8152            b.launch(cfg)?;
8153        }
8154        Ok(())
8155    }
8156
8157    /// Slot-fed quantize_q8_1 twin (alloc-free capture lane).
8158    pub fn quantize_q8_1_into(
8159        &self,
8160        x: &CudaSlice<f32>,
8161        m: usize,
8162        in_f: usize,
8163        q: &mut CudaSlice<i8>,
8164        d: &mut CudaSlice<f32>,
8165    ) -> Result<(), Box<dyn std::error::Error>> {
8166        let nblk = in_f / 32;
8167        debug_assert!(q.len() >= m * in_f && d.len() >= m * nblk);
8168        let cfg = LaunchConfig::for_num_elems((m * in_f) as u32);
8169        let (inf, mi) = (in_f as i32, m as i32);
8170        if Self::pdl_on() && Self::pdl_wb_on() {
8171            use cudarc::driver::{DevicePtr, DevicePtrMut};
8172            let s = &self.gpu.stream();
8173            let (px, _g0) = x.device_ptr(s);
8174            let (pq, _g1) = q.device_ptr_mut(s);
8175            let (pd, _g2) = d.device_ptr_mut(s);
8176            let mut ps = [
8177                &px as *const _ as *mut std::ffi::c_void,
8178                &pq as *const _ as *mut _,
8179                &pd as *const _ as *mut _,
8180                &inf as *const _ as *mut _,
8181                &mi as *const _ as *mut _,
8182            ];
8183            unsafe {
8184                self.launch_pdl("quantize_q8_1", cfg.grid_dim, cfg.block_dim, &mut ps)?;
8185            }
8186            return Ok(());
8187        }
8188        let f = self.func("quantize_q8_1");
8189        let __s_b = self.gpu.stream();
8190        let mut b = __s_b.launch_builder(&f);
8191        b.arg(x).arg(&mut *q).arg(&mut *d).arg(&inf).arg(&mi);
8192        unsafe {
8193            b.launch(cfg)?;
8194        }
8195        Ok(())
8196    }
8197
8198    /// DECODE GLUE-FUSION LEVER: `res = a+b; z = rms_norm(res)*w` with z emitted as q8_1. `res` is
8199    /// still written (the post-ffn residual add reads it). Fuses add_rms_norm + quantize_q8_1.
8200    /// Returns (out_q, out_d) for matmul_pre. BIT-IDENTICAL. ncols % 32 == 0.
8201    pub fn add_rms_norm_q8_1(
8202        &self,
8203        a: &CudaSlice<f32>,
8204        b_in: &CudaSlice<f32>,
8205        w: &CudaSlice<f32>,
8206        res: &mut CudaSlice<f32>,
8207        ncols: usize,
8208        nrows: usize,
8209        eps: f32,
8210    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8211        let nblk = ncols / 32;
8212        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
8213        let mut d = self.alloc_uninit::<f32>(nrows * nblk)?;
8214        let f = self.func("add_rms_norm_q8_1");
8215        // 1024 threads: same single-CTA-at-decode reasoning as rms_norm_q8_1.
8216        let cfg = LaunchConfig {
8217            grid_dim: (nrows as u32, 1, 1),
8218            block_dim: (1024, 1, 1),
8219            shared_mem_bytes: 0,
8220        };
8221        let (nc, e) = (ncols as i32, eps);
8222        let __s_bld = self.gpu.stream();
8223        let mut bld = __s_bld.launch_builder(&f);
8224        bld.arg(a)
8225            .arg(b_in)
8226            .arg(w)
8227            .arg(res)
8228            .arg(&mut q)
8229            .arg(&mut d)
8230            .arg(&nc)
8231            .arg(&e);
8232        unsafe {
8233            bld.launch(cfg)?;
8234        }
8235        Ok((q, d))
8236    }
8237
8238    /// RANK3 LEVER (add+rmsnorm fuse): `res = a + b; dst = rms_norm(res) * w` in ONE launch. Fuses
8239    /// e.add(a,b,res) + e.rms_norm(res,w,dst), removing one launch + one HBM read of the residual per
8240    /// residual+norm pair. BIT-IDENTICAL to the two-kernel sequence (same IEEE add, same reduction).
8241    pub fn add_rms_norm(
8242        &self,
8243        a: &CudaSlice<f32>,
8244        b: &CudaSlice<f32>,
8245        w: &CudaSlice<f32>,
8246        res: &mut CudaSlice<f32>,
8247        dst: &mut CudaSlice<f32>,
8248        ncols: usize,
8249        nrows: usize,
8250        eps: f32,
8251    ) -> Result<(), Box<dyn std::error::Error>> {
8252        let (nc, e) = (ncols as i32, eps);
8253        if Self::pdl_on() && Self::pdl_wb_on() {
8254            use cudarc::driver::{DevicePtr, DevicePtrMut};
8255            let s = &self.gpu.stream();
8256            let (pa, _g0) = a.device_ptr(s);
8257            let (pb, _g1) = b.device_ptr(s);
8258            let (pw, _g2) = w.device_ptr(s);
8259            let (pr, _g3) = res.device_ptr_mut(s);
8260            let (pd, _g4) = dst.device_ptr_mut(s);
8261            let mut ps = [
8262                &pa as *const _ as *mut std::ffi::c_void,
8263                &pb as *const _ as *mut _,
8264                &pw as *const _ as *mut _,
8265                &pr as *const _ as *mut _,
8266                &pd as *const _ as *mut _,
8267                &nc as *const _ as *mut _,
8268                &e as *const _ as *mut _,
8269            ];
8270            unsafe {
8271                self.launch_pdl(
8272                    "add_rms_norm_f32",
8273                    (nrows as u32, 1, 1),
8274                    (rms_block(), 1, 1),
8275                    &mut ps,
8276                )?;
8277            }
8278            return Ok(());
8279        }
8280        let f = self.func("add_rms_norm_f32");
8281        let cfg = LaunchConfig {
8282            grid_dim: (nrows as u32, 1, 1),
8283            block_dim: (rms_block(), 1, 1),
8284            shared_mem_bytes: 0,
8285        };
8286        let __s_b2 = self.gpu.stream();
8287        let mut b2 = __s_b2.launch_builder(&f);
8288        b2.arg(a)
8289            .arg(b)
8290            .arg(w)
8291            .arg(&mut *res)
8292            .arg(&mut *dst)
8293            .arg(&nc)
8294            .arg(&e);
8295        unsafe {
8296            b2.launch(cfg)?;
8297        }
8298        Ok(())
8299    }
8300
8301    /// E4B glue fusion: rms(a, wa) prologue + add_rms_norm — folds the post-attn norm into
8302    /// the tail entry (res = rms(a)*wa + b; dst = rms(res)*w).
8303    #[allow(clippy::too_many_arguments)]
8304    pub fn rms_pre_add_rms_norm(
8305        &self,
8306        a: &CudaSlice<f32>,
8307        wa: &CudaSlice<f32>,
8308        b: &CudaSlice<f32>,
8309        w: &CudaSlice<f32>,
8310        res: &mut CudaSlice<f32>,
8311        dst: &mut CudaSlice<f32>,
8312        ncols: usize,
8313        nrows: usize,
8314        eps: f32,
8315    ) -> Result<(), Box<dyn std::error::Error>> {
8316        let f = self.func("rms_pre_add_rms_norm_f32");
8317        let cfg = LaunchConfig {
8318            grid_dim: (nrows as u32, 1, 1),
8319            block_dim: (rms_block(), 1, 1),
8320            shared_mem_bytes: 0,
8321        };
8322        let (nc, e) = (ncols as i32, eps);
8323        let __s_b2 = self.gpu.stream();
8324        let mut b2 = __s_b2.launch_builder(&f);
8325        b2.arg(a)
8326            .arg(wa)
8327            .arg(b)
8328            .arg(w)
8329            .arg(&mut *res)
8330            .arg(&mut *dst)
8331            .arg(&nc)
8332            .arg(&e);
8333        unsafe {
8334            b2.launch(cfg)?;
8335        }
8336        Ok(())
8337    }
8338
8339    /// wave-2 fold: rms(a,wa) + add + ffn-norm with zsh EMITTED q8_1 (fused2 consumes it).
8340    #[allow(clippy::too_many_arguments)]
8341    pub fn rms_pre_add_rms_norm_q8z(
8342        &self,
8343        a: &CudaSlice<f32>,
8344        wa: &CudaSlice<f32>,
8345        b: &CudaSlice<f32>,
8346        w: &CudaSlice<f32>,
8347        res: &mut CudaSlice<f32>,
8348        dst: &mut CudaSlice<f32>,
8349        ncols: usize,
8350        nrows: usize,
8351        eps: f32,
8352    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8353        debug_assert!(ncols % 128 == 0);
8354        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8355        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8356        let (nc, e) = (ncols as i32, eps);
8357        if Self::pdl_on() {
8358            {
8359                use cudarc::driver::{DevicePtr, DevicePtrMut};
8360                let s = &self.gpu.stream();
8361                let (pa, _g0) = a.device_ptr(s);
8362                let (pwa, _g1) = wa.device_ptr(s);
8363                let (pb, _g2) = b.device_ptr(s);
8364                let (pw, _g3) = w.device_ptr(s);
8365                let (pr, _g4) = res.device_ptr_mut(s);
8366                let (pdst, _g5) = dst.device_ptr_mut(s);
8367                let (pq, _g6) = out_q.device_ptr_mut(s);
8368                let (pd, _g7) = out_d.device_ptr_mut(s);
8369                let mut ps = [
8370                    &pa as *const _ as *mut std::ffi::c_void,
8371                    &pwa as *const _ as *mut _,
8372                    &pb as *const _ as *mut _,
8373                    &pw as *const _ as *mut _,
8374                    &pr as *const _ as *mut _,
8375                    &pdst as *const _ as *mut _,
8376                    &pq as *const _ as *mut _,
8377                    &pd as *const _ as *mut _,
8378                    &nc as *const _ as *mut _,
8379                    &e as *const _ as *mut _,
8380                ];
8381                unsafe {
8382                    self.launch_pdl(
8383                        "rms_pre_add_rms_norm_q8z_f32",
8384                        (nrows as u32, 1, 1),
8385                        (rms_block(), 1, 1),
8386                        &mut ps,
8387                    )?;
8388                }
8389            }
8390            return Ok((out_q, out_d));
8391        }
8392        let f = self.func("rms_pre_add_rms_norm_q8z_f32");
8393        let cfg = LaunchConfig {
8394            grid_dim: (nrows as u32, 1, 1),
8395            block_dim: (rms_block(), 1, 1),
8396            shared_mem_bytes: 0,
8397        };
8398        let __s_b2 = self.gpu.stream();
8399        let mut b2 = __s_b2.launch_builder(&f);
8400        b2.arg(a)
8401            .arg(wa)
8402            .arg(b)
8403            .arg(w)
8404            .arg(&mut *res)
8405            .arg(&mut *dst)
8406            .arg(&mut out_q)
8407            .arg(&mut out_d)
8408            .arg(&nc)
8409            .arg(&e);
8410        unsafe {
8411            b2.launch(cfg)?;
8412        }
8413        Ok((out_q, out_d))
8414    }
8415
8416    /// wave-4b: OUT-dim concat of three Q4_0 tensors (same in_features; rows are independent
8417    /// blocks, so the concat is a D2D byte concat of the GGUF-layout planes). Returns None
8418    /// off-class (non-Q4_0, mismatched widths, or any tensor already rp-swapped in place).
8419    pub fn build_q4_out_concat3(
8420        &self,
8421        w0: &crate::model::GpuTensor,
8422        w1: &crate::model::GpuTensor,
8423        w2: &crate::model::GpuTensor,
8424    ) -> Result<Option<crate::model::GpuTensor>, Box<dyn std::error::Error>> {
8425        use crate::model::GpuTensor;
8426        let part = |w: &GpuTensor| -> Option<(usize, usize)> {
8427            match w {
8428                GpuTensor::Quant {
8429                    qtype,
8430                    row_bytes,
8431                    rp,
8432                    ..
8433                } if *qtype == QT_Q4_0 && !*rp => Some((*row_bytes, w.out_features())),
8434                _ => None,
8435            }
8436        };
8437        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (part(w0), part(w1), part(w2))
8438        else {
8439            return Ok(None);
8440        };
8441        if rb0 != rb1
8442            || rb0 != rb2
8443            || w0.in_features() != w1.in_features()
8444            || w0.in_features() != w2.in_features()
8445        {
8446            return Ok(None);
8447        }
8448        fn bytes_of(w: &crate::model::GpuTensor) -> &CudaSlice<u8> {
8449            match w {
8450                crate::model::GpuTensor::Quant { bytes, .. } => bytes,
8451                _ => unreachable!(),
8452            }
8453        }
8454        let (b0, b1, b2) = (bytes_of(w0), bytes_of(w1), bytes_of(w2));
8455        let total = rb0 * (o0 + o1 + o2);
8456        let mut cat = self.alloc_u8(total)?;
8457        self.copy_u8_into(&mut cat, 0, b0, rb0 * o0)?;
8458        self.copy_u8_into(&mut cat, rb0 * o0, b1, rb1 * o1)?;
8459        self.copy_u8_into(&mut cat, rb0 * (o0 + o1), b2, rb2 * o2)?;
8460        Ok(Some(GpuTensor::Quant {
8461            bytes: cat,
8462            qtype: QT_Q4_0,
8463            row_bytes: rb0,
8464            ne: vec![w0.in_features() as u64, (o0 + o1 + o2) as u64],
8465            scale: 1.0,
8466            rp: false,
8467            #[cfg(memra_cutlass)]
8468            cutlass: None,
8469            fp8: None,
8470            blk: None,
8471            rp4: None,
8472            f16: None,
8473        }))
8474    }
8475
8476    /// wave-4b: the qkv-cat twin — one contiguous [rq+2*rk, hd] input from the concat matvec.
8477    #[allow(clippy::too_many_arguments)]
8478    pub fn rms_norm_qkv_rope_cat(
8479        &self,
8480        qkv: &CudaSlice<f32>,
8481        wq: &CudaSlice<f32>,
8482        wk: &CudaSlice<f32>,
8483        wv: &CudaSlice<f32>,
8484        q: &mut CudaSlice<f32>,
8485        k: &mut CudaSlice<f32>,
8486        v: &mut CudaSlice<f32>,
8487        head_dim: usize,
8488        rq: usize,
8489        rk: usize,
8490        pos: &CudaSlice<i32>,
8491        nh_q: usize,
8492        nh_k: usize,
8493        base: f32,
8494        freq_scale: f32,
8495        ff: Option<&CudaSlice<f32>>,
8496        eps: f32,
8497    ) -> Result<(), Box<dyn std::error::Error>> {
8498        let rows = rq + rk + rk;
8499        let theta_scale = base.powf(-2.0 / head_dim as f32);
8500        let (nc, rqi, rki, nhq, nhk) = (
8501            head_dim as i32,
8502            rq as i32,
8503            rk as i32,
8504            nh_q as i32,
8505            nh_k as i32,
8506        );
8507        if Self::pdl_on() {
8508            use cudarc::driver::{DevicePtr, DevicePtrMut};
8509            let s = &self.gpu.stream();
8510            let (pqkv, _g0) = qkv.device_ptr(s);
8511            let (pwq, _g1) = wq.device_ptr(s);
8512            let (pwk, _g2) = wk.device_ptr(s);
8513            let (pwv, _g3) = wv.device_ptr(s);
8514            let (pq, _g4) = q.device_ptr_mut(s);
8515            let (pk, _g5) = k.device_ptr_mut(s);
8516            let (pv, _g6) = v.device_ptr_mut(s);
8517            let (ppos, _g7) = pos.device_ptr(s);
8518            let (pff, _g8) = match ff {
8519                Some(t) => {
8520                    let (p, g) = t.device_ptr(s);
8521                    (p, Some(g))
8522                }
8523                None => (0, None),
8524            };
8525            let mut ps = [
8526                &pqkv as *const _ as *mut std::ffi::c_void,
8527                &pwq as *const _ as *mut _,
8528                &pwk as *const _ as *mut _,
8529                &pwv as *const _ as *mut _,
8530                &pq as *const _ as *mut _,
8531                &pk as *const _ as *mut _,
8532                &pv as *const _ as *mut _,
8533                &nc as *const _ as *mut _,
8534                &rqi as *const _ as *mut _,
8535                &rki as *const _ as *mut _,
8536                &ppos as *const _ as *mut _,
8537                &nhq as *const _ as *mut _,
8538                &nhk as *const _ as *mut _,
8539                &theta_scale as *const _ as *mut _,
8540                &freq_scale as *const _ as *mut _,
8541                &pff as *const _ as *mut _,
8542                &eps as *const _ as *mut _,
8543            ];
8544            unsafe {
8545                self.launch_pdl(
8546                    "rms_norm_qkv_rope_cat_f32",
8547                    (rows as u32, 1, 1),
8548                    (rms_block(), 1, 1),
8549                    &mut ps,
8550                )?;
8551            }
8552            return Ok(());
8553        }
8554        let f = self.func("rms_norm_qkv_rope_cat_f32");
8555        let cfg = LaunchConfig {
8556            grid_dim: (rows as u32, 1, 1),
8557            block_dim: (rms_block(), 1, 1),
8558            shared_mem_bytes: 0,
8559        };
8560        let __s_b = self.gpu.stream();
8561        let mut b = __s_b.launch_builder(&f);
8562        match ff {
8563            Some(t) => {
8564                b.arg(qkv)
8565                    .arg(wq)
8566                    .arg(wk)
8567                    .arg(wv)
8568                    .arg(&mut *q)
8569                    .arg(&mut *k)
8570                    .arg(&mut *v)
8571                    .arg(&nc)
8572                    .arg(&rqi)
8573                    .arg(&rki)
8574                    .arg(pos)
8575                    .arg(&nhq)
8576                    .arg(&nhk)
8577                    .arg(&theta_scale)
8578                    .arg(&freq_scale)
8579                    .arg(t)
8580                    .arg(&eps);
8581                unsafe {
8582                    b.launch(cfg)?;
8583                }
8584            }
8585            None => {
8586                let null: u64 = 0;
8587                b.arg(qkv)
8588                    .arg(wq)
8589                    .arg(wk)
8590                    .arg(wv)
8591                    .arg(&mut *q)
8592                    .arg(&mut *k)
8593                    .arg(&mut *v)
8594                    .arg(&nc)
8595                    .arg(&rqi)
8596                    .arg(&rki)
8597                    .arg(pos)
8598                    .arg(&nhq)
8599                    .arg(&nhk)
8600                    .arg(&theta_scale)
8601                    .arg(&freq_scale)
8602                    .arg(&null)
8603                    .arg(&eps);
8604                unsafe {
8605                    b.launch(cfg)?;
8606                }
8607            }
8608        }
8609        Ok(())
8610    }
8611
8612    /// wave-3 fold: rms_norm_qkv + rope_neox2 in ONE launch (n_dims == head_dim; ff nullable).
8613    #[allow(clippy::too_many_arguments)]
8614    pub fn rms_norm_qkv_rope(
8615        &self,
8616        q0: &CudaSlice<f32>,
8617        k0: &CudaSlice<f32>,
8618        v0: &CudaSlice<f32>,
8619        wq: &CudaSlice<f32>,
8620        wk: &CudaSlice<f32>,
8621        wv: &CudaSlice<f32>,
8622        q: &mut CudaSlice<f32>,
8623        k: &mut CudaSlice<f32>,
8624        v: &mut CudaSlice<f32>,
8625        head_dim: usize,
8626        rq: usize,
8627        rk: usize,
8628        pos: &CudaSlice<i32>,
8629        nh_q: usize,
8630        nh_k: usize,
8631        base: f32,
8632        freq_scale: f32,
8633        ff: Option<&CudaSlice<f32>>,
8634        eps: f32,
8635    ) -> Result<(), Box<dyn std::error::Error>> {
8636        let f = self.func("rms_norm_qkv_rope_f32");
8637        let rows = rq + rk + rk; // q rows + k rows + v rows (rk == rv)
8638        let cfg = LaunchConfig {
8639            grid_dim: (rows as u32, 1, 1),
8640            block_dim: (rms_block(), 1, 1),
8641            shared_mem_bytes: 0,
8642        };
8643        let theta_scale = base.powf(-2.0 / head_dim as f32);
8644        let (nc, rqi, rki, nhq, nhk) = (
8645            head_dim as i32,
8646            rq as i32,
8647            rk as i32,
8648            nh_q as i32,
8649            nh_k as i32,
8650        );
8651        let __s_b = self.gpu.stream();
8652        let mut b = __s_b.launch_builder(&f);
8653        match ff {
8654            Some(t) => {
8655                b.arg(q0)
8656                    .arg(k0)
8657                    .arg(v0)
8658                    .arg(wq)
8659                    .arg(wk)
8660                    .arg(wv)
8661                    .arg(&mut *q)
8662                    .arg(&mut *k)
8663                    .arg(&mut *v)
8664                    .arg(&nc)
8665                    .arg(&rqi)
8666                    .arg(&rki)
8667                    .arg(pos)
8668                    .arg(&nhq)
8669                    .arg(&nhk)
8670                    .arg(&theta_scale)
8671                    .arg(&freq_scale)
8672                    .arg(t)
8673                    .arg(&eps);
8674                unsafe {
8675                    b.launch(cfg)?;
8676                }
8677            }
8678            None => {
8679                let null: u64 = 0;
8680                b.arg(q0)
8681                    .arg(k0)
8682                    .arg(v0)
8683                    .arg(wq)
8684                    .arg(wk)
8685                    .arg(wv)
8686                    .arg(&mut *q)
8687                    .arg(&mut *k)
8688                    .arg(&mut *v)
8689                    .arg(&nc)
8690                    .arg(&rqi)
8691                    .arg(&rki)
8692                    .arg(pos)
8693                    .arg(&nhq)
8694                    .arg(&nhk)
8695                    .arg(&theta_scale)
8696                    .arg(&freq_scale)
8697                    .arg(&null)
8698                    .arg(&eps);
8699                unsafe {
8700                    b.launch(cfg)?;
8701                }
8702            }
8703        }
8704        Ok(())
8705    }
8706
8707    /// FUSED norm+rope+APPEND (m=1 decode, 2026-07-23): one launch replaces the
8708    /// rms_norm_qkv_rope + append_kv_quantized_dc pair. Kernel lives in the flash fatbins
8709    /// (format-flavored quant tail) — `g` must mirror the append path's flavor exactly.
8710    #[allow(clippy::too_many_arguments)]
8711    pub fn rms_norm_qkv_rope_append_dc(
8712        &self,
8713        q0: &CudaSlice<f32>,
8714        k0: &CudaSlice<f32>,
8715        v0: &CudaSlice<f32>,
8716        wq: &CudaSlice<f32>,
8717        wk: &CudaSlice<f32>,
8718        wv: &CudaSlice<f32>,
8719        q: &mut CudaSlice<f32>,
8720        k: &mut CudaSlice<f32>,
8721        v: &mut CudaSlice<f32>,
8722        head_dim: usize,
8723        rq: usize,
8724        rk: usize,
8725        pos: &CudaSlice<i32>,
8726        nh_q: usize,
8727        nh_k: usize,
8728        base: f32,
8729        freq_scale: f32,
8730        ff: Option<&CudaSlice<f32>>,
8731        eps: f32,
8732        kc: &mut CudaSlice<u8>,
8733        vc: &mut CudaSlice<u8>,
8734        t_dev: &CudaSlice<i32>,
8735        k_tok_bytes: usize,
8736        v_tok_bytes: usize,
8737        g: bool,
8738    ) -> Result<(), Box<dyn std::error::Error>> {
8739        let rows = rq + rk + rk;
8740        let theta_scale = base.powf(-2.0 / head_dim as f32);
8741        let (nc, rqi, rki, nhq, nhk) = (
8742            head_dim as i32,
8743            rq as i32,
8744            rk as i32,
8745            nh_q as i32,
8746            nh_k as i32,
8747        );
8748        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
8749        if Self::pdl_on() && Self::pdl_wb_on() {
8750            use cudarc::driver::{DevicePtr, DevicePtrMut};
8751            let s = &self.gpu.stream();
8752            let (p0, _a0) = q0.device_ptr(s);
8753            let (p1, _a1) = k0.device_ptr(s);
8754            let (p2, _a2) = v0.device_ptr(s);
8755            let (pwq, _a3) = wq.device_ptr(s);
8756            let (pwk, _a4) = wk.device_ptr(s);
8757            let (pwv, _a5) = wv.device_ptr(s);
8758            let (pq, _a6) = q.device_ptr_mut(s);
8759            let (pk, _a7) = k.device_ptr_mut(s);
8760            let (pv, _a8) = v.device_ptr_mut(s);
8761            let (pp, _a9) = pos.device_ptr(s);
8762            let pff: u64 = match ff {
8763                Some(t) => {
8764                    let (p, _gg) = t.device_ptr(s);
8765                    p as u64
8766                }
8767                None => 0,
8768            };
8769            let (pkc, _a10) = kc.device_ptr_mut(s);
8770            let (pvc, _a11) = vc.device_ptr_mut(s);
8771            let (pt, _a12) = t_dev.device_ptr(s);
8772            let mut ps = [
8773                &p0 as *const _ as *mut std::ffi::c_void,
8774                &p1 as *const _ as *mut _,
8775                &p2 as *const _ as *mut _,
8776                &pwq as *const _ as *mut _,
8777                &pwk as *const _ as *mut _,
8778                &pwv as *const _ as *mut _,
8779                &pq as *const _ as *mut _,
8780                &pk as *const _ as *mut _,
8781                &pv as *const _ as *mut _,
8782                &nc as *const _ as *mut _,
8783                &rqi as *const _ as *mut _,
8784                &rki as *const _ as *mut _,
8785                &pp as *const _ as *mut _,
8786                &nhq as *const _ as *mut _,
8787                &nhk as *const _ as *mut _,
8788                &theta_scale as *const _ as *mut _,
8789                &freq_scale as *const _ as *mut _,
8790                &pff as *const _ as *mut _,
8791                &eps as *const _ as *mut _,
8792                &pkc as *const _ as *mut _,
8793                &pvc as *const _ as *mut _,
8794                &pt as *const _ as *mut _,
8795                &ktb as *const _ as *mut _,
8796                &vtb as *const _ as *mut _,
8797            ];
8798            unsafe {
8799                self.launch_pdl_flash(
8800                    g,
8801                    "rms_norm_qkv_rope_append_dc_f32",
8802                    (rows as u32, 1, 1),
8803                    (rms_block(), 1, 1),
8804                    0,
8805                    &mut ps,
8806                )?;
8807            }
8808            return Ok(());
8809        }
8810        let f = if g {
8811            self.func_g("rms_norm_qkv_rope_append_dc_f32")
8812        } else {
8813            self.func("rms_norm_qkv_rope_append_dc_f32")
8814        };
8815        let cfg = LaunchConfig {
8816            grid_dim: (rows as u32, 1, 1),
8817            block_dim: (rms_block(), 1, 1),
8818            shared_mem_bytes: 0,
8819        };
8820        let __s_b = self.gpu.stream();
8821        let mut b = __s_b.launch_builder(&f);
8822        match ff {
8823            Some(t) => {
8824                b.arg(q0)
8825                    .arg(k0)
8826                    .arg(v0)
8827                    .arg(wq)
8828                    .arg(wk)
8829                    .arg(wv)
8830                    .arg(&mut *q)
8831                    .arg(&mut *k)
8832                    .arg(&mut *v)
8833                    .arg(&nc)
8834                    .arg(&rqi)
8835                    .arg(&rki)
8836                    .arg(pos)
8837                    .arg(&nhq)
8838                    .arg(&nhk)
8839                    .arg(&theta_scale)
8840                    .arg(&freq_scale)
8841                    .arg(t)
8842                    .arg(&eps)
8843                    .arg(&mut *kc)
8844                    .arg(&mut *vc)
8845                    .arg(t_dev)
8846                    .arg(&ktb)
8847                    .arg(&vtb);
8848                unsafe {
8849                    b.launch(cfg)?;
8850                }
8851            }
8852            None => {
8853                let null: u64 = 0;
8854                b.arg(q0)
8855                    .arg(k0)
8856                    .arg(v0)
8857                    .arg(wq)
8858                    .arg(wk)
8859                    .arg(wv)
8860                    .arg(&mut *q)
8861                    .arg(&mut *k)
8862                    .arg(&mut *v)
8863                    .arg(&nc)
8864                    .arg(&rqi)
8865                    .arg(&rki)
8866                    .arg(pos)
8867                    .arg(&nhq)
8868                    .arg(&nhk)
8869                    .arg(&theta_scale)
8870                    .arg(&freq_scale)
8871                    .arg(&null)
8872                    .arg(&eps)
8873                    .arg(&mut *kc)
8874                    .arg(&mut *vc)
8875                    .arg(t_dev)
8876                    .arg(&ktb)
8877                    .arg(&vtb);
8878                unsafe {
8879                    b.launch(cfg)?;
8880                }
8881            }
8882        }
8883        Ok(())
8884    }
8885
8886    /// wave-2 fold: a + b with the sum emitted q8_1 alongside f32.
8887    pub fn add_q8_1(
8888        &self,
8889        a: &CudaSlice<f32>,
8890        b: &CudaSlice<f32>,
8891        res: &mut CudaSlice<f32>,
8892        ncols: usize,
8893        nrows: usize,
8894    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8895        debug_assert!(ncols % 128 == 0);
8896        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8897        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8898        let f = self.func("add_q8_1_f32");
8899        let cfg = LaunchConfig {
8900            grid_dim: (nrows as u32, 1, 1),
8901            block_dim: (rms_block(), 1, 1),
8902            shared_mem_bytes: 0,
8903        };
8904        let nc = ncols as i32;
8905        let __s_b2 = self.gpu.stream();
8906        let mut b2 = __s_b2.launch_builder(&f);
8907        b2.arg(a)
8908            .arg(b)
8909            .arg(&mut *res)
8910            .arg(&mut out_q)
8911            .arg(&mut out_d)
8912            .arg(&nc);
8913        unsafe {
8914            b2.launch(cfg)?;
8915        }
8916        Ok((out_q, out_d))
8917    }
8918
8919    /// E4B FFN-tail exit fusion (glue wave 5): resid = b + rms(a, wa) emitted f32 + q8_1 pair
8920    /// in ONE launch — replaces rms_norm(a,wa->sn) + add_q8_1(sn,b). Same rms_block() config
8921    /// as both parents (bit-identity: identical reduction + quad-walk quantize).
8922    pub fn rms_pre_add_q8_1(
8923        &self,
8924        a: &CudaSlice<f32>,
8925        wa: &CudaSlice<f32>,
8926        b: &CudaSlice<f32>,
8927        res: &mut CudaSlice<f32>,
8928        ncols: usize,
8929        nrows: usize,
8930        eps: f32,
8931    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
8932        debug_assert!(ncols % 128 == 0);
8933        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
8934        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
8935        let f = self.func("rms_pre_add_q8_1_f32");
8936        let cfg = LaunchConfig {
8937            grid_dim: (nrows as u32, 1, 1),
8938            block_dim: (rms_block(), 1, 1),
8939            shared_mem_bytes: 0,
8940        };
8941        let (nc, ep) = (ncols as i32, eps);
8942        let __s_b2 = self.gpu.stream();
8943        let mut b2 = __s_b2.launch_builder(&f);
8944        b2.arg(a)
8945            .arg(wa)
8946            .arg(b)
8947            .arg(&mut *res)
8948            .arg(&mut out_q)
8949            .arg(&mut out_d)
8950            .arg(&nc)
8951            .arg(&ep);
8952        unsafe {
8953            b2.launch(cfg)?;
8954        }
8955        Ok((out_q, out_d))
8956    }
8957
8958    /// L2 norm per row (head_dim), no weight.
8959    /// PREFILL l2 dispatch (round 27): the warp-per-row float4 v2 when the numeric-config
8960    /// seam allows (MEMRA_L2_V2, default ON, d_state==128 only); else the strided kernel.
8961    pub fn l2_v2_on(ncols: usize) -> bool {
8962        ncols == 128 && std::env::var("MEMRA_L2_V2").as_deref() != Ok("0")
8963    }
8964
8965    pub fn l2_norm_pp(
8966        &self,
8967        x: &CudaSlice<f32>,
8968        dst: &mut CudaSlice<f32>,
8969        dst16: Option<&mut CudaSlice<u8>>,
8970        ncols: usize,
8971        nrows: usize,
8972        eps: f32,
8973    ) -> Result<(), Box<dyn std::error::Error>> {
8974        if Self::l2_v2_on(ncols) {
8975            let f = self.func("l2_norm_pp_v2_f32");
8976            let rows_per_block = 8u32; // 256 threads = 8 warps = 8 rows
8977            let cfg = LaunchConfig {
8978                grid_dim: ((nrows as u32).div_ceil(rows_per_block), 1, 1),
8979                block_dim: (256, 1, 1),
8980                shared_mem_bytes: 0,
8981            };
8982            let (nc, nr, e) = (ncols as i32, nrows as i32, eps);
8983            // mirror-fold: bf16 twin address by value (0 = skip; matches the nullable param)
8984            let d16: u64 = match dst16 {
8985                Some(d) => self.addr_u8(d),
8986                None => 0,
8987            };
8988            let __s_b = self.gpu.stream();
8989            let mut b = __s_b.launch_builder(&f);
8990            b.arg(x).arg(dst).arg(&d16).arg(&nc).arg(&nr).arg(&e);
8991            unsafe {
8992                b.launch(cfg)?;
8993            }
8994            return Ok(());
8995        }
8996        self.l2_norm(x, dst, ncols, nrows, eps)
8997    }
8998
8999    pub fn l2_norm(
9000        &self,
9001        x: &CudaSlice<f32>,
9002        dst: &mut CudaSlice<f32>,
9003        ncols: usize,
9004        nrows: usize,
9005        eps: f32,
9006    ) -> Result<(), Box<dyn std::error::Error>> {
9007        let f = self.func("l2_norm_f32");
9008        let cfg = LaunchConfig {
9009            grid_dim: (nrows as u32, 1, 1),
9010            block_dim: (256, 1, 1),
9011            shared_mem_bytes: 0,
9012        };
9013        let (nc, e) = (ncols as i32, eps);
9014        let __s_b = self.gpu.stream();
9015        let mut b = __s_b.launch_builder(&f);
9016        b.arg(x).arg(dst).arg(&nc).arg(&e);
9017        unsafe {
9018            b.launch(cfg)?;
9019        }
9020        Ok(())
9021    }
9022
9023    /// L2-norm with blockDim=32 (warp-tree reduction) — BIT-IDENTICAL to gdn_prep_decode_f32's
9024    /// per-warp L2 norm. The verify path MUST use this to match decode's FP accumulation order:
9025    /// l2_norm at blockDim=256 produces a different shfl-tree reduction of the 128-element
9026    /// squared-sum (pairwise tree vs serial-4-then-warp-tree), causing ULP differences that
9027    /// propagate through gdn_scan and flip argmax on marginal logits.
9028    pub fn l2_norm_decode(
9029        &self,
9030        x: &CudaSlice<f32>,
9031        dst: &mut CudaSlice<f32>,
9032        ncols: usize,
9033        nrows: usize,
9034        eps: f32,
9035    ) -> Result<(), Box<dyn std::error::Error>> {
9036        let f = self.func("l2_norm_f32");
9037        let cfg = LaunchConfig {
9038            grid_dim: (nrows as u32, 1, 1),
9039            block_dim: (32, 1, 1),
9040            shared_mem_bytes: 0,
9041        };
9042        let (nc, e) = (ncols as i32, eps);
9043        let __s_b = self.gpu.stream();
9044        let mut b = __s_b.launch_builder(&f);
9045        b.arg(x).arg(dst).arg(&nc).arg(&e);
9046        unsafe {
9047            b.launch(cfg)?;
9048        }
9049        Ok(())
9050    }
9051
9052    /// RoPE NEOX in-place. x:[head_dim, n_heads, n_tokens], pos:[n_tokens].
9053    pub fn rope_neox(
9054        &self,
9055        x: &mut CudaSlice<f32>,
9056        pos: &CudaSlice<i32>,
9057        head_dim: usize,
9058        n_dims: usize,
9059        n_heads: usize,
9060        n_tokens: usize,
9061        freq_base: f32,
9062        freq_scale: f32,
9063    ) -> Result<(), Box<dyn std::error::Error>> {
9064        let f = self.func("rope_neox_f32");
9065        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9066        let grid = (n_heads * n_tokens) as u32;
9067        let cfg = LaunchConfig {
9068            grid_dim: (grid, 1, 1),
9069            block_dim: ((head_dim / 2) as u32, 1, 1),
9070            shared_mem_bytes: 0,
9071        };
9072        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9073        let __s_b = self.gpu.stream();
9074        let mut b = __s_b.launch_builder(&f);
9075        b.arg(x)
9076            .arg(pos)
9077            .arg(&hd)
9078            .arg(&nd)
9079            .arg(&nh)
9080            .arg(&theta_scale)
9081            .arg(&freq_scale);
9082        unsafe {
9083            b.launch(cfg)?;
9084        }
9085        Ok(())
9086    }
9087
9088    /// RoPE NEOX with per-dim freq factors (gemma4 global layers, rope_freqs.weight [n_dims/2]).
9089    pub fn rope_neox_ff(
9090        &self,
9091        x: &mut CudaSlice<f32>,
9092        pos: &CudaSlice<i32>,
9093        head_dim: usize,
9094        n_dims: usize,
9095        n_heads: usize,
9096        n_tokens: usize,
9097        freq_base: f32,
9098        freq_scale: f32,
9099        ff: &CudaSlice<f32>,
9100    ) -> Result<(), Box<dyn std::error::Error>> {
9101        let f = self.func("rope_neox_ff_f32");
9102        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9103        let grid = (n_heads * n_tokens) as u32;
9104        let cfg = LaunchConfig {
9105            grid_dim: (grid, 1, 1),
9106            block_dim: ((head_dim / 2) as u32, 1, 1),
9107            shared_mem_bytes: 0,
9108        };
9109        let (hd, nd, nh) = (head_dim as i32, n_dims as i32, n_heads as i32);
9110        let __s_b = self.gpu.stream();
9111        let mut b = __s_b.launch_builder(&f);
9112        b.arg(x)
9113            .arg(pos)
9114            .arg(&hd)
9115            .arg(&nd)
9116            .arg(&nh)
9117            .arg(&theta_scale)
9118            .arg(&freq_scale)
9119            .arg(ff);
9120        unsafe {
9121            b.launch(cfg)?;
9122        }
9123        Ok(())
9124    }
9125
9126    /// gemma4: rope q and k in one launch (per-row chain = rope_neox / rope_neox_ff verbatim).
9127    #[allow(clippy::too_many_arguments)]
9128    pub fn rope_neox2(
9129        &self,
9130        q: &mut CudaSlice<f32>,
9131        k: &mut CudaSlice<f32>,
9132        pos: &CudaSlice<i32>,
9133        head_dim: usize,
9134        n_dims: usize,
9135        nh_q: usize,
9136        nh_k: usize,
9137        n_tokens: usize,
9138        freq_base: f32,
9139        freq_scale: f32,
9140        ff: Option<&CudaSlice<f32>>,
9141    ) -> Result<(), Box<dyn std::error::Error>> {
9142        let f = self.func("rope_neox2_f32");
9143        let theta_scale = (freq_base).powf(-2.0 / n_dims as f32);
9144        let grid = ((nh_q + nh_k) * n_tokens) as u32;
9145        let cfg = LaunchConfig {
9146            grid_dim: (grid, 1, 1),
9147            block_dim: ((head_dim / 2) as u32, 1, 1),
9148            shared_mem_bytes: 0,
9149        };
9150        let (hd, nd, nq, nk, nt) = (
9151            head_dim as i32,
9152            n_dims as i32,
9153            nh_q as i32,
9154            nh_k as i32,
9155            n_tokens as i32,
9156        );
9157        let __s_b = self.gpu.stream();
9158        let mut b = __s_b.launch_builder(&f);
9159        b.arg(q)
9160            .arg(k)
9161            .arg(pos)
9162            .arg(&hd)
9163            .arg(&nd)
9164            .arg(&nq)
9165            .arg(&nk)
9166            .arg(&nt)
9167            .arg(&theta_scale)
9168            .arg(&freq_scale);
9169        match ff {
9170            Some(ffv) => {
9171                b.arg(ffv);
9172                unsafe {
9173                    b.launch(cfg)?;
9174                }
9175            }
9176            None => {
9177                let null: u64 = 0;
9178                b.arg(&null);
9179                unsafe {
9180                    b.launch(cfg)?;
9181                }
9182            }
9183        }
9184        Ok(())
9185    }
9186
9187    /// gemma4 R1: dst = GELU_tanh(gate) * up.
9188    pub fn gelu_tanh_mul(
9189        &self,
9190        gate: &CudaSlice<f32>,
9191        up: &CudaSlice<f32>,
9192        dst: &mut CudaSlice<f32>,
9193        n: usize,
9194    ) -> Result<(), Box<dyn std::error::Error>> {
9195        let f = self.func("gelu_tanh_mul_f32");
9196        let cfg = LaunchConfig::for_num_elems(n as u32);
9197        let ni = n as i32;
9198        let __s_b = self.gpu.stream();
9199        let mut b = __s_b.launch_builder(&f);
9200        b.arg(gate).arg(up).arg(dst).arg(&ni);
9201        unsafe {
9202            b.launch(cfg)?;
9203        }
9204        Ok(())
9205    }
9206
9207    pub fn silu_mul(
9208        &self,
9209        gate: &CudaSlice<f32>,
9210        up: &CudaSlice<f32>,
9211        dst: &mut CudaSlice<f32>,
9212        n: usize,
9213    ) -> Result<(), Box<dyn std::error::Error>> {
9214        let f = self.func("silu_mul_f32");
9215        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9216        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9217        let ni = n as i32;
9218        let __s_b = self.gpu.stream();
9219        let mut b = __s_b.launch_builder(&f);
9220        b.arg(gate).arg(up).arg(dst).arg(&ni);
9221        unsafe {
9222            b.launch(cfg)?;
9223        }
9224        Ok(())
9225    }
9226
9227    /// f16out twin of `silu_mul` (task #17): the epilogue also emits the fp16 GEMM operand
9228    /// for the down projection — kills the standalone convert pass. Bit-identical class.
9229    pub fn silu_mul_f16out(
9230        &self,
9231        gate: &CudaSlice<f32>,
9232        up: &CudaSlice<f32>,
9233        dst: &mut CudaSlice<f32>,
9234        dst16: &mut CudaSlice<u8>,
9235        n: usize,
9236    ) -> Result<(), Box<dyn std::error::Error>> {
9237        let f = self.func("silu_mul_f16out_f32");
9238        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9239        let ni = n as i32;
9240        let __s_b = self.gpu.stream();
9241        let mut b = __s_b.launch_builder(&f);
9242        b.arg(gate).arg(up).arg(dst).arg(dst16).arg(&ni);
9243        unsafe {
9244            b.launch(cfg)?;
9245        }
9246        Ok(())
9247    }
9248
9249    /// FFN SwiGLU epilogue fusion (RANK3 LEVER 2): `dst = silu(gate*gs) * (up*us)` in ONE launch,
9250    /// folding the per-tensor NVFP4 macro-scale (`gs`,`us`) that would otherwise be two separate
9251    /// `scale_inplace` launches on the gate/up matmul outputs. BIT-IDENTICAL to
9252    /// scale_inplace(gate,gs); scale_inplace(up,us); silu_mul(gate,up,dst) — identical float ops in
9253    /// identical order. For non-NVFP4 weights gs==us==1.0 -> identical to `silu_mul`. Net: -2
9254    /// launches per dense FFN layer (the gate+up post-matmul scales).
9255    pub fn silu_mul_scaled(
9256        &self,
9257        gate: &CudaSlice<f32>,
9258        up: &CudaSlice<f32>,
9259        gs: f32,
9260        us: f32,
9261        dst: &mut CudaSlice<f32>,
9262        n: usize,
9263    ) -> Result<(), Box<dyn std::error::Error>> {
9264        let f = self.func("silu_mul_scaled_f32");
9265        let cfg = LaunchConfig::for_num_elems(n as u32);
9266        let ni = n as i32;
9267        let (gsf, usf) = (gs, us);
9268        let __s_b = self.gpu.stream();
9269        let mut b = __s_b.launch_builder(&f);
9270        b.arg(gate).arg(up).arg(&gsf).arg(&usf).arg(dst).arg(&ni);
9271        unsafe {
9272            b.launch(cfg)?;
9273        }
9274        Ok(())
9275    }
9276
9277    /// swigluoai (MiniMax-M3 / GPT-OSS): clamped SwiGLU epilogue, math 1:1 vs llama.cpp
9278    /// ggml_cuda_op_swiglu_oai_single. `dst = swish_alpha(clamp(gate*gs)) * (1 + clamp(up*us))`.
9279    /// gs/us fold the NVFP4 macro-scales exactly like `silu_mul_scaled`.
9280    #[allow(clippy::too_many_arguments)]
9281    pub fn swigluoai_mul_scaled(
9282        &self,
9283        gate: &CudaSlice<f32>,
9284        up: &CudaSlice<f32>,
9285        gs: f32,
9286        us: f32,
9287        alpha: f32,
9288        limit: f32,
9289        dst: &mut CudaSlice<f32>,
9290        n: usize,
9291    ) -> Result<(), Box<dyn std::error::Error>> {
9292        let f = self.func("swigluoai_mul_scaled_f32");
9293        let cfg = LaunchConfig::for_num_elems(n as u32);
9294        let ni = n as i32;
9295        let __s_b = self.gpu.stream();
9296        let mut b = __s_b.launch_builder(&f);
9297        b.arg(gate)
9298            .arg(up)
9299            .arg(&gs)
9300            .arg(&us)
9301            .arg(&alpha)
9302            .arg(&limit)
9303            .arg(dst)
9304            .arg(&ni);
9305        unsafe {
9306            b.launch(cfg)?;
9307        }
9308        Ok(())
9309    }
9310
9311    /// RANK2 LEVER (q8_1 quant-fold): SwiGLU epilogue that EMITS the q8_1 quantization of `act`
9312    /// directly (aq int8 [n] + ad f32 [n/32]), so ffn_down's standalone `quantize_q8_1` launch is
9313    /// removed — the down-proj activation has one consumer, so the quant folds into the producer for
9314    /// free (no extra HBM read; no f32 `act` write). gs/us fold the gate/up NVFP4 macro-scales like
9315    /// `silu_mul_scaled`. BIT-IDENTICAL q8_1 to silu_mul_scaled(...) then quantize_q8_1(...). Only
9316    /// valid when ffn_down uses the q8_1 dp4a/mmvq path; the caller checks `uses_q8_1_fast(ffn_down)`.
9317    /// n must be a multiple of 32 (n_ff always is).
9318    pub fn silu_mul_scaled_q8_1(
9319        &self,
9320        gate: &CudaSlice<f32>,
9321        up: &CudaSlice<f32>,
9322        gs: f32,
9323        us: f32,
9324        n: usize,
9325    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
9326        let f = self.func("silu_mul_scaled_q8_1");
9327        let nblk = n / 32;
9328        let mut aq = self.alloc_uninit::<i8>(n)?; // full-overwrite output
9329        let mut ad = self.alloc_uninit::<f32>(nblk)?; // full-overwrite output
9330        // WARP-PER-BLOCK kernel: one warp (32 lanes) per 32-block -> n threads total.
9331        let cfg = LaunchConfig::for_num_elems(n as u32);
9332        let (gsf, usf, ni) = (gs, us, n as i32);
9333        let __s_b = self.gpu.stream();
9334        let mut b = __s_b.launch_builder(&f);
9335        b.arg(gate)
9336            .arg(up)
9337            .arg(&gsf)
9338            .arg(&usf)
9339            .arg(&mut aq)
9340            .arg(&mut ad)
9341            .arg(&ni);
9342        unsafe {
9343            b.launch(cfg)?;
9344        }
9345        Ok((aq, ad))
9346    }
9347
9348    pub fn add(
9349        &self,
9350        a: &CudaSlice<f32>,
9351        b_in: &CudaSlice<f32>,
9352        dst: &mut CudaSlice<f32>,
9353        n: usize,
9354    ) -> Result<(), Box<dyn std::error::Error>> {
9355        let f = self.func("add_f32");
9356        // float4 kernel: one thread per 4 elements (tail handled in-kernel)
9357        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
9358        let ni = n as i32;
9359        let __s_bld = self.gpu.stream();
9360        let mut bld = __s_bld.launch_builder(&f);
9361        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9362        unsafe {
9363            bld.launch(cfg)?;
9364        }
9365        Ok(())
9366    }
9367
9368    pub fn mul(
9369        &self,
9370        a: &CudaSlice<f32>,
9371        b_in: &CudaSlice<f32>,
9372        dst: &mut CudaSlice<f32>,
9373        n: usize,
9374    ) -> Result<(), Box<dyn std::error::Error>> {
9375        let f = self.func("mul_f32");
9376        let cfg = LaunchConfig::for_num_elems(n as u32);
9377        let ni = n as i32;
9378        let __s_bld = self.gpu.stream();
9379        let mut bld = __s_bld.launch_builder(&f);
9380        bld.arg(a).arg(b_in).arg(dst).arg(&ni);
9381        unsafe {
9382            bld.launch(cfg)?;
9383        }
9384        Ok(())
9385    }
9386
9387    /// Unified weight-tensor matmul: dispatches quant tensors to qmatvec (weights packed) and
9388    /// float tensors to cuBLASLt. y[m,out] = x[m,in] @ W[out,in]^T.
9389    pub fn matmul(
9390        &self,
9391        w: &crate::model::GpuTensor,
9392        x: &CudaSlice<f32>,
9393        m: usize,
9394    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9395        use crate::model::GpuTensor;
9396        let in_f = w.in_features();
9397        let out_f = w.out_features();
9398        // PREFILL (T>1) ROOT FIX: batched tensor-core int8 GEMM. Decodes each weight tile to int8
9399        // in smem ONCE and reuses across all tokens via mma — vs the dp4a matvec's per-token weight
9400        // re-read. Only the 4 daily-hot dtypes; m=1 decode keeps dp4a (it's bandwidth-bound, mma
9401        // gives nothing). Quantize the activation once here then call the GEMM.
9402        // m cutoff FIXED at 16: the m=4 MMA-verify A/B (2026-07-06, was MEMRA_GEMM_M) measured
9403        // NEGATIVE — the MMA tile grid starves at m=4 (BN=256 -> grid.y=1) and its FP order
9404        // shifted verify argmax at tight margins. Do not lower without re-running that battery.
9405        #[allow(non_snake_case)]
9406        // VERIFY-EXACT scope pushes the GEMM crossover out of reach (usize::MAX) — the
9407        // t>=16 dflash verify must ride the decode-exact batched class (parity law).
9408        let GEMM_M_THRESHOLD = if self.verify_exact_on() {
9409            usize::MAX
9410        } else {
9411            16usize
9412        };
9413
9414        // PREFILL GEMM (m>=16). ACCURACY-FIRST dispatch (2026-06-28, prefill-gemm-beat-research wf
9415        // wllbyo6vc step 1): the int8 W4A8 GEMM (qmatvec_gemm, q8_1 activation, s32 accumulate) is
9416        // ACCURATE (prefill logit maxdiff 0.159, < dp4a 0.55) and the default. The FP4 W4A4 mxf4 path
9417        // (try_fp4_gemm) quantizes the ACTIVATION to e2m1 4-bit (8 magnitude levels) -> maxdiff 1.0
9418        // when combined — a real accuracy loss, NOT a math bug. So FP4-W4A4 is taken ONLY under the
9419        // explicit MEMRA_FP4 opt-in AND it must come SECOND (int8 W4A8 is the correct default for NVFP4).
9420        // The workflow plan rebuilds the FP4 path (kill per-K repack, widen K, deepen pipeline, TMA) to
9421        // be both fast AND accurate; until then NVFP4 prefill defaults to the accurate int8 GEMM.
9422        // TINY-OUT_F GUARD (2026-06-28, ncu trace): the tiling GEMM's grid is (ceil(out_f/BM=64),
9423        // ceil(m/BN=256)). For tiny out_f (ssm_beta/ssm_alpha out_f=num_v_heads~32), grid.x=1 -> only
9424        // ceil(m/256) CTAs (e.g. 2 for m=512) on 82 SMs = 0.39% SM throughput, 852us EACH (measured
9425        // worst offender). The dp4a path grids (out_f, m) = far more CTAs, filling the GPU. So route
9426        // out_f < 2*BM to dp4a (skip the tiling GEMM which structurally can't fill the SMs here).
9427        const GEMM_MIN_OUT_F: usize = 128; // 2*BM; below this the GEMM grid.x starves the 82 SMs
9428        // VENDORED llama MMQ prefill GEMMs. NVFP4 W4A8 is DEFAULT-ON (2026-07-05 flip: same int8
9429        // accuracy class as the int8 GEMM below at ~1.9x pp512, rp-loader coexists with the A6
9430        // repack; MEMRA_MMQ_W4A8=0 = escape hatch). W4A4 mxf4nvf4 + Q4_K/Q5_K stay behind MEMRA_MMQ=1.
9431        // The env policy lives in mmq_supports/qmatvec_mmq. Feeds raw f32 activation `x` (the
9432        // launcher quantizes internally). out_f>=MMQ_Y/2 keeps the tile grid from starving the SMs.
9433        // FP8-ACT PREFILL (MEMRA_PP_FP8=1, probe verdict 2026-07-08): F8-E4M3-origin projections
9434        // carry their raw e4m3 device bytes (the `fp8` operand stashed at load next to the Q8_0
9435        // re-encode) — cuBLASLt FP8 TN at 620-795 TF vs 47-72 TF for this class's int8 GEMM.
9436        // Weight side EXACT (checkpoint bytes); activation rides ONE per-batch e4m3 scale
9437        // (amax/448) folded with weight_scale in-GEMM. Prefill only; decode keeps Q8_0 untouched.
9438        if m >= GEMM_M_THRESHOLD {
9439            if let Some(y) = self.try_fp8_gemm(w, x, m)? {
9440                return Ok(y);
9441            }
9442            // PER-BLOCK FP8 MMQ (lane/fp8-mmq): the block-128 class try_fp8_gemm skips (cuBLASLt
9443            // takes no block grid on sm_120). Exact per block — the checkpoint's e4m3 bytes and its
9444            // f32 grid go into the tile unchanged. TWO SOURCES, TWO DEFAULTS: the load-time stash is
9445            // opt-in (MEMRA_FP8_MMQ=1), the native-resident QT_F8_E4M3_BLK grid is DEFAULT ON
9446            // (MEMRA_FP8_MMQ=0 reverts it to dequant-per-call) — see fp8_ffi.rs for why the same
9447            // tile defaults differently by operand source.
9448            if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
9449                return Ok(y);
9450            }
9451            // FP16-mirror prefill (MEMRA_PP_F16=1, probe 2026-07-26: 3.2-3.7x the MMQ class).
9452            // Mirror presence IS the gate (only built under the env). Decode never reaches here.
9453            if let Some(y) = self.try_f16_gemm(w, x, m)? {
9454                return Ok(y);
9455            }
9456        }
9457        // F8-E4M3 BLOCK-128 (QT_F8_E4M3_BLK, lane/fp8-blk128-decode). TWO arms, split at the SAME
9458        // m threshold the rest of this method uses:
9459        //   * m >= threshold (prefill): dequant-per-call to the ARM B' Q8_0 slab and recurse, so
9460        //     prefill keeps the floor's kernels AND the floor's bits (try_e4m3_blk_prefill).
9461        //   * m <  threshold: the native per-block GEMV — m=1 decode and the m=2..15 verify tiers.
9462        //     grid.y=m runs the exact m=1 program per (token,row), so the decode-parity law holds
9463        //     across every tier by construction with no batched twin needed.
9464        //
9465        // NOT gated on `fast`: this dtype has no dp4a twin and no Stage-A f32-dequant oracle (the
9466        // generic `deq()` switch has no block-scale input), exactly as QT_F8_E4M3 has none, so
9467        // MEMRA_FAST=0 cannot route it anywhere else. Placed before every GEMM/MMQ arm below
9468        // because gemm_supports/mmq_supports/mmvq_supports all deliberately REFUSE this qtype —
9469        // reaching the generic tail would panic rather than produce wrong numbers, and this pair of
9470        // arms is what makes sure it never gets there.
9471        if let GpuTensor::Quant { qtype, .. } = w {
9472            if *qtype == QT_F8_E4M3_BLK {
9473                if m >= GEMM_M_THRESHOLD {
9474                    if let Some(y) = self.try_e4m3_blk_prefill(w, x, m)? {
9475                        return Ok(y);
9476                    }
9477                }
9478                let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9479                if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
9480                    return Ok(y);
9481                }
9482            }
9483        }
9484        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.mmq_supports(w) {
9485            return self.qmatvec_mmq(w, x, m);
9486        }
9487        if m >= GEMM_M_THRESHOLD && out_f >= GEMM_MIN_OUT_F && self.gemm_supports(w) {
9488            let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9489            return self.qmatvec_gemm(w, &aq, &ad, m);
9490        }
9491        // FP4 W4A4 only as an explicit speed/accuracy tradeoff opt-in, and only if the int8 GEMM
9492        // above didn't already handle this weight (e.g. NVFP4 with in_f%64!=0, or MEMRA_NO_GEMM set).
9493        if m >= GEMM_M_THRESHOLD {
9494            if let Some(y) = self.try_fp4_gemm(w, x, m, in_f, out_f)? {
9495                return Ok(y);
9496            }
9497        }
9498        // Stage-B fast int8 dp4a is the DEFAULT since 2026-07-08 (it has been the daily path
9499        // for weeks; the old opt-in flag was a silent-slow-path landmine). MEMRA_FAST=0 reverts
9500        // to Stage-A f32-dequant (the correctness oracle path).
9501        let fast = std::env::var("MEMRA_FAST").as_deref() != Ok("0");
9502        // PERF-3 decode-GEMV: m=1 warp-per-row MMVQ (MEMRA_MMVQ). The big decode matvecs reach
9503        // `matmul` directly (ffn_down, lm_head output, wo), so route them here too — not only the
9504        // matmul_pre siblings. qmatvec_mmvq_raw quantizes the activation internally (q8_1) like the
9505        // _fast paths; the NVFP4 macro-scale is applied by the `scale != 1.0` block below.
9506        if m == 1 && fast {
9507            if let GpuTensor::Quant {
9508                bytes,
9509                qtype,
9510                row_bytes,
9511                rp,
9512                rp4,
9513                scale,
9514                ..
9515            } = w
9516            {
9517                if self.mmvq_supports(*qtype) {
9518                    // NVFP4 macro-scale rides the kernel's fused epilogue arg (one launch total);
9519                    // non-NVFP4 has scale==1.0 so qmatvec_mmvq skips scale_inplace either way.
9520                    // Q4_0 split-plane mirror (rp4): the decode arm reads it via the _rp twins.
9521                    let (bytes, rp) = match rp4 {
9522                        Some(m4) => (m4, true),
9523                        None => (bytes, *rp),
9524                    };
9525                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9526                    return self.qmatvec_mmvq(
9527                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, rp,
9528                    );
9529                }
9530            }
9531        }
9532        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward's ffn_down, wo, and
9533        // lm_head `output` reach `matmul` directly at m=T=2..4). Walks the weight ONCE, dp4a vs all m
9534        // activation columns -> 1 weight read for m tokens (vs grid.y=m re-reading m times below). Quant
9535        // the activation once here (q8_1) like the _fast paths; macro-scale applied via the scale!=1.0
9536        // block below. MEMRA_NO_BATCHED -> per-m path.
9537        //
9538        // DECODE-PARITY GATE (2026-07-07, the 9B synth K=3/4/6 spec FAIL root cause): the batched
9539        // kernels are bit-identical per (token,row) to MMVQ's 32-thread warp reduce, NOT to the
9540        // dp4a kernels' 128-thread two-level reduce. Without MEMRA_MMVQ the m=1 decode chain rides
9541        // dp4a, so a verify riding batched here has a DIFFERENT FP order than the decode it must
9542        // match bit-for-bit — greedy spec flips at tight-margin tokens (the old HANDOVER "ENV LAW:
9543        // FAST+MMVQ both required" footgun, closed here). Parity law: the m>1 kernel CLASS must be
9544        // a pure function of (dtype, env) equal to the m=1 class — batched iff MMVQ. Without MMVQ
9545        // the verify falls to the per-m grid.y=m dp4a path below (each column = the exact m=1
9546        // dp4a program). MEMRA_MMVQ=1 (the daily config) is dispatch-unchanged.
9547        if (2..=16).contains(&m)
9548            && fast
9549            && std::env::var("MEMRA_NO_BATCHED").is_err()
9550            && (m <= 4 || Self::b8_enabled())
9551        {
9552            // b16 tier (2026-07-11, spec K>7): Q4_0/Q6_K have base+_rp b16 kernels; Q8_0's
9553            // b16 exists only as the split-plane _rp twin, so it joins iff the q8rp mirror
9554            // is present (rp4) — the mirror pick below then routes to the _rp family.
9555            // QT_F8_E4M3 joins unconditionally (lane/rp-on-st): its b16 IS the base kernel,
9556            // because the native e4m3 row layout is already aligned and needs no mirror.
9557            // NVFP4/Q4_K/Q8_0 all join unconditionally now (lane/rp-on-st): each has base + _rp
9558            // b16 twins, so either residency layout has its aligned form at this width. Q8_0's
9559            // old `rp4.is_some()` precondition is GONE — the mirror is a bandwidth lever, not the
9560            // exact tier's admission ticket (it was refusing FP8-ST over 23.9 MiB of ssm_beta).
9561            let m_ok = m <= 8
9562                || matches!(w, GpuTensor::Quant { qtype, .. }
9563                if *qtype == QT_Q4_0 || *qtype == QT_Q6_K || *qtype == QT_F8_E4M3
9564                    || *qtype == QT_NVFP4 || *qtype == QT_Q4_K || *qtype == QT_Q5_K || *qtype == QT_Q8_0);
9565            if m_ok {
9566                if let GpuTensor::Quant {
9567                    bytes,
9568                    qtype,
9569                    row_bytes,
9570                    rp,
9571                    rp4,
9572                    ..
9573                } = w
9574                {
9575                    if self.batched_supports(*qtype) && self.mmvq_supports(*qtype) {
9576                        let (bytes, rp) = match rp4 {
9577                            Some(m4) => (m4, true),
9578                            None => (bytes, *rp),
9579                        };
9580                        let mcols = Self::batched_mcols(m);
9581                        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9582                        let mut y = self.qmatvec_mmvq_batched(
9583                            bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, mcols, 1.0, rp,
9584                        )?;
9585                        if let GpuTensor::Quant { scale, .. } = w {
9586                            if *scale != 1.0 {
9587                                self.scale_inplace(&mut y, *scale, m * out_f)?;
9588                            }
9589                        }
9590                        return Ok(y);
9591                    }
9592                }
9593            }
9594        }
9595        // F8-E4M3 (MEMRA_ST_E4M3) catch-all for the m<16 band the arms above didn't take (m=9..15,
9596        // the K=8 verify tier; or m=2..8 under MEMRA_NO_BATCHED/MEMRA_B8=0): grid.y=m e4m3 mmvq —
9597        // the SAME per-(token,row) program as the m=1 decode launch (bit-identical by construction),
9598        // weight re-read m times (rare tier; exactness over bandwidth here). There is no _dp4a twin
9599        // for this dtype, so the generic match below must never see it under `fast`.
9600        if fast {
9601            if let GpuTensor::Quant {
9602                bytes,
9603                qtype,
9604                row_bytes,
9605                scale,
9606                ..
9607            } = w
9608            {
9609                if *qtype == QT_F8_E4M3 {
9610                    let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
9611                    return self.qmatvec_mmvq(
9612                        bytes, &aq, &ad, m, in_f, out_f, *qtype, *row_bytes, *scale, false,
9613                    );
9614                }
9615            }
9616        }
9617        let mut y = match w {
9618            GpuTensor::Quant {
9619                bytes,
9620                qtype,
9621                row_bytes,
9622                ..
9623            } if fast && *qtype == QT_Q8_0 => {
9624                self.qmatvec_q8_0_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9625            }
9626            GpuTensor::Quant {
9627                bytes,
9628                qtype,
9629                row_bytes,
9630                ..
9631            } if fast && *qtype == QT_Q4_K => {
9632                self.qmatvec_q4_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9633            }
9634            GpuTensor::Quant {
9635                bytes,
9636                qtype,
9637                row_bytes,
9638                ..
9639            } if fast && *qtype == QT_Q6_K => {
9640                self.qmatvec_q6_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9641            }
9642            GpuTensor::Quant {
9643                bytes,
9644                qtype,
9645                row_bytes,
9646                ..
9647            } if fast && *qtype == QT_Q5_K => {
9648                self.qmatvec_q5_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9649            }
9650            GpuTensor::Quant {
9651                bytes,
9652                qtype,
9653                row_bytes,
9654                ..
9655            } if fast && *qtype == QT_Q3_K => {
9656                self.qmatvec_q3_K_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9657            }
9658            GpuTensor::Quant {
9659                bytes,
9660                qtype,
9661                row_bytes,
9662                rp,
9663                ..
9664            } if fast && *qtype == QT_NVFP4 => self.qmatvec_dp4a_named(
9665                if *rp {
9666                    "qmatvec_nvfp4_dp4a_rp"
9667                } else {
9668                    "qmatvec_nvfp4_dp4a"
9669                },
9670                bytes,
9671                x,
9672                m,
9673                in_f,
9674                out_f,
9675                *row_bytes,
9676            )?,
9677            // IQ4_XS trunk fast path — DEFAULT ON since 2026-08-02 (MEMRA_IQ_FAST=0 reverts to
9678            // Stage-A; see iq_fast_enabled). The old opt-in default was the KAT-Coder decode
9679            // anomaly (research/kat-anomaly-20260802/).
9680            GpuTensor::Quant {
9681                bytes,
9682                qtype,
9683                row_bytes,
9684                ..
9685            } if fast && *qtype == QT_IQ4_XS && Self::iq_fast_enabled() => {
9686                self.qmatvec_iq4_XS_fast(bytes, x, m, in_f, out_f, *row_bytes)?
9687            }
9688            // B3: IQ3_S uses the Stage-A f32 dequant-in-kernel path. There is NO
9689            // qmatvec_iq3_s_dp4a kernel — do NOT add a `*qtype == QT_IQ3_S` fast guard here
9690            // without first writing the matching kernel, or func() will panic
9691            // "kernel ... not in any fatbin".
9692            GpuTensor::Quant {
9693                bytes,
9694                qtype,
9695                row_bytes,
9696                rp,
9697                ..
9698            } =>
9699            // Stage-A generic: repacked NVFP4 uses the device-side split-plane tag (the
9700            // deq(row,j) form cannot address the planes; same value/product order).
9701            {
9702                self.qmatvec(
9703                    bytes,
9704                    x,
9705                    m,
9706                    in_f,
9707                    out_f,
9708                    if *rp && *qtype == QT_NVFP4 {
9709                        QT_NVFP4_RP
9710                    } else {
9711                        *qtype
9712                    },
9713                    *row_bytes,
9714                )?
9715            }
9716            GpuTensor::Float { data, .. } => self.linear(x, data, m, in_f, out_f)?,
9717            // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use to f32 scratch, then the same
9718            // cuBLASLt f32 GEMV as the Float arm.
9719            GpuTensor::FloatBf16 { data, .. } => {
9720                self.linear_bf16_chunked(x, data, m, in_f, out_f, false)?
9721            }
9722        };
9723        // NVFP4 per-tensor macro-scale (post-matmul). scale==1.0 for all other quants/float -> no-op.
9724        if let GpuTensor::Quant { scale, .. } = w {
9725            if *scale != 1.0 {
9726                self.scale_inplace(&mut y, *scale, m * out_f)?;
9727            }
9728        }
9729        Ok(y)
9730    }
9731
9732    /// True if `w` would take the int8-dp4a fast path under MEMRA_FAST (so its activation can be
9733    /// pre-quantized once and shared across sibling matmuls via `matmul_pre`).
9734    pub fn uses_q8_1_fast(&self, w: &crate::model::GpuTensor) -> bool {
9735        use crate::model::GpuTensor;
9736        if std::env::var("MEMRA_FAST").as_deref() == Ok("0") {
9737            return false;
9738        }
9739        match w {
9740            // QT_F8_E4M3_BLK is admitted for the same reason QT_F8_E4M3 is: its ONLY kernel class
9741            // takes the shared q8_1 activation, so callers may pre-quantize once and share it
9742            // across siblings. It is NOT admitted to any of the fused/dual epilogue doors those
9743            // siblings can then open (`q8_fused_params`, `e4m3_fused_params` and
9744            // `matmul_pre_dual_noscale` all match on their own qtype and refuse this one) — the
9745            // block class has no fused twin yet, so each of its projections takes its own launch.
9746            GpuTensor::Quant { qtype, .. } => {
9747                matches!(
9748                    *qtype,
9749                    QT_Q8_0
9750                        | QT_Q4_K
9751                        | QT_Q6_K
9752                        | QT_Q5_K
9753                        | QT_Q3_K
9754                        | QT_NVFP4
9755                        | QT_F8_E4M3
9756                        | QT_F8_E4M3_BLK
9757                        | QT_Q4_0
9758                ) || (*qtype == QT_IQ4_XS && Self::iq_fast_enabled())
9759            }
9760            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
9761        }
9762    }
9763
9764    /// matmul with a PRE-QUANTIZED q8_1 activation (aq,ad from `quantize_q8_1`). Skips the
9765    /// per-matmul re-quantize so sibling matmuls that share an input (gate+up share `z`;
9766    /// q/k/v + wqkv/gate/beta/alpha share `h`) quantize ONCE. Caller MUST have checked
9767    /// `uses_q8_1_fast(w)`; falls back to plain `matmul` otherwise (Stage-A / Float / non-fast).
9768    pub fn matmul_pre(
9769        &self,
9770        w: &crate::model::GpuTensor,
9771        aq: &CudaSlice<i8>,
9772        ad: &CudaSlice<f32>,
9773        x_fallback: &CudaSlice<f32>,
9774        m: usize,
9775    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9776        use crate::model::GpuTensor;
9777        // Every raw-f32 arm below (fp8/f16/MMQ/fp4) reads m*in_f from x_fallback. Callers that
9778        // pre-quantized and dropped the f32 input pass an EMPTY x_fallback (E4B's fusion port:
9779        // h = zeros(0)) — the length guard keeps those on the aq/ad GEMM instead of feeding a
9780        // 0-byte buffer to a convert kernel (illegal address -> cublasLt status 13; the E4B
9781        // rc=30013 dig, 2026-07-31).
9782        let x_raw_ok = x_fallback.len() >= m * w.in_features();
9783        // FP8-ACT PREFILL (MEMRA_PP_FP8=1): same arm as `matmul` — the fp8 operand needs the RAW
9784        // f32 activation (per-batch e4m3 quant differs from q8_1), so x_fallback not aq/ad.
9785        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9786            if let Some(y) = self.try_fp8_gemm(w, x_fallback, m)? {
9787                return Ok(y);
9788            }
9789            // PER-BLOCK FP8 MMQ — same arm as `matmul` (stash opt-in, native-resident default ON);
9790            // its own quantizer wants the RAW f32 activation, so x_fallback not aq/ad.
9791            if let Some(y) = self.try_fp8_blk_mmq(w, x_fallback, m)? {
9792                return Ok(y);
9793            }
9794            // FP16-mirror prefill (same arm as `matmul` — fp16 wants the RAW f32 activation).
9795            if let Some(y) = self.try_f16_gemm(w, x_fallback, m)? {
9796                return Ok(y);
9797            }
9798        }
9799        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK) — the same two arms as `matmul`, split at the same m, and
9800        // placed at the same point in the order (after the prefill GEMM hooks, before every arm
9801        // that refuses this qtype). The prefill arm needs the RAW f32 activation for the Q8_0
9802        // dispatch it recurses into, so it takes x_fallback and is skipped when that is empty
9803        // (a pre-quantized caller that dropped its f32 input never runs at prefill m anyway).
9804        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9805            if let Some(y) = self.try_e4m3_blk_prefill(w, x_fallback, m)? {
9806                return Ok(y);
9807            }
9808        }
9809        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
9810            return Ok(y);
9811        }
9812        // VENDORED llama MMQ prefill GEMMs (NVFP4 W4A8 default-on; W4A4/k-quant behind MEMRA_MMQ=1
9813        // — policy in mmq_supports) — use the RAW f32 activation (their own internal quant:
9814        // q8_1 D4 for NVFP4 W4A8, FP8/UE4M3 for W4A4, q8_1 DS4 for Q4_K/Q5_K), so x_fallback not
9815        // aq/ad.
9816        if m >= 16
9817            && w.out_features() >= 128
9818            && self.mmq_supports(w)
9819            && !self.verify_exact_on()
9820            && x_raw_ok
9821        {
9822            return self.qmatvec_mmq(w, x_fallback, m);
9823        }
9824        // Stage-C FP4 prefill (MEMRA_FP4): native mxf4 GEMM needs the f32 activation (FP4-quant differs
9825        // from q8_1), so re-quantize from x_fallback rather than reuse aq/ad. NVFP4 only, m>=16.
9826        if m >= 16 && x_raw_ok && !self.verify_exact_on() {
9827            if let Some(y) =
9828                self.try_fp4_gemm(w, x_fallback, m, w.in_features(), w.out_features())?
9829            {
9830                return Ok(y);
9831            }
9832        }
9833        // Prefill GEMM root fix: if T>1 and the dtype has a GEMM kernel, batch via tensor cores
9834        // (reuses the already-quantized aq/ad — no extra quantize). m=1 falls through to dp4a.
9835        if m >= 16 && self.gemm_supports(w) && !self.verify_exact_on() {
9836            return self.qmatvec_gemm(w, aq, ad, m);
9837        }
9838        if !self.uses_q8_1_fast(w) {
9839            return self.matmul(w, x_fallback, m);
9840        }
9841        let in_f = w.in_features();
9842        let out_f = w.out_features();
9843        let (bytes, qtype, row_bytes, scale, rp) = match w {
9844            GpuTensor::Quant {
9845                bytes,
9846                qtype,
9847                row_bytes,
9848                scale,
9849                rp,
9850                ..
9851            } => (bytes, *qtype, *row_bytes, *scale, *rp),
9852            _ => unreachable!("uses_q8_1_fast guaranteed Quant"),
9853        };
9854        // Q4_0 split-plane mirror: only the mmvq/batched decode arms read it (the _rp twins);
9855        // the dp4a/oracle tails below keep the raw GGUF bytes.
9856        let (mbytes, mrp) = match w {
9857            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
9858            _ => (bytes, rp),
9859        };
9860        // PERF-3 decode-GEMV: warp-per-row MMVQ for the m=1 decode arm, gated behind MEMRA_MMVQ.
9861        // Only the 4 daily-hot dtypes have an _mmvq kernel (Q8_0/Q4_K/Q6_K/NVFP4); Q5_K/Q3_K/IQ4_XS
9862        // keep _dp4a (the oracle/fallback). Bit-equivalent to _dp4a up to f32 reduction order.
9863        if m == 1 && self.mmvq_supports(qtype) {
9864            return self.qmatvec_mmvq(mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, mrp);
9865        }
9866        // BATCHED weight-resident matvec for the m=2-4 band (the MTP/verify forward: full_attn_verify
9867        // and decode_step_t run their projections at m=T=k=2..4). The plain _dp4a path below launches
9868        // grid.y=m INDEPENDENT blocks per output row -> the weight row is re-read m times from HBM/L2.
9869        // The _b2/_b4 kernels walk the weight ONCE and dp4a vs all m activation columns, so m tokens
9870        // cost ~1 weight read instead of m (decode is weight-BW-bound). BIT-IDENTICAL per (token,row)
9871        // to the _mmvq path (32-thread warp reduce — NOT the dp4a 128-thread reduce below).
9872        // m=2 -> mcols=2; m∈{3,4} -> mcols=4; m∈{5..8} -> mcols=8 (kernel guards c>=m).
9873        // MEMRA_NO_BATCHED forces the per-m grid.y=m path (the A/B reference); MEMRA_B8=0 keeps
9874        // m=5..8 on the old per-m path (b8-tier-only seam).
9875        // DECODE-PARITY GATE (2026-07-07): batched iff mmvq_supports — see matmul's parity note.
9876        // Without MEMRA_MMVQ, m=1 decode rides dp4a (the arm below at m=1); the verify must ride
9877        // the SAME class per column (grid.y=m dp4a = the exact m=1 dp4a program per column).
9878        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
9879            && std::env::var("MEMRA_NO_BATCHED").is_err()
9880            && (m <= 4 || Self::b8_enabled())
9881            // b16 tier: every class routed here now has base + _rp b16 kernels (Q4_0/Q6_K
9882            // pre-existing; NVFP4/Q4_K/Q8_0-base/F8_E4M3 added lane/rp-on-st 2026-08-06), so
9883            // there is no mirror precondition left — `mrp` still selects the LAYOUT below.
9884            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_NVFP4
9885                || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_F8_E4M3 || qtype == QT_Q8_0)
9886        {
9887            let mcols = Self::batched_mcols(m);
9888            return self.qmatvec_mmvq_batched(
9889                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, mrp,
9890            );
9891        }
9892        // F8-E4M3 catch-all (m=9..15 / batched-disabled seams): grid.y=m e4m3 mmvq — this dtype
9893        // has NO _dp4a twin, and per (token,row) the mmvq body is the exact m=1 decode program.
9894        // Q4_0 joins the catch-all (2026-07-11): adaptive-K cap 8 makes verify t=9 reachable
9895        // for the first time (past the b8 tier) and Q4_0 has no dp4a twin either. The mirror
9896        // (mbytes/mrp) keeps the rp layout consistent with the m=1 decode program.
9897        if qtype == QT_F8_E4M3 || qtype == QT_Q4_0 {
9898            let (b2, r2) = if qtype == QT_Q4_0 {
9899                (mbytes, mrp)
9900            } else {
9901                (bytes, rp)
9902            };
9903            return self.qmatvec_mmvq(b2, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, r2);
9904        }
9905        let name = match qtype {
9906            QT_Q8_0 => "qmatvec_q8_0_dp4a",
9907            QT_Q4_K => "qmatvec_q4_K_dp4a",
9908            QT_Q6_K => "qmatvec_q6_K_dp4a",
9909            QT_Q5_K => "qmatvec_q5_K_dp4a",
9910            QT_Q3_K => "qmatvec_q3_K_dp4a",
9911            QT_NVFP4 => {
9912                if rp {
9913                    "qmatvec_nvfp4_dp4a_rp"
9914                } else {
9915                    "qmatvec_nvfp4_dp4a"
9916                }
9917            }
9918            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
9919            _ => unreachable!(),
9920        };
9921        let f = self.func(name);
9922        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
9923        let cfg = LaunchConfig {
9924            grid_dim: (out_f as u32, m as u32, 1),
9925            block_dim: (128, 1, 1),
9926            shared_mem_bytes: 0,
9927        };
9928        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
9929        let __s_b = self.gpu.stream();
9930        let mut b = __s_b.launch_builder(&f);
9931        b.arg(bytes)
9932            .arg(aq)
9933            .arg(ad)
9934            .arg(&mut y)
9935            .arg(&inf)
9936            .arg(&outf)
9937            .arg(&mi)
9938            .arg(&rb);
9939        unsafe {
9940            b.launch(cfg)?;
9941        }
9942        if scale != 1.0 {
9943            self.scale_inplace(&mut y, scale, m * out_f)?;
9944        }
9945        Ok(y)
9946    }
9947
9948    /// DECODE-EXACT matmul at any m: guarantees the SAME warp-per-row (MMVQ, 32-thread) FP
9949    /// accumulation order as the T=1 decode path for EVERY token row. The spec-decode verify MUST
9950    /// use this for linear-attn projections to be bit-identical to greedy decode. The dp4a kernel
9951    /// (128 threads, two-level reduction) used by `matmul`/`matmul_pre` at m>=5 has a different
9952    /// shfl-tree shape that produces ULP differences propagating through gdn_scan into argmax flips.
9953    /// The MMVQ kernel with grid.y=m already processes each row independently (same 32-thread warp
9954    /// reduce as m=1); this method just forces that path unconditionally.
9955    pub fn matmul_decode_exact(
9956        &self,
9957        w: &crate::model::GpuTensor,
9958        x: &CudaSlice<f32>,
9959        m: usize,
9960    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
9961        use crate::model::GpuTensor;
9962        // FLOAT tensors (35B ssm_beta/ssm_alpha on every linear layer, F32 ne=[2048,32]): the
9963        // generic path is cuBLASLt, whose reduction splits are n-DEPENDENT — m=1 vs m=2 col-0
9964        // outputs differ in every bit (probe 2026-07-06: 32/32 bit-diff, maxdiff 3.5e-3), which
9965        // shifted 35B verify logits 0.26-0.56 vs eager and flipped greedy at tight margins (the
9966        // p3 spec FAIL). Decode-exact contract: per-COLUMN m=1 cuBLASLt calls — each column's
9967        // reduction is the exact kernel the T=1 decode path runs, so verify==decode bit-for-bit.
9968        // m<=10 here (K+2 verify tier), so the extra launches are a handful of 4us gemvs.
9969        if let GpuTensor::Float { data, .. } = w {
9970            return self.linear_decode_exact(x, data, m, w.in_features(), w.out_features());
9971        }
9972        // MEMRA_FULL_PREC bf16-resident weight: dequant-on-use, then the per-column decode-exact
9973        // float linear (same n-independent reduction contract as the Float arm above).
9974        if let GpuTensor::FloatBf16 { data, .. } = w {
9975            let (in_f, out_f) = (w.in_features(), w.out_features());
9976            return self.linear_bf16_chunked(x, data, m, in_f, out_f, true);
9977        }
9978        if !self.uses_q8_1_fast(w) {
9979            return self.matmul(w, x, m);
9980        }
9981        let in_f = w.in_features();
9982        let out_f = w.out_features();
9983        let (bytes, qtype, row_bytes, scale, rp) = match w {
9984            GpuTensor::Quant {
9985                bytes,
9986                qtype,
9987                row_bytes,
9988                scale,
9989                rp,
9990                ..
9991            } => (bytes, *qtype, *row_bytes, *scale, *rp),
9992            _ => return self.matmul(w, x, m),
9993        };
9994        // Q4_0 split-plane mirror for the mmvq/batched arms below (dp4a tail = matmul_pre,
9995        // which does its own mirror pick).
9996        let (bytes, rp) = match w {
9997            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
9998            _ => (bytes, rp),
9999        };
10000        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10001        // BLOCK-128 e4m3 (QT_F8_E4M3_BLK): the same single kernel every other entry dispatches, so
10002        // the decode-exact contract needs nothing special — grid.y=m runs the m=1 program per
10003        // (token,row) by construction, which is exactly what this method exists to guarantee.
10004        if let Some(y) = self.try_e4m3_blk_pre(w, &aq, &ad, m)? {
10005            return Ok(y);
10006        }
10007        // Batched weight-resident matvec for m=2-8: BIT-IDENTICAL per (token,row) to MMVQ (exact
10008        // integer dp4a, same warp reduce — kernel-check gate rel=0.00e0), one weight read for m
10009        // tokens. The dispatch the divergence fix must avoid is dp4a's 128-thread two-level
10010        // reduce, NOT this. m=5..8 is the K=4..7 spec-verify tier (b8): pre-b8 T=5 fell to the
10011        // grid.y=m per-row MMVQ below = 5 full weight reads/launch — the measured 27B K=4 cliff.
10012        // DECODE-PARITY GATE (2026-07-07): batched (MMVQ-class order) only when the m=1 decode
10013        // chain rides MMVQ too — without MEMRA_MMVQ decode is dp4a, so the exact-contract here
10014        // must be per-column dp4a (matmul_pre fallthrough), not the MMVQ order.
10015        if (2..=16).contains(&m) && self.batched_supports(qtype) && self.mmvq_supports(qtype)
10016            && std::env::var("MEMRA_NO_BATCHED").is_err()
10017            && (m <= 4 || Self::b8_enabled())
10018            // Every b16 class has base + _rp twins after lane/rp-on-st (see matmul_pre's note):
10019            // no mirror precondition, `rp` selects the layout only.
10020            && (m <= 8 || qtype == QT_Q4_0 || qtype == QT_Q6_K || qtype == QT_F8_E4M3
10021                || qtype == QT_NVFP4 || qtype == QT_Q4_K || qtype == QT_Q5_K || qtype == QT_Q8_0)
10022        {
10023            let mcols = Self::batched_mcols(m);
10024            return self.qmatvec_mmvq_batched(
10025                bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10026            );
10027        }
10028        if self.mmvq_supports(qtype) {
10029            // MMVQ at grid.y=m: each row is processed by its own warp independently — same 32-thread
10030            // accumulation + warp_reduce_sum as m=1 decode. Bit-identical per row.
10031            return self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10032        }
10033        // Fallback for non-MMVQ quant types (Q5_K, Q3_K): use dp4a (the only available kernel).
10034        // These types are not used in the 27B's linear-attn NVFP4+Q4_K layers.
10035        self.matmul_pre(w, &aq, &ad, x, m)
10036    }
10037
10038    /// DECODE-EXACT matmul from a PRE-QUANTIZED q8_1 activation (batched-verify epilogue
10039    /// re-fuse, lane/vt-fixes fix 2, 2026-08-03): the EXACT `matmul_decode_exact` dispatch for
10040    /// q8_1-fast Quant tensors, with the caller's (aq, ad) replacing the internal
10041    /// `quantize_q8_1`. quantize_q8_1 is deterministic (same input bytes -> same q8 bytes), so
10042    /// sharing one quantize across sibling matmuls of the same activation — or consuming the
10043    /// q8 emitted by a fused epilogue (rms_norm_q8_1 / add_rms_norm_q8_1 /
10044    /// silu_mul_scaled_q8_1 / gated_rmsnorm_q8_1, all kernel-check-pinned bit-identical to
10045    /// their unfused chains) — cannot change any dispatched kernel's input bytes.
10046    /// Caller MUST guarantee `uses_q8_1_fast(w)` (the fused epilogues only exist on that path).
10047    pub fn matmul_decode_exact_pre(
10048        &self,
10049        w: &crate::model::GpuTensor,
10050        aq: &CudaSlice<i8>,
10051        ad: &CudaSlice<f32>,
10052        m: usize,
10053    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
10054        use crate::model::GpuTensor;
10055        debug_assert!(
10056            self.uses_q8_1_fast(w),
10057            "matmul_decode_exact_pre: caller must guarantee q8_1-fast"
10058        );
10059        // BLOCK-128 e4m3: same single kernel, all m — see matmul_decode_exact's note.
10060        if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
10061            return Ok(y);
10062        }
10063        let in_f = w.in_features();
10064        let out_f = w.out_features();
10065        let (bytes, qtype, row_bytes, scale, rp) = match w {
10066            GpuTensor::Quant {
10067                bytes,
10068                qtype,
10069                row_bytes,
10070                scale,
10071                rp,
10072                ..
10073            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10074            _ => {
10075                return Err(
10076                    "matmul_decode_exact_pre: Quant tensor required (q8_1-fast contract)".into(),
10077                );
10078            }
10079        };
10080        // Q4_0 split-plane mirror — same pick as matmul_decode_exact.
10081        let (bytes, rp) = match w {
10082            GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
10083            _ => (bytes, rp),
10084        };
10085        // Dispatch mirror of matmul_decode_exact's q8_1-fast tail, condition for condition.
10086        if (2..=16).contains(&m)
10087            && self.batched_supports(qtype)
10088            && self.mmvq_supports(qtype)
10089            && std::env::var("MEMRA_NO_BATCHED").is_err()
10090            && (m <= 4 || Self::b8_enabled())
10091            && (m <= 8
10092                || qtype == QT_Q4_0
10093                || qtype == QT_Q6_K
10094                || qtype == QT_F8_E4M3
10095                || qtype == QT_NVFP4
10096                || qtype == QT_Q4_K
10097                || qtype == QT_Q5_K
10098                || qtype == QT_Q8_0)
10099        {
10100            let mcols = Self::batched_mcols(m);
10101            return self.qmatvec_mmvq_batched(
10102                bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, mcols, scale, rp,
10103            );
10104        }
10105        if self.mmvq_supports(qtype) {
10106            return self.qmatvec_mmvq(bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp);
10107        }
10108        // Non-MMVQ quant types (Q5_K/Q3_K under MEMRA_MMVQ=0): dp4a via matmul_pre — the same
10109        // fallback matmul_decode_exact takes. m <= 16 on the verify tier never reads x_fallback.
10110        let x0 = self.zeros(0)?;
10111        self.matmul_pre(w, aq, ad, &x0, m)
10112    }
10113
10114    /// DUAL gate+up batched matvec from a PRE-QUANTIZED activation, macro-scales DEFERRED
10115    /// (lane/vt-fixes fix 2): same eligibility as `matmul_decode_exact_dual`, but the caller's
10116    /// (aq, ad) replaces the internal quantize and the NVFP4 per-tensor scales are RETURNED
10117    /// instead of applied via two `scale_inplace` launches — the fused SwiGLU epilogue
10118    /// (`silu_mul_scaled_q8_1`) folds them, exactly like the m=1 decode chain does. Deferring
10119    /// is value-exact: `y[i]*s` inline in the epilogue is the same IEEE multiply scale_inplace
10120    /// would store (f32 store/load round-trips are exact). None -> caller falls back to the
10121    /// per-tensor path.
10122    pub fn matmul_decode_exact_dual_pre(
10123        &self,
10124        w0: &crate::model::GpuTensor,
10125        w1: &crate::model::GpuTensor,
10126        aq: &CudaSlice<i8>,
10127        ad: &CudaSlice<f32>,
10128        m: usize,
10129    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10130    {
10131        use crate::model::GpuTensor;
10132        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10133        let on = *ON.get_or_init(|| {
10134            std::env::var("MEMRA_SPEC_DUAL_T")
10135                .map(|v| v != "0")
10136                .unwrap_or(true)
10137        });
10138        if !on
10139            || !(2..=7).contains(&m)
10140            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10141            || !self.uses_q8_1_fast(w0)
10142            || !self.uses_q8_1_fast(w1)
10143        {
10144            return Ok(None);
10145        }
10146        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — the dual
10147        // kernels are the MMVQ warp-reduce family, and without MEMRA_MMVQ the m=1 decode
10148        // chain this verify must match bit-for-bit rides dp4a (see matmul_decode_exact's
10149        // note). The singles enforce this via `mmvq_supports`; the dual door skipped it.
10150        if !self.mmvq_supports(QT_NVFP4) {
10151            return Ok(None);
10152        }
10153        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10154        if w1.in_features() != in_f || w1.out_features() != out_f {
10155            return Ok(None);
10156        }
10157        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10158            (
10159                GpuTensor::Quant {
10160                    bytes: b0,
10161                    qtype: q0,
10162                    row_bytes: rb0,
10163                    scale: s0,
10164                    rp: rp0,
10165                    rp4: None,
10166                    ..
10167                },
10168                GpuTensor::Quant {
10169                    bytes: b1,
10170                    qtype: q1,
10171                    row_bytes: rb1,
10172                    scale: s1,
10173                    rp: rp1,
10174                    rp4: None,
10175                    ..
10176                },
10177            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10178                (b0, b1, *rb0, *s0, *s1, *rp0)
10179            }
10180            _ => return Ok(None),
10181        };
10182        // m=5..7: only the exact-width rp duals exist (vt-fixes fix 1b); GGUF layout keeps
10183        // the singles. The b8 dual (MCOLS=8 at m=5..8) measured FLAT and stays dead.
10184        if m > 4 && !(rp && Self::b8_enabled() && std::env::var("MEMRA_B567").as_deref() != Ok("0"))
10185        {
10186            return Ok(None);
10187        }
10188        let (y0, y1) =
10189            self.qmatvec_batched_dual_raw(b0, b1, aq, ad, m, in_f, out_f, row_bytes, rp)?;
10190        Ok(Some(((y0, s0), (y1, s1))))
10191    }
10192
10193    /// DUAL gate+up BATCHED matvec at verify t=2..8 (lane/verify-economics, 2026-08-02): ONE
10194    /// launch computes both FFN projections of a verify batch — same activation, same shape,
10195    /// blockIdx.y selects the tensor. Per (tensor, token, row) the kernel body is the single
10196    /// batched program on the SAME layout (split-plane rp: b2 rp / b4 rpr2 / b8 rpr2; GGUF:
10197    /// b2 base / b4 r2 / b8 r2) -> BIT-IDENTICAL to the two single `matmul_decode_exact`
10198    /// launches (kernel-check gates bitwise on both layouts; run-spec K=1..8 arbitrates e2e).
10199    /// The one activation quantize replaces two IDENTICAL quantizes of the same `x` (same
10200    /// kernel, same input -> same q8_1 bytes), and the two independent weight streams in one
10201    /// grid restore the memory-level parallelism the two-launch form loses to tail drain +
10202    /// launch gap (m=1 dual_mr2 precedent: DRAM 40% -> 47-50% on the 27B pair).
10203    /// `Some((y0, y1))` only when both tensors are NVFP4, the SAME layout (both rp or both
10204    /// GGUF, no rp4 mirror), identical (in_f, out_f, row_bytes), q8_1-fast, and m in 2..=4
10205    /// (the b2/b4 tiers = verify T for K=1..3, the profitable-K window — the b8 dual measured
10206    /// FLAT vs the rpsc singles x3 interleaved, research/verify-economics-20260802, and was
10207    /// killed per doctrine). None -> caller runs the two singles. MEMRA_SPEC_DUAL_T=0 rollback.
10208    pub fn matmul_decode_exact_dual(
10209        &self,
10210        w0: &crate::model::GpuTensor,
10211        w1: &crate::model::GpuTensor,
10212        x: &CudaSlice<f32>,
10213        m: usize,
10214    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10215        use crate::model::GpuTensor;
10216        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
10217        let on = *ON.get_or_init(|| {
10218            std::env::var("MEMRA_SPEC_DUAL_T")
10219                .map(|v| v != "0")
10220                .unwrap_or(true)
10221        });
10222        if !on
10223            || !(2..=4).contains(&m)
10224            || std::env::var("MEMRA_NO_BATCHED").is_ok()
10225            || !self.uses_q8_1_fast(w0)
10226            || !self.uses_q8_1_fast(w1)
10227        {
10228            return Ok(None);
10229        }
10230        // DECODE-PARITY GATE (lane/nvfp4-strict, 2026-08-05): batched iff MMVQ — same law as
10231        // the singles' `batched_supports && mmvq_supports` check in matmul_decode_exact,
10232        // which this dual door bypassed. Without MEMRA_MMVQ the m=1 decode is dp4a; the
10233        // verify must ride the per-column dp4a class, not the MMVQ-family dual.
10234        if !self.mmvq_supports(QT_NVFP4) {
10235            return Ok(None);
10236        }
10237        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10238        if w1.in_features() != in_f || w1.out_features() != out_f {
10239            return Ok(None);
10240        }
10241        let (b0, b1, row_bytes, s0, s1, rp) = match (w0, w1) {
10242            (
10243                GpuTensor::Quant {
10244                    bytes: b0,
10245                    qtype: q0,
10246                    row_bytes: rb0,
10247                    scale: s0,
10248                    rp: rp0,
10249                    rp4: None,
10250                    ..
10251                },
10252                GpuTensor::Quant {
10253                    bytes: b1,
10254                    qtype: q1,
10255                    row_bytes: rb1,
10256                    scale: s1,
10257                    rp: rp1,
10258                    rp4: None,
10259                    ..
10260                },
10261            ) if *q0 == QT_NVFP4 && *q1 == QT_NVFP4 && rb0 == rb1 && rp0 == rp1 => {
10262                (b0, b1, *rb0, *s0, *s1, *rp0)
10263            }
10264            _ => return Ok(None),
10265        };
10266        // Engagement receipt (MEMRA_DEBUG=1): the first dead-arm A/B lesson — a `rp: false`
10267        // gate silently no-op'd the whole experiment; prove the arm is live in the log.
10268        if std::env::var("MEMRA_DEBUG").is_ok() {
10269            static ONCE: std::sync::Once = std::sync::Once::new();
10270            ONCE.call_once(|| eprintln!("[memra] dual gate+up batched ENGAGED (m={m} rp={rp})"));
10271        }
10272        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
10273        let (y0, y1) =
10274            self.qmatvec_batched_dual_raw(b0, b1, &aq, &ad, m, in_f, out_f, row_bytes, rp)?;
10275        let mut y0 = y0;
10276        let mut y1 = y1;
10277        if s0 != 1.0 {
10278            self.scale_inplace(&mut y0, s0, m * out_f)?;
10279        }
10280        if s1 != 1.0 {
10281            self.scale_inplace(&mut y1, s1, m * out_f)?;
10282        }
10283        Ok(Some((y0, y1)))
10284    }
10285
10286    /// Launch body of the dual batched twins from raw NVFP4 weight bytes + a pre-quantized q8_1
10287    /// activation (kernel-check's bit-equivalence entry; matmul_decode_exact_dual's core).
10288    /// mcols tier = batched_mcols(m); macro-scale NOT applied. `rp` selects the split-plane
10289    /// twins (both buffers must be the repacked layout).
10290    #[allow(clippy::too_many_arguments)]
10291    pub fn qmatvec_batched_dual_raw(
10292        &self,
10293        b0: &CudaSlice<u8>,
10294        b1: &CudaSlice<u8>,
10295        aq: &CudaSlice<i8>,
10296        ad: &CudaSlice<f32>,
10297        m: usize,
10298        in_f: usize,
10299        out_f: usize,
10300        row_bytes: usize,
10301        rp: bool,
10302    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10303        const ROWS_PER_BLOCK: u32 = 4;
10304        let mcols = Self::batched_mcols(m);
10305        // EXACT-WIDTH duals at m=5..7 (vt-fixes fix 1b): rp-only; bit-identical to the two
10306        // b5/b6/b7 singles (blockIdx.y selects the tensor, same template body).
10307        let tiny_rp1 = rp
10308            && mcols == 4
10309            && out_f <= 128
10310            && std::env::var("MEMRA_NVFP4_AUX_DUAL").as_deref() != Ok("0");
10311        let (name, rows_per_block) = if tiny_rp1 {
10312            ("qmatvec_nvfp4_mmvq_dual_b4_rp", ROWS_PER_BLOCK)
10313        } else {
10314            match (mcols, rp, m) {
10315                (2, false, _) => ("qmatvec_nvfp4_mmvq_dual_b2", ROWS_PER_BLOCK),
10316                (4, false, _) => ("qmatvec_nvfp4_mmvq_dual_b4_r2", ROWS_PER_BLOCK * 2),
10317                (2, true, _) => ("qmatvec_nvfp4_mmvq_dual_b2_rp", ROWS_PER_BLOCK),
10318                (4, true, _) => ("qmatvec_nvfp4_mmvq_dual_b4_rpr2", ROWS_PER_BLOCK * 2),
10319                (8, true, 5) => ("qmatvec_nvfp4_mmvq_dual_b5_rpr2", ROWS_PER_BLOCK * 2),
10320                (8, true, 6) => ("qmatvec_nvfp4_mmvq_dual_b6_rpr2", ROWS_PER_BLOCK * 2),
10321                (8, true, 7) => ("qmatvec_nvfp4_mmvq_dual_b7_rpr2", ROWS_PER_BLOCK * 2),
10322                _ => {
10323                    return Err(
10324                        format!("qmatvec_batched_dual_raw: no dual kernel for m {m}").into(),
10325                    );
10326                }
10327            }
10328        };
10329        let f = self.func(name);
10330        let mut y0 = self.alloc_uninit::<f32>(m * out_f)?;
10331        let mut y1 = self.alloc_uninit::<f32>(m * out_f)?;
10332        let cfg = LaunchConfig {
10333            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10334            block_dim: (32, ROWS_PER_BLOCK, 1),
10335            shared_mem_bytes: 0,
10336        };
10337        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
10338        let __s_b = self.gpu.stream();
10339        let mut b = __s_b.launch_builder(&f);
10340        b.arg(b0)
10341            .arg(b1)
10342            .arg(aq)
10343            .arg(ad)
10344            .arg(&mut y0)
10345            .arg(&mut y1)
10346            .arg(&inf)
10347            .arg(&outf)
10348            .arg(&mi)
10349            .arg(&rb);
10350        unsafe {
10351            b.launch(cfg)?;
10352        }
10353        Ok((y0, y1))
10354    }
10355
10356    /// Like `matmul_pre` but RETURNS THE RAW (un-macro-scaled) matmul output together with the
10357    /// per-tensor NVFP4 scale, instead of applying `scale_inplace` internally. Used by the fused
10358    /// SwiGLU epilogue (RANK3 LEVER 2) so the gate/up scales fold into one `silu_mul_scaled` launch.
10359    /// `Some((y_raw, scale))` only on the m==1 decode fast path (mmvq / dp4a) where the scale is a
10360    /// separate post-launch op we can defer; returns `None` for every other path (prefill GEMM, FP4
10361    /// GEMM, Stage-A, Float) so the caller falls back to the scaled `matmul_pre` + `silu_mul`.
10362    /// DUAL gate+up NVFP4 matvec (mm-fusion): ONE launch computes both projections (same
10363    /// activation, same shape) — grid.y selects the tensor. Bit-identical per element to two
10364    /// mr2 launches at m=1. Returns (gate_raw, up_raw) un-scaled (caller folds the two macro
10365    /// scales into the SwiGLU epilogue, same as the matmul_pre_noscale contract). None unless
10366    /// both tensors are NVFP4 q8_1-fast with identical (in_f, out_f, row_bytes) and m==1.
10367    pub fn matmul_pre_dual_noscale(
10368        &self,
10369        w0: &crate::model::GpuTensor,
10370        w1: &crate::model::GpuTensor,
10371        aq: &CudaSlice<i8>,
10372        ad: &CudaSlice<f32>,
10373        m: usize,
10374    ) -> Result<Option<((CudaSlice<f32>, f32), (CudaSlice<f32>, f32))>, Box<dyn std::error::Error>>
10375    {
10376        use crate::model::GpuTensor;
10377        if m != 1 || !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10378            return Ok(None);
10379        }
10380        // FP-ORDER LAW (lane/nvfp4-strict, 2026-08-05): every kernel this door can dispatch
10381        // (q8_0 fused2, nvfp4 dual_mr2) is the MMVQ family — 32-thread warp reduce. Without
10382        // MEMRA_MMVQ the m=1 singles ride dp4a (128-thread two-level reduce), so fusing here
10383        // would mix dispatch families across the pair — the exact class `q8_fused_params`
10384        // already refuses for Q8_0. The NVFP4 arm lacked this check, which is why
10385        // decode-batch-gate `--mode strict`'s equalizing env (MEMRA_MMVQ=0) never pinned
10386        // NVFP4 models: decode_step_h kept riding dual_mr2 while the batched body fell to
10387        // dp4a (gate1 maxdiff 1.639e-1 / gate2 step-8 divergence at the 2026-08-05 train
10388        // HEAD, research/nvfp4-strict-20260805/). Default env (MMVQ on) is dispatch-unchanged.
10389        if !self.mmvq_supports(QT_NVFP4) {
10390            return Ok(None);
10391        }
10392        let (in_f, out_f) = (w0.in_features(), w0.out_features());
10393        if w1.in_features() != in_f || w1.out_features() != out_f {
10394            return Ok(None);
10395        }
10396        // Q8_0 ARM (lane/q27-deepdive, 2026-08-05): the dense-FFN gate+up pair on a Q8_0 trunk fell
10397        // through this NVFP4-only gate to two `matmul_pre_noscale` launches — measured 128 of the
10398        // 1015 launches/token on q27-Q8_0 decode, the single largest un-fused class in the tick
10399        // (nsys `research/q27-deepdive-20260805/nsys/`). `q8_fused2_core` already serves the same
10400        // pair shape for the shared-expert gate/up, and its kernel body is `qmatvec_q8_0_mmvq`
10401        // VERBATIM per (tensor,row) -> BIT-IDENTICAL to the two separate launches. Q8_0 carries no
10402        // macro-scale (q8_fused_params requires scale==1.0), so the noscale contract is satisfied
10403        // by returning 1.0 for both: the SwiGLU epilogue's fold becomes the identity it already is
10404        // on this dtype today. Seam: MEMRA_Q8_FFN_FUSE2=0 rolls back to the two-launch pair.
10405        // rp4 guard: with MEMRA_Q8RP the singles route to the `_rp` split-plane twin over the
10406        // mirror buffer; the fused2 kernel has no `_rp` form, so fusing there would swap
10407        // dispatch families mid-model. Bail and let the two singles run (mirror lane unchanged).
10408        let no_mirror =
10409            |w: &crate::model::GpuTensor| !matches!(w, GpuTensor::Quant { rp4: Some(_), .. });
10410        if self.q8_ffn_fuse2_on()
10411            && no_mirror(w0)
10412            && no_mirror(w1)
10413            && let Some([p0, p1]) = self.q8_fused_params(&[w0, w1])
10414        {
10415            let (y0, y1) = self.q8_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2)?;
10416            return Ok(Some(((y0, 1.0), (y1, 1.0))));
10417        }
10418        // F8-E4M3 ARM (lane/fp8-decode-v1, 2026-08-05): with native e4m3 residency the FFN gate+up
10419        // pair (and the ssm beta+alpha dual, which routes through this same entry) fell through
10420        // both the NVFP4 gate below and the Q8_0 arm above to two `matmul_pre_noscale` launches —
10421        // native residency was UN-FUSING the trunk relative to the Q8_0 slab it replaces. The
10422        // fused2 kernel body is `qmatvec_e4m3_mmvq` VERBATIM per (tensor,row). Contract match:
10423        // `matmul_pre_noscale` on e4m3 launches with scale 1.0 and RETURNS the per-tensor
10424        // weight_scale for the caller to fold, so we pass ws=1.0 here and return (s0,s1) — same
10425        // bits, and the two macro-scale multiplies still fold into the SwiGLU epilogue.
10426        // MEMRA_E4M3_DUAL=0 rolls back to the two-launch pair.
10427        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10428            let (y0, y1) =
10429                self.e4m3_fused2_core(p0.0, p1.0, aq, ad, in_f, p0.1, p1.1, p0.2, 1.0, 1.0)?;
10430            return Ok(Some(((y0, p0.3), (y1, p1.3))));
10431        }
10432        let (b0, q0, rb0, s0, rp0) = match w0 {
10433            GpuTensor::Quant {
10434                bytes,
10435                qtype,
10436                row_bytes,
10437                scale,
10438                rp,
10439                ..
10440            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10441            _ => return Ok(None),
10442        };
10443        let (b1, q1, rb1, s1, rp1) = match w1 {
10444            GpuTensor::Quant {
10445                bytes,
10446                qtype,
10447                row_bytes,
10448                scale,
10449                rp,
10450                ..
10451            } => (bytes, *qtype, *row_bytes, *scale, *rp),
10452            _ => return Ok(None),
10453        };
10454        if q0 != QT_NVFP4 || q1 != QT_NVFP4 || rb0 != rb1 || rp0 != rp1 {
10455            return Ok(None);
10456        }
10457        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10458        const RPW: u32 = 2;
10459        let rows_per_block = ROWS_PER_BLOCK * RPW;
10460        let f = self.func(if rp0 {
10461            "qmatvec_nvfp4_mmvq_dual_mr2_rp"
10462        } else {
10463            "qmatvec_nvfp4_mmvq_dual_mr2"
10464        });
10465        let mut y0 = self.alloc_uninit::<f32>(out_f)?;
10466        let mut y1 = self.alloc_uninit::<f32>(out_f)?;
10467        let cfg = LaunchConfig {
10468            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 2, 1),
10469            block_dim: (32, ROWS_PER_BLOCK, 1),
10470            shared_mem_bytes: 0,
10471        };
10472        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, rb0 as i64);
10473        // noscale contract: the caller folds s0/s1 into the SwiGLU epilogue — the kernel's fused
10474        // yscale args stay 1.0 here (they exist for the single-tensor callers).
10475        let one = 1.0f32;
10476        let __s_b = self.gpu.stream();
10477        let mut b = __s_b.launch_builder(&f);
10478        b.arg(b0)
10479            .arg(b1)
10480            .arg(aq)
10481            .arg(ad)
10482            .arg(&mut y0)
10483            .arg(&mut y1)
10484            .arg(&inf)
10485            .arg(&outf)
10486            .arg(&mi)
10487            .arg(&rb)
10488            .arg(&one)
10489            .arg(&one);
10490        unsafe {
10491            b.launch(cfg)?;
10492        }
10493        Ok(Some(((y0, s0), (y1, s1))))
10494    }
10495
10496    /// FUSED Q8_0 m=1 matvec PAIR with UNEQUAL out_f (trunk launch-fusion, 2026-07-05). Folds two
10497    /// same-input q8_0 projections (35B trunk: wqkv+wqkv_gate 8192/4096, gate_shexp+up_shexp
10498    /// 512/512) into ONE launch via a block-offset split (blocks [0,nb0) -> w0, rest -> w1) — the
10499    /// dual-mr2 recipe with the same-out_f restriction lifted. Per (tensor,row) the kernel body is
10500    /// qmatvec_q8_0_mmvq VERBATIM -> BIT-IDENTICAL to two separate m=1 launches. Returns None when
10501    /// ineligible (not both Q8_0 / in_f mismatch / MEMRA_MMVQ off / MEMRA_Q8_DUAL=0) — caller falls
10502    /// back to the per-tensor path.
10503    pub fn matmul_q8_fused2(
10504        &self,
10505        w0: &crate::model::GpuTensor,
10506        w1: &crate::model::GpuTensor,
10507        aq: &CudaSlice<i8>,
10508        ad: &CudaSlice<f32>,
10509    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10510        // e4m3 twin (lane/fp8-decode-v1): this entry is the trunk's generic m=1 pair door
10511        // (wqkv+wqkv_gate, ssm_beta+alpha, gate_shexp+up_shexp), so admitting QT_F8_E4M3 here
10512        // fuses the NATIVE-RESIDENCY FP8 trunk at every existing call site with no call-site
10513        // change. Scale is folded in-kernel per range -> the returned buffers are already scaled,
10514        // exactly like the per-tensor `matmul_pre` e4m3 dispatch this replaces.
10515        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10516            return Ok(Some(self.e4m3_fused2_core(
10517                p0.0,
10518                p1.0,
10519                aq,
10520                ad,
10521                w0.in_features(),
10522                p0.1,
10523                p1.1,
10524                p0.2,
10525                p0.3,
10526                p1.3,
10527            )?));
10528        }
10529        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10530            return Ok(None);
10531        };
10532        Ok(Some(self.q8_fused2_core(
10533            p0.0,
10534            p1.0,
10535            aq,
10536            ad,
10537            w0.in_features(),
10538            p0.1,
10539            p1.1,
10540            p0.2,
10541        )?))
10542    }
10543
10544    #[allow(clippy::too_many_arguments)]
10545    fn q8_fused2_core(
10546        &self,
10547        b0: &CudaSlice<u8>,
10548        b1: &CudaSlice<u8>,
10549        aq: &CudaSlice<i8>,
10550        ad: &CudaSlice<f32>,
10551        in_f: usize,
10552        out0: usize,
10553        out1: usize,
10554        row_bytes: usize,
10555    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10556        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
10557        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
10558        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
10559        let f = self.func("qmatvec_q8_0_mmvq_fused2");
10560        let mut y0 = self.alloc_uninit::<f32>(out0)?;
10561        let mut y1 = self.alloc_uninit::<f32>(out1)?;
10562        let cfg = LaunchConfig {
10563            grid_dim: (nb0 + nb1, 1, 1),
10564            block_dim: (32, ROWS_PER_BLOCK, 1),
10565            shared_mem_bytes: 0,
10566        };
10567        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
10568        let __s_b = self.gpu.stream();
10569        let mut b = __s_b.launch_builder(&f);
10570        b.arg(b0)
10571            .arg(b1)
10572            .arg(aq)
10573            .arg(ad)
10574            .arg(&mut y0)
10575            .arg(&mut y1)
10576            .arg(&inf)
10577            .arg(&o0)
10578            .arg(&o1)
10579            .arg(&rbl);
10580        unsafe {
10581            b.launch(cfg)?;
10582        }
10583        Ok((y0, y1))
10584    }
10585
10586    /// f32-activation entry for the fused2 pair: quantizes x to q8_1 ONCE then runs the fused
10587    /// launch — replaces two `matmul(w, x, 1)` calls that would each re-quantize the same x
10588    /// (35B shared-expert gate+up per MoE layer per token). Same bits: quantize_q8_1 is
10589    /// deterministic, the fused body is the MMVQ kernel verbatim. None when ineligible (the
10590    /// callers' m==1-under-MEMRA_FAST dispatch would take MMVQ; anything else falls back).
10591    pub fn matmul_q8_fused2_x(
10592        &self,
10593        w0: &crate::model::GpuTensor,
10594        w1: &crate::model::GpuTensor,
10595        x: &CudaSlice<f32>,
10596    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10597        if !self.uses_q8_1_fast(w0) || !self.uses_q8_1_fast(w1) {
10598            return Ok(None);
10599        }
10600        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
10601            let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10602            return Ok(Some(self.e4m3_fused2_core(
10603                p0.0,
10604                p1.0,
10605                &aq,
10606                &ad,
10607                w0.in_features(),
10608                p0.1,
10609                p1.1,
10610                p0.2,
10611                p0.3,
10612                p1.3,
10613            )?));
10614        }
10615        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
10616            return Ok(None);
10617        };
10618        let (aq, ad) = self.quantize_q8_1(x, 1, w0.in_features())?;
10619        Ok(Some(self.q8_fused2_core(
10620            p0.0,
10621            p1.0,
10622            &aq,
10623            &ad,
10624            w0.in_features(),
10625            p0.1,
10626            p1.1,
10627            p0.2,
10628        )?))
10629    }
10630
10631    /// Test entry for the kernel_check gate: launch the fused2 kernel from raw weight bytes,
10632    /// quantizing the f32 activation internally (mirrors qmatvec_mmvq_raw; no env gating).
10633    #[allow(clippy::too_many_arguments)]
10634    pub fn qmatvec_q8_fused2_raw(
10635        &self,
10636        b0: &CudaSlice<u8>,
10637        b1: &CudaSlice<u8>,
10638        x: &CudaSlice<f32>,
10639        in_f: usize,
10640        out0: usize,
10641        out1: usize,
10642        row_bytes: usize,
10643    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
10644        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
10645        self.q8_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes)
10646    }
10647
10648    /// FUSED Q8_0 m=1 matvec TRIPLE (wq+wk+wv on the 35B full-attn layers: out_f 8192/512/512).
10649    /// Same block-offset recipe as `matmul_q8_fused2` with three ranges. BIT-IDENTICAL per
10650    /// (tensor,row) to three separate m=1 MMVQ launches.
10651    /// FUSED Q4_0 m=1 TRIPLE (gemma q/k/v — same quantized input; per (tensor,row) chain
10652    /// identical to the mr2 kernel). Returns None unless all three are Q4_0 with equal in_f.
10653    pub fn matmul_q4_fused3(
10654        &self,
10655        w0: &crate::model::GpuTensor,
10656        w1: &crate::model::GpuTensor,
10657        w2: &crate::model::GpuTensor,
10658        aq: &CudaSlice<i8>,
10659        ad: &CudaSlice<f32>,
10660    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
10661    {
10662        use crate::model::GpuTensor;
10663        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10664            match w {
10665                GpuTensor::Quant {
10666                    qtype, row_bytes, ..
10667                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10668                _ => None,
10669            }
10670        };
10671        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
10672            return Ok(None);
10673        };
10674        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
10675            return Ok(None);
10676        }
10677        // Effective (bytes, rp) per tensor: mirror (rp4) OR the in-place swap (rp flag,
10678        // bytes already split). Mixed layouts cannot share one fused launch -> fall back to
10679        // the separate matvecs (each routes its own rp).
10680        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
10681            match w {
10682                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
10683                    Some(m) => (m, true),
10684                    None => (bytes, *rp),
10685                },
10686                _ => unreachable!(),
10687            }
10688        }
10689        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
10690        if rp0 != rp1 || rp1 != rp2 {
10691            return Ok(None);
10692        }
10693        let rp = rp0;
10694        let rpb: u32 = 4;
10695        // mr1 (one row/warp, 2026-07-14): follows the singles' MEMRA_Q40_MR default — the
10696        // fused t=1 kernels were left on mr2 when the singles flipped (DRAM-duty map:
10697        // fused3 57% / fused2 86%; small qkv segments starve under mr2's half grid).
10698        let mr1 = rp && Self::q40_mr1_on();
10699        let nb = |o: usize| {
10700            if mr1 {
10701                (o as u32).div_ceil(rpb)
10702            } else {
10703                (o as u32).div_ceil(2).div_ceil(rpb)
10704            }
10705        };
10706        let grid = nb(o0) + nb(o1) + nb(o2);
10707        let mut y0 = self.alloc_uninit::<f32>(o0)?;
10708        let mut y1 = self.alloc_uninit::<f32>(o1)?;
10709        let mut y2 = self.alloc_uninit::<f32>(o2)?;
10710        let f = self.func(if mr1 {
10711            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
10712        } else if rp {
10713            "qmatvec_q4_0_mmvq_fused3_rp"
10714        } else {
10715            "qmatvec_q4_0_mmvq_fused3"
10716        });
10717        let cfg = LaunchConfig {
10718            grid_dim: (grid, 1, 1),
10719            block_dim: (32, rpb, 1),
10720            shared_mem_bytes: 0,
10721        };
10722        let inf = w0.in_features() as i32;
10723        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
10724        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
10725        // PDL wave-A (2026-07-23): the mr1 kernel carries MEMRA_PDL_ENTRY; only that
10726        // variant may take the programmatic-serialization launch.
10727        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
10728            {
10729                use cudarc::driver::{DevicePtr, DevicePtrMut};
10730                let s = &self.gpu.stream();
10731                let (p0, _g0) = b0.device_ptr(s);
10732                let (p1, _g1) = b1.device_ptr(s);
10733                let (p2, _g2) = b2.device_ptr(s);
10734                let (paq, _g3) = aq.device_ptr(s);
10735                let (pad, _g4) = ad.device_ptr(s);
10736                let (py0, _g5) = y0.device_ptr_mut(s);
10737                let (py1, _g6) = y1.device_ptr_mut(s);
10738                let (py2, _g7) = y2.device_ptr_mut(s);
10739                let mut ps = [
10740                    &p0 as *const _ as *mut std::ffi::c_void,
10741                    &p1 as *const _ as *mut _,
10742                    &p2 as *const _ as *mut _,
10743                    &paq as *const _ as *mut _,
10744                    &pad as *const _ as *mut _,
10745                    &py0 as *const _ as *mut _,
10746                    &py1 as *const _ as *mut _,
10747                    &py2 as *const _ as *mut _,
10748                    &inf as *const _ as *mut _,
10749                    &oo0 as *const _ as *mut _,
10750                    &oo1 as *const _ as *mut _,
10751                    &oo2 as *const _ as *mut _,
10752                    &r0 as *const _ as *mut _,
10753                    &r1 as *const _ as *mut _,
10754                    &r2 as *const _ as *mut _,
10755                ];
10756                unsafe {
10757                    self.launch_pdl(
10758                        "qmatvec_q4_0_mmvq_fused3_mr1_rp",
10759                        (grid, 1, 1),
10760                        (32, rpb, 1),
10761                        &mut ps,
10762                    )?;
10763                }
10764            }
10765            return Ok(Some((y0, y1, y2)));
10766        }
10767        let __s_b = self.gpu.stream();
10768        let mut b = __s_b.launch_builder(&f);
10769        b.arg(b0)
10770            .arg(b1)
10771            .arg(b2)
10772            .arg(aq)
10773            .arg(ad)
10774            .arg(&mut y0)
10775            .arg(&mut y1)
10776            .arg(&mut y2)
10777            .arg(&inf)
10778            .arg(&oo0)
10779            .arg(&oo1)
10780            .arg(&oo2)
10781            .arg(&r0)
10782            .arg(&r1)
10783            .arg(&r2);
10784        unsafe {
10785            b.launch(cfg)?;
10786        }
10787        Ok(Some((y0, y1, y2)))
10788    }
10789
10790    /// Slot-fed fused3 twin (alloc-free capture lane): identical launch, caller-owned outputs.
10791    /// Returns Ok(false) when the fused path is unavailable (caller falls back).
10792    #[allow(clippy::too_many_arguments)]
10793    pub fn matmul_q4_fused3_into(
10794        &self,
10795        w0: &crate::model::GpuTensor,
10796        w1: &crate::model::GpuTensor,
10797        w2: &crate::model::GpuTensor,
10798        aq: &CudaSlice<i8>,
10799        ad: &CudaSlice<f32>,
10800        y0: &mut CudaSlice<f32>,
10801        y1: &mut CudaSlice<f32>,
10802        y2: &mut CudaSlice<f32>,
10803    ) -> Result<bool, Box<dyn std::error::Error>> {
10804        use crate::model::GpuTensor;
10805        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10806            match w {
10807                GpuTensor::Quant {
10808                    qtype, row_bytes, ..
10809                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10810                _ => None,
10811            }
10812        };
10813        let (Some((rb0, o0)), Some((rb1, o1)), Some((rb2, o2))) = (q4(w0), q4(w1), q4(w2)) else {
10814            return Ok(false);
10815        };
10816        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
10817            return Ok(false);
10818        }
10819        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
10820            match w {
10821                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
10822                    Some(m) => (m, true),
10823                    None => (bytes, *rp),
10824                },
10825                _ => unreachable!(),
10826            }
10827        }
10828        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
10829        if rp0 != rp1 || rp1 != rp2 {
10830            return Ok(false);
10831        }
10832        let rp = rp0;
10833        let rpb: u32 = 4;
10834        let mr1 = rp && Self::q40_mr1_on();
10835        let nb = |o: usize| {
10836            if mr1 {
10837                (o as u32).div_ceil(rpb)
10838            } else {
10839                (o as u32).div_ceil(2).div_ceil(rpb)
10840            }
10841        };
10842        let grid = nb(o0) + nb(o1) + nb(o2);
10843        debug_assert!(y0.len() >= o0 && y1.len() >= o1 && y2.len() >= o2);
10844        let f = self.func(if mr1 {
10845            "qmatvec_q4_0_mmvq_fused3_mr1_rp"
10846        } else if rp {
10847            "qmatvec_q4_0_mmvq_fused3_rp"
10848        } else {
10849            "qmatvec_q4_0_mmvq_fused3"
10850        });
10851        let cfg = LaunchConfig {
10852            grid_dim: (grid, 1, 1),
10853            block_dim: (32, rpb, 1),
10854            shared_mem_bytes: 0,
10855        };
10856        let inf = w0.in_features() as i32;
10857        let (oo0, oo1, oo2) = (o0 as i32, o1 as i32, o2 as i32);
10858        let (r0, r1, r2) = (rb0 as i64, rb1 as i64, rb2 as i64);
10859        // PDL wave-A: identical to the owned twin (capture-lane parity).
10860        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
10861            use cudarc::driver::{DevicePtr, DevicePtrMut};
10862            let s = &self.gpu.stream();
10863            let (p0, _g0) = b0.device_ptr(s);
10864            let (p1, _g1) = b1.device_ptr(s);
10865            let (p2, _g2) = b2.device_ptr(s);
10866            let (paq, _g3) = aq.device_ptr(s);
10867            let (pad, _g4) = ad.device_ptr(s);
10868            let (py0, _g5) = y0.device_ptr_mut(s);
10869            let (py1, _g6) = y1.device_ptr_mut(s);
10870            let (py2, _g7) = y2.device_ptr_mut(s);
10871            let mut ps = [
10872                &p0 as *const _ as *mut std::ffi::c_void,
10873                &p1 as *const _ as *mut _,
10874                &p2 as *const _ as *mut _,
10875                &paq as *const _ as *mut _,
10876                &pad as *const _ as *mut _,
10877                &py0 as *const _ as *mut _,
10878                &py1 as *const _ as *mut _,
10879                &py2 as *const _ as *mut _,
10880                &inf as *const _ as *mut _,
10881                &oo0 as *const _ as *mut _,
10882                &oo1 as *const _ as *mut _,
10883                &oo2 as *const _ as *mut _,
10884                &r0 as *const _ as *mut _,
10885                &r1 as *const _ as *mut _,
10886                &r2 as *const _ as *mut _,
10887            ];
10888            unsafe {
10889                self.launch_pdl(
10890                    "qmatvec_q4_0_mmvq_fused3_mr1_rp",
10891                    (grid, 1, 1),
10892                    (32, rpb, 1),
10893                    &mut ps,
10894                )?;
10895            }
10896            return Ok(true);
10897        }
10898        let __s_b = self.gpu.stream();
10899        let mut b = __s_b.launch_builder(&f);
10900        b.arg(b0)
10901            .arg(b1)
10902            .arg(b2)
10903            .arg(aq)
10904            .arg(ad)
10905            .arg(&mut *y0)
10906            .arg(&mut *y1)
10907            .arg(&mut *y2)
10908            .arg(&inf)
10909            .arg(&oo0)
10910            .arg(&oo1)
10911            .arg(&oo2)
10912            .arg(&r0)
10913            .arg(&r1)
10914            .arg(&r2);
10915        unsafe {
10916            b.launch(cfg)?;
10917        }
10918        Ok(true)
10919    }
10920
10921    /// FUSED Q4_0 m=1 PAIR (gemma shared gate+up).
10922    pub fn matmul_q4_fused2(
10923        &self,
10924        w0: &crate::model::GpuTensor,
10925        w1: &crate::model::GpuTensor,
10926        aq: &CudaSlice<i8>,
10927        ad: &CudaSlice<f32>,
10928    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
10929        use crate::model::GpuTensor;
10930        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
10931            match w {
10932                GpuTensor::Quant {
10933                    qtype, row_bytes, ..
10934                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
10935                _ => None,
10936            }
10937        };
10938        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
10939            return Ok(None);
10940        };
10941        if w0.in_features() != w1.in_features() {
10942            return Ok(None);
10943        }
10944        // Effective (bytes, rp) per tensor (mirror or in-place swap); mixed -> separate matvecs.
10945        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
10946            match w {
10947                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
10948                    Some(m) => (m, true),
10949                    None => (bytes, *rp),
10950                },
10951                _ => unreachable!(),
10952            }
10953        }
10954        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
10955        if rp0 != rp1 {
10956            return Ok(None);
10957        }
10958        let rp = rp0;
10959        let rpb: u32 = 4;
10960        // mr1 twin — see matmul_q4_fused3.
10961        let mr1 = rp && Self::q40_mr1_on();
10962        let nb = |o: usize| {
10963            if mr1 {
10964                (o as u32).div_ceil(rpb)
10965            } else {
10966                (o as u32).div_ceil(2).div_ceil(rpb)
10967            }
10968        };
10969        let grid = nb(o0) + nb(o1);
10970        let mut y0 = self.alloc_uninit::<f32>(o0)?;
10971        let mut y1 = self.alloc_uninit::<f32>(o1)?;
10972        let f = self.func(if mr1 {
10973            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
10974        } else if rp {
10975            "qmatvec_q4_0_mmvq_fused2_rp"
10976        } else {
10977            "qmatvec_q4_0_mmvq_fused2"
10978        });
10979        let cfg = LaunchConfig {
10980            grid_dim: (grid, 1, 1),
10981            block_dim: (32, rpb, 1),
10982            shared_mem_bytes: 0,
10983        };
10984        let inf = w0.in_features() as i32;
10985        let (oo0, oo1) = (o0 as i32, o1 as i32);
10986        let (r0, r1) = (rb0 as i64, rb1 as i64);
10987        // PDL wave-A: mr1 kernel carries MEMRA_PDL_ENTRY.
10988        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
10989            {
10990                use cudarc::driver::{DevicePtr, DevicePtrMut};
10991                let s = &self.gpu.stream();
10992                let (p0, _g0) = b0.device_ptr(s);
10993                let (p1, _g1) = b1.device_ptr(s);
10994                let (paq, _g2) = aq.device_ptr(s);
10995                let (pad, _g3) = ad.device_ptr(s);
10996                let (py0, _g4) = y0.device_ptr_mut(s);
10997                let (py1, _g5) = y1.device_ptr_mut(s);
10998                let mut ps = [
10999                    &p0 as *const _ as *mut std::ffi::c_void,
11000                    &p1 as *const _ as *mut _,
11001                    &paq as *const _ as *mut _,
11002                    &pad as *const _ as *mut _,
11003                    &py0 as *const _ as *mut _,
11004                    &py1 as *const _ as *mut _,
11005                    &inf as *const _ as *mut _,
11006                    &oo0 as *const _ as *mut _,
11007                    &oo1 as *const _ as *mut _,
11008                    &r0 as *const _ as *mut _,
11009                    &r1 as *const _ as *mut _,
11010                ];
11011                unsafe {
11012                    self.launch_pdl(
11013                        "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11014                        (grid, 1, 1),
11015                        (32, rpb, 1),
11016                        &mut ps,
11017                    )?;
11018                }
11019            }
11020            return Ok(Some((y0, y1)));
11021        }
11022        let __s_b = self.gpu.stream();
11023        let mut b = __s_b.launch_builder(&f);
11024        b.arg(b0)
11025            .arg(b1)
11026            .arg(aq)
11027            .arg(ad)
11028            .arg(&mut y0)
11029            .arg(&mut y1)
11030            .arg(&inf)
11031            .arg(&oo0)
11032            .arg(&oo1)
11033            .arg(&r0)
11034            .arg(&r1);
11035        unsafe {
11036            b.launch(cfg)?;
11037        }
11038        Ok(Some((y0, y1)))
11039    }
11040
11041    /// Slot-fed fused2 twin (alloc-free capture lane): identical launch, caller-owned outputs.
11042    pub fn matmul_q4_fused2_into(
11043        &self,
11044        w0: &crate::model::GpuTensor,
11045        w1: &crate::model::GpuTensor,
11046        aq: &CudaSlice<i8>,
11047        ad: &CudaSlice<f32>,
11048        y0: &mut CudaSlice<f32>,
11049        y1: &mut CudaSlice<f32>,
11050    ) -> Result<bool, Box<dyn std::error::Error>> {
11051        use crate::model::GpuTensor;
11052        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11053            match w {
11054                GpuTensor::Quant {
11055                    qtype, row_bytes, ..
11056                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11057                _ => None,
11058            }
11059        };
11060        let (Some((rb0, o0)), Some((rb1, o1))) = (q4(w0), q4(w1)) else {
11061            return Ok(false);
11062        };
11063        if w0.in_features() != w1.in_features() {
11064            return Ok(false);
11065        }
11066        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11067            match w {
11068                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11069                    Some(m) => (m, true),
11070                    None => (bytes, *rp),
11071                },
11072                _ => unreachable!(),
11073            }
11074        }
11075        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11076        if rp0 != rp1 {
11077            return Ok(false);
11078        }
11079        let rp = rp0;
11080        let rpb: u32 = 4;
11081        let mr1 = rp && Self::q40_mr1_on();
11082        let nb = |o: usize| {
11083            if mr1 {
11084                (o as u32).div_ceil(rpb)
11085            } else {
11086                (o as u32).div_ceil(2).div_ceil(rpb)
11087            }
11088        };
11089        let grid = nb(o0) + nb(o1);
11090        debug_assert!(y0.len() >= o0 && y1.len() >= o1);
11091        let f = self.func(if mr1 {
11092            "qmatvec_q4_0_mmvq_fused2_mr1_rp"
11093        } else if rp {
11094            "qmatvec_q4_0_mmvq_fused2_rp"
11095        } else {
11096            "qmatvec_q4_0_mmvq_fused2"
11097        });
11098        let cfg = LaunchConfig {
11099            grid_dim: (grid, 1, 1),
11100            block_dim: (32, rpb, 1),
11101            shared_mem_bytes: 0,
11102        };
11103        let inf = w0.in_features() as i32;
11104        let (oo0, oo1) = (o0 as i32, o1 as i32);
11105        let (r0, r1) = (rb0 as i64, rb1 as i64);
11106        // PDL wave-A: identical to the owned twin (capture-lane parity).
11107        if mr1 && Self::pdl_on() && Self::pdl_mmvq_on() {
11108            use cudarc::driver::{DevicePtr, DevicePtrMut};
11109            let s = &self.gpu.stream();
11110            let (p0, _g0) = b0.device_ptr(s);
11111            let (p1, _g1) = b1.device_ptr(s);
11112            let (paq, _g2) = aq.device_ptr(s);
11113            let (pad, _g3) = ad.device_ptr(s);
11114            let (py0, _g4) = y0.device_ptr_mut(s);
11115            let (py1, _g5) = y1.device_ptr_mut(s);
11116            let mut ps = [
11117                &p0 as *const _ as *mut std::ffi::c_void,
11118                &p1 as *const _ as *mut _,
11119                &paq as *const _ as *mut _,
11120                &pad as *const _ as *mut _,
11121                &py0 as *const _ as *mut _,
11122                &py1 as *const _ as *mut _,
11123                &inf as *const _ as *mut _,
11124                &oo0 as *const _ as *mut _,
11125                &oo1 as *const _ as *mut _,
11126                &r0 as *const _ as *mut _,
11127                &r1 as *const _ as *mut _,
11128            ];
11129            unsafe {
11130                self.launch_pdl(
11131                    "qmatvec_q4_0_mmvq_fused2_mr1_rp",
11132                    (grid, 1, 1),
11133                    (32, rpb, 1),
11134                    &mut ps,
11135                )?;
11136            }
11137            return Ok(true);
11138        }
11139        let __s_b = self.gpu.stream();
11140        let mut b = __s_b.launch_builder(&f);
11141        b.arg(b0)
11142            .arg(b1)
11143            .arg(aq)
11144            .arg(ad)
11145            .arg(&mut *y0)
11146            .arg(&mut *y1)
11147            .arg(&inf)
11148            .arg(&oo0)
11149            .arg(&oo1)
11150            .arg(&r0)
11151            .arg(&r1);
11152        unsafe {
11153            b.launch(cfg)?;
11154        }
11155        Ok(true)
11156    }
11157
11158    /// BATCHED fused2 (2026-07-13, megakernel-microcosm probe): gate+up b-tier matvecs in
11159    /// ONE segmented-grid launch — the up segment fills SMs as the gate segment drains
11160    /// (the per-launch tail waves behind the 6x-falsified b-tier plateau). Bit-identical
11161    /// per row to two mr2_rp launches. rp layout required; m in 2..=8 (b16 has no twin).
11162    pub fn matmul_q4_fused2_batched(
11163        &self,
11164        w0: &crate::model::GpuTensor,
11165        w1: &crate::model::GpuTensor,
11166        aq: &CudaSlice<i8>,
11167        ad: &CudaSlice<f32>,
11168        m: usize,
11169    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11170        use crate::model::GpuTensor;
11171        if m < 2 || m > 8 {
11172            return Ok(None);
11173        }
11174        let q4 = |w: &GpuTensor| -> Option<(usize, usize)> {
11175            match w {
11176                GpuTensor::Quant {
11177                    qtype, row_bytes, ..
11178                } if *qtype == QT_Q4_0 => Some((*row_bytes, w.out_features())),
11179                _ => None,
11180            }
11181        };
11182        let (Some((rb0, o0)), Some((_rb1, o1))) = (q4(w0), q4(w1)) else {
11183            return Ok(None);
11184        };
11185        if w0.in_features() != w1.in_features() {
11186            return Ok(None);
11187        }
11188        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11189            match w {
11190                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11191                    Some(mr) => (mr, true),
11192                    None => (bytes, *rp),
11193                },
11194                _ => unreachable!(),
11195            }
11196        }
11197        let ((b0, rp0), (b1, rp1)) = (eff(w0), eff(w1));
11198        if !rp0 || !rp1 {
11199            return Ok(None);
11200        }
11201        let mcols = Self::batched_mcols(m);
11202        let rpb: u32 = 4;
11203        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11204        let grid = nb(o0) + nb(o1);
11205        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11206        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11207        let f = self.func(match mcols {
11208            2 => "qmatvec_q4_0_mmvq_b2_f2_rp",
11209            4 => "qmatvec_q4_0_mmvq_b4_f2_rp",
11210            _ => "qmatvec_q4_0_mmvq_b8_f2_rp",
11211        });
11212        let cfg = LaunchConfig {
11213            grid_dim: (grid, 1, 1),
11214            block_dim: (32, rpb, 1),
11215            shared_mem_bytes: 0,
11216        };
11217        let inf = w0.in_features() as i32;
11218        let (oo0, oo1, mi) = (o0 as i32, o1 as i32, m as i32);
11219        let rb = rb0 as i64;
11220        let __s_b = self.gpu.stream();
11221        let mut b = __s_b.launch_builder(&f);
11222        b.arg(b0)
11223            .arg(b1)
11224            .arg(aq)
11225            .arg(ad)
11226            .arg(&mut y0)
11227            .arg(&mut y1)
11228            .arg(&inf)
11229            .arg(&oo0)
11230            .arg(&oo1)
11231            .arg(&mi)
11232            .arg(&rb);
11233        unsafe {
11234            b.launch(cfg)?;
11235        }
11236        Ok(Some((y0, y1)))
11237    }
11238
11239    /// BATCHED fused3 (see matmul_q4_fused2_batched): three-segment single launch for the
11240    /// verify qkv triple. Same-in_f q4_0 rp tensors, m in 2..=8. Bit-identical per row.
11241    #[allow(clippy::too_many_arguments)]
11242    pub fn matmul_q4_fused3_batched(
11243        &self,
11244        w0: &crate::model::GpuTensor,
11245        w1: &crate::model::GpuTensor,
11246        w2: &crate::model::GpuTensor,
11247        aq: &CudaSlice<i8>,
11248        ad: &CudaSlice<f32>,
11249        m: usize,
11250    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11251    {
11252        use crate::model::GpuTensor;
11253        if m < 2 || m > 8 {
11254            return Ok(None);
11255        }
11256        let q4 = |w: &GpuTensor| -> Option<usize> {
11257            match w {
11258                GpuTensor::Quant { qtype, .. } if *qtype == QT_Q4_0 => Some(w.out_features()),
11259                _ => None,
11260            }
11261        };
11262        let (Some(o0), Some(o1), Some(o2)) = (q4(w0), q4(w1), q4(w2)) else {
11263            return Ok(None);
11264        };
11265        if w0.in_features() != w1.in_features() || w0.in_features() != w2.in_features() {
11266            return Ok(None);
11267        }
11268        fn eff(w: &GpuTensor) -> (&CudaSlice<u8>, bool) {
11269            match w {
11270                GpuTensor::Quant { bytes, rp4, rp, .. } => match rp4 {
11271                    Some(mr) => (mr, true),
11272                    None => (bytes, *rp),
11273                },
11274                _ => unreachable!(),
11275            }
11276        }
11277        let ((b0, rp0), (b1, rp1), (b2, rp2)) = (eff(w0), eff(w1), eff(w2));
11278        if !rp0 || !rp1 || !rp2 {
11279            return Ok(None);
11280        }
11281        let mcols = Self::batched_mcols(m);
11282        let rpb: u32 = 4;
11283        let nb = |o: usize| (o as u32).div_ceil(2 * rpb);
11284        let grid = nb(o0) + nb(o1) + nb(o2);
11285        let mut y0 = self.alloc_uninit::<f32>(m * o0)?;
11286        let mut y1 = self.alloc_uninit::<f32>(m * o1)?;
11287        let mut y2 = self.alloc_uninit::<f32>(m * o2)?;
11288        let f = self.func(match mcols {
11289            2 => "qmatvec_q4_0_mmvq_b2_f3_rp",
11290            4 => "qmatvec_q4_0_mmvq_b4_f3_rp",
11291            _ => "qmatvec_q4_0_mmvq_b8_f3_rp",
11292        });
11293        let cfg = LaunchConfig {
11294            grid_dim: (grid, 1, 1),
11295            block_dim: (32, rpb, 1),
11296            shared_mem_bytes: 0,
11297        };
11298        let inf = w0.in_features() as i32;
11299        let (oo0, oo1, oo2, mi) = (o0 as i32, o1 as i32, o2 as i32, m as i32);
11300        let rb = 0i64;
11301        let __s_b = self.gpu.stream();
11302        let mut b = __s_b.launch_builder(&f);
11303        b.arg(b0)
11304            .arg(b1)
11305            .arg(b2)
11306            .arg(aq)
11307            .arg(ad)
11308            .arg(&mut y0)
11309            .arg(&mut y1)
11310            .arg(&mut y2)
11311            .arg(&inf)
11312            .arg(&oo0)
11313            .arg(&oo1)
11314            .arg(&oo2)
11315            .arg(&mi)
11316            .arg(&rb);
11317        unsafe {
11318            b.launch(cfg)?;
11319        }
11320        Ok(Some((y0, y1, y2)))
11321    }
11322
11323    pub fn matmul_q8_fused3(
11324        &self,
11325        w0: &crate::model::GpuTensor,
11326        w1: &crate::model::GpuTensor,
11327        w2: &crate::model::GpuTensor,
11328        aq: &CudaSlice<i8>,
11329        ad: &CudaSlice<f32>,
11330    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11331    {
11332        // e4m3 twin (lane/fp8-decode-v1): the full-attn wq/wk/wv triple — on the NV-27B those three
11333        // are per-tensor FP8, so native residency without this arm meant three separate launches.
11334        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11335            return Ok(Some(self.e4m3_fused3_core(
11336                p0.0,
11337                p1.0,
11338                p2.0,
11339                aq,
11340                ad,
11341                w0.in_features(),
11342                p0.1,
11343                p1.1,
11344                p2.1,
11345                p0.2,
11346                p0.3,
11347                p1.3,
11348                p2.3,
11349            )?));
11350        }
11351        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11352            return Ok(None);
11353        };
11354        Ok(Some(self.q8_fused3_core(
11355            p0.0,
11356            p1.0,
11357            p2.0,
11358            aq,
11359            ad,
11360            w0.in_features(),
11361            p0.1,
11362            p1.1,
11363            p2.1,
11364            p0.2,
11365        )?))
11366    }
11367
11368    #[allow(clippy::too_many_arguments)]
11369    fn q8_fused3_core(
11370        &self,
11371        b0: &CudaSlice<u8>,
11372        b1: &CudaSlice<u8>,
11373        b2: &CudaSlice<u8>,
11374        aq: &CudaSlice<i8>,
11375        ad: &CudaSlice<f32>,
11376        in_f: usize,
11377        out0: usize,
11378        out1: usize,
11379        out2: usize,
11380        row_bytes: usize,
11381    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11382        const ROWS_PER_BLOCK: u32 = 4;
11383        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11384        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11385        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11386        let f = self.func("qmatvec_q8_0_mmvq_fused3");
11387        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11388        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11389        let mut y2 = self.alloc_uninit::<f32>(out2)?;
11390        let cfg = LaunchConfig {
11391            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11392            block_dim: (32, ROWS_PER_BLOCK, 1),
11393            shared_mem_bytes: 0,
11394        };
11395        let (inf, o0, o1, o2, rbl) = (
11396            in_f as i32,
11397            out0 as i32,
11398            out1 as i32,
11399            out2 as i32,
11400            row_bytes as i64,
11401        );
11402        let __s_b = self.gpu.stream();
11403        let mut b = __s_b.launch_builder(&f);
11404        b.arg(b0)
11405            .arg(b1)
11406            .arg(b2)
11407            .arg(aq)
11408            .arg(ad)
11409            .arg(&mut y0)
11410            .arg(&mut y1)
11411            .arg(&mut y2)
11412            .arg(&inf)
11413            .arg(&o0)
11414            .arg(&o1)
11415            .arg(&o2)
11416            .arg(&rbl);
11417        unsafe {
11418            b.launch(cfg)?;
11419        }
11420        Ok((y0, y1, y2))
11421    }
11422
11423    /// Test entry for the kernel_check gate: fused3 from raw weight bytes (internal q8_1 quant).
11424    #[allow(clippy::too_many_arguments)]
11425    pub fn qmatvec_q8_fused3_raw(
11426        &self,
11427        b0: &CudaSlice<u8>,
11428        b1: &CudaSlice<u8>,
11429        b2: &CudaSlice<u8>,
11430        x: &CudaSlice<f32>,
11431        in_f: usize,
11432        out0: usize,
11433        out1: usize,
11434        out2: usize,
11435        row_bytes: usize,
11436    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11437        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
11438        self.q8_fused3_core(b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes)
11439    }
11440
11441    /// BATCHED twin of `matmul_q8_fused2` for the verify t=2-4 tier (MEMRA_SPEC_FUSED_T call
11442    /// sites, lane/close35b): ONE launch computes both same-input Q8_0 projections for m tokens.
11443    /// Per (tensor,token,row) the kernel body is q8_0_mmvq_batched VERBATIM with the identical
11444    /// row mapping (Q8_0's batched_variant is always "base") -> BIT-IDENTICAL to the two
11445    /// per-tensor _b2/_b4 launches `matmul_decode_exact` dispatches at m=2-4, with the caller's
11446    /// single shared q8_1 activation replacing two per-call re-quantizes (quantize_q8_1 is
11447    /// deterministic -> same bytes). None when ineligible (m outside 2..=4 / not both Q8_0 /
11448    /// in_f mismatch / MEMRA_MMVQ=0 / MEMRA_Q8_DUAL=0 / MEMRA_NO_BATCHED set — the last keeps
11449    /// dispatch parity: without batched kernels decode-exact runs grid.y=m MMVQ, and the fused
11450    /// twin must not introduce a batched program the reference path would not run).
11451    pub fn matmul_q8_fused2_t(
11452        &self,
11453        w0: &crate::model::GpuTensor,
11454        w1: &crate::model::GpuTensor,
11455        aq: &CudaSlice<i8>,
11456        ad: &CudaSlice<f32>,
11457        m: usize,
11458    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>> {
11459        // m<=8 (lane/q27-deepdive, 2026-08-05): was 2..=4 (the verify tier's mcols 2/4). The
11460        // serving tick's mcols-8 tier now has its fused2_b8 wrapper, so c=5..8 batched decode
11461        // fuses too — same template body, still bit-identical to the two _b8 launches.
11462        if !(2..=8).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11463            return Ok(None);
11464        }
11465        // e4m3 twin: MEMRA_B8 parity — without it m=5..8 e4m3 decode runs the per-m grid.y=m path,
11466        // so the fused b8 launch would introduce a batched program the reference path would not run.
11467        if let Some([p0, p1]) = self.e4m3_fused_params(&[w0, w1]) {
11468            if m > 4 && !Self::b8_enabled() {
11469                return Ok(None);
11470            }
11471            return Ok(Some(self.e4m3_fused2_t_core(
11472                p0.0,
11473                p1.0,
11474                aq,
11475                ad,
11476                m,
11477                w0.in_features(),
11478                p0.1,
11479                p1.1,
11480                p0.2,
11481                p0.3,
11482                p1.3,
11483            )?));
11484        }
11485        let Some([p0, p1]) = self.q8_fused_params(&[w0, w1]) else {
11486            return Ok(None);
11487        };
11488        Ok(Some(self.q8_fused2_t_core(
11489            p0.0,
11490            p1.0,
11491            aq,
11492            ad,
11493            m,
11494            w0.in_features(),
11495            p0.1,
11496            p1.1,
11497            p0.2,
11498        )?))
11499    }
11500
11501    #[allow(clippy::too_many_arguments)]
11502    fn q8_fused2_t_core(
11503        &self,
11504        b0: &CudaSlice<u8>,
11505        b1: &CudaSlice<u8>,
11506        aq: &CudaSlice<i8>,
11507        ad: &CudaSlice<f32>,
11508        m: usize,
11509        in_f: usize,
11510        out0: usize,
11511        out1: usize,
11512        row_bytes: usize,
11513    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11514        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11515        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11516        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11517        let f = self.func(match Self::batched_mcols(m) {
11518            2 => "qmatvec_q8_0_mmvq_fused2_b2",
11519            4 => "qmatvec_q8_0_mmvq_fused2_b4",
11520            // b8 = the SERVING tier (lane/q27-deepdive): c=5..8 batched decode.
11521            _ => "qmatvec_q8_0_mmvq_fused2_b8",
11522        });
11523        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11524        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11525        let cfg = LaunchConfig {
11526            grid_dim: (nb0 + nb1, 1, 1),
11527            block_dim: (32, ROWS_PER_BLOCK, 1),
11528            shared_mem_bytes: 0,
11529        };
11530        let (inf, o0, o1, mi, rbl) = (
11531            in_f as i32,
11532            out0 as i32,
11533            out1 as i32,
11534            m 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(aq)
11542            .arg(ad)
11543            .arg(&mut y0)
11544            .arg(&mut y1)
11545            .arg(&inf)
11546            .arg(&o0)
11547            .arg(&o1)
11548            .arg(&mi)
11549            .arg(&rbl);
11550        unsafe {
11551            b.launch(cfg)?;
11552        }
11553        Ok((y0, y1))
11554    }
11555
11556    /// Test entry for the kernel_check gate: fused2 batched from raw weight bytes (internal
11557    /// q8_1 quant of the [m, in_f] activation), no env gating.
11558    #[allow(clippy::too_many_arguments)]
11559    pub fn qmatvec_q8_fused2_t_raw(
11560        &self,
11561        b0: &CudaSlice<u8>,
11562        b1: &CudaSlice<u8>,
11563        x: &CudaSlice<f32>,
11564        m: usize,
11565        in_f: usize,
11566        out0: usize,
11567        out1: usize,
11568        row_bytes: usize,
11569    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11570        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11571        self.q8_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes)
11572    }
11573
11574    /// BATCHED twin of `matmul_q8_fused3` (wq+wk+wv at verify t=2-4). Same contract as
11575    /// `matmul_q8_fused2_t` with three ranges.
11576    #[allow(clippy::too_many_arguments)]
11577    pub fn matmul_q8_fused3_t(
11578        &self,
11579        w0: &crate::model::GpuTensor,
11580        w1: &crate::model::GpuTensor,
11581        w2: &crate::model::GpuTensor,
11582        aq: &CudaSlice<i8>,
11583        ad: &CudaSlice<f32>,
11584        m: usize,
11585    ) -> Result<Option<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>)>, Box<dyn std::error::Error>>
11586    {
11587        if !(2..=4).contains(&m) || std::env::var("MEMRA_NO_BATCHED").is_ok() {
11588            return Ok(None);
11589        }
11590        if let Some([p0, p1, p2]) = self.e4m3_fused_params(&[w0, w1, w2]) {
11591            return Ok(Some(self.e4m3_fused3_t_core(
11592                p0.0,
11593                p1.0,
11594                p2.0,
11595                aq,
11596                ad,
11597                m,
11598                w0.in_features(),
11599                p0.1,
11600                p1.1,
11601                p2.1,
11602                p0.2,
11603                p0.3,
11604                p1.3,
11605                p2.3,
11606            )?));
11607        }
11608        let Some([p0, p1, p2]) = self.q8_fused_params(&[w0, w1, w2]) else {
11609            return Ok(None);
11610        };
11611        Ok(Some(self.q8_fused3_t_core(
11612            p0.0,
11613            p1.0,
11614            p2.0,
11615            aq,
11616            ad,
11617            m,
11618            w0.in_features(),
11619            p0.1,
11620            p1.1,
11621            p2.1,
11622            p0.2,
11623        )?))
11624    }
11625
11626    #[allow(clippy::too_many_arguments)]
11627    fn q8_fused3_t_core(
11628        &self,
11629        b0: &CudaSlice<u8>,
11630        b1: &CudaSlice<u8>,
11631        b2: &CudaSlice<u8>,
11632        aq: &CudaSlice<i8>,
11633        ad: &CudaSlice<f32>,
11634        m: usize,
11635        in_f: usize,
11636        out0: usize,
11637        out1: usize,
11638        out2: usize,
11639        row_bytes: usize,
11640    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11641        const ROWS_PER_BLOCK: u32 = 4;
11642        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11643        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11644        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11645        let f = self.func(if Self::batched_mcols(m) == 2 {
11646            "qmatvec_q8_0_mmvq_fused3_b2"
11647        } else {
11648            "qmatvec_q8_0_mmvq_fused3_b4"
11649        });
11650        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11651        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11652        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
11653        let cfg = LaunchConfig {
11654            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11655            block_dim: (32, ROWS_PER_BLOCK, 1),
11656            shared_mem_bytes: 0,
11657        };
11658        let (inf, o0, o1, o2, mi, rbl) = (
11659            in_f as i32,
11660            out0 as i32,
11661            out1 as i32,
11662            out2 as i32,
11663            m as i32,
11664            row_bytes as i64,
11665        );
11666        let __s_b = self.gpu.stream();
11667        let mut b = __s_b.launch_builder(&f);
11668        b.arg(b0)
11669            .arg(b1)
11670            .arg(b2)
11671            .arg(aq)
11672            .arg(ad)
11673            .arg(&mut y0)
11674            .arg(&mut y1)
11675            .arg(&mut y2)
11676            .arg(&inf)
11677            .arg(&o0)
11678            .arg(&o1)
11679            .arg(&o2)
11680            .arg(&mi)
11681            .arg(&rbl);
11682        unsafe {
11683            b.launch(cfg)?;
11684        }
11685        Ok((y0, y1, y2))
11686    }
11687
11688    /// Test entry for the kernel_check gate: fused3 batched from raw weight bytes.
11689    #[allow(clippy::too_many_arguments)]
11690    pub fn qmatvec_q8_fused3_t_raw(
11691        &self,
11692        b0: &CudaSlice<u8>,
11693        b1: &CudaSlice<u8>,
11694        b2: &CudaSlice<u8>,
11695        x: &CudaSlice<f32>,
11696        m: usize,
11697        in_f: usize,
11698        out0: usize,
11699        out1: usize,
11700        out2: usize,
11701        row_bytes: usize,
11702    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11703        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
11704        self.q8_fused3_t_core(b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes)
11705    }
11706
11707    /// Rollback seam for the Q8_0 dense-FFN gate+up fusion arm in `matmul_pre_dual_noscale`
11708    /// (lane/q27-deepdive, 2026-08-05). Default ON; `MEMRA_Q8_FFN_FUSE2=0` restores the
11709    /// two-`matmul_pre_noscale` pair. Read once — the dispatch must not vary within a run.
11710    pub fn q8_ffn_fuse2_on(&self) -> bool {
11711        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11712        *ON.get_or_init(|| std::env::var("MEMRA_Q8_FFN_FUSE2").as_deref() != Ok("0"))
11713    }
11714
11715    /// Eligibility + param extraction for the fused q8_0 launches: every tensor must be Quant Q8_0
11716    /// with macro-scale 1.0 (always true for GGUF q8_0; only NVFP4 carries scale) and share w[0]'s
11717    /// in_f (q8_0 row_bytes is a pure function of in_f, so equal in_f => equal row_bytes). MEMRA_MMVQ
11718    /// must be on: the fused body is the MMVQ kernel; without it decode m=1 runs dp4a and fusing
11719    /// would mix dispatch families (FP-order law). MEMRA_Q8_DUAL=0 = rollback seam.
11720    #[allow(clippy::type_complexity)]
11721    fn q8_fused_params<'w, const N: usize>(
11722        &self,
11723        ws: &[&'w crate::model::GpuTensor; N],
11724    ) -> Option<[(&'w CudaSlice<u8>, usize, usize); N]> {
11725        use crate::model::GpuTensor;
11726        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
11727            return None;
11728        }
11729        if std::env::var("MEMRA_Q8_DUAL").is_ok_and(|v| v == "0") {
11730            return None;
11731        }
11732        let in_f = ws[0].in_features();
11733        let mut out: [Option<(&CudaSlice<u8>, usize, usize)>; N] = [None; N];
11734        for (i, w) in ws.iter().enumerate() {
11735            match w {
11736                GpuTensor::Quant {
11737                    bytes,
11738                    qtype,
11739                    row_bytes,
11740                    scale,
11741                    ..
11742                } if *qtype == QT_Q8_0 && *scale == 1.0 && w.in_features() == in_f => {
11743                    out[i] = Some((bytes, w.out_features(), *row_bytes))
11744                }
11745                _ => return None,
11746            }
11747        }
11748        Some(out.map(|o| o.unwrap()))
11749    }
11750
11751    /// Rollback seam for the F8-E4M3 launch-fusion arm (lane/fp8-decode-v1, 2026-08-05).
11752    /// Default ON; `MEMRA_E4M3_DUAL=0` restores the per-tensor m=1/batched launches.
11753    pub fn e4m3_dual_on(&self) -> bool {
11754        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
11755        *ON.get_or_init(|| std::env::var("MEMRA_E4M3_DUAL").as_deref() != Ok("0"))
11756    }
11757
11758    /// Eligibility + param extraction for the FUSED e4m3 launches — the QT_F8_E4M3 twin of
11759    /// `q8_fused_params`. Differences that are inherent to the dtype, not policy:
11760    ///   * each tensor carries its OWN per-tensor `weight_scale` (returned as the 4th field);
11761    ///     Q8_0 hard-requires scale==1.0 because it has no macro-scale at all.
11762    ///   * no MEMRA_MMVQ gate: `mmvq_supports` exempts QT_F8_E4M3 (the e4m3 mmvq family is that
11763    ///     dtype's ONLY int8-act kernel class), so the per-tensor fallback these fused kernels
11764    ///     replace is ALWAYS the same mmvq body under every env — the FP-order law holds.
11765    ///   * `row_bytes == in_f` is asserted rather than derived: the native-residency load arm keeps
11766    ///     the checkpoint's raw [out_f, in_f] rows, and a re-encoded slab must never reach here.
11767    /// Rejects any split-plane mirror (`rp`/`rp4`): there is no `_rp` e4m3 fused form, so fusing
11768    /// there would swap dispatch families mid-model. MEMRA_E4M3_DUAL=0 = rollback seam.
11769    #[allow(clippy::type_complexity)]
11770    fn e4m3_fused_params<'w, const N: usize>(
11771        &self,
11772        ws: &[&'w crate::model::GpuTensor; N],
11773    ) -> Option<[(&'w CudaSlice<u8>, usize, usize, f32); N]> {
11774        use crate::model::GpuTensor;
11775        if !self.e4m3_dual_on() {
11776            return None;
11777        }
11778        let in_f = ws[0].in_features();
11779        let mut out: [Option<(&CudaSlice<u8>, usize, usize, f32)>; N] = [None; N];
11780        for (i, w) in ws.iter().enumerate() {
11781            match w {
11782                GpuTensor::Quant {
11783                    bytes,
11784                    qtype,
11785                    row_bytes,
11786                    scale,
11787                    rp,
11788                    rp4,
11789                    ..
11790                } if *qtype == QT_F8_E4M3
11791                    && w.in_features() == in_f
11792                    && *row_bytes == in_f
11793                    && !*rp
11794                    && rp4.is_none() =>
11795                {
11796                    out[i] = Some((bytes, w.out_features(), *row_bytes, *scale))
11797                }
11798                _ => return None,
11799            }
11800        }
11801        Some(out.map(|o| o.unwrap()))
11802    }
11803
11804    /// FUSED e4m3 m=1 PAIR. Block-offset split (`qmatvec_e4m3_mmvq_fused2`), per-tensor
11805    /// weight_scale folded at the write like the single-tensor `qmatvec_e4m3_mmvq` — so per
11806    /// (tensor,row) this is BIT-IDENTICAL to two separate m=1 launches, scale included.
11807    #[allow(clippy::too_many_arguments)]
11808    fn e4m3_fused2_core(
11809        &self,
11810        b0: &CudaSlice<u8>,
11811        b1: &CudaSlice<u8>,
11812        aq: &CudaSlice<i8>,
11813        ad: &CudaSlice<f32>,
11814        in_f: usize,
11815        out0: usize,
11816        out1: usize,
11817        row_bytes: usize,
11818        ws0: f32,
11819        ws1: f32,
11820    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11821        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
11822        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11823        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11824        let f = self.func("qmatvec_e4m3_mmvq_fused2");
11825        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11826        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11827        let cfg = LaunchConfig {
11828            grid_dim: (nb0 + nb1, 1, 1),
11829            block_dim: (32, ROWS_PER_BLOCK, 1),
11830            shared_mem_bytes: 0,
11831        };
11832        let (inf, o0, o1, rbl) = (in_f as i32, out0 as i32, out1 as i32, row_bytes as i64);
11833        let __s_b = self.gpu.stream();
11834        let mut b = __s_b.launch_builder(&f);
11835        b.arg(b0)
11836            .arg(b1)
11837            .arg(aq)
11838            .arg(ad)
11839            .arg(&mut y0)
11840            .arg(&mut y1)
11841            .arg(&inf)
11842            .arg(&o0)
11843            .arg(&o1)
11844            .arg(&rbl)
11845            .arg(&ws0)
11846            .arg(&ws1);
11847        unsafe {
11848            b.launch(cfg)?;
11849        }
11850        Ok((y0, y1))
11851    }
11852
11853    /// FUSED e4m3 m=1 TRIPLE (`qmatvec_e4m3_mmvq_fused3`). Same contract as the pair.
11854    #[allow(clippy::too_many_arguments)]
11855    fn e4m3_fused3_core(
11856        &self,
11857        b0: &CudaSlice<u8>,
11858        b1: &CudaSlice<u8>,
11859        b2: &CudaSlice<u8>,
11860        aq: &CudaSlice<i8>,
11861        ad: &CudaSlice<f32>,
11862        in_f: usize,
11863        out0: usize,
11864        out1: usize,
11865        out2: usize,
11866        row_bytes: usize,
11867        ws0: f32,
11868        ws1: f32,
11869        ws2: f32,
11870    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11871        const ROWS_PER_BLOCK: u32 = 4;
11872        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11873        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11874        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
11875        let f = self.func("qmatvec_e4m3_mmvq_fused3");
11876        let mut y0 = self.alloc_uninit::<f32>(out0)?;
11877        let mut y1 = self.alloc_uninit::<f32>(out1)?;
11878        let mut y2 = self.alloc_uninit::<f32>(out2)?;
11879        let cfg = LaunchConfig {
11880            grid_dim: (nb0 + nb1 + nb2, 1, 1),
11881            block_dim: (32, ROWS_PER_BLOCK, 1),
11882            shared_mem_bytes: 0,
11883        };
11884        let (inf, o0, o1, o2, rbl) = (
11885            in_f as i32,
11886            out0 as i32,
11887            out1 as i32,
11888            out2 as i32,
11889            row_bytes as i64,
11890        );
11891        let __s_b = self.gpu.stream();
11892        let mut b = __s_b.launch_builder(&f);
11893        b.arg(b0)
11894            .arg(b1)
11895            .arg(b2)
11896            .arg(aq)
11897            .arg(ad)
11898            .arg(&mut y0)
11899            .arg(&mut y1)
11900            .arg(&mut y2)
11901            .arg(&inf)
11902            .arg(&o0)
11903            .arg(&o1)
11904            .arg(&o2)
11905            .arg(&rbl)
11906            .arg(&ws0)
11907            .arg(&ws1)
11908            .arg(&ws2);
11909        unsafe {
11910            b.launch(cfg)?;
11911        }
11912        Ok((y0, y1, y2))
11913    }
11914
11915    /// BATCHED FUSED e4m3 pair (m=2..8). The batched kernels carry no `ws` arg (every batched
11916    /// kernel in the tree is scale-free), so each output takes its own `scale_inplace` — the
11917    /// SAME post-op the per-tensor batched dispatch applies, hence still bit-identical.
11918    #[allow(clippy::too_many_arguments)]
11919    fn e4m3_fused2_t_core(
11920        &self,
11921        b0: &CudaSlice<u8>,
11922        b1: &CudaSlice<u8>,
11923        aq: &CudaSlice<i8>,
11924        ad: &CudaSlice<f32>,
11925        m: usize,
11926        in_f: usize,
11927        out0: usize,
11928        out1: usize,
11929        row_bytes: usize,
11930        ws0: f32,
11931        ws1: f32,
11932    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11933        const ROWS_PER_BLOCK: u32 = 4;
11934        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
11935        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
11936        let f = self.func(match Self::batched_mcols(m) {
11937            2 => "qmatvec_e4m3_mmvq_fused2_b2",
11938            4 => "qmatvec_e4m3_mmvq_fused2_b4",
11939            _ => "qmatvec_e4m3_mmvq_fused2_b8",
11940        });
11941        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
11942        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
11943        let cfg = LaunchConfig {
11944            grid_dim: (nb0 + nb1, 1, 1),
11945            block_dim: (32, ROWS_PER_BLOCK, 1),
11946            shared_mem_bytes: 0,
11947        };
11948        let (inf, o0, o1, mi, rbl) = (
11949            in_f as i32,
11950            out0 as i32,
11951            out1 as i32,
11952            m as i32,
11953            row_bytes as i64,
11954        );
11955        let __s_b = self.gpu.stream();
11956        let mut b = __s_b.launch_builder(&f);
11957        b.arg(b0)
11958            .arg(b1)
11959            .arg(aq)
11960            .arg(ad)
11961            .arg(&mut y0)
11962            .arg(&mut y1)
11963            .arg(&inf)
11964            .arg(&o0)
11965            .arg(&o1)
11966            .arg(&mi)
11967            .arg(&rbl);
11968        unsafe {
11969            b.launch(cfg)?;
11970        }
11971        if ws0 != 1.0 {
11972            self.scale_inplace(&mut y0, ws0, m * out0)?;
11973        }
11974        if ws1 != 1.0 {
11975            self.scale_inplace(&mut y1, ws1, m * out1)?;
11976        }
11977        Ok((y0, y1))
11978    }
11979
11980    /// BATCHED FUSED e4m3 triple (m=2..4). Same contract as the batched pair.
11981    #[allow(clippy::too_many_arguments)]
11982    fn e4m3_fused3_t_core(
11983        &self,
11984        b0: &CudaSlice<u8>,
11985        b1: &CudaSlice<u8>,
11986        b2: &CudaSlice<u8>,
11987        aq: &CudaSlice<i8>,
11988        ad: &CudaSlice<f32>,
11989        m: usize,
11990        in_f: usize,
11991        out0: usize,
11992        out1: usize,
11993        out2: usize,
11994        row_bytes: usize,
11995        ws0: f32,
11996        ws1: f32,
11997        ws2: f32,
11998    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
11999        const ROWS_PER_BLOCK: u32 = 4;
12000        let nb0 = (out0 as u32).div_ceil(ROWS_PER_BLOCK);
12001        let nb1 = (out1 as u32).div_ceil(ROWS_PER_BLOCK);
12002        let nb2 = (out2 as u32).div_ceil(ROWS_PER_BLOCK);
12003        let f = self.func(if Self::batched_mcols(m) == 2 {
12004            "qmatvec_e4m3_mmvq_fused3_b2"
12005        } else {
12006            "qmatvec_e4m3_mmvq_fused3_b4"
12007        });
12008        let mut y0 = self.alloc_uninit::<f32>(m * out0)?;
12009        let mut y1 = self.alloc_uninit::<f32>(m * out1)?;
12010        let mut y2 = self.alloc_uninit::<f32>(m * out2)?;
12011        let cfg = LaunchConfig {
12012            grid_dim: (nb0 + nb1 + nb2, 1, 1),
12013            block_dim: (32, ROWS_PER_BLOCK, 1),
12014            shared_mem_bytes: 0,
12015        };
12016        let (inf, o0, o1, o2, mi, rbl) = (
12017            in_f as i32,
12018            out0 as i32,
12019            out1 as i32,
12020            out2 as i32,
12021            m as i32,
12022            row_bytes as i64,
12023        );
12024        let __s_b = self.gpu.stream();
12025        let mut b = __s_b.launch_builder(&f);
12026        b.arg(b0)
12027            .arg(b1)
12028            .arg(b2)
12029            .arg(aq)
12030            .arg(ad)
12031            .arg(&mut y0)
12032            .arg(&mut y1)
12033            .arg(&mut y2)
12034            .arg(&inf)
12035            .arg(&o0)
12036            .arg(&o1)
12037            .arg(&o2)
12038            .arg(&mi)
12039            .arg(&rbl);
12040        unsafe {
12041            b.launch(cfg)?;
12042        }
12043        if ws0 != 1.0 {
12044            self.scale_inplace(&mut y0, ws0, m * out0)?;
12045        }
12046        if ws1 != 1.0 {
12047            self.scale_inplace(&mut y1, ws1, m * out1)?;
12048        }
12049        if ws2 != 1.0 {
12050            self.scale_inplace(&mut y2, ws2, m * out2)?;
12051        }
12052        Ok((y0, y1, y2))
12053    }
12054
12055    /// BLOCK-128 e4m3 MMVQ launcher (`qmatvec_e4m3_blk_mmvq`, lane/fp8-blk128-decode 2026-08-05).
12056    /// The per-block-dequant twin of `qmatvec_mmvq`'s QT_F8_E4M3 arm: same grid/block decomposition
12057    /// (warp per output row, ROWS_PER_BLOCK warps per block, grid.y = m), same q8_1 activation, but
12058    /// the weight scale is a resident [rows, cols] f32 grid read per k128 block inside the kernel
12059    /// instead of one scalar folded at the write. It cannot share `qmatvec_mmvq`'s body because
12060    /// that launcher's arg list is fixed at (bytes, aq, ad, y, in_f, out_f, m, row_bytes [, scale]).
12061    ///
12062    /// `mr` and `rp` have no analogue here (no split-plane e4m3 layout exists), so there is exactly
12063    /// one kernel and no name table — a shape this cannot serve must be refused at LOAD, not here.
12064    pub fn qmatvec_e4m3_blk_mmvq(
12065        &self,
12066        bytes: &CudaSlice<u8>,
12067        aq: &CudaSlice<i8>,
12068        ad: &CudaSlice<f32>,
12069        scales: &CudaSlice<f32>,
12070        m: usize,
12071        in_f: usize,
12072        out_f: usize,
12073        row_bytes: usize,
12074        scale_cols: usize,
12075    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12076        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite output: skip memset
12077        self.qmatvec_e4m3_blk_mmvq_into(
12078            bytes, aq, ad, scales, m, in_f, out_f, row_bytes, scale_cols, &mut y,
12079        )?;
12080        Ok(y)
12081    }
12082
12083    /// Slot-fed twin of `qmatvec_e4m3_blk_mmvq` (caller-owned output; the alloc-free capture lane).
12084    #[allow(clippy::too_many_arguments)]
12085    pub fn qmatvec_e4m3_blk_mmvq_into(
12086        &self,
12087        bytes: &CudaSlice<u8>,
12088        aq: &CudaSlice<i8>,
12089        ad: &CudaSlice<f32>,
12090        scales: &CudaSlice<f32>,
12091        m: usize,
12092        in_f: usize,
12093        out_f: usize,
12094        row_bytes: usize,
12095        scale_cols: usize,
12096        y: &mut CudaSlice<f32>,
12097    ) -> Result<(), Box<dyn std::error::Error>> {
12098        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12099        let f = self.func("qmatvec_e4m3_blk_mmvq");
12100        let cfg = LaunchConfig {
12101            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), m as u32, 1),
12102            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row
12103            shared_mem_bytes: 0,                // warp-only reduce
12104        };
12105        let (inf, outf, mi, rb, sc) = (
12106            in_f as i32,
12107            out_f as i32,
12108            m as i32,
12109            row_bytes as i64,
12110            scale_cols as i32,
12111        );
12112        let __s_b = self.gpu.stream();
12113        let mut b = __s_b.launch_builder(&f);
12114        b.arg(bytes)
12115            .arg(aq)
12116            .arg(ad)
12117            .arg(scales)
12118            .arg(&mut *y)
12119            .arg(&inf)
12120            .arg(&outf)
12121            .arg(&mi)
12122            .arg(&rb)
12123            .arg(&sc);
12124        unsafe {
12125            b.launch(cfg)?;
12126        }
12127        Ok(())
12128    }
12129
12130    /// BLOCK-128 e4m3 BATCHED matvec (lane/rp-on-st, 2026-08-06): the weight-read-once twin of
12131    /// `qmatvec_e4m3_blk_mmvq` for m=2..16. Per (token,row) BIT-IDENTICAL to the grid.y=m launch
12132    /// (same fmaf chain, same per-k32 `s * ad` fold, same warp reduce), so it inherits the
12133    /// decode-exactness contract while reading the weight ONCE for up to `mcols` columns instead
12134    /// of `m` times. `mcols` must be one of {2,4,8,16} and satisfy `mcols >= m`.
12135    #[allow(clippy::too_many_arguments)]
12136    pub fn qmatvec_e4m3_blk_mmvq_batched(
12137        &self,
12138        bytes: &CudaSlice<u8>,
12139        aq: &CudaSlice<i8>,
12140        ad: &CudaSlice<f32>,
12141        scales: &CudaSlice<f32>,
12142        m: usize,
12143        in_f: usize,
12144        out_f: usize,
12145        row_bytes: usize,
12146        scale_cols: usize,
12147        mcols: usize,
12148    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12149        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12150        debug_assert!(mcols >= m, "blk batched: mcols {mcols} < m {m}");
12151        let name = match mcols {
12152            2 => "qmatvec_e4m3_blk_mmvq_b2",
12153            4 => "qmatvec_e4m3_blk_mmvq_b4",
12154            8 => "qmatvec_e4m3_blk_mmvq_b8",
12155            16 => "qmatvec_e4m3_blk_mmvq_b16",
12156            _ => {
12157                return Err(
12158                    format!("qmatvec_e4m3_blk_mmvq_batched: no kernel for mcols {mcols}").into(),
12159                );
12160            }
12161        };
12162        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12163        let f = self.func(name);
12164        let cfg = LaunchConfig {
12165            grid_dim: ((out_f as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
12166            block_dim: (32, ROWS_PER_BLOCK, 1),
12167            shared_mem_bytes: 0,
12168        };
12169        let (inf, outf, mi, rb, sc) = (
12170            in_f as i32,
12171            out_f as i32,
12172            m as i32,
12173            row_bytes as i64,
12174            scale_cols as i32,
12175        );
12176        let __s_b = self.gpu.stream();
12177        let mut b = __s_b.launch_builder(&f);
12178        b.arg(bytes)
12179            .arg(aq)
12180            .arg(ad)
12181            .arg(scales)
12182            .arg(&mut y)
12183            .arg(&inf)
12184            .arg(&outf)
12185            .arg(&mi)
12186            .arg(&rb)
12187            .arg(&sc);
12188        unsafe {
12189            b.launch(cfg)?;
12190        }
12191        Ok(y)
12192    }
12193
12194    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 batched MMVQ from raw
12195    /// bytes with an internal q8_1 quantize (mirrors `qmatvec_batched_raw`).
12196    #[allow(clippy::too_many_arguments)]
12197    pub fn qmatvec_e4m3_blk_batched_raw(
12198        &self,
12199        bytes: &CudaSlice<u8>,
12200        x: &CudaSlice<f32>,
12201        scales: &CudaSlice<f32>,
12202        m: usize,
12203        in_f: usize,
12204        out_f: usize,
12205        row_bytes: usize,
12206        scale_cols: usize,
12207        mcols: usize,
12208    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12209        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12210        self.qmatvec_e4m3_blk_mmvq_batched(
12211            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols, mcols,
12212        )
12213    }
12214
12215    /// Test entry for the kernel_check exactness gate: the block-128 e4m3 MMVQ from raw bytes with
12216    /// an internal q8_1 quantize (mirrors `qmatvec_mmvq_raw`).
12217    #[allow(clippy::too_many_arguments)]
12218    pub fn qmatvec_e4m3_blk_mmvq_raw(
12219        &self,
12220        bytes: &CudaSlice<u8>,
12221        x: &CudaSlice<f32>,
12222        scales: &CudaSlice<f32>,
12223        m: usize,
12224        in_f: usize,
12225        out_f: usize,
12226        row_bytes: usize,
12227        scale_cols: usize,
12228    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12229        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12230        self.qmatvec_e4m3_blk_mmvq(
12231            bytes, &aq, &ad, scales, m, in_f, out_f, row_bytes, scale_cols,
12232        )
12233    }
12234
12235    /// Test entries for the kernel_check bit-parity gate: fused e4m3 launches from raw weight
12236    /// bytes with internal q8_1 quantize, no env gating (mirrors `qmatvec_q8_fused*_raw`).
12237    #[allow(clippy::too_many_arguments)]
12238    pub fn qmatvec_e4m3_fused2_raw(
12239        &self,
12240        b0: &CudaSlice<u8>,
12241        b1: &CudaSlice<u8>,
12242        x: &CudaSlice<f32>,
12243        in_f: usize,
12244        out0: usize,
12245        out1: usize,
12246        row_bytes: usize,
12247        ws0: f32,
12248        ws1: f32,
12249    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12250        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12251        self.e4m3_fused2_core(b0, b1, &aq, &ad, in_f, out0, out1, row_bytes, ws0, ws1)
12252    }
12253
12254    #[allow(clippy::too_many_arguments)]
12255    pub fn qmatvec_e4m3_fused3_raw(
12256        &self,
12257        b0: &CudaSlice<u8>,
12258        b1: &CudaSlice<u8>,
12259        b2: &CudaSlice<u8>,
12260        x: &CudaSlice<f32>,
12261        in_f: usize,
12262        out0: usize,
12263        out1: usize,
12264        out2: usize,
12265        row_bytes: usize,
12266        ws0: f32,
12267        ws1: f32,
12268        ws2: f32,
12269    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12270        let (aq, ad) = self.quantize_q8_1(x, 1, in_f)?;
12271        self.e4m3_fused3_core(
12272            b0, b1, b2, &aq, &ad, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12273        )
12274    }
12275
12276    #[allow(clippy::too_many_arguments)]
12277    pub fn qmatvec_e4m3_fused2_t_raw(
12278        &self,
12279        b0: &CudaSlice<u8>,
12280        b1: &CudaSlice<u8>,
12281        x: &CudaSlice<f32>,
12282        m: usize,
12283        in_f: usize,
12284        out0: usize,
12285        out1: usize,
12286        row_bytes: usize,
12287        ws0: f32,
12288        ws1: f32,
12289    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12290        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12291        self.e4m3_fused2_t_core(b0, b1, &aq, &ad, m, in_f, out0, out1, row_bytes, ws0, ws1)
12292    }
12293
12294    #[allow(clippy::too_many_arguments)]
12295    pub fn qmatvec_e4m3_fused3_t_raw(
12296        &self,
12297        b0: &CudaSlice<u8>,
12298        b1: &CudaSlice<u8>,
12299        b2: &CudaSlice<u8>,
12300        x: &CudaSlice<f32>,
12301        m: usize,
12302        in_f: usize,
12303        out0: usize,
12304        out1: usize,
12305        out2: usize,
12306        row_bytes: usize,
12307        ws0: f32,
12308        ws1: f32,
12309        ws2: f32,
12310    ) -> Result<(CudaSlice<f32>, CudaSlice<f32>, CudaSlice<f32>), Box<dyn std::error::Error>> {
12311        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12312        self.e4m3_fused3_t_core(
12313            b0, b1, b2, &aq, &ad, m, in_f, out0, out1, out2, row_bytes, ws0, ws1, ws2,
12314        )
12315    }
12316
12317    /// THE single dispatch point for `QT_F8_E4M3_BLK` from a PRE-QUANTIZED q8_1 activation
12318    /// (lane/fp8-blk128-decode). Every `matmul_pre`-family entry calls this first, so the block-128
12319    /// class has exactly ONE code path across `matmul`, `matmul_pre`, `matmul_pre_noscale`,
12320    /// `matmul_decode_exact` and `matmul_decode_exact_pre` — the same kernel at the same grid for
12321    /// every m, which is what makes verify == decode bit-for-bit at every tier for free.
12322    ///
12323    /// Returns None for any other qtype (the caller continues its normal dispatch). The `blk: Some`
12324    /// pattern is part of the match, not an unwrap: qtype and grid presence are set together in the
12325    /// one residency arm that builds this tensor, and a qtype-without-grid would be a construction
12326    /// bug — better to fall through and hit a loud refusal than to unwrap a None here.
12327    fn try_e4m3_blk_pre(
12328        &self,
12329        w: &crate::model::GpuTensor,
12330        aq: &CudaSlice<i8>,
12331        ad: &CudaSlice<f32>,
12332        m: usize,
12333    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12334        use crate::model::GpuTensor;
12335        if let GpuTensor::Quant {
12336            bytes,
12337            qtype,
12338            row_bytes,
12339            blk: Some(g),
12340            ..
12341        } = w
12342        {
12343            if *qtype == QT_F8_E4M3_BLK {
12344                // BATCHED tier m=2..16 (lane/rp-on-st): weight read ONCE for up to mcols columns
12345                // instead of m grid.y re-reads. Bit-identical per (token,row) to the grid.y=m form
12346                // below, so the decode-exactness contract is preserved at every width. Gated by
12347                // the same seams the other batched families honor (MEMRA_NO_BATCHED, MEMRA_B8) so
12348                // one rollback door covers every dtype's batched tier.
12349                if (2..=16).contains(&m)
12350                    && std::env::var("MEMRA_NO_BATCHED").is_err()
12351                    && (m <= 4 || Self::b8_enabled())
12352                {
12353                    let mcols = Self::batched_mcols(m);
12354                    return Ok(Some(self.qmatvec_e4m3_blk_mmvq_batched(
12355                        bytes,
12356                        aq,
12357                        ad,
12358                        &g.scales,
12359                        m,
12360                        w.in_features(),
12361                        w.out_features(),
12362                        *row_bytes,
12363                        g.cols,
12364                        mcols,
12365                    )?));
12366                }
12367                return Ok(Some(self.qmatvec_e4m3_blk_mmvq(
12368                    bytes,
12369                    aq,
12370                    ad,
12371                    &g.scales,
12372                    m,
12373                    w.in_features(),
12374                    w.out_features(),
12375                    *row_bytes,
12376                    g.cols,
12377                )?));
12378            }
12379        }
12380        Ok(None)
12381    }
12382
12383    /// PREFILL (m >= GEMM_M_THRESHOLD) for `QT_F8_E4M3_BLK` — DEQUANT-PER-CALL to the Q8_0 slab
12384    /// this class's residency replaced, then the ordinary Q8_0 prefill dispatch on the transient.
12385    ///
12386    /// WHY THIS EXISTS AT ALL, i.e. the regression it prevents: the decode kernel is a warp-per-row
12387    /// GEMV. At grid.y=m it re-reads the whole weight once PER TOKEN, so letting a 512-token prefill
12388    /// chunk reach it would be a ~500x weight-traffic blowup on the single most bandwidth-bound part
12389    /// of the forward. Native residency is a DECODE win and must not be paid for in prefill, so
12390    /// prefill keeps the floor's arithmetic and the floor's kernels.
12391    ///
12392    /// WHY DEQUANT-PER-CALL rather than a second resident slab: a resident slab is dual residency —
12393    /// it gives back the entire 1.0-vs-1.0625 B/weight win this lane exists to capture (and then
12394    /// some, since the e4m3 copy stays too). The transient costs one linear device pass per
12395    /// (projection, prefill call) and frees immediately.
12396    ///
12397    /// NUMERICALLY IT IS THE FLOOR, EXACTLY: `fp8_blk_dequant_q8_0` is the merged ARM B' kernel,
12398    /// gate-proven BYTE-IDENTICAL to the host dequant+re-encode (kernel-check `fp8-blk-gpu`). So the
12399    /// slab these bytes form is bit-for-bit the slab the `MEMRA_ST_E4M3_BLK=0` arm makes resident,
12400    /// and every prefill kernel downstream sees identical input — prefill logits under this lane are
12401    /// bit-identical to prefill logits under the floor, which is what makes the decode A/B a clean
12402    /// single-variable comparison instead of a two-variable one.
12403    ///
12404    /// WHAT IT COSTS, MEASURED, AND WHY THAT COST IS MOSTLY STRUCTURAL (27B block-128 ckpt, pp512,
12405    /// this rig = RTX 5090 Laptop, ~896 GB/s GDDR7). This arm makes prefill move the weight THREE
12406    /// times instead of once: read 6.88 GB of e4m3, write 7.31 GB of Q8_0, then the MMQ reads that
12407    /// 7.31 GB back. The two extra passes are 14.19 GB = 15.8 ms at this card's roofline against a
12408    /// ~332 ms pp512, i.e. **~-4.5% pp is a floor no kernel tuning can remove** — only deleting the
12409    /// dequant can. Measured: the dequant kernel costs 27.9 ms/pass (nsys, 208 projections) after
12410    /// the 2026-08-05 vector rewrite (was 66.5 ms at one byte per thread), and e2e pp512 is
12411    /// 1451.4 vs the slab arm's 1541.6 tok/s = -5.8% (N=3 interleaved pairs). So ~1.3pp of the
12412    /// -5.8% is residual kernel inefficiency and ~4.5pp is the extra traffic itself.
12413    ///
12414    /// SO THE DEQUANT IS NO LONGER THE DEFAULT ROUTE — it is the FALLBACK. The per-block FP8 MMQ
12415    /// tile (`try_fp8_blk_mmq`) consumes the resident e4m3 bytes + grid DIRECTLY, deleting both extra
12416    /// passes, and since 2026-08-05 it runs FIRST and by default for the native-resident source
12417    /// (`fp8_blk_mmq_native_enabled`; `MEMRA_FP8_MMQ=0` is the seam back to this dequant). On paper
12418    /// the trade was unassumable — lane/fp8-mmq-v2 measured that tile at 0.85-1.09x the Q8_0 MMQ
12419    /// floor GEMM-only, so it swapped a -4.5% traffic cost for a 0-to-15% GEMM cost of unknown sign.
12420    /// Measured on the 27B (3 arms interleaved, N=3, research/fp8blk-20260805/VERDICT.md): slab
12421    /// 1540.5 / this dequant 1449.1 / the tile 1553.3 tok/s, min(tile) > max(slab). The tile wins
12422    /// because v2's denominator had its slab already resident while this class's floor must build it
12423    /// every call; same tile, opposite sign, because the question changed.
12424    ///
12425    /// THIS ARM STILL RUNS, and is not dead code: every `try_fp8_blk_mmq` precondition (in_f % 16,
12426    /// grid dims vs shape, per-tensor scale == 1.0, the e4m3-NaN scan) refuses by falling through to
12427    /// here, so a checkpoint the tile cannot take keeps exact prefill on the floor's own bits rather
12428    /// than losing the class. It is also what `MEMRA_FP8_MMQ=0` reverts to.
12429    fn try_e4m3_blk_prefill(
12430        &self,
12431        w: &crate::model::GpuTensor,
12432        x: &CudaSlice<f32>,
12433        m: usize,
12434    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
12435        use crate::model::GpuTensor;
12436        let GpuTensor::Quant {
12437            bytes,
12438            qtype,
12439            blk: Some(g),
12440            ..
12441        } = w
12442        else {
12443            return Ok(None);
12444        };
12445        if *qtype != QT_F8_E4M3_BLK {
12446            return Ok(None);
12447        }
12448        // NO-DEQUANT ROUTE, THE DEFAULT (MEMRA_FP8_MMQ=0 reverts): the per-block MMQ tile eats the
12449        // resident e4m3 bytes and grid as-is, so neither extra weight pass happens. Its own
12450        // preconditions (in_f % 16, grid dims, scale == 1.0, no e4m3 NaN code) can refuse — fall
12451        // through to the dequant below when they do, never silently produce nothing.
12452        if let Some(y) = self.try_fp8_blk_mmq(w, x, m)? {
12453            return Ok(Some(y));
12454        }
12455        let (in_f, out_f) = (w.in_features(), w.out_features());
12456        let slab = self.fp8_blk_dequant_q8_0_dev(bytes, &g.scales, out_f, in_f)?;
12457        let tmp = GpuTensor::Quant {
12458            bytes: slab,
12459            qtype: QT_Q8_0,
12460            row_bytes: in_f / 32 * 34,
12461            ne: vec![in_f as u64, out_f as u64],
12462            scale: 1.0,
12463            rp: false,
12464            #[cfg(memra_cutlass)]
12465            cutlass: None,
12466            fp8: None,
12467            blk: None,
12468            f16: None,
12469            rp4: None,
12470        };
12471        // Recursion terminates: `tmp` is QT_Q8_0 with `blk: None`, so it cannot re-enter this arm.
12472        Ok(Some(self.matmul(&tmp, x, m)?))
12473    }
12474
12475    pub fn matmul_pre_noscale(
12476        &self,
12477        w: &crate::model::GpuTensor,
12478        aq: &CudaSlice<i8>,
12479        ad: &CudaSlice<f32>,
12480        m: usize,
12481    ) -> Result<Option<(CudaSlice<f32>, f32)>, Box<dyn std::error::Error>> {
12482        use crate::model::GpuTensor;
12483        // BLOCK-128 e4m3: every scale factor is folded inside the kernel per k128, so the
12484        // "separable post-op scale" this entry exists to defer is 1.0 — return it explicitly
12485        // rather than let the tail below refuse and cost the caller a re-dispatch.
12486        if m == 1 {
12487            if let Some(y) = self.try_e4m3_blk_pre(w, aq, ad, m)? {
12488                return Ok(Some((y, 1.0)));
12489            }
12490        }
12491        // Only the m==1 fast path applies the scale as a separable post-op; bail everywhere else.
12492        if m != 1 || !self.uses_q8_1_fast(w) {
12493            return Ok(None);
12494        }
12495        let in_f = w.in_features();
12496        let out_f = w.out_features();
12497        let (bytes, qtype, row_bytes, scale, rp) = match w {
12498            GpuTensor::Quant {
12499                bytes,
12500                qtype,
12501                row_bytes,
12502                scale,
12503                rp,
12504                ..
12505            } => (bytes, *qtype, *row_bytes, *scale, *rp),
12506            _ => return Ok(None),
12507        };
12508        // MMVQ warp-per-row (scale==1.0 passed -> kernel skips its internal scale; we return scale).
12509        if self.mmvq_supports(qtype) {
12510            // Q4_0 split-plane mirror (dp4a fallback below keeps the raw GGUF bytes).
12511            let (mbytes, mrp) = match w {
12512                GpuTensor::Quant { rp4: Some(m4), .. } => (m4, true),
12513                _ => (bytes, rp),
12514            };
12515            let y = self.qmatvec_mmvq(
12516                mbytes, aq, ad, m, in_f, out_f, qtype, row_bytes, /*scale*/ 1.0, mrp,
12517            )?;
12518            return Ok(Some((y, scale)));
12519        }
12520        // dp4a fallback: same launch as matmul_pre but WITHOUT the post scale_inplace.
12521        let name = match qtype {
12522            QT_Q8_0 => "qmatvec_q8_0_dp4a",
12523            QT_Q4_K => "qmatvec_q4_K_dp4a",
12524            QT_Q6_K => "qmatvec_q6_K_dp4a",
12525            QT_Q5_K => "qmatvec_q5_K_dp4a",
12526            QT_Q3_K => "qmatvec_q3_K_dp4a",
12527            QT_NVFP4 => {
12528                if rp {
12529                    "qmatvec_nvfp4_dp4a_rp"
12530                } else {
12531                    "qmatvec_nvfp4_dp4a"
12532                }
12533            }
12534            QT_IQ4_XS => "qmatvec_iq4_XS_dp4a",
12535            _ => return Ok(None),
12536        };
12537        let f = self.func(name);
12538        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
12539        let cfg = LaunchConfig {
12540            grid_dim: (out_f as u32, m as u32, 1),
12541            block_dim: (128, 1, 1),
12542            shared_mem_bytes: 0,
12543        };
12544        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12545        let __s_b = self.gpu.stream();
12546        let mut b = __s_b.launch_builder(&f);
12547        b.arg(bytes)
12548            .arg(aq)
12549            .arg(ad)
12550            .arg(&mut y)
12551            .arg(&inf)
12552            .arg(&outf)
12553            .arg(&mi)
12554            .arg(&rb);
12555        unsafe {
12556            b.launch(cfg)?;
12557        }
12558        Ok(Some((y, scale)))
12559    }
12560
12561    /// True if `qtype` has a warp-per-row MMVQ decode kernel AND MEMRA_MMVQ is set. Only the 4
12562    /// daily-hot dtypes (Q8_0, Q4_K, Q6_K, NVFP4) — others keep the _dp4a matvec (oracle/fallback).
12563    pub fn mmvq_supports(&self, qtype: i32) -> bool {
12564        // DEFAULT ON since 2026-07-08 (MEMRA_MMVQ=0 reverts to the _dp4a matvec class).
12565        // QT_F8_E4M3 is exempt from the MEMRA_MMVQ=0 escape: the e4m3 mmvq family is that dtype's
12566        // ONLY int8-act kernel class (there is no _dp4a twin), so its m=1/verify/batched dispatch
12567        // is a pure function of the dtype — the decode-parity law holds under every env.
12568        if qtype == QT_F8_E4M3 {
12569            return true;
12570        }
12571        if std::env::var("MEMRA_MMVQ").as_deref() == Ok("0") {
12572            return false;
12573        }
12574        matches!(
12575            qtype,
12576            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_Q4_0
12577        )
12578    }
12579
12580    /// PERF-3 warp-per-row MMVQ launcher (decode m=1 hot path). block=(32,ROWS_PER_BLOCK,1):
12581    /// one warp owns one output row, warp-only __shfl reduction (no smem barrier). Bit-equivalent
12582    /// to qmatvec_*_dp4a up to f32 reduction order. Pre-quantized q8_1 activation (aq,ad). NVFP4
12583    /// per-tensor macro-scale applied post (scale==1.0 for other dtypes -> no-op).
12584    pub fn qmatvec_mmvq(
12585        &self,
12586        bytes: &CudaSlice<u8>,
12587        aq: &CudaSlice<i8>,
12588        ad: &CudaSlice<f32>,
12589        m: usize,
12590        in_f: usize,
12591        out_f: usize,
12592        qtype: i32,
12593        row_bytes: usize,
12594        scale: f32,
12595        rp: bool,
12596    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12597        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
12598        self.qmatvec_mmvq_into(
12599            bytes, aq, ad, m, in_f, out_f, qtype, row_bytes, scale, rp, &mut y,
12600        )?;
12601        Ok(y)
12602    }
12603
12604    /// Slot-fed MMVQ twin (alloc-free capture lane): full policy body, caller-owned output.
12605    #[allow(clippy::too_many_arguments)]
12606    pub fn qmatvec_mmvq_into(
12607        &self,
12608        bytes: &CudaSlice<u8>,
12609        aq: &CudaSlice<i8>,
12610        ad: &CudaSlice<f32>,
12611        m: usize,
12612        in_f: usize,
12613        out_f: usize,
12614        qtype: i32,
12615        row_bytes: usize,
12616        scale: f32,
12617        rp: bool,
12618        y: &mut CudaSlice<f32>,
12619    ) -> Result<(), Box<dyn std::error::Error>> {
12620        debug_assert!(y.len() >= m * out_f);
12621        const ROWS_PER_BLOCK: u32 = 4; // matches MEMRA_MMVQ_ROWS in qmatvec.cu
12622        // SMALL-SHAPE GRID FILL (H100 lane, 2026-07-26 microbench: attn qkv out_f=2048 =
12623        // 0.97 waves at the 4-warp block -> 66% of peak). The g2 twin (2 warps/block)
12624        // doubles the grid when the 4-warp launch would be sub-wave; per-row program
12625        // identical -> bit-identical. MEMRA_Q80_G2=0 reverts.
12626        if qtype == QT_Q8_0
12627            && rp
12628            && m == 1
12629            && out_f >= 64
12630            && (out_f as u32).div_ceil(ROWS_PER_BLOCK) < 4 * self.sm_count() as u32
12631            && {
12632                static G2: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12633                *G2.get_or_init(|| std::env::var("MEMRA_Q80_G2").as_deref() != Ok("0"))
12634            }
12635        {
12636            let f = self.func("qmatvec_q8_0_mmvq_rp_g2");
12637            let cfg = LaunchConfig {
12638                grid_dim: ((out_f as u32).div_ceil(2), 1, 1),
12639                block_dim: (32, 2, 1),
12640                shared_mem_bytes: 0,
12641            };
12642            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, 1i32, row_bytes as i64);
12643            let __s_b = self.gpu.stream();
12644            let mut b = __s_b.launch_builder(&f);
12645            b.arg(bytes)
12646                .arg(aq)
12647                .arg(ad)
12648                .arg(&mut *y)
12649                .arg(&inf)
12650                .arg(&outf)
12651                .arg(&mi)
12652                .arg(&rb);
12653            unsafe {
12654                b.launch(cfg)?;
12655            }
12656            if scale != 1.0 {
12657                self.scale_inplace(y, scale, out_f)?;
12658            }
12659            return Ok(());
12660        }
12661        // Multi-row-per-warp (mr2) policy, fixed since the 2026-07 sweeps (the MEMRA_MMVQ_MR
12662        // override + mr4 kernel were retired 2026-07-08 — mr4 regressed on register pressure and
12663        // crashed under rp; q4_K/q6_K mr2 measured flat, "no gain = no change"):
12664        //   NVFP4 m=1 -> mr2 (clean +1-2% on 9B: RPW acc chains hide the weight-load latency
12665        //     that pins the single-row kernel at 30-46% DRAM). Bit-identical per row.
12666        //   Q5_K m=1 -> mr2 (2026-07-05: the FR-Spec trimmed draft head is Q5_K 32768 rows = 8%
12667        //     of the 27B p3 spec wall; latency-bound like the other k-quants pre-fix).
12668        //   Q4_K/Q6_K m=1 -> single-row (mr2 measured +0.7% / flat — weight-bandwidth-bound).
12669        let mut mr: u32 = if m == 1 && (qtype == QT_NVFP4 || qtype == QT_Q5_K) {
12670            2
12671        } else {
12672            1
12673        };
12674        // Q4_0 mr (gemma trunk): DEFAULT 1 since 2026-07-13 (MEMRA_Q40_MR=2 reverts) — the
12675        // mr1 rp twin doubles the block count and wins the tail-quantization/latency battle
12676        // on every gemma model (E4B +3.75%: 198.9 vs 191.7; 26B +0.7%; 31B +0.9%; N=2-3
12677        // valid-window interleaved, bit-identical per row — same dot program).
12678        if m == 1 && qtype == QT_Q4_0 {
12679            static Q40MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
12680            // shape policy PROBED NEGATIVE (2026-07-13): tall-only mr1 197.2 vs
12681            // mr1-everywhere 198.7 — mr1 wins wide-output shapes too; arm removed.
12682            mr = *Q40MR.get_or_init(|| {
12683                std::env::var("MEMRA_Q40_MR")
12684                    .ok()
12685                    .and_then(|v| v.parse().ok())
12686                    .unwrap_or(1)
12687            });
12688        }
12689        // q5issue lane (2026-07-08): MEMRA_Q5K_ISSUE swaps the q5_K m=1 mmvq kernels for the
12690        // issue-reduced `_il` bodies (uint4 header/qh/qs loads + branchless scale decode —
12691        // cuts ~34 LDG.U16 + ~5 LDG.U8 + a warp-divergent scale branch per 32-elem group-row
12692        // to 5 LDG.128). Bit-identical per (token,row) to the reference kernels.
12693        // `1` = shape-aware policy (N=3 clock-locked micro-bench, mem P0, synthetic real shapes):
12694        //   out_f <= 65536 (trunk/frspec regime): il at the default mr — mr2_il -9.5%/-10.5%
12695        //     on 4096x4096/4096x8192, -3.1% on the 32768 frspec head vs the mr2-ref default;
12696        //   out_f > 65536 (the 248320-row 27B lm_head, already ~97% of the mem wall): mr2_il
12697        //     REGRESSES +22% there but mr1_il wins -2.1% vs the mr2-ref default -> force mr=1.
12698        // `2` = force il at the current mr for EVERY shape (A/B probe seam). Default OFF.
12699        let q5_mode = std::env::var("MEMRA_Q5K_ISSUE").ok();
12700        let q5_force = q5_mode.as_deref() == Some("2");
12701        // DEFAULT ON since 2026-07-08 (MEMRA_Q5K_ISSUE=0 reverts): +1.8% 9B plain e2e N=3
12702        // (128.2 -> 130.4), 27B flat (its big head is already at the mem wall), all gates green.
12703        let q5_il = qtype == QT_Q5_K
12704            && m == 1
12705            && (q5_force || q5_mode.as_deref().map(|v| v != "0").unwrap_or(true));
12706        if q5_il && !q5_force && out_f > 65536 {
12707            mr = 1;
12708        }
12709        // Q4_0 split-plane rp: mr2 default; MEMRA_Q40_MR=1 reaches the mr1 rp twin
12710        // (2026-07-13 — the tall-input/short-output tail-quantization probe).
12711        if qtype == QT_Q4_0 && rp && mr != 1 {
12712            mr = 2;
12713        }
12714        // Q8_0 rp (H100 lane): mr1 default — the q4_0 mr2 recipe MEASURED NEGATIVE on H100
12715        // (2026-07-26 N=3: mr1 186.2 vs mr2 171.5 tok/s; halving the grid on 132 SMs costs
12716        // more than 2-row ILP buys). mr2 kernel stays behind MEMRA_Q80_MR=2 for the corpus.
12717        if qtype == QT_Q8_0 && rp {
12718            static Q80MR: std::sync::OnceLock<u32> = std::sync::OnceLock::new();
12719            mr = *Q80MR.get_or_init(|| {
12720                std::env::var("MEMRA_Q80_MR")
12721                    .ok()
12722                    .and_then(|v| v.parse().ok())
12723                    .unwrap_or(1)
12724            });
12725        }
12726        let name = match (qtype, mr, rp) {
12727            (QT_NVFP4, 2, false) => "qmatvec_nvfp4_mmvq_mr2",
12728            (QT_NVFP4, 2, true) => "qmatvec_nvfp4_mmvq_mr2_rp",
12729            (QT_NVFP4, _, true) => "qmatvec_nvfp4_mmvq_rp",
12730            (QT_Q4_0, 1, true) => "qmatvec_q4_0_mmvq_rp",
12731            (QT_Q4_0, _, true) => "qmatvec_q4_0_mmvq_mr2_rp",
12732            (QT_Q5_K, 2, _) => {
12733                if q5_il {
12734                    "qmatvec_q5_K_mmvq_mr2_il"
12735                } else {
12736                    "qmatvec_q5_K_mmvq_mr2"
12737                }
12738            }
12739            (QT_Q8_0, 2, true) => "qmatvec_q8_0_mmvq_mr2_rp",
12740            // rpca (cp.async-staged weight ring): MEASURED NEGATIVE on H100 for Q8_0
12741            // (2026-07-26 N=3: 181.8 vs plain rp 185.5 — the smem round-trip exceeds the
12742            // latency it hides for 8-bit direct-dp4a; the NVFP4 win case overlaps table
12743            // decode with half the bytes). OPT-IN via MEMRA_Q80_CA=1 for the corpus.
12744            (QT_Q8_0, _, true)
12745                if in_f % 1024 == 0 && {
12746                    static CA: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12747                    *CA.get_or_init(|| std::env::var("MEMRA_Q80_CA").as_deref() == Ok("1"))
12748                } =>
12749            {
12750                "qmatvec_q8_0_mmvq_rpca"
12751            }
12752            (QT_Q8_0, _, true) => "qmatvec_q8_0_mmvq_rp",
12753            (QT_Q8_0, _, _) => "qmatvec_q8_0_mmvq",
12754            // K-quant split-plane twins (H100 K-quant coalescing fix, 2026-08-01): the rp4
12755            // mirror routes here; GGUF layout keeps the plain kernels. rp bytes MUST never
12756            // reach a GGUF-layout kernel or vice versa.
12757            (QT_Q4_K, _, true) => "qmatvec_q4_K_mmvq_rp",
12758            (QT_Q6_K, _, true) => "qmatvec_q6_K_mmvq_rp",
12759            (QT_Q4_K, _, _) => "qmatvec_q4_K_mmvq",
12760            (QT_Q4_0, 2, false) => "qmatvec_q4_0_mmvq_mr2",
12761            (QT_Q4_0, _, false) => "qmatvec_q4_0_mmvq",
12762            (QT_Q5_K, _, _) => {
12763                if q5_il {
12764                    "qmatvec_q5_K_mmvq_il"
12765                } else {
12766                    "qmatvec_q5_K_mmvq"
12767                }
12768            }
12769            (QT_Q6_K, _, _) => "qmatvec_q6_K_mmvq",
12770            (QT_NVFP4, _, false) => "qmatvec_nvfp4_mmvq",
12771            (QT_F8_E4M3, _, _) => "qmatvec_e4m3_mmvq",
12772            _ => panic!("qmatvec_mmvq: qtype {qtype} has no MMVQ kernel"),
12773        };
12774        let f = self.func(name);
12775        // each block still has ROWS_PER_BLOCK warps; with mr rows/warp it covers ROWS_PER_BLOCK*mr rows.
12776        let rows_per_block = ROWS_PER_BLOCK * mr;
12777        let cfg = LaunchConfig {
12778            grid_dim: (
12779                (out_f as u32 + rows_per_block - 1) / rows_per_block,
12780                m as u32,
12781                1,
12782            ),
12783            block_dim: (32, ROWS_PER_BLOCK, 1), // warp-per-row (x mr rows each)
12784            shared_mem_bytes: 0,                // warp-only reduce at m=1
12785        };
12786        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
12787        let __s_b = self.gpu.stream();
12788        let mut b = __s_b.launch_builder(&f);
12789        // NVFP4 + e4m3 mmvq kernels take the macro-scale as a fused epilogue arg (applied at the
12790        // write — bit-identical to the old separate scale_inplace pass, minus one launch per matvec:
12791        // 53 scale launches/token on the 9B; for e4m3 the scale is the checkpoint's per-tensor f32
12792        // weight_scale). Other mmvq kernels keep the 8-arg signature.
12793        if qtype == QT_NVFP4 || qtype == QT_F8_E4M3 {
12794            b.arg(bytes)
12795                .arg(aq)
12796                .arg(ad)
12797                .arg(&mut *y)
12798                .arg(&inf)
12799                .arg(&outf)
12800                .arg(&mi)
12801                .arg(&rb)
12802                .arg(&scale);
12803            unsafe {
12804                b.launch(cfg)?;
12805            }
12806        } else if Self::pdl_on()
12807            && Self::pdl_mmvq_on()
12808            && matches!(
12809                name,
12810                "qmatvec_q4_0_mmvq_rp" | "qmatvec_q6_K_mmvq" | "qmatvec_q6_K_mmvq_rp"
12811            )
12812        {
12813            // PDL wave-A (2026-07-23): the two decode-hot single-matvec kernels carry
12814            // MEMRA_PDL_ENTRY — grid launches while the producer drains. ONLY the marked
12815            // names may take this launch (unmarked kernels would read unordered).
12816            {
12817                use cudarc::driver::{DevicePtr, DevicePtrMut};
12818                let s = &self.gpu.stream();
12819                let (pw, _g0) = bytes.device_ptr(s);
12820                let (paq, _g1) = aq.device_ptr(s);
12821                let (pad, _g2) = ad.device_ptr(s);
12822                let (py, _g3) = y.device_ptr_mut(s);
12823                let mut ps = [
12824                    &pw as *const _ as *mut std::ffi::c_void,
12825                    &paq as *const _ as *mut _,
12826                    &pad as *const _ as *mut _,
12827                    &py as *const _ as *mut _,
12828                    &inf as *const _ as *mut _,
12829                    &outf as *const _ as *mut _,
12830                    &mi as *const _ as *mut _,
12831                    &rb as *const _ as *mut _,
12832                ];
12833                unsafe {
12834                    self.launch_pdl(name, cfg.grid_dim, cfg.block_dim, &mut ps)?;
12835                }
12836            }
12837            if scale != 1.0 {
12838                self.scale_inplace(y, scale, m * out_f)?;
12839            }
12840        } else {
12841            b.arg(bytes)
12842                .arg(aq)
12843                .arg(ad)
12844                .arg(&mut *y)
12845                .arg(&inf)
12846                .arg(&outf)
12847                .arg(&mi)
12848                .arg(&rb);
12849            unsafe {
12850                b.launch(cfg)?;
12851            }
12852            if scale != 1.0 {
12853                self.scale_inplace(y, scale, m * out_f)?;
12854            }
12855        }
12856        Ok(())
12857    }
12858
12859    /// Test entry for the kernel_check bit-equivalence gate: run the warp-per-row MMVQ directly
12860    /// from raw weight bytes (quantize the f32 activation `x` to q8_1 internally). NVFP4 per-tensor
12861    /// macro-scale is NOT applied (caller compares bare, like qmatvec_*_fast). Mirrors qmatvec_gemm_raw.
12862    pub fn qmatvec_mmvq_raw(
12863        &self,
12864        bytes: &CudaSlice<u8>,
12865        x: &CudaSlice<f32>,
12866        m: usize,
12867        in_f: usize,
12868        out_f: usize,
12869        qtype: i32,
12870        row_bytes: usize,
12871        rp: bool,
12872    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
12873        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
12874        self.qmatvec_mmvq(bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, 1.0, rp)
12875    }
12876
12877    /// True if `qtype` has a batched weight-resident (`_b2`/`_b4`) matvec kernel. These mirror the
12878    /// `_mmvq` kernels but iterate the m token columns INSIDE one warp/row, so the weight bytes leave
12879    /// HBM/L2 once for m tokens (vs grid.y=m re-reading m times). The 5 daily-hot dtypes have them.
12880    pub fn batched_supports(&self, qtype: i32) -> bool {
12881        matches!(
12882            qtype,
12883            QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q6_K | QT_NVFP4 | QT_F8_E4M3 | QT_Q4_0
12884        )
12885    }
12886
12887    /// IQ4_XS trunk fast seam: MEMRA_IQ_FAST=0 reverts non-expert IQ4_XS matmuls to the Stage-A
12888    /// f32 oracle path. Default ON since 2026-08-02 (research/kat-anomaly-20260802/): the old
12889    /// opt-in default left every IQ4_XS-trunk artifact (KAT-Coder IQ4_XS: attn_qkv/attn_gate/
12890    /// ssm_out/shexp, ~0.52GB re-read per decode tick) on the oracle kernel — decode 106.7 ->
12891    /// 193.4 tok/s (x5 interleaved), pp512 228 -> 697, same bytes, via qmatvec_iq4_XS_dp4a. The
12892    /// supported artifacts carry IQ4_XS only in EXPERT banks (their own dispatch, not this seam),
12893    /// so this admission is dispatch-unchanged for every non-IQ4_XS-trunk model.
12894    pub fn iq_fast_enabled() -> bool {
12895        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12896        *ON.get_or_init(|| {
12897            std::env::var("MEMRA_IQ_FAST")
12898                .map(|v| v != "0")
12899                .unwrap_or(true)
12900        })
12901    }
12902
12903    /// b8 tier seam: MEMRA_B8=0 keeps m=5..8 on the per-m grid.y=m path (m=2..4 batched dispatch
12904    /// unaffected). Default ON — the K=4..7 spec-verify weight-read-once fix.
12905    pub fn b8_enabled() -> bool {
12906        static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
12907        *ON.get_or_init(|| std::env::var("MEMRA_B8").map(|v| v != "0").unwrap_or(true))
12908    }
12909
12910    /// Compile-time column batch for a runtime m: 2 -> b2, 3..4 -> b4, 5..8 -> b8.
12911    pub fn batched_mcols(m: usize) -> usize {
12912        if m == 2 {
12913            2
12914        } else if m <= 4 {
12915            4
12916        } else if m <= 8 {
12917            8
12918        } else {
12919            16
12920        }
12921    }
12922
12923    /// Kernel name for the batched matvec of `(qtype, mcols)`. mcols ∈ {2,4,8}. The b8 tier is the
12924    /// K=4..7 spec-verify fix (T=5..8): pre-b8 those T fell to grid.y=m per-row MMVQ = m full
12925    /// weight reads/launch — the measured 27B K=4 cliff (101 -> 73 tok/s at p3 despite acceptance
12926    /// holding 54%). One b8 launch reads the weight ONCE for up to 8 columns (c >= m masked).
12927    fn batched_kernel_name(qtype: i32, mcols: usize) -> Option<&'static str> {
12928        Some(match (qtype, mcols) {
12929            (QT_Q8_0, 2) => "qmatvec_q8_0_mmvq_b2",
12930            (QT_Q8_0, 4) => "qmatvec_q8_0_mmvq_b4",
12931            (QT_Q8_0, 8) => "qmatvec_q8_0_mmvq_b8",
12932            // b16 now has BOTH forms (lane/rp-on-st, 2026-08-06). It used to be rp-ONLY, which
12933            // made the q8rp mirror the exact-16 tier's admission ticket for any model carrying a
12934            // single Q8_0 matmul — measured as the FP8-ST refusal (`L0.ssm_beta qtype=0
12935            // rp4=false`, 96 t / 23.9 MiB = 0.143% of resident weight). The mirror stays a
12936            // BANDWIDTH lever on Q8_0-dominant GGUFs; it is no longer a correctness prerequisite.
12937            (QT_Q8_0, 16) => "qmatvec_q8_0_mmvq_b16",
12938            (QT_Q4_K, 2) => "qmatvec_q4_K_mmvq_b2",
12939            (QT_Q4_K, 4) => "qmatvec_q4_K_mmvq_b4",
12940            (QT_Q4_K, 8) => "qmatvec_q4_K_mmvq_b8",
12941            // b16 base + _rp (lane/rp-on-st): the 9B NVFP4 GGUF's blocker — real NVFP4 GGUFs keep
12942            // Q4_K attention next to NVFP4 MLP, and the tier's predicate is an ALL.
12943            (QT_Q4_K, 16) => "qmatvec_q4_K_mmvq_b16",
12944            (QT_Q5_K, 2) => "qmatvec_q5_K_mmvq_b2",
12945            (QT_Q5_K, 4) => "qmatvec_q5_K_mmvq_b4",
12946            (QT_Q5_K, 8) => "qmatvec_q5_K_mmvq_b8",
12947            // b16 base only (lane/rp-on-st): Q5_K has no rp twins at any width, so there is
12948            // nothing to mirror. Named by the diagnostic as `L0.wqkv_gate qtype=3` on the 9B.
12949            (QT_Q5_K, 16) => "qmatvec_q5_K_mmvq_b16",
12950            (QT_Q6_K, 2) => "qmatvec_q6_K_mmvq_b2",
12951            (QT_Q6_K, 4) => "qmatvec_q6_K_mmvq_b4",
12952            (QT_Q6_K, 8) => "qmatvec_q6_K_mmvq_b8",
12953            (QT_Q6_K, 16) => "qmatvec_q6_K_mmvq_b16",
12954            (QT_NVFP4, 2) => "qmatvec_nvfp4_mmvq_b2",
12955            (QT_NVFP4, 4) => "qmatvec_nvfp4_mmvq_b4",
12956            (QT_NVFP4, 8) => "qmatvec_nvfp4_mmvq_b8",
12957            // b16 (lane/rp-on-st): no mirror needed — NVFP4's 36 B/k32 block is already the
12958            // aligned form its own kernel walks. Unlocks the exact-16 tier for every NVFP4 model
12959            // AND for the mixed FP8-ST artifact, whose 193 NVFP4 tensors were refusing it.
12960            (QT_NVFP4, 16) => "qmatvec_nvfp4_mmvq_b16",
12961            (QT_F8_E4M3, 2) => "qmatvec_e4m3_mmvq_b2",
12962            (QT_F8_E4M3, 4) => "qmatvec_e4m3_mmvq_b4",
12963            (QT_F8_E4M3, 8) => "qmatvec_e4m3_mmvq_b8",
12964            // b16 tier (lane/rp-on-st): e4m3 needs NO split-plane mirror to reach it — its native
12965            // row-major layout is already 32B-aligned per k32 block, so the base kernel IS the
12966            // aligned form. Contrast Q8_0, whose b16 exists only as the `_rp` twin (hence q8rp).
12967            (QT_F8_E4M3, 16) => "qmatvec_e4m3_mmvq_b16",
12968            (QT_Q4_0, 2) => "qmatvec_q4_0_mmvq_b2",
12969            (QT_Q4_0, 4) => "qmatvec_q4_0_mmvq_b4",
12970            (QT_Q4_0, 8) => "qmatvec_q4_0_mmvq_b8",
12971            (QT_Q4_0, 16) => "qmatvec_q4_0_mmvq_b16",
12972            _ => return None,
12973        })
12974    }
12975
12976    /// BATCHED weight-tile-resident matvec from a PRE-QUANTIZED q8_1 activation (the m=2-8 verify/MTP
12977    /// win). One warp walks the weight row ONCE, dp4a vs all m activation columns -> weight HBM/L2
12978    /// traffic 1x for m tokens (vs grid.y=m re-reading it m times). `mcols` ∈ {2,4,8} is the
12979    /// compile-time batch; m must be <= mcols (the c >= m columns are masked in-kernel). y is
12980    /// [m, out_f] token-major. NVFP4 per-tensor macro-scale applied post
12981    /// (scale==1.0 for other dtypes -> no-op). BIT-IDENTICAL per (token,row) to qmatvec_*_mmvq.
12982    ///
12983    /// NVFP4 VARIANT DISPATCH: the batched NVFP4 kernel measured memory-LATENCY bound on the real
12984    /// 27B verify (ncu --set full, 12 steady launches: long_scoreboard 18-30 stalls/issue vs <=1.7
12985    /// for every other reason, DRAM only 41-51% active, lg_throttle 0.7, L1 hit 94% — ONE 6-LDG
12986    /// weight wavefront in flight per warp is the binding constraint, NOT bandwidth and NOT the
12987    /// column-unroll break). Two exactness-free fixes, chosen PER SHAPE from the DRAM-cold 8-copy
12988    /// msweep on all six 27B shapes (2026-07-03):
12989    ///   `pf` = next-g weight-prefetch double-buffer (48 regs, occupancy intact) — wins everywhere
12990    ///          it applies for b4 (-3..-14%), never loses;
12991    ///   `r2` = two rows/warp (67 regs -> 7 resident blocks/SM) — the bigger win (-8.5..-30%) but
12992    ///          wave-quantization-sensitive: with the grid halved to ceil(out_f/8) blocks, a
12993    ///          fractional straggler wave (waves in ~1.05-1.5) costs a full extra latency round on
12994    ///          a latency-bound kernel (27B ffn_down 640 blocks / 574 resident = 1.11 waves: +17%),
12995    ///          while <=1 wave (9B ffn_down 0.89: -30%) or >=2 waves (tail amortized; qkv 2.2:
12996    ///          -8.5%, ffn_gate 3.8: -12.5%) win. For b2, r2 wins on DEEP k-loops (in_f>=6144:
12997    ///          -8..-19%) where the 2-col body starves weight MLP hardest; pf measured negative.
12998    /// b4: r2 when waves(out_f) <= 1 (and grid fills >=half the SMs) or >= 2, else pf.
12999    /// b2: in_f>=6144 -> r2, else base.
13000    /// MEMRA_MMVQ_BV=base|pf|r2|pfr2 forces one variant everywhere (A/B + rollback seam).
13001    /// All variants BIT-IDENTICAL per (token,row): same dp4a order, scales, adg factor, reduce —
13002    /// only load issue time and the row->warp mapping change (kernel-check gates all of them).
13003    /// `rp` = the weight buffer is the A6 SPLIT-PLANE repacked layout (NVFP4 only): the same
13004    /// wave-aware auto rule applies, mapped onto the `_rp` twins (rp/rpr2/rpr2w8 mirror
13005    /// pf/r2/r2w8 — regs 44/67/64 land in the same residency classes).
13006    /// The variant the batched dispatch will pick for this (shape, m, mcols, layout) — exposed so
13007    /// gates can distinguish bit-identical variants (bit-bad==0 required) from the k-split family
13008    /// (deterministic but k-reduce-order-shifted: rel<1e-3 + run-to-run bit-identity required).
13009    /// Device SM count (cached) — grid-fill policy input.
13010    pub fn sm_count(&self) -> i32 {
13011        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13012        *SMS.get_or_init(|| {
13013            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13014            self.gpu
13015                .ctx
13016                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13017                .unwrap_or(82)
13018        })
13019    }
13020
13021    pub fn batched_variant(
13022        &self,
13023        _m: usize,
13024        in_f: usize,
13025        out_f: usize,
13026        qtype: i32,
13027        row_bytes: usize,
13028        mcols: usize,
13029        rp: bool,
13030    ) -> &'static str {
13031        // Q8_0 never joined the auto variant machinery (on sm_120 its only batched shapes
13032        // were tiny aux tensors). On Q8_0-trunk models the layout is the whole game: the
13033        // split-plane mirror (rp) routes to the _rp twins (H100 coalescing fix, 2026-07-26);
13034        // GGUF layout stays "base". rp bytes MUST never reach the base kernel or vice versa.
13035        if qtype == QT_Q8_0 {
13036            return if rp { "rp" } else { "base" };
13037        }
13038        static BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13039        let bv = *BV.get_or_init(|| match std::env::var("MEMRA_MMVQ_BV").as_deref() {
13040            Ok("base") => "base",
13041            Ok("pf") => "pf",
13042            Ok("r2") => "r2",
13043            Ok("r2w8") => "r2w8",
13044            Ok("pfr2") => "pfr2",
13045            Ok("ca") => "ca",
13046            Ok("car2") => "car2",
13047            // rp* = SPLIT-PLANE REPACKED layout kernels (A6 prototype): W must already be the
13048            // repacked buffer (msweep MSWEEP_RP harness) — never valid on GGUF-layout weights.
13049            Ok("rp") => "rp",
13050            Ok("rpr2") => "rpr2",
13051            Ok("rpr2w8") => "rpr2w8",
13052            // rpca* = cp.async software-pipelined split-plane (2026-07-05): hides the _rp
13053            // long_scoreboard load stall. rp-layout only; b4/b2 (no b8 twin).
13054            Ok("rpca") => "rpca",
13055            Ok("rpcar2") => "rpcar2",
13056            // 2026-07-06 m-small latency arc: rpsc = rpr2 + per-warp smem scale prestage (kills
13057            // the scale-plane global dependency, zero reg growth); rpms/rpmsc = m-split x2
13058            // across warp pairs (2x blocks of rpr2, column halves per warp, BIT-identical to
13059            // _rp); rpks/rpksc = k-split x2 (fastest microbench cells but k-reduce-order-shifted:
13060            // run-spec self-consistency FAILED on the 27B daily driver — verify logits must be
13061            // bit-identical to the decode path — measurement corpus ONLY, never auto).
13062            Ok("rpsc") => "rpsc",
13063            Ok("rpms") => "rpms",
13064            Ok("rpmsc") => "rpmsc",
13065            Ok("rpks") => "rpks",
13066            Ok("rpksc") => "rpksc",
13067            _ => "auto",
13068        });
13069        // cp.async ring variants need 16B-aligned rows (in_f%256==0 -> (in_f/64)*36 % 16 == 0)
13070        // and whole 32-group warp iterations (nsb%32==0 <=> in_f%1024==0). All 27B/9B trunk
13071        // shapes qualify; anything else falls back to the register variants.
13072        let ca_ok = qtype == QT_NVFP4 && (row_bytes % 16 == 0) && (in_f % 1024 == 0);
13073        // rpsc: smem scale plane fits (nsb64 <= 272) + int4-aligned staging (nsb64 % 4 == 0).
13074        // rpks/rpksc: half-plane staging alignment needs nsb64 % 8 == 0 (in_f % 512 == 0).
13075        // MEMRA_KS=0 removes the 2026-07-06 rpsc/rpks/rpksc entries from AUTO (rollback seam;
13076        // forced MEMRA_MMVQ_BV values still work).
13077        static KS_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13078        let ks_on = *KS_ON.get_or_init(|| std::env::var("MEMRA_KS").as_deref() != Ok("0"));
13079        let sc_ok = ks_on && qtype == QT_NVFP4 && (in_f % 256 == 0) && (in_f / 64 <= 272);
13080        let ks_ok = ks_on && qtype == QT_NVFP4 && (in_f % 512 == 0) && (in_f / 64 <= 272);
13081        static SMS: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
13082        let sms = *SMS.get_or_init(|| {
13083            use cudarc::driver::sys::CUdevice_attribute_enum as A;
13084            self.gpu
13085                .ctx
13086                .attribute(A::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
13087                .unwrap_or(82)
13088        });
13089        // k-quant r2 port (2026-07-04): q4_K/q5_K/q6_K have _r2/_r2w8 twins. ncu on the DRAM-cold
13090        // 9B msweep showed q4_K/q5_K b4 memory-latency bound like NVFP4 pre-fix (long_scoreboard
13091        // 19.6/16.4 per issue, DRAM 47.7/38.2%, L2 weight hit ~13%); q6_K lm_head is the exception
13092        // at DRAM 90-91% = wall-bound (yet r2 still wins -8%: deeper MLP raises achieved DRAM).
13093        // No _pf port (a k-quant group stages 10+ words vs NVFP4's 5 — register cost outweighs;
13094        // r2 covers the same MLP) and no rp (GGUF layout only). Q8_0 stays base: its only real
13095        // batched shapes are the tiny out_f=32 ssm_alpha/beta (8-block grids never fill one SM).
13096        // AUTO RULE = the measured winners table (differs from NVFP4's!):
13097        //   r2w8 NEVER in auto — the reg squeeze (72 -> 64 regs = stack spill) loses to unbounded
13098        //     r2 on every measured k-quant cell, incl. the wave-crossing lm_heads (q6_K 1316 vs
13099        //     r2 1258us) — kernels kept behind the force seam for the corpus;
13100        //   q4_K: r2 whenever the halved grid fills the SMs (blocks >= 4*SMs), INCLUDING the
13101        //     1.05-2.0 straggler window where NVFP4's r2 lost (qkv 1.78 waves: r2 -15% here; the
13102        //     k-quant base kernel leaves more latency on the table than a straggler wave costs);
13103        //   q5_K/q6_K: r2 only at waves >= 2 (the 248320-row lm_heads, 48+ waves: q6_K -8%, q5_K
13104        //     -2%); mid shapes measured base-or-flat (q5_K qkv 49.1 base vs 49.7 r2, attn_gate
13105        //     flat, attn_k base) — the 5/6-bit two-stream unpack makes r2's staging pricier.
13106        //   b2 same table with 8-row blocks: q4_K r2 when filled (-3..-22% all measured shapes),
13107        //     q5_K/q6_K r2 at waves >= 2 (27B lm_head -2.9%; 9B q6_K flat, harmless).
13108        let kq_r2 = matches!(qtype, QT_Q4_K | QT_Q5_K | QT_Q6_K);
13109        // MEMRA_KQ_BV=base|r2|r2w8 forces the k-quant variant WITHOUT touching the NVFP4 dispatch
13110        // (MEMRA_MMVQ_BV is global — an interleaved k-quant-only e2e A/B needs this narrower seam).
13111        static KQBV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13112        let kq_bv = *KQBV.get_or_init(|| match std::env::var("MEMRA_KQ_BV").as_deref() {
13113            Ok("base") => "base",
13114            Ok("r2") => "r2",
13115            Ok("r2w8") => "r2w8",
13116            _ => "auto",
13117        });
13118        let variant: &'static str = if qtype == QT_Q4_0 {
13119            // Q4_0 r2 (gemma verify trunk, 2026-07-10): shared activation loads + the
13120            // row-independent ones-sum computed once per (col,group) for 2 rows. Same
13121            // fill rule as q4_K: r2 when the halved grid still fills the SMs.
13122            static Q40BV: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
13123            let q40 = *Q40BV.get_or_init(|| match std::env::var("MEMRA_Q40_BV").as_deref() {
13124                // ms/sm/la = force-only measurement seams (ALL FLAT/NEGATIVE 2026-07-13,
13125                // never auto): m-split flat (nvcc keeps 72 regs); smem-slab −11% (staging
13126                // + syncs cost more than the stalls, bank-pad made no difference);
13127                // register load-ahead flat (nvcc already reorders). The b-tier limiter
13128                // is still unidentified — see the jsonl row.
13129                Ok("base") => "base",
13130                Ok("r2") => "r2",
13131                Ok("ms") => "ms",
13132                Ok("sm") => "sm",
13133                Ok("la") => "la",
13134                _ => "auto",
13135            });
13136            let v = if q40 != "auto" {
13137                q40
13138            } else if (out_f as u32).div_ceil(8) >= 4 * sms as u32 {
13139                "r2"
13140            } else {
13141                "base"
13142            };
13143            // split-plane mirror twins (2026-07-10): same fill rule, _rp names.
13144            // (m-split r2 pair twin PROBED FLAT 2026-07-13 — nvcc kept 72 regs either way
13145            // and the limiter is the per-column activation load chain (long_scoreboard
13146            // 42.5%), not occupancy; arm killed per doctrine, jsonl row is the record.)
13147            if rp {
13148                match v {
13149                    "ms" => "r2ms_rp",
13150                    "sm" => "r2sm_rp",
13151                    "la" => "r2la_rp",
13152                    "r2" => "r2_rp",
13153                    _ => "rp",
13154                }
13155            } else if matches!(v, "ms" | "sm" | "la") {
13156                "r2"
13157            } else {
13158                v
13159            }
13160        } else if qtype != QT_NVFP4 && !kq_r2 {
13161            "base"
13162        } else if kq_r2 && rp {
13163            // K-quant split-plane mirror (2026-08-01): only the plain _rp batched twins are
13164            // compiled for q4_K/q6_K — rp is a LAYOUT, it must survive every heuristic
13165            // (split-plane bytes through a GGUF-layout kernel = NaN). q5_K never mirrors.
13166            "rp"
13167        } else if kq_r2 {
13168            // k-quant r2w8 only exists at b4 (b2_r2 already 8-resident; b8 has no w8 twin) ->
13169            // mcols != 4 forced r2w8 falls to unbounded r2.
13170            if kq_bv != "auto" {
13171                if kq_bv == "r2w8" && mcols != 4 {
13172                    "r2"
13173                } else {
13174                    kq_bv
13175                }
13176            } else if bv != "auto" {
13177                match bv {
13178                    "r2" | "pfr2" | "rpr2" | "car2" => "r2",
13179                    "r2w8" | "rpr2w8" => {
13180                        if mcols != 4 {
13181                            "r2"
13182                        } else {
13183                            "r2w8"
13184                        }
13185                    }
13186                    _ => "base", // base/pf/ca/rp forced -> base (no such k-quant kernels)
13187                }
13188            } else {
13189                let blocks = (out_f + 7) / 8;
13190                let waves = blocks as f64 / (7 * sms as usize) as f64;
13191                let filled = blocks >= 4 * sms as usize;
13192                let use_r2 = if qtype == QT_Q4_K {
13193                    filled
13194                } else {
13195                    waves >= 2.0
13196                };
13197                if use_r2 { "r2" } else { "base" }
13198            }
13199        } else if bv != "auto" {
13200            // r2w8 only exists for b4/b8 (the b2_r2 kernel is already 8-blocks-resident at 60 regs).
13201            // ca/car2 need the alignment gate AND have no b8 twins; pfr2 has no b8 twin either —
13202            // unsupported (shape, mcols) combos fall back to pf/r2.
13203            // On rp buffers, forced legacy names map to their rp twins (layout law).
13204            let v = if bv == "r2w8" && mcols == 2 {
13205                "r2"
13206            } else if bv == "ca" && (!ca_ok || mcols == 8) {
13207                "pf"
13208            } else if bv == "car2" && (!ca_ok || mcols == 8) {
13209                "r2"
13210            } else if bv == "pfr2" && mcols == 8 {
13211                "r2"
13212            } else if (bv == "rpr2w8" || bv == "rpr2") && mcols == 2 {
13213                "rpr2"
13214            }
13215            // rpca* has no b8 twin (falls to rpr2w8/rpr2); needs the ca alignment gate.
13216            else if (bv == "rpca" || bv == "rpcar2") && (!ca_ok || mcols == 8) {
13217                if mcols == 8 { "rpr2w8" } else { "rpr2" }
13218            } else if bv == "rpcar2" && mcols == 2 {
13219                "rpca"
13220            }
13221            // rpsc/rpmsc/rpks* gate on smem-fit + alignment; fall to rpr2 outside it
13222            // (rpms has no smem and no alignment need — always valid on rp buffers).
13223            else if (bv == "rpsc" || bv == "rpmsc") && !sc_ok {
13224                "rpr2"
13225            } else if (bv == "rpks" || bv == "rpksc") && !ks_ok {
13226                "rpr2"
13227            } else {
13228                bv
13229            };
13230            if rp {
13231                match v {
13232                    "base" | "pf" | "ca" | "rp" => "rp",
13233                    "r2" | "pfr2" | "car2" | "rpr2" => "rpr2",
13234                    "r2w8" | "rpr2w8" => {
13235                        if mcols == 2 {
13236                            "rpr2"
13237                        } else {
13238                            "rpr2w8"
13239                        }
13240                    }
13241                    other => other, // rpca/rpcar2/rpsc/rpks/rpksc pass through (already rp-layout)
13242                }
13243            } else {
13244                v
13245            }
13246        } else if mcols == 8 {
13247            // b8 AUTO (2026-07-06 m-small latency arc, g7e DRAM-cold rp msweep m=5/6/8 all five
13248            // 27B shapes): rpsc — the rpr2w8 schedule with the warp's scale rows prestaged to
13249            // smem, leaving ONE global dependency (the quant stream) in the k-loop at zero reg
13250            // growth. BIT-identical to rpr2w8 and wins or ties EVERY b8 cell: ffn_gate m5
13251            // 50.7->46.9 m8 64.1->57.1 (-11%), qkv m8 34.6->33.0, ssm_out m8 29.7->28.8,
13252            // attn_gate m8 26.9->26.1, ffn_down m5 58.2->56.9. The faster split-grid twins are
13253            // OUT: rpksc (k-split, ffn_down m5 -21%) broke run-spec self-consistency (k-reduce
13254            // order shifts verify argmax at tie margins — verify must stay bit-identical to the
13255            // m=1 decode chain); rpmsc (m-split, bit-identical) measured NEGATIVE everywhere
13256            // (twin warp's duplicated weight stream: ffn_down m5 85.7 vs 56.9).
13257            if rp {
13258                if sc_ok { "rpsc" } else { "rpr2w8" }
13259            } else {
13260                "r2w8"
13261            }
13262        } else if mcols >= 4 {
13263            // r2 runs 7 resident blocks/SM (67 regs); its __launch_bounds__(128,8) twin `r2w8`
13264            // (64 regs) runs 8. grid = ceil(out_f/8) for both. rp twins land in the same
13265            // residency classes (rp 44 regs ~ pf-class occupancy, rpr2 67, rpr2w8 64).
13266            let blocks = (out_f + 7) / 8;
13267            let r7 = 7 * sms as usize;
13268            let r8 = 8 * sms as usize;
13269            let waves = blocks as f64 / r7 as f64;
13270            let filled = blocks >= 4 * sms as usize;
13271            // 2026-07-06 m-small latency arc: b4 keeps the wave rule (rpms/rpmsc measured
13272            // flat-to-negative at m=3/4 on every shape — the m-split twin duplicates the weight
13273            // stream; rpsc b4 also negative on r2-class picks, ffn_down m4 51.1 vs 46.5).
13274            if filled && blocks.div_ceil(r8) < blocks.div_ceil(r7) {
13275                // the extra residency drops the INTEGER wave count -> the straggler wave a
13276                // latency-bound kernel pays in full disappears (ffn_down 1.11 -> 0.98 waves:
13277                // 112.5 -> 81.6us, beats pf 90.1; qkv 2.23 -> 1.95: 58.1 -> 51.1).
13278                if rp { "rpr2w8" } else { "r2w8" }
13279            } else if waves >= 2.0 || (waves <= 1.0 && filled) {
13280                // tail amortized (>=2 waves) or single wave: unbounded r2 (no reg-squeeze tax —
13281                // gate/up 81.1 vs 83.9 bounded, attn_q 61.0 vs 63.4).
13282                if rp { "rpr2" } else { "r2" }
13283            } else {
13284                // fractional straggler-wave window with no crossing, or grid too small to fill
13285                // the SMs (tiny out_f<=1024 shapes want max row-parallelism): prefetch variant
13286                // (rp = the r1 split-plane twin — measured the attn_gate winner, 35.4 vs pf 36.4).
13287                if rp { "rp" } else { "pf" }
13288            }
13289        } else if in_f >= 6144 {
13290            // b2 deep-k (2026-07-06): every new twin measured flat-to-negative here (rpms 44.1
13291            // vs rpr2 40.8 ffn_down; rpsc 43.6; the winning rpks is banned on k-order) — rpr2
13292            // stays.
13293            if rp { "rpr2" } else { "r2" }
13294        } else if rp {
13295            // b2 shallow-k: qkv (out_f=10240, 0.97 waves at 7-resident) is the one measured cell
13296            // where the r2-schedule scale-prestage twin beats the r1 rp pick (24.7 vs 28.9us
13297            // -15%); the wider (ffn_gate 1.65 waves) and smaller (attn_gate 0.58) shapes LOSE
13298            // (41.8 vs 38.2 / 16.6 vs 14.6) — gate on the single-wave window.
13299            let waves = ((out_f + 7) / 8) as f64 / (7 * sms as usize) as f64;
13300            if sc_ok && waves >= 0.9 && waves <= 1.1 {
13301                "rpsc"
13302            } else {
13303                "rp"
13304            }
13305        } else {
13306            "base"
13307        };
13308        variant
13309    }
13310
13311    pub fn qmatvec_mmvq_batched(
13312        &self,
13313        bytes: &CudaSlice<u8>,
13314        aq: &CudaSlice<i8>,
13315        ad: &CudaSlice<f32>,
13316        m: usize,
13317        in_f: usize,
13318        out_f: usize,
13319        qtype: i32,
13320        row_bytes: usize,
13321        mcols: usize,
13322        scale: f32,
13323        rp: bool,
13324    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13325        const ROWS_PER_BLOCK: u32 = 4;
13326        // TUNE SEAM (H100 lane): MEMRA_BVAR forces the batched-variant pick for the whole
13327        // process — the auto heuristics were tuned on sm_120 (82 SMs / 858 GB/s) and the
13328        // sm_90a re-tune sweeps this seam empirically. Layout variants stay safe: an rp
13329        // weight keeps its rp-layout kernel family regardless of the override.
13330        let forced: Option<&'static str> = {
13331            static V: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
13332            V.get_or_init(|| std::env::var("MEMRA_BVAR").ok())
13333                .as_deref()
13334                .map(|s| Box::leak(s.to_string().into_boxed_str()) as &'static str)
13335        };
13336        let variant = match forced {
13337            Some(v) if !rp || v.contains("rp") => v,
13338            _ => self.batched_variant(m, in_f, out_f, qtype, row_bytes, mcols, rp),
13339        };
13340        let base_name = Self::batched_kernel_name(qtype, mcols).ok_or_else(|| {
13341            format!("qmatvec_mmvq_batched: no kernel for qtype {qtype} mcols {mcols}")
13342        })?;
13343        // b16 tier (t=9..16 verify): only base/_rp b16 kernels are compiled — the b2..b8
13344        // per-shape perf variants (r2/pf/...) do not apply at this width. rp is a LAYOUT,
13345        // not a perf variant: it must survive (base kernel on split-plane bytes = NaN).
13346        let variant = if mcols == 16 {
13347            if rp { "rp" } else { "base" }
13348        } else {
13349            variant
13350        };
13351        // EXACT-WIDTH b5/b6/b7 twins (lane/vt-fixes fix 1, 2026-08-03): the b8 kernels
13352        // allocate acc[WROWS][8] at ANY m, so T=5..7 verify paid the full 8-wide register
13353        // tax — the measured T=4->5 cliff. The same template at MCOLS=m runs the identical
13354        // per-(token,row) chain (columns c >= m never execute in either form) ->
13355        // BIT-IDENTICAL to the b8 launch. NVFP4 split-plane only (the sm_120 default trunk);
13356        // covers both b8 auto schedules (rpsc, rpr2w8). MEMRA_B567=0 rollback.
13357        static B567: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
13358        let b567 = *B567.get_or_init(|| std::env::var("MEMRA_B567").as_deref() != Ok("0"));
13359        if b567
13360            && qtype == QT_NVFP4
13361            && rp
13362            && mcols == 8
13363            && (5..=7).contains(&m)
13364            && matches!(variant, "rpsc" | "rpr2w8")
13365        {
13366            let f = self.func(&format!("qmatvec_nvfp4_mmvq_b{m}_{variant}"));
13367            let rows_per_block = ROWS_PER_BLOCK * 2; // r2-class schedules: 2 rows/warp
13368            let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13369            let cfg = LaunchConfig {
13370                grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13371                block_dim: (32, ROWS_PER_BLOCK, 1),
13372                shared_mem_bytes: 0,
13373            };
13374            let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13375            let __s_b = self.gpu.stream();
13376            let mut b = __s_b.launch_builder(&f);
13377            b.arg(bytes)
13378                .arg(aq)
13379                .arg(ad)
13380                .arg(&mut y)
13381                .arg(&inf)
13382                .arg(&outf)
13383                .arg(&mi)
13384                .arg(&rb);
13385            unsafe {
13386                b.launch(cfg)?;
13387            }
13388            if scale != 1.0 {
13389                self.scale_inplace(&mut y, scale, m * out_f)?;
13390            }
13391            return Ok(y);
13392        }
13393        let (name, rows_per_block): (std::borrow::Cow<'static, str>, u32) = match variant {
13394            "base" => (base_name.into(), ROWS_PER_BLOCK),
13395            "pf" => (format!("{base_name}_pf").into(), ROWS_PER_BLOCK),
13396            "ca" => (format!("{base_name}_ca").into(), ROWS_PER_BLOCK),
13397            "rp" => (format!("{base_name}_rp").into(), ROWS_PER_BLOCK),
13398            "rpca" => (format!("{base_name}_rpca").into(), ROWS_PER_BLOCK), // 1 row/warp cp.async
13399            // split families: 2 warp-pairs x 2 rows = 4 rows/block (the k-range or column set
13400            // splits across the pair's two warps; grid.x doubles vs rpr2 at the same regs).
13401            "rpks" => (format!("{base_name}_rpks").into(), ROWS_PER_BLOCK),
13402            "rpksc" => (format!("{base_name}_rpksc").into(), ROWS_PER_BLOCK),
13403            "rpms" => (format!("{base_name}_rpms").into(), ROWS_PER_BLOCK),
13404            "rpmsc" => (format!("{base_name}_rpmsc").into(), ROWS_PER_BLOCK),
13405            "r2ms_rp" => (format!("{base_name}_r2ms_rp").into(), ROWS_PER_BLOCK),
13406            "r2sm_rp" => (format!("{base_name}_r2sm_rp").into(), ROWS_PER_BLOCK * 2),
13407            "r2la_rp" => (format!("{base_name}_r2la_rp").into(), ROWS_PER_BLOCK * 2),
13408            v => (format!("{base_name}_{v}").into(), ROWS_PER_BLOCK * 2), // r2-class: 2 rows/warp
13409        };
13410        debug_assert!(
13411            !rp || name.contains("_rp"),
13412            "rp weight dispatched to a GGUF-layout kernel"
13413        );
13414        let f = self.func(&name);
13415        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
13416        // r2sm_rp: [MCOLS][32 blk][8 int] activation slab + [MCOLS][32] f32 scales.
13417        let smem = if name.contains("_r2sm_rp") {
13418            (mcols * 32 * 9 * 4 + mcols * 32 * 4) as u32
13419        } else {
13420            0
13421        };
13422        let cfg = LaunchConfig {
13423            grid_dim: ((out_f as u32 + rows_per_block - 1) / rows_per_block, 1, 1),
13424            block_dim: (32, ROWS_PER_BLOCK, 1),
13425            shared_mem_bytes: smem,
13426        };
13427        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13428        let __s_b = self.gpu.stream();
13429        let mut b = __s_b.launch_builder(&f);
13430        b.arg(bytes)
13431            .arg(aq)
13432            .arg(ad)
13433            .arg(&mut y)
13434            .arg(&inf)
13435            .arg(&outf)
13436            .arg(&mi)
13437            .arg(&rb);
13438        unsafe {
13439            b.launch(cfg)?;
13440        }
13441        if scale != 1.0 {
13442            self.scale_inplace(&mut y, scale, m * out_f)?;
13443        }
13444        Ok(y)
13445    }
13446
13447    /// BATCHED weight-tile-resident matvec from raw weight bytes (quantizes the f32 activation `x` to
13448    /// q8_1 internally; macro-scale NOT applied — caller compares bare, like qmatvec_*_fast). For the
13449    /// kernel_check bit-equivalence gate. `mcols` ∈ {2,4,8}. Works for Q8_0/Q4_K/Q5_K/Q6_K/NVFP4.
13450    pub fn qmatvec_batched_raw(
13451        &self,
13452        bytes: &CudaSlice<u8>,
13453        x: &CudaSlice<f32>,
13454        m: usize,
13455        in_f: usize,
13456        out_f: usize,
13457        qtype: i32,
13458        row_bytes: usize,
13459        mcols: usize,
13460        rp: bool,
13461    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13462        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13463        self.qmatvec_mmvq_batched(
13464            bytes, &aq, &ad, m, in_f, out_f, qtype, row_bytes, mcols, 1.0, rp,
13465        )
13466    }
13467
13468    /// Back-compat NVFP4-only batched raw launcher (used by older gates). Delegates to the generic one.
13469    pub fn qmatvec_nvfp4_batched_raw(
13470        &self,
13471        bytes: &CudaSlice<u8>,
13472        x: &CudaSlice<f32>,
13473        m: usize,
13474        in_f: usize,
13475        out_f: usize,
13476        row_bytes: usize,
13477        mcols: usize,
13478        rp: bool,
13479    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13480        self.qmatvec_batched_raw(bytes, x, m, in_f, out_f, QT_NVFP4, row_bytes, mcols, rp)
13481    }
13482
13483    /// Stage-C FP4 gate (MEMRA_FP4): if `w` is an NVFP4 weight with in_f%64==0, run the native mxf4
13484    /// block-scale GEMM and apply the per-tensor macro-scale, returning Some(y). Else None (caller
13485    /// falls through to the int8 GEMM / dp4a). Strict opt-in over the proven int8 path; m>=16 only.
13486    fn try_fp4_gemm(
13487        &self,
13488        w: &crate::model::GpuTensor,
13489        x: &CudaSlice<f32>,
13490        m: usize,
13491        in_f: usize,
13492        out_f: usize,
13493    ) -> Result<Option<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13494        use crate::model::GpuTensor;
13495        if cfg!(memra_portable_cuda) {
13496            return Ok(None);
13497        }
13498        if std::env::var("MEMRA_FP4").is_err() {
13499            return Ok(None);
13500        }
13501        // CUTLASS prefill branch (m>=128 + MEMRA_FP4_CUTLASS + a repacked CutlassWeight present): route
13502        // to the CUTLASS sm120 NVFP4 GEMM, folding the per-tensor macro-scale into the epilogue alpha
13503        // (1/scale) — no post-matmul scale_inplace. Decode (m<128) and the m∈[16,128) middle band keep
13504        // the hand-roll below: CUTLASS's 128-row M-tile wastes work under 128.
13505        // The hand-roll applies the per-tensor macro-scale as a POST-matmul MULTIPLY (scale_inplace(y,
13506        // scale)); CUTLASS's epilogue does D = alpha * (A@B^T), so alpha == scale reproduces it exactly
13507        // (NOT 1/scale — the plan sketch had this inverted; the kernel_check arm gates it). scale==1.0
13508        // for the common no-macro-scale case.
13509        #[cfg(memra_cutlass)]
13510        if m >= 128 && std::env::var("MEMRA_FP4_CUTLASS").is_ok() {
13511            if let GpuTensor::Quant {
13512                bytes,
13513                qtype,
13514                scale,
13515                row_bytes,
13516                cutlass,
13517                ..
13518            } = w
13519            {
13520                if *qtype == QT_NVFP4 && in_f % 64 == 0 {
13521                    if let Some(cw) = cutlass {
13522                        // Resident fast path: load-time-repacked B + swizzled SFB (no per-call repack).
13523                        let y = self.cutlass_fp4_gemm(
13524                            &cw.b_packed,
13525                            &cw.sfb_swizzled,
13526                            x,
13527                            *scale,
13528                            m,
13529                            out_f,
13530                            in_f,
13531                        )?;
13532                        return Ok(Some(y));
13533                    } else if std::env::var("MEMRA_FP4_CUTLASS_OTF").is_ok() {
13534                        // On-the-fly repack (MEMRA_FP4_CUTLASS_OTF): de-interleave + swizzle the B operand
13535                        // from raw bytes per prefill call. No resident doubling of the NVFP4 weight VRAM
13536                        // (the load-time repack ~doubles it) — needed for models that don't fit the
13537                        // resident path (e.g. the 27B on 24GB). Slower (per-call repack) but argmax-exact.
13538                        let (b_packed, sfb_sw) =
13539                            self.build_cutlass_weight(bytes, out_f, in_f, *row_bytes)?;
13540                        let y =
13541                            self.cutlass_fp4_gemm(&b_packed, &sfb_sw, x, *scale, m, out_f, in_f)?;
13542                        return Ok(Some(y));
13543                    }
13544                }
13545            }
13546        }
13547        if let GpuTensor::Quant {
13548            bytes,
13549            qtype,
13550            row_bytes,
13551            scale,
13552            rp,
13553            ..
13554        } = w
13555        {
13556            // A6: the hand-rolled W4A4 mxf4 GEMM reads 36B GGUF blocks — no rp port (MEMRA_FP4 is
13557            // an opt-in accuracy tradeoff); repacked tensors fall through to the int8 GEMM.
13558            if *qtype == QT_NVFP4 && in_f % 64 == 0 && !*rp {
13559                let y =
13560                    self.qmatvec_gemm_nvfp4_fp4(bytes, x, m, in_f, out_f, *row_bytes, *scale)?;
13561                return Ok(Some(y));
13562            }
13563        }
13564        Ok(None)
13565    }
13566
13567    /// rms_norm + fused fp16 twin (task #14): f32 output verbatim `rms_norm` + the fp16
13568    /// copy the f16-mirror GEMM group would otherwise produce with a standalone convert
13569    /// launch. BIT-IDENTICAL end-to-end (same reduction, same __float2half values).
13570    pub fn rms_norm_f16out(
13571        &self,
13572        x: &CudaSlice<f32>,
13573        w: &CudaSlice<f32>,
13574        dst: &mut CudaSlice<f32>,
13575        dst16: &mut CudaSlice<u8>,
13576        ncols: usize,
13577        nrows: usize,
13578        eps: f32,
13579    ) -> Result<(), Box<dyn std::error::Error>> {
13580        let f = self.func("rms_norm_f16out_f32");
13581        let cfg = LaunchConfig {
13582            grid_dim: (nrows as u32, 1, 1),
13583            block_dim: (rms_block(), 1, 1),
13584            shared_mem_bytes: 0,
13585        };
13586        let (nc, e) = (ncols as i32, eps);
13587        let __s_b = self.gpu.stream();
13588        let mut b = __s_b.launch_builder(&f);
13589        b.arg(x).arg(w).arg(dst).arg(dst16).arg(&nc).arg(&e);
13590        unsafe {
13591            b.launch(cfg)?;
13592        }
13593        Ok(())
13594    }
13595
13596    /// add+norm(+f16out) fusion for the prefill trunk (round 28; add_rms_norm precedent —
13597    /// bit-identical to add_f32 -> rms_norm_f16out). block_dim matches rms_norm_f16out's.
13598    #[allow(clippy::too_many_arguments)]
13599    pub fn add_rms_norm_f16out(
13600        &self,
13601        a: &CudaSlice<f32>,
13602        b: &CudaSlice<f32>,
13603        w: &CudaSlice<f32>,
13604        res: &mut CudaSlice<f32>,
13605        dst: &mut CudaSlice<f32>,
13606        dst16: &mut CudaSlice<u8>,
13607        ncols: usize,
13608        nrows: usize,
13609        eps: f32,
13610    ) -> Result<(), Box<dyn std::error::Error>> {
13611        let f = self.func("add_rms_norm_f16out_f32");
13612        let cfg = LaunchConfig {
13613            grid_dim: (nrows as u32, 1, 1),
13614            block_dim: (rms_block(), 1, 1),
13615            shared_mem_bytes: 0,
13616        };
13617        let (nc, e) = (ncols as i32, eps);
13618        let __s_lb = self.gpu.stream();
13619        let mut lb = __s_lb.launch_builder(&f);
13620        lb.arg(a)
13621            .arg(b)
13622            .arg(w)
13623            .arg(res)
13624            .arg(dst)
13625            .arg(dst16)
13626            .arg(&nc)
13627            .arg(&e);
13628        unsafe {
13629            lb.launch(cfg)?;
13630        }
13631        Ok(())
13632    }
13633
13634    /// matmul_group with a PRE-EMITTED fp16 activation (task #14: the producer norm fused
13635    /// the convert). Mirror-less members fall back to `matmul` on the f32 activation.
13636    pub fn matmul_group_xh(
13637        &self,
13638        ws: &[&crate::model::GpuTensor],
13639        x: &CudaSlice<f32>,
13640        xh: &CudaSlice<u8>,
13641        m: usize,
13642    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13643        let mut out = Vec::with_capacity(ws.len());
13644        let in_f = ws[0].in_features();
13645        for w in ws {
13646            if w.in_features() == in_f && m >= 16 && !self.verify_exact_on() {
13647                if let Some(y) = self.try_f16_gemm_pre(w, xh, m)? {
13648                    out.push(y);
13649                    continue;
13650                }
13651            }
13652            out.push(self.matmul(w, x, m)?);
13653        }
13654        Ok(out)
13655    }
13656
13657    /// task #14 pad-proofing: zero beta/g_log at rows >= len_d[0] (pads become identity
13658    /// GDN steps). Layouts [T, H].
13659    pub fn gdn_pad_mask(
13660        &self,
13661        beta: &mut CudaSlice<f32>,
13662        g_log: &mut CudaSlice<f32>,
13663        len_d: &CudaSlice<i32>,
13664        h: usize,
13665        t: usize,
13666    ) -> Result<(), Box<dyn std::error::Error>> {
13667        let f = self.func("gdn_pad_mask_f32");
13668        let cfg = LaunchConfig::for_num_elems((t * h) as u32);
13669        let (hi, ti) = (h as i32, t as i32);
13670        let __s_b = self.gpu.stream();
13671        let mut b = __s_b.launch_builder(&f);
13672        b.arg(beta).arg(g_log).arg(len_d).arg(&hi).arg(&ti);
13673        unsafe {
13674            b.launch(cfg)?;
13675        }
13676        Ok(())
13677    }
13678
13679    /// task #14 pad-proofing: dst[ncols] = src row (len_d[0]-1) — device-indexed last-row
13680    /// gather for the padded prime graph's h_seed/hlast.
13681    pub fn row_gather_dev(
13682        &self,
13683        src: &CudaSlice<f32>,
13684        dst: &mut CudaSlice<f32>,
13685        len_d: &CudaSlice<i32>,
13686        ncols: usize,
13687    ) -> Result<(), Box<dyn std::error::Error>> {
13688        let f = self.func("row_gather_dev_f32");
13689        let cfg = LaunchConfig::for_num_elems(ncols as u32);
13690        let nc = ncols as i32;
13691        let __s_b = self.gpu.stream();
13692        let mut b = __s_b.launch_builder(&f);
13693        b.arg(src).arg(dst).arg(len_d).arg(&nc);
13694        unsafe {
13695            b.launch(cfg)?;
13696        }
13697        Ok(())
13698    }
13699
13700    /// Grouped matmul: several weights consuming ONE activation (hybrid layers: the GDN
13701    /// 4-tuple wqkv/gate/beta/alpha, attention q/k/v, ffn gate/up). Semantics identical to
13702    /// calling `matmul` per weight; the f16-mirror arm converts the activation ONCE for the
13703    /// whole group instead of once per GEMM (the standalone converts were ~250 launches/prime
13704    /// of small-kernel gap fuel — nsys 2026-07-26). Any member without a mirror (or with a
13705    /// different in_f) falls back to its own `matmul` — behavior unchanged.
13706    pub fn matmul_group(
13707        &self,
13708        ws: &[&crate::model::GpuTensor],
13709        x: &CudaSlice<f32>,
13710        m: usize,
13711    ) -> Result<Vec<CudaSlice<f32>>, Box<dyn std::error::Error>> {
13712        use crate::model::GpuTensor;
13713        let mut out = Vec::with_capacity(ws.len());
13714        let any_mirror = ws
13715            .iter()
13716            .any(|w| matches!(w, GpuTensor::Quant { f16: Some(_), .. }));
13717        if m >= 16 && any_mirror && !self.verify_exact_on() {
13718            let in_f = ws[0].in_features();
13719            let xh = self.f16_act(x, m * in_f, in_f)?;
13720            for w in ws {
13721                if w.in_features() == in_f {
13722                    if let Some(y) = self.try_f16_gemm_pre(w, &xh, m)? {
13723                        out.push(y);
13724                        continue;
13725                    }
13726                }
13727                out.push(self.matmul(w, x, m)?);
13728            }
13729            return Ok(out);
13730        }
13731        for w in ws {
13732            out.push(self.matmul(w, x, m)?);
13733        }
13734        Ok(out)
13735    }
13736
13737    /// Cross-request grouped matmul (task #13): run ONE projection group over the
13738    /// CONCATENATION of several sequences' activations (m = sum of per-seq rows — the
13739    /// GEMM-batch win vLLM gets from continuous batching), then split each output back
13740    /// into per-seq buffers. Zero view plumbing: gather/scatter are stream-ordered D2D
13741    /// copies (~us at prime sizes). NUMERIC CONFIG NOTE: a GEMM at m=sum tiles K
13742    /// differently than per-seq GEMMs — argmax-gated like every prefill GEMM change.
13743    pub fn matmul_group_multi(
13744        &self,
13745        ws: &[&crate::model::GpuTensor],
13746        xs: &[&CudaSlice<f32>],
13747        ms: &[usize],
13748    ) -> Result<Vec<Vec<CudaSlice<f32>>>, Box<dyn std::error::Error>> {
13749        assert_eq!(xs.len(), ms.len());
13750        let in_f = ws[0].in_features();
13751        let total: usize = ms.iter().sum();
13752        let mut xcat = self.uninit(total * in_f)?;
13753        let mut off = 0usize;
13754        for (x, &m) in xs.iter().zip(ms) {
13755            self.copy_into(&mut xcat, off * in_f, x, m * in_f)?;
13756            off += m;
13757        }
13758        let ys = self.matmul_group(ws, &xcat, total)?;
13759        let mut out: Vec<Vec<CudaSlice<f32>>> = (0..xs.len()).map(|_| Vec::new()).collect();
13760        for (w, y) in ws.iter().zip(ys) {
13761            let out_f = w.out_features();
13762            let mut off = 0usize;
13763            for (s, &m) in ms.iter().enumerate() {
13764                let mut ys_s = self.uninit(m * out_f)?;
13765                let src = y.slice(off * out_f..(off + m) * out_f);
13766                self.gpu.stream().memcpy_dtod(&src, &mut ys_s)?;
13767                out[s].push(ys_s);
13768                off += m;
13769            }
13770        }
13771        Ok(out)
13772    }
13773
13774    /// True if `w`'s qtype has a batched tensor-core GEMM kernel (the prefill T>1 root fix).
13775    /// Only the 4 daily-hot dtypes: Q8_0, Q4_K, Q6_K, NVFP4. NVFP4 needs in_f % 64 == 0.
13776    /// DEFAULT-ON (2026-06-28): measured pp512 9B-NVFP4 = 1413 tok/s WITH this GEMM vs 298 with the
13777    /// dp4a fallback (4.7x) AND MORE accurate (prefill logit maxdiff 0.159 vs dp4a 0.55, both argmax
13778    /// MATCH). The int8 tensor-core GEMM is unconditional (its historical MEMRA_GEMM opt-in gate
13779    /// shipped with Phase 0 — mma + smem swizzle + cp.async — and was removed). Prefill-only
13780    /// (m>=GEMM_M_THRESHOLD); m=1 decode keeps dp4a/MMVQ (this returns true but matmul only calls it
13781    /// at m>=threshold). Portable CUDA targets always use the correctness fallback; on sm_120a,
13782    /// MEMRA_NO_GEMM forces that same dp4a fallback (the bit-reference).
13783    pub fn gemm_supports(&self, w: &crate::model::GpuTensor) -> bool {
13784        use crate::model::GpuTensor;
13785        if !legacy_quant_gemm_allowed(
13786            cfg!(memra_portable_cuda),
13787            cfg!(memra_hopper_mma),
13788            std::env::var_os("MEMRA_NO_GEMM").is_some(),
13789        ) {
13790            return false;
13791        }
13792        match w {
13793            GpuTensor::Quant { qtype, .. } => {
13794                matches!(*qtype, QT_Q8_0 | QT_Q4_K | QT_Q6_K | QT_Q5_K | QT_Q4_0)
13795                    || (*qtype == QT_NVFP4 && w.in_features() % 64 == 0)
13796            }
13797            GpuTensor::Float { .. } | GpuTensor::FloatBf16 { .. } => false,
13798        }
13799    }
13800
13801    /// Batched tensor-core int8 GEMM with a PRE-QUANTIZED q8_1 activation (aq,ad). The prefill
13802    /// (T>1) root fix: decode each weight 32-block to int8 in shared memory ONCE per (row-tile,
13803    /// K-step) and reuse it across all BN tokens via mma.sync.m16n8k32.s8 — amortizing the weight
13804    /// read/decode N-fold (vs the dp4a matvec's per-token re-read). s32 accumulate is exact vs
13805    /// dp4a; only the final f32 block-scale rounding differs. Caller MUST have checked
13806    /// `gemm_supports(w)`. y[m,out] token-major. NVFP4 per-tensor macro-scale applied post.
13807    pub fn qmatvec_gemm(
13808        &self,
13809        w: &crate::model::GpuTensor,
13810        aq: &CudaSlice<i8>,
13811        ad: &CudaSlice<f32>,
13812        m: usize,
13813    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13814        use crate::model::GpuTensor;
13815        let in_f = w.in_features();
13816        let out_f = w.out_features();
13817        let (bytes, qtype, row_bytes, scale, rp) = match w {
13818            GpuTensor::Quant {
13819                bytes,
13820                qtype,
13821                row_bytes,
13822                scale,
13823                rp,
13824                ..
13825            } => (bytes, *qtype, *row_bytes, *scale, *rp),
13826            _ => unreachable!("gemm_supports guaranteed Quant"),
13827        };
13828        // wgmma arm (sm_90a, task 8): the m64n64k32 warpgroup kernel reads the rp4 split-plane
13829        // mirror AS-IS (qplane rows = its A operand, the half dplane its scales) and the same
13830        // (aq, ad) activation planes. Same numeric class as the mma kernel below (exact s32 per
13831        // 32-block, one f32 scale fold per block, ascending K) — argmax/tolerance gated like
13832        // every prefill GEMM, not bit-gated. MEMRA_WGMMA=0 restores the portable kernel.
13833        if cfg!(memra_hopper_mma) && qtype == QT_Q8_0 && out_f % 64 == 0 && wgmma_gemm_enabled() {
13834            if let GpuTensor::Quant { rp4: Some(m4), .. } = w {
13835                let mut y = self.qmatvec_gemm_q8_0_wgmma_raw(m4, aq, ad, m, in_f, out_f)?;
13836                if scale != 1.0 {
13837                    self.scale_inplace(&mut y, scale, m * out_f)?;
13838                }
13839                return Ok(y);
13840            }
13841        }
13842        let name = match qtype {
13843            QT_Q8_0 => "qmatvec_gemm_q8_0",
13844            QT_Q4_K => "qmatvec_gemm_q4_K",
13845            QT_Q4_0 => {
13846                if rp {
13847                    "qmatvec_gemm_q4_0_rp"
13848                } else {
13849                    "qmatvec_gemm_q4_0"
13850                }
13851            }
13852            QT_Q5_K => "qmatvec_gemm_q5_K",
13853            QT_Q6_K => "qmatvec_gemm_q6_K",
13854            QT_NVFP4 => {
13855                if rp {
13856                    "qmatvec_gemm_nvfp4_rp"
13857                } else {
13858                    "qmatvec_gemm_nvfp4"
13859                }
13860            }
13861            _ => unreachable!(),
13862        };
13863        let f = self.func(name);
13864        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
13865        // CTA tile MUST match the .cu per-kernel tile. MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) runs llama's
13866        // 128x128 SQUARE tile (K1_BM=128 x K1_BN=128, 8 warps); kernel2 (Q6_K/NVFP4) keeps 64x256, 4 warps
13867        // (the macro BM/BN in the .cu). Grid dims are selected by qtype so each launches its own tile.
13868        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
13869        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
13870        let k1_tile = if is_k1 {
13871            k1_launch_override().unwrap_or((128, 128, 8))
13872        } else {
13873            (128, 128, 8)
13874        };
13875        let (bm, bn): (u32, u32) = if is_k1 {
13876            (k1_tile.0, k1_tile.1)
13877        } else {
13878            (64, 256)
13879        };
13880        let warps: u32 = if is_k1 {
13881            k1_tile.2
13882        } else {
13883            match qtype {
13884                QT_NVFP4 => 8,
13885                _ => 4,
13886            }
13887        };
13888        let cfg = LaunchConfig {
13889            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
13890            block_dim: (32, warps, 1),
13891            shared_mem_bytes: 0,
13892        };
13893        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13894        let __s_b = self.gpu.stream();
13895        let mut b = __s_b.launch_builder(&f);
13896        b.arg(bytes)
13897            .arg(aq)
13898            .arg(ad)
13899            .arg(&mut y)
13900            .arg(&inf)
13901            .arg(&outf)
13902            .arg(&mi)
13903            .arg(&rb);
13904        unsafe {
13905            b.launch(cfg)?;
13906        }
13907        if scale != 1.0 {
13908            self.scale_inplace(&mut y, scale, m * out_f)?;
13909        }
13910        Ok(y)
13911    }
13912
13913    /// Test entry: run the GEMM directly from raw weight bytes + qtype (no GpuTensor). Quantizes
13914    /// the f32 activation `x` to q8_1 internally then launches the tensor-core GEMM. NVFP4 per-tensor
13915    /// macro-scale is NOT applied here (caller passes it separately, like the dp4a path). Used by
13916    /// kernel_check for the bit-equivalence gate vs qmatvec_*_dp4a.
13917    pub fn qmatvec_gemm_raw(
13918        &self,
13919        bytes: &CudaSlice<u8>,
13920        x: &CudaSlice<f32>,
13921        m: usize,
13922        in_f: usize,
13923        out_f: usize,
13924        qtype: i32,
13925        row_bytes: usize,
13926    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13927        let (aq, ad) = self.quantize_q8_1(x, m, in_f)?;
13928        let name = match qtype {
13929            QT_Q8_0 => "qmatvec_gemm_q8_0",
13930            QT_Q4_K => "qmatvec_gemm_q4_K",
13931            QT_Q4_0 => "qmatvec_gemm_q4_0",
13932            QT_Q5_K => "qmatvec_gemm_q5_K",
13933            QT_Q6_K => "qmatvec_gemm_q6_K",
13934            QT_NVFP4 => "qmatvec_gemm_nvfp4",
13935            QT_NVFP4_RP => "qmatvec_gemm_nvfp4_rp",
13936            _ => panic!("qmatvec_gemm_raw: qtype {qtype} has no GEMM kernel"),
13937        };
13938        let f = self.func(name);
13939        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output: skip memset
13940        // MMQ-PORT: kernel1 (Q8_0/Q4_K/Q5_K) = llama 128x128 tile, 8 warps; kernel2 (Q6_K/NVFP4) = 64x256,
13941        // 4/8 warps. Grid tile per qtype (must match the .cu K1_BM/K1_BN vs BM/BN). KEEP IN SYNC w/ qmatvec_gemm.
13942        let is_k1 = matches!(qtype, QT_Q8_0 | QT_Q4_K | QT_Q5_K | QT_Q4_0);
13943        // TUNE SEAM: MEMRA_GEMM_K1_LAUNCH overrides kernel1's launch tile to match a -D-swept fatbin.
13944        let k1_tile = if is_k1 {
13945            k1_launch_override().unwrap_or((128, 128, 8))
13946        } else {
13947            (128, 128, 8)
13948        };
13949        let (bm, bn): (u32, u32) = if is_k1 {
13950            (k1_tile.0, k1_tile.1)
13951        } else {
13952            (64, 256)
13953        };
13954        let warps: u32 = if is_k1 {
13955            k1_tile.2
13956        } else {
13957            match qtype {
13958                QT_NVFP4 | QT_NVFP4_RP => 8,
13959                _ => 4,
13960            }
13961        };
13962        let cfg = LaunchConfig {
13963            grid_dim: ((out_f as u32 + bm - 1) / bm, (m as u32 + bn - 1) / bn, 1),
13964            block_dim: (32, warps, 1),
13965            shared_mem_bytes: 0,
13966        };
13967        let (inf, outf, mi, rb) = (in_f as i32, out_f as i32, m as i32, row_bytes as i64);
13968        let __s_b = self.gpu.stream();
13969        let mut b = __s_b.launch_builder(&f);
13970        b.arg(bytes)
13971            .arg(&aq)
13972            .arg(&ad)
13973            .arg(&mut y)
13974            .arg(&inf)
13975            .arg(&outf)
13976            .arg(&mi)
13977            .arg(&rb);
13978        unsafe {
13979            b.launch(cfg)?;
13980        }
13981        Ok(y)
13982    }
13983
13984    /// H100 warpgroup GEMM raw entry (task 8): launch `qmatvec_gemm_q8_0_wgmma` on an rp4
13985    /// split-plane mirror + pre-quantized (aq, ad) activation planes. One warpgroup (128 thr)
13986    /// owns a 64x64 C tile; grid (out_f/64, ceil(m/64)). out_f % 64 == 0 REQUIRED (row loads
13987    /// and dplane scale reads are unguarded); the token edge is guarded in-kernel.
13988    /// Standalone harness verdict (tools/bench_q8_gemm_wgmma.cu, 4096x4096x512): rel 1.6e-05
13989    /// vs CPU ref, 179us vs the portable mma kernel's 688us (3.84x, unpipelined).
13990    pub fn qmatvec_gemm_q8_0_wgmma_raw(
13991        &self,
13992        rp4: &CudaSlice<u8>,
13993        aq: &CudaSlice<i8>,
13994        ad: &CudaSlice<f32>,
13995        m: usize,
13996        in_f: usize,
13997        out_f: usize,
13998    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
13999        assert!(
14000            out_f % 64 == 0 && in_f % 32 == 0,
14001            "wgmma GEMM needs out_f%64==0, in_f%32==0"
14002        );
14003        let f = self.func("qmatvec_gemm_q8_0_wgmma");
14004        let mut y = self.alloc_uninit::<f32>(m * out_f)?; // full-overwrite GEMM output
14005        let cfg = LaunchConfig {
14006            grid_dim: ((out_f / 64) as u32, (m as u32).div_ceil(64), 1),
14007            block_dim: (128, 1, 1),
14008            shared_mem_bytes: 0,
14009        };
14010        let (inf, outf, mi) = (in_f as i32, out_f as i32, m as i32);
14011        let __s_b = self.gpu.stream();
14012        let mut b = __s_b.launch_builder(&f);
14013        b.arg(rp4)
14014            .arg(aq)
14015            .arg(ad)
14016            .arg(&mut y)
14017            .arg(&inf)
14018            .arg(&outf)
14019            .arg(&mi);
14020        unsafe {
14021            b.launch(cfg)?;
14022        }
14023        Ok(y)
14024    }
14025
14026    /// y[i] *= s. NVFP4 per-tensor macro-scale broadcast over the whole output.
14027    pub fn scale_inplace(
14028        &self,
14029        y: &mut CudaSlice<f32>,
14030        s: f32,
14031        n: usize,
14032    ) -> Result<(), Box<dyn std::error::Error>> {
14033        let f = self.func("scale_f32");
14034        let cfg = LaunchConfig::for_num_elems(n as u32);
14035        let (sf, ni) = (s, n as i32);
14036        let __s_b = self.gpu.stream();
14037        let mut b = __s_b.launch_builder(&f);
14038        b.arg(y).arg(&sf).arg(&ni);
14039        unsafe {
14040            b.launch(cfg)?;
14041        }
14042        Ok(())
14043    }
14044
14045    /// MEMRA_FULL_PREC dequant-on-use: expand a bf16-resident weight (`GpuTensor::FloatBf16`, raw
14046    /// bf16 bytes) to a transient f32 scratch of `n` elements, which then feeds the existing f32
14047    /// cuBLASLt GEMV. The scratch is freed when the caller drops it, so peak VRAM = resident bf16
14048    /// weights + ONE (largest) weight's f32 expansion + activations. SLOW IS FINE (research mode).
14049    pub fn bf16_to_f32(
14050        &self,
14051        data: &cudarc::driver::CudaView<'_, u8>,
14052        n: usize,
14053    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14054        let mut out = self.alloc_uninit::<f32>(n)?;
14055        let f = self.func("bf16_to_f32");
14056        let cfg = LaunchConfig::for_num_elems(n as u32);
14057        let ni = n as i32;
14058        let __s_b = self.gpu.stream();
14059        let mut b = __s_b.launch_builder(&f);
14060        b.arg(data).arg(&mut out).arg(&ni);
14061        unsafe {
14062            b.launch(cfg)?;
14063        }
14064        Ok(out)
14065    }
14066
14067    /// Chunked bf16 linear (MEMRA_FULL_PREC): y[m,out] = x @ W_bf16^T with the f32 dequant scratch
14068    /// bounded to CHUNK_ROWS rows (256MB at in_f=4096) instead of the whole weight — the 4GB
14069    /// lm_head expansion OOM'd the 24GB budget. Row-chunking partitions OUTPUT rows; each row's
14070    /// dot is computed by the identical kernel on identical bytes, so per-(token,row) results are
14071    /// bit-identical to the unchunked form. `exact` selects linear_decode_exact (per-column m=1
14072    /// calls, the spec-verify contract) vs plain linear.
14073    fn linear_bf16_chunked(
14074        &self,
14075        x: &CudaSlice<f32>,
14076        data: &CudaSlice<u8>,
14077        m: usize,
14078        in_f: usize,
14079        out_f: usize,
14080        exact: bool,
14081    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14082        const CHUNK_BYTES: usize = 256 << 20;
14083        let chunk_rows = (CHUNK_BYTES / (in_f * 4)).max(1).min(out_f);
14084        if chunk_rows >= out_f {
14085            let wf32 = self.bf16_to_f32(&data.slice(0..in_f * out_f * 2), in_f * out_f)?;
14086            return if exact {
14087                self.linear_decode_exact(x, &wf32, m, in_f, out_f)
14088            } else {
14089                self.linear(x, &wf32, m, in_f, out_f)
14090            };
14091        }
14092        let mut y = self.alloc_uninit::<f32>(m * out_f)?;
14093        let mut r0 = 0usize;
14094        while r0 < out_f {
14095            let rows = chunk_rows.min(out_f - r0);
14096            let wslice = data.slice(r0 * in_f * 2..(r0 + rows) * in_f * 2);
14097            let wf32 = self.bf16_to_f32(&wslice, in_f * rows)?;
14098            let yc = if exact {
14099                self.linear_decode_exact(x, &wf32, m, in_f, rows)?
14100            } else {
14101                self.linear(x, &wf32, m, in_f, rows)?
14102            };
14103            // scatter [m, rows] into y[m, out_f] at column offset r0 (m is tiny in decode/verify)
14104            for mi in 0..m {
14105                let src = yc.slice(mi * rows..(mi + 1) * rows);
14106                let mut dst = y.slice_mut(mi * out_f + r0..mi * out_f + r0 + rows);
14107                self.gpu.stream().memcpy_dtod(&src, &mut dst)?;
14108            }
14109            r0 += rows;
14110        }
14111        Ok(y)
14112    }
14113
14114    /// On-device linear: y[m,out] = x[m,in] @ W[out,in]^T, weights row-major [out,in] (ggml).
14115    /// cuBLASLt col-major mapping (see memra_runtime::Gpu::linear_f32 for the derivation).
14116    /// DECODE-EXACT float linear: per-column m=1 cuBLASLt calls. cuBLASLt's reduction split is
14117    /// n-dependent (lt_ndep probe: m=1 vs m=2 col0 differs every bit), so spec-verify batches
14118    /// must not batch float matmuls the T=1 decode chain runs at m=1. Used by the small-t MoE
14119    /// router/shexp sites and matmul_decode_exact's Float arm.
14120    pub fn linear_decode_exact(
14121        &self,
14122        x: &CudaSlice<f32>,
14123        w: &CudaSlice<f32>,
14124        m_tokens: usize,
14125        in_f: usize,
14126        out_f: usize,
14127    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14128        if m_tokens == 1 {
14129            return self.linear(x, w, 1, in_f, out_f);
14130        }
14131        let xv = self.view(x, m_tokens * in_f);
14132        let mut y = self.alloc_uninit::<f32>(m_tokens * out_f)?;
14133        for t in 0..m_tokens {
14134            let row = xv.slice(t * in_f..(t + 1) * in_f);
14135            let mut xr = self.alloc_uninit::<f32>(in_f)?;
14136            self.copy_view_into(&mut xr, 0, &row, in_f)?;
14137            let yr = self.linear(&xr, w, 1, in_f, out_f)?;
14138            self.copy_into(&mut y, t * out_f, &yr, out_f)?;
14139        }
14140        Ok(y)
14141    }
14142
14143    pub fn linear(
14144        &self,
14145        x: &CudaSlice<f32>,
14146        w: &CudaSlice<f32>,
14147        m_tokens: usize,
14148        in_f: usize,
14149        out_f: usize,
14150    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
14151        use cudarc::cublaslt::{Matmul, MatmulConfig};
14152        let mut c = self.alloc_uninit::<f32>(m_tokens * out_f)?; // cuBLASLt beta=0: C fully written
14153        let cfg = MatmulConfig {
14154            transa: true,
14155            transb: false,
14156            transc: false,
14157            m: out_f as u64,
14158            n: m_tokens as u64,
14159            k: in_f as u64,
14160            alpha: 1.0,
14161            lda: in_f as i64,
14162            ldb: in_f as i64,
14163            beta: 0.0,
14164            ldc: out_f as i64,
14165            stride_a: None,
14166            stride_b: None,
14167            stride_c: None,
14168            stride_bias: None,
14169            batch_size: None,
14170        };
14171        unsafe {
14172            self.gpu.blas.matmul(cfg, w, x, &mut c, None, None)?;
14173        }
14174        Ok(c)
14175    }
14176
14177    /// Naive SDPA. Q:[head_dim,n_head,T], K/V:[head_dim,n_head_kv,T_kv] -> O:[head_dim,n_head,T].
14178    pub fn sdpa_naive(
14179        &self,
14180        q: &CudaSlice<f32>,
14181        k: &CudaSlice<f32>,
14182        v: &CudaSlice<f32>,
14183        o: &mut CudaSlice<f32>,
14184        head_dim: usize,
14185        n_head: usize,
14186        n_head_kv: usize,
14187        t: usize,
14188        t_kv: usize,
14189        scale: f32,
14190        causal: bool,
14191    ) -> Result<(), Box<dyn std::error::Error>> {
14192        let f = self.func("sdpa_naive_f32");
14193        let cfg = LaunchConfig {
14194            grid_dim: (n_head as u32, t as u32, 1),
14195            block_dim: (128, 1, 1),
14196            shared_mem_bytes: (t_kv * 4) as u32,
14197        };
14198        let (hd, nh, nhkv, ti, tkvi, cz) = (
14199            head_dim as i32,
14200            n_head as i32,
14201            n_head_kv as i32,
14202            t as i32,
14203            t_kv as i32,
14204            causal as i32,
14205        );
14206        let __s_b = self.gpu.stream();
14207        let mut b = __s_b.launch_builder(&f);
14208        b.arg(q)
14209            .arg(k)
14210            .arg(v)
14211            .arg(o)
14212            .arg(&hd)
14213            .arg(&nh)
14214            .arg(&nhkv)
14215            .arg(&ti)
14216            .arg(&tkvi)
14217            .arg(&scale)
14218            .arg(&cz);
14219        unsafe {
14220            b.launch(cfg)?;
14221        }
14222        Ok(())
14223    }
14224
14225    /// Windowed sdpa_naive twin (gemma4 R6): masks keys older than q_pos-(window-1).
14226    #[allow(clippy::too_many_arguments)]
14227    pub fn sdpa_naive_w(
14228        &self,
14229        q: &CudaSlice<f32>,
14230        k: &CudaSlice<f32>,
14231        v: &CudaSlice<f32>,
14232        o: &mut CudaSlice<f32>,
14233        head_dim: usize,
14234        n_head: usize,
14235        n_head_kv: usize,
14236        t: usize,
14237        t_kv: usize,
14238        scale: f32,
14239        causal: bool,
14240        window: usize,
14241    ) -> Result<(), Box<dyn std::error::Error>> {
14242        let f = self.func("sdpa_naive_w_f32");
14243        let cfg = LaunchConfig {
14244            grid_dim: (n_head as u32, t as u32, 1),
14245            block_dim: (128, 1, 1),
14246            shared_mem_bytes: (t_kv * 4) as u32,
14247        };
14248        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14249            head_dim as i32,
14250            n_head as i32,
14251            n_head_kv as i32,
14252            t as i32,
14253            t_kv as i32,
14254            causal as i32,
14255            window as i32,
14256        );
14257        let __s_b = self.gpu.stream();
14258        let mut b = __s_b.launch_builder(&f);
14259        b.arg(q)
14260            .arg(k)
14261            .arg(v)
14262            .arg(o)
14263            .arg(&hd)
14264            .arg(&nh)
14265            .arg(&nhkv)
14266            .arg(&ti)
14267            .arg(&tkvi)
14268            .arg(&scale)
14269            .arg(&cz)
14270            .arg(&wi);
14271        unsafe {
14272            b.launch(cfg)?;
14273        }
14274        Ok(())
14275    }
14276
14277    /// SDPA where K/V are CudaViews into a resident KV cache (decode hot path, no host round-trip).
14278    pub fn sdpa_naive_view(
14279        &self,
14280        q: &CudaSlice<f32>,
14281        k: &cudarc::driver::CudaView<f32>,
14282        v: &cudarc::driver::CudaView<f32>,
14283        o: &mut CudaSlice<f32>,
14284        head_dim: usize,
14285        n_head: usize,
14286        n_head_kv: usize,
14287        t: usize,
14288        t_kv: usize,
14289        scale: f32,
14290        causal: bool,
14291    ) -> Result<(), Box<dyn std::error::Error>> {
14292        let f = self.func("sdpa_naive_f32");
14293        let cfg = LaunchConfig {
14294            grid_dim: (n_head as u32, t as u32, 1),
14295            block_dim: (128, 1, 1),
14296            shared_mem_bytes: (t_kv * 4) as u32,
14297        };
14298        let (hd, nh, nhkv, ti, tkvi, cz) = (
14299            head_dim as i32,
14300            n_head as i32,
14301            n_head_kv as i32,
14302            t as i32,
14303            t_kv as i32,
14304            causal as i32,
14305        );
14306        let __s_b = self.gpu.stream();
14307        let mut b = __s_b.launch_builder(&f);
14308        b.arg(q)
14309            .arg(k)
14310            .arg(v)
14311            .arg(o)
14312            .arg(&hd)
14313            .arg(&nh)
14314            .arg(&nhkv)
14315            .arg(&ti)
14316            .arg(&tkvi)
14317            .arg(&scale)
14318            .arg(&cz);
14319        unsafe {
14320            b.launch(cfg)?;
14321        }
14322        Ok(())
14323    }
14324
14325    /// Correctness fallback for quantized resident K/V views. Dequantizes K and V once into f32
14326    /// workspaces, then calls `sdpa_naive`. This is an explicit API: the optimized prefill view
14327    /// dispatch remains unchanged, so callers can use it as a reference or compatibility path.
14328    /// Dequant a quantized KV view into caller-owned f32 buffers (one grid-stride launch).
14329    /// `g` picks the kf8vf8-module stamp for e4m3 caches (same flag contract as fa_decode/
14330    /// fa_prefill_view). Used by the E4B shared-KV prefill arms (2026-07-31) to feed the
14331    /// f32 fa_prefill_w / fa_prefill_hd512 twins from the target layer's quantized rows.
14332    #[allow(clippy::too_many_arguments)]
14333    pub fn fa_dequant_kv_view_f32(
14334        &self,
14335        k: &cudarc::driver::CudaView<u8>,
14336        v: &cudarc::driver::CudaView<u8>,
14337        kf: &mut CudaSlice<f32>,
14338        vf: &mut CudaSlice<f32>,
14339        kv_dim_k: usize,
14340        kv_dim_v: usize,
14341        t_kv: usize,
14342        k_tok_bytes: usize,
14343        v_tok_bytes: usize,
14344        g: bool,
14345    ) -> Result<(), Box<dyn std::error::Error>> {
14346        let f = if g {
14347            self.func_g("fa_dequant_kv_ws_f32")
14348        } else {
14349            self.func("fa_dequant_kv_ws_f32")
14350        };
14351        let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
14352        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14353        let cfg = LaunchConfig {
14354            grid_dim: (nblk.max(1), 1, 1),
14355            block_dim: (256, 1, 1),
14356            shared_mem_bytes: 0,
14357        };
14358        let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
14359        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
14360        let __s_b = self.gpu.stream();
14361        let mut b = __s_b.launch_builder(&f);
14362        b.arg(k)
14363            .arg(v)
14364            .arg(&mut *kf)
14365            .arg(&mut *vf)
14366            .arg(&kdk)
14367            .arg(&kdv)
14368            .arg(&tkvi)
14369            .arg(&ktb)
14370            .arg(&vtb);
14371        unsafe {
14372            b.launch(cfg)?;
14373        }
14374        Ok(())
14375    }
14376
14377    #[allow(clippy::too_many_arguments)]
14378    pub fn sdpa_naive_quantized_view(
14379        &self,
14380        q: &CudaSlice<f32>,
14381        k: &cudarc::driver::CudaView<u8>,
14382        v: &cudarc::driver::CudaView<u8>,
14383        o: &mut CudaSlice<f32>,
14384        head_dim: usize,
14385        n_head: usize,
14386        n_head_kv: usize,
14387        t: usize,
14388        t_kv: usize,
14389        scale: f32,
14390        causal: bool,
14391        k_tok_bytes: usize,
14392        v_tok_bytes: usize,
14393    ) -> Result<(), Box<dyn std::error::Error>> {
14394        let kv_dim = n_head_kv * head_dim;
14395        let mut kf = self.uninit(t_kv * kv_dim)?;
14396        let mut vf = self.uninit(t_kv * kv_dim)?;
14397        let f = self.func("fa_dequant_kv_ws_f32");
14398        let total = (2 * t_kv * kv_dim) as u64;
14399        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14400        let cfg = LaunchConfig {
14401            grid_dim: (nblk.max(1), 1, 1),
14402            block_dim: (256, 1, 1),
14403            shared_mem_bytes: 0,
14404        };
14405        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14406        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14407        let __s_b = self.gpu.stream();
14408        let mut b = __s_b.launch_builder(&f);
14409        b.arg(k)
14410            .arg(v)
14411            .arg(&mut kf)
14412            .arg(&mut vf)
14413            .arg(&kv_dim_i)
14414            .arg(&kv_dim_i)
14415            .arg(&t_kv_i)
14416            .arg(&k_tok_bytes_i)
14417            .arg(&v_tok_bytes_i);
14418        unsafe { b.launch(cfg)? };
14419        self.sdpa_naive(
14420            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14421        )
14422    }
14423
14424    /// WINDOWED twin of `sdpa_naive_quantized_view` (step35 SWA prefill): dequant the KV byte
14425    /// view into f32 workspaces with the SAME `fa_dequant_kv_ws_f32` launch, then run
14426    /// `sdpa_naive_w` instead of `sdpa_naive`. `window == 0` is the unwindowed form (the kernel
14427    /// treats a non-positive window as "no window mask"), so this is a strict superset of the
14428    /// unwindowed function above and produces bit-identical output at window == 0.
14429    ///
14430    /// Why this exists: EVERY windowed FlashAttention stamp in flash_attn.cu is head_dim-256
14431    /// only (`fa_prefill_w_f32` == `fa_prefill_f32_body<256>`, and the quantized-view windowed
14432    /// twins likewise), while step35 is head_dim 128. Its SWA layers therefore have no windowed
14433    /// FA path and take this f32 floor in v0 — same cache bytes, same numeric class as the
14434    /// unwindowed quantized-view fallback, so the chunk-invariance contract holds on both.
14435    #[allow(clippy::too_many_arguments)]
14436    pub fn sdpa_naive_w_quantized_view(
14437        &self,
14438        q: &CudaSlice<f32>,
14439        k: &cudarc::driver::CudaView<u8>,
14440        v: &cudarc::driver::CudaView<u8>,
14441        o: &mut CudaSlice<f32>,
14442        head_dim: usize,
14443        n_head: usize,
14444        n_head_kv: usize,
14445        t: usize,
14446        t_kv: usize,
14447        scale: f32,
14448        causal: bool,
14449        window: usize,
14450        k_tok_bytes: usize,
14451        v_tok_bytes: usize,
14452    ) -> Result<(), Box<dyn std::error::Error>> {
14453        let kv_dim = n_head_kv * head_dim;
14454        let mut kf = self.uninit(t_kv * kv_dim)?;
14455        let mut vf = self.uninit(t_kv * kv_dim)?;
14456        let f = self.func("fa_dequant_kv_ws_f32");
14457        let total = (2 * t_kv * kv_dim) as u64;
14458        let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
14459        let cfg = LaunchConfig {
14460            grid_dim: (nblk.max(1), 1, 1),
14461            block_dim: (256, 1, 1),
14462            shared_mem_bytes: 0,
14463        };
14464        let (kv_dim_i, t_kv_i) = (kv_dim as i32, t_kv as i32);
14465        let (k_tok_bytes_i, v_tok_bytes_i) = (k_tok_bytes as i64, v_tok_bytes as i64);
14466        let __s_b = self.gpu.stream();
14467        let mut b = __s_b.launch_builder(&f);
14468        b.arg(k)
14469            .arg(v)
14470            .arg(&mut kf)
14471            .arg(&mut vf)
14472            .arg(&kv_dim_i)
14473            .arg(&kv_dim_i)
14474            .arg(&t_kv_i)
14475            .arg(&k_tok_bytes_i)
14476            .arg(&v_tok_bytes_i);
14477        unsafe { b.launch(cfg)? };
14478        self.sdpa_naive_w(
14479            q, &kf, &vf, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
14480        )
14481    }
14482
14483    /// Hand-written FlashAttention prefill (sm_120, FA-2 online softmax on validated mma.sync,
14484    /// head_dim 256 or 128 (template-stamped twins), GQA, causal). Replaces sdpa_naive for T>1.
14485    /// Q/K/V/O [head_dim, n_head(_kv), T].
14486    pub fn fa_prefill(
14487        &self,
14488        q: &CudaSlice<f32>,
14489        k: &CudaSlice<f32>,
14490        v: &CudaSlice<f32>,
14491        o: &mut CudaSlice<f32>,
14492        head_dim: usize,
14493        n_head: usize,
14494        n_head_kv: usize,
14495        t: usize,
14496        t_kv: usize,
14497        scale: f32,
14498        causal: bool,
14499    ) -> Result<(), Box<dyn std::error::Error>> {
14500        if portable_mma_gated() {
14501            return self.sdpa_naive(
14502                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
14503            );
14504        }
14505        // FA3 v10 arm (task #20, OPT-IN MEMRA_FA3=1 — harness-proven 883us vs the shipped
14506        // kernel's 993us at T=2048): TMA-swizzled wgmma FA, fresh causal hd256 only.
14507        // NEW NUMERIC CONFIG (GDN-mma precedent): online softmax / bf16-P class — the
14508        // run-gen argmax + greedy-stream batteries arbitrate; not bit-paired.
14509        // PROMOTED default-ON hopper (2026-07-27): 3-seed 2048-prime -> 128-decode
14510        // streams MATCH vs mma, full battery green, lane interleaved 5/5 (+2.4%).
14511        // MEMRA_FA3=0 reverts; kernel-check pins the mma config regardless.
14512        let fa3_on = head_dim == 256
14513            && causal
14514            && t == t_kv
14515            && match std::env::var("MEMRA_FA3").as_deref() {
14516                Ok("0") => false,
14517                Ok("1") => true,
14518                _ => cfg!(memra_hopper_mma),
14519            };
14520        if fa3_on {
14521            let n = t * n_head * head_dim;
14522            let nkv = t * n_head_kv * head_dim;
14523            let mut q16 = self.alloc_u8_uninit(n * 2)?;
14524            let mut k16 = self.alloc_u8_uninit(nkv * 2)?;
14525            let mut v16 = self.alloc_u8_uninit(nkv * 2)?;
14526            self.f32_to_bf16_into(q, &mut q16, n)?;
14527            self.f32_to_bf16_into(k, &mut k16, nkv)?;
14528            self.f32_to_bf16_into(v, &mut v16, nkv)?;
14529            let rc = {
14530                use cudarc::driver::{DevicePtr, DevicePtrMut};
14531                let stream = self.gpu.stream();
14532                let (qp, _g1) = q16.device_ptr(&stream);
14533                let (kp, _g2) = k16.device_ptr(&stream);
14534                let (vp, _g3) = v16.device_ptr(&stream);
14535                let (op, _g4) = o.device_ptr_mut(&stream);
14536                unsafe {
14537                    memra_fa3_prefill(
14538                        qp as *const core::ffi::c_void,
14539                        kp as *const core::ffi::c_void,
14540                        vp as *const core::ffi::c_void,
14541                        op as *mut f32,
14542                        t as i32,
14543                        n_head as i32,
14544                        n_head_kv as i32,
14545                        head_dim as i32,
14546                        scale,
14547                        stream.cu_stream() as *mut core::ffi::c_void,
14548                    )
14549                }
14550            };
14551            if rc != 0 {
14552                return Err(format!("memra_fa3_prefill rc={rc}").into());
14553            }
14554            return Ok(());
14555        }
14556        // FLOOR PORT (P2+P0a+P0b+P1): 4 warps/CTA, BLOCK_Q=64 query rows, BK=32 KV tile,
14557        // Q-in-reg + register-O, grid.y=n_head_kv (4 Q-heads share staged K/V).
14558        // P1 plain arm (MEMRA_FA_P1=1 opt-in until the qwen battery): the engine-study body
14559        // (FA2 schedule + boundary split + swizzle) on the non-windowed lane. bf16 pre-convert.
14560        static FA_P1: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14561        let fa_p1 = *FA_P1.get_or_init(|| std::env::var("MEMRA_FA_P1").as_deref() == Ok("1"));
14562        if fa_p1 && head_dim == 256 && !std::env::var("MEMRA_FA_FLOOR").is_ok() {
14563            const BLOCK_Q: usize = 64;
14564            const BKX: usize = 32;
14565            let f = self.func("fa_prefill_bf16_p1");
14566            let shmem = (2 * (2 * BKX * head_dim + BLOCK_Q * BKX)
14567                + 4 * (BLOCK_Q * BKX + 2 * BLOCK_Q)) as u32;
14568            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14569            f.set_attribute(
14570                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14571                shmem as i32,
14572            )?;
14573            let cfg = LaunchConfig {
14574                grid_dim: (
14575                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14576                    n_head as u32,
14577                    1,
14578                ),
14579                block_dim: (32, 4, 1),
14580                shared_mem_bytes: shmem,
14581            };
14582            let (hd, nh, nhkv, ti, tkvi, cz) = (
14583                head_dim as i32,
14584                n_head as i32,
14585                n_head_kv as i32,
14586                t as i32,
14587                t_kv as i32,
14588                causal as i32,
14589            );
14590            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
14591            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
14592            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
14593            let __s_b = self.gpu.stream();
14594            let mut b = __s_b.launch_builder(&f);
14595            b.arg(&qb)
14596                .arg(&kb)
14597                .arg(&vb)
14598                .arg(o)
14599                .arg(&hd)
14600                .arg(&nh)
14601                .arg(&nhkv)
14602                .arg(&ti)
14603                .arg(&tkvi)
14604                .arg(&scale)
14605                .arg(&cz);
14606            unsafe {
14607                b.launch(cfg)?;
14608            }
14609            return Ok(());
14610        }
14611        // Edge 5a (DEFAULT): fa_prefill_f32_pp — register-resident softmax (no sSw smem
14612        // round-trip), the FA3 softmax-GEMM overlap variant. ncu (pp512): short_scoreboard
14613        // 4.32->3.47, wait 1.99->1.45, per-call ~577us->~440us (1.31x) at flat 12.1% warps /
14614        // 255 regs / 2 CTAs (occupancy preserved). Bit-safe: 9B+27B argmax MATCH, rel 2.55e-3
14615        // vs floor 3.03e-3. MEMRA_FA_FLOOR reverts to the serialized-softmax floor kernel.
14616        const BK: usize = 32;
14617        // W2 lane (MEMRA_FA_PP_W2=1, ncu 2026-07-26): 2-warp/32-row CTA tile doubles grid.x —
14618        // bit-identical per-row math, pure coverage trade for the 6.25%-occupancy starvation.
14619        let w2 = std::env::var("MEMRA_FA_PP_W2").as_deref() == Ok("1");
14620        let (block_q, warps, w2_sfx): (usize, u32, &str) =
14621            if w2 { (32, 2, "_w2") } else { (64, 4, "") };
14622        // hd128 twins (2026-07-07): the prefill kernels are template-stamped at 256 (original
14623        // names, dispatch unchanged) and 128 (`_hd128`, the MiniMax-M3 class). Callers gate
14624        // other head_dims to sdpa_naive before reaching here.
14625        let hd_sfx = fa_hd_suffix(head_dim)?;
14626        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
14627        // BF16-KV staging lane (2026-07-26, default ON): the kernel converts K/V to bf16
14628        // during staging anyway — pre-converting to bf16 mirrors is BIT-IDENTICAL (same
14629        // __float2bfloat16 values into the same mma) and turns the 67%-of-stalls scalar
14630        // staging into int4 vector copies. MEMRA_FA_BF16KV=0 reverts.
14631        let bf16kv = !floor && !w2 && std::env::var("MEMRA_FA_BF16KV").as_deref() != Ok("0");
14632        let (kb16, vb16) = if bf16kv {
14633            let n = t_kv * n_head_kv * head_dim;
14634            let mut kb = self.alloc_u8_uninit(n * 2)?;
14635            let mut vb = self.alloc_u8_uninit(n * 2)?;
14636            let fcv = self.func("f32_to_bf16_bulk");
14637            let ni = n as i64;
14638            let cfgc = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
14639            let __s_b = self.gpu.stream();
14640            let mut b = __s_b.launch_builder(&fcv);
14641            b.arg(k).arg(&mut kb).arg(&ni);
14642            unsafe {
14643                b.launch(cfgc)?;
14644            }
14645            let __s_b = self.gpu.stream();
14646            let mut b = __s_b.launch_builder(&fcv);
14647            b.arg(v).arg(&mut vb).arg(&ni);
14648            unsafe {
14649                b.launch(cfgc)?;
14650            }
14651            (Some(kb), Some(vb))
14652        } else {
14653            (None, None)
14654        };
14655        let f = self.func(&if bf16kv {
14656            format!("fa_prefill_bf16kv_pp{hd_sfx}")
14657        } else {
14658            format!(
14659                "fa_prefill_f32{}{}{hd_sfx}",
14660                if floor { "" } else { "_pp" },
14661                if floor { "" } else { w2_sfx }
14662            )
14663        });
14664        // persistent smem: bf16*(KV_STAGES*(sK + sV) + sP) + f32*(sS + sM + sL);
14665        // the bf16kv ring doubles the K/V stages (KV_STAGES=2).
14666        let kv_stages = if bf16kv { 2 } else { 1 };
14667        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
14668            + 4 * (block_q * BK + 2 * block_q)) as u32;
14669        use cudarc::driver::sys::CUfunction_attribute_enum as A;
14670        f.set_attribute(
14671            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14672            shmem as i32,
14673        )?;
14674        let cfg = LaunchConfig {
14675            grid_dim: (
14676                (t as u32 + block_q as u32 - 1) / block_q as u32,
14677                n_head as u32,
14678                1,
14679            ),
14680            block_dim: (32, warps, 1),
14681            shared_mem_bytes: shmem,
14682        };
14683        let (hd, nh, nhkv, ti, tkvi, cz) = (
14684            head_dim as i32,
14685            n_head as i32,
14686            n_head_kv as i32,
14687            t as i32,
14688            t_kv as i32,
14689            causal as i32,
14690        );
14691        let __s_b = self.gpu.stream();
14692        let mut b = __s_b.launch_builder(&f);
14693        b.arg(q);
14694        match (&kb16, &vb16) {
14695            (Some(kb), Some(vb)) => {
14696                b.arg(kb).arg(vb);
14697            }
14698            _ => {
14699                b.arg(k).arg(v);
14700            }
14701        }
14702        b.arg(o)
14703            .arg(&hd)
14704            .arg(&nh)
14705            .arg(&nhkv)
14706            .arg(&ti)
14707            .arg(&tkvi)
14708            .arg(&scale)
14709            .arg(&cz);
14710        unsafe {
14711            b.launch(cfg)?;
14712        }
14713        Ok(())
14714    }
14715
14716    /// Windowed FA prefill (gemma4 SWA layers past the sliding window, hd256): fa_prefill's
14717    /// exact dispatch (pp default, MEMRA_FA_FLOOR seam) with the sliding-window mask + tile
14718    /// skip in-kernel. Replaces the O(T*T_kv) scalar sdpa_naive_w on the prime path.
14719    #[allow(clippy::too_many_arguments)]
14720    pub fn fa_prefill_w(
14721        &self,
14722        q: &CudaSlice<f32>,
14723        k: &CudaSlice<f32>,
14724        v: &CudaSlice<f32>,
14725        o: &mut CudaSlice<f32>,
14726        head_dim: usize,
14727        n_head: usize,
14728        n_head_kv: usize,
14729        t: usize,
14730        t_kv: usize,
14731        scale: f32,
14732        causal: bool,
14733        window: usize,
14734    ) -> Result<(), Box<dyn std::error::Error>> {
14735        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — the raw
14736        // portable_cuda gate was stale-conservative on Hopper; fa_prefill already flipped).
14737        if portable_mma_gated() {
14738            return self.sdpa_naive_w(
14739                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal, window,
14740            );
14741        }
14742        // Default: bf16-prestaged twin (same treatment as hd512 — Q/K/V pre-converted once,
14743        // int4 stage copies; bit-identical, kernel_check-gated). MEMRA_FAW_STAGE=f32 reverts;
14744        // MEMRA_FA_FLOOR keeps the f32 floor stamp untouched.
14745        static FAW_F32: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14746        let faw_f32 =
14747            *FAW_F32.get_or_init(|| std::env::var("MEMRA_FAW_STAGE").as_deref() == Ok("f32"));
14748        let floor = std::env::var("MEMRA_FA_FLOOR").is_ok();
14749        self.fa_prefill_w_arm(
14750            q,
14751            k,
14752            v,
14753            o,
14754            head_dim,
14755            n_head,
14756            n_head_kv,
14757            t,
14758            t_kv,
14759            scale,
14760            causal,
14761            window,
14762            floor || faw_f32,
14763            floor,
14764        )
14765    }
14766
14767    /// Windowed FA prefill with PRE-CONVERTED bf16 operands (producer-emitted; 31B glue lane).
14768    /// Launches the P1 stamp directly — callers guarantee qb/kb/vb hold the exact bf16 of q/k/v.
14769    #[allow(clippy::too_many_arguments)]
14770    pub fn fa_prefill_w_pre(
14771        &self,
14772        qb: &CudaSlice<u8>,
14773        kb: &CudaSlice<u8>,
14774        vb: &CudaSlice<u8>,
14775        o: &mut CudaSlice<f32>,
14776        head_dim: usize,
14777        n_head: usize,
14778        n_head_kv: usize,
14779        t: usize,
14780        t_kv: usize,
14781        scale: f32,
14782        causal: bool,
14783        window: usize,
14784        v_f16: bool,
14785    ) -> Result<(), Box<dyn std::error::Error>> {
14786        const BLOCK_Q: usize = 64;
14787        const BK: usize = 32;
14788        debug_assert_eq!(head_dim, 256);
14789        let hp = fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
14790        debug_assert!(!v_f16 || hp, "f16 V emitted but the SWA hp arm is off");
14791        if hp {
14792            const BLOCK_QH: usize = 32;
14793            // V bytes must be f16 for the h2 stamp; producer normally emits f16 (v_f16),
14794            // else re-encode through the pooled scratch (stream-ordered reuse).
14795            let mut vguard = self.fa_vf16_scratch.lock().unwrap();
14796            let vh: &CudaSlice<u8> = if v_f16 {
14797                vb
14798            } else {
14799                let n = t_kv * n_head_kv * head_dim;
14800                if vguard.as_ref().map(|b| b.len() < n * 2).unwrap_or(true) {
14801                    *vguard = Some(self.alloc_uninit::<u8>(n * 2)?);
14802                }
14803                self.bf16_to_f16_into(vb, n, vguard.as_mut().unwrap())?;
14804                vguard.as_ref().unwrap()
14805            };
14806            let f = self.func("fa_prefill_w_bf16_p1h2");
14807            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
14808            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14809            f.set_attribute(
14810                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14811                shmem as i32,
14812            )?;
14813            let cfg = LaunchConfig {
14814                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
14815                block_dim: (32, 4, 1),
14816                shared_mem_bytes: shmem,
14817            };
14818            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
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                window as i32,
14826            );
14827            let __s_b = self.gpu.stream();
14828            let mut b = __s_b.launch_builder(&f);
14829            b.arg(qb)
14830                .arg(kb)
14831                .arg(vh)
14832                .arg(o)
14833                .arg(&hd)
14834                .arg(&nh)
14835                .arg(&nhkv)
14836                .arg(&ti)
14837                .arg(&tkvi)
14838                .arg(&scale)
14839                .arg(&cz)
14840                .arg(&wi);
14841            unsafe {
14842                b.launch(cfg)?;
14843            }
14844            return Ok(());
14845        }
14846        let f = self.func("fa_prefill_w_bf16_p1");
14847        let shmem =
14848            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
14849        use cudarc::driver::sys::CUfunction_attribute_enum as A;
14850        f.set_attribute(
14851            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14852            shmem as i32,
14853        )?;
14854        let cfg = LaunchConfig {
14855            grid_dim: (
14856                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14857                n_head as u32,
14858                1,
14859            ),
14860            block_dim: (32, 4, 1),
14861            shared_mem_bytes: shmem,
14862        };
14863        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14864            head_dim as i32,
14865            n_head as i32,
14866            n_head_kv as i32,
14867            t as i32,
14868            t_kv as i32,
14869            causal as i32,
14870            window as i32,
14871        );
14872        let __s_b = self.gpu.stream();
14873        let mut b = __s_b.launch_builder(&f);
14874        b.arg(qb)
14875            .arg(kb)
14876            .arg(vb)
14877            .arg(o)
14878            .arg(&hd)
14879            .arg(&nh)
14880            .arg(&nhkv)
14881            .arg(&ti)
14882            .arg(&tkvi)
14883            .arg(&scale)
14884            .arg(&cz)
14885            .arg(&wi);
14886        unsafe {
14887            b.launch(cfg)?;
14888        }
14889        Ok(())
14890    }
14891
14892    /// Windowed FA prefill with the stage arm FORCED — the kernel_check bit-identity entry.
14893    #[allow(clippy::too_many_arguments)]
14894    pub fn fa_prefill_w_arm(
14895        &self,
14896        q: &CudaSlice<f32>,
14897        k: &CudaSlice<f32>,
14898        v: &CudaSlice<f32>,
14899        o: &mut CudaSlice<f32>,
14900        head_dim: usize,
14901        n_head: usize,
14902        n_head_kv: usize,
14903        t: usize,
14904        t_kv: usize,
14905        scale: f32,
14906        causal: bool,
14907        window: usize,
14908        f32_stage: bool,
14909        floor: bool,
14910    ) -> Result<(), Box<dyn std::error::Error>> {
14911        const BLOCK_Q: usize = 64;
14912        const BK: usize = 32;
14913        debug_assert_eq!(head_dim, 256, "fa_prefill_w is stamped hd256 only");
14914        // P1 (2026-07-22 engine study): per-head Br=64 stamp with the FA2 schedule (V-copy
14915        // over GEMM0, next-K over softmax+GEMM1) + boundary/interior mask split. FP order
14916        // preserved -> bit-identical (gated). MEMRA_FAW_P1=0 reverts to the g4/o2 arms.
14917        static P1_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
14918        let p1 = !floor
14919            && !f32_stage
14920            && *P1_ON.get_or_init(|| {
14921                std::env::var("MEMRA_FAW_P1")
14922                    .map(|v| v != "0")
14923                    .unwrap_or(true)
14924            });
14925        let hp =
14926            p1 && fa_f16pv_on() && faw_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
14927        if hp {
14928            const BLOCK_QH: usize = 32;
14929            let f = self.func("fa_prefill_w_bf16_p1h2");
14930            let shmem = (2 * (2 * BK * head_dim + 2 * BLOCK_QH * BK) + 4 * (2 * BLOCK_QH)) as u32;
14931            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14932            f.set_attribute(
14933                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14934                shmem as i32,
14935            )?;
14936            let cfg = LaunchConfig {
14937                grid_dim: ((t as u32).div_ceil(BLOCK_QH as u32), (n_head / 2) as u32, 1),
14938                block_dim: (32, 4, 1),
14939                shared_mem_bytes: shmem,
14940            };
14941            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14942                head_dim as i32,
14943                n_head as i32,
14944                n_head_kv as i32,
14945                t as i32,
14946                t_kv as i32,
14947                causal as i32,
14948                window as i32,
14949            );
14950            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
14951            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
14952            let vh = self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?;
14953            let __s_b = self.gpu.stream();
14954            let mut b = __s_b.launch_builder(&f);
14955            b.arg(&qb)
14956                .arg(&kb)
14957                .arg(&vh)
14958                .arg(o)
14959                .arg(&hd)
14960                .arg(&nh)
14961                .arg(&nhkv)
14962                .arg(&ti)
14963                .arg(&tkvi)
14964                .arg(&scale)
14965                .arg(&cz)
14966                .arg(&wi);
14967            unsafe {
14968                b.launch(cfg)?;
14969            }
14970            return Ok(());
14971        }
14972        if p1 {
14973            let f = self.func("fa_prefill_w_bf16_p1");
14974            let shmem =
14975                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
14976            use cudarc::driver::sys::CUfunction_attribute_enum as A;
14977            f.set_attribute(
14978                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
14979                shmem as i32,
14980            )?;
14981            let cfg = LaunchConfig {
14982                grid_dim: (
14983                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
14984                    n_head as u32,
14985                    1,
14986                ),
14987                block_dim: (32, 4, 1),
14988                shared_mem_bytes: shmem,
14989            };
14990            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
14991                head_dim as i32,
14992                n_head as i32,
14993                n_head_kv as i32,
14994                t as i32,
14995                t_kv as i32,
14996                causal as i32,
14997                window as i32,
14998            );
14999            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15000            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15001            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15002            let __s_b = self.gpu.stream();
15003            let mut b = __s_b.launch_builder(&f);
15004            b.arg(&qb)
15005                .arg(&kb)
15006                .arg(&vb)
15007                .arg(o)
15008                .arg(&hd)
15009                .arg(&nh)
15010                .arg(&nhkv)
15011                .arg(&ti)
15012                .arg(&tkvi)
15013                .arg(&scale)
15014                .arg(&cz)
15015                .arg(&wi);
15016            unsafe {
15017                b.launch(cfg)?;
15018            }
15019            return Ok(());
15020        }
15021        // MQA head-grouping (MEMRA_FAW_G4=0 reverts): 4 heads/CTA share the staged K/V —
15022        // per-(head,row) FP chain identical to the per-head stamp -> bit-identical (gated).
15023        static G4_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15024        let g4 = !floor
15025            && !f32_stage
15026            && n_head_kv == 1
15027            && n_head % 4 == 0
15028            && *G4_ON.get_or_init(|| {
15029                std::env::var("MEMRA_FAW_G4")
15030                    .map(|v| v != "0")
15031                    .unwrap_or(true)
15032            });
15033        if g4 {
15034            const SP_M: usize = 16;
15035            // Occupancy-2 twin (MEMRA_FAW_O2=0 reverts): one shared K/V buffer inside the dead
15036            // Q-stage region -> ~36.5KB smem, 2 CTA/SM (the llama hd256 mechanism). Bit-identical.
15037            static O2_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15038            let o2 = *O2_ON.get_or_init(|| {
15039                std::env::var("MEMRA_FAW_O2")
15040                    .map(|v| v != "0")
15041                    .unwrap_or(true)
15042            });
15043            let f = self.func(if o2 {
15044                "fa_prefill_w_bf16_g4o2"
15045            } else {
15046                "fa_prefill_w_bf16_g4"
15047            });
15048            let shmem = if o2 {
15049                (2 * (4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M)) as u32
15050            } else {
15051                (2 * (2 * BK * head_dim + 4 * SP_M * head_dim + 4 * SP_M * BK) + 4 * (4 * SP_M))
15052                    as u32
15053            };
15054            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15055            f.set_attribute(
15056                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15057                shmem as i32,
15058            )?;
15059            let cfg = LaunchConfig {
15060                grid_dim: ((t as u32).div_ceil(SP_M as u32), (n_head / 4) as u32, 1),
15061                block_dim: (32, 4, 1),
15062                shared_mem_bytes: shmem,
15063            };
15064            let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15065                head_dim as i32,
15066                n_head as i32,
15067                n_head_kv as i32,
15068                t as i32,
15069                t_kv as i32,
15070                causal as i32,
15071                window as i32,
15072            );
15073            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15074            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15075            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15076            let __s_b = self.gpu.stream();
15077            let mut b = __s_b.launch_builder(&f);
15078            b.arg(&qb)
15079                .arg(&kb)
15080                .arg(&vb)
15081                .arg(o)
15082                .arg(&hd)
15083                .arg(&nh)
15084                .arg(&nhkv)
15085                .arg(&ti)
15086                .arg(&tkvi)
15087                .arg(&scale)
15088                .arg(&cz)
15089                .arg(&wi);
15090            unsafe {
15091                b.launch(cfg)?;
15092            }
15093            return Ok(());
15094        }
15095        let f = self.func(if floor {
15096            "fa_prefill_w_f32"
15097        } else if f32_stage {
15098            "fa_prefill_w_f32_pp"
15099        } else {
15100            "fa_prefill_w_bf16_pp"
15101        });
15102        let shmem =
15103            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15104        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15105        f.set_attribute(
15106            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15107            shmem as i32,
15108        )?;
15109        let cfg = LaunchConfig {
15110            grid_dim: (
15111                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15112                n_head as u32,
15113                1,
15114            ),
15115            block_dim: (32, 4, 1),
15116            shared_mem_bytes: shmem,
15117        };
15118        let (hd, nh, nhkv, ti, tkvi, cz, wi) = (
15119            head_dim as i32,
15120            n_head as i32,
15121            n_head_kv as i32,
15122            t as i32,
15123            t_kv as i32,
15124            causal as i32,
15125            window as i32,
15126        );
15127        if f32_stage {
15128            let __s_b = self.gpu.stream();
15129            let mut b = __s_b.launch_builder(&f);
15130            b.arg(q)
15131                .arg(k)
15132                .arg(v)
15133                .arg(o)
15134                .arg(&hd)
15135                .arg(&nh)
15136                .arg(&nhkv)
15137                .arg(&ti)
15138                .arg(&tkvi)
15139                .arg(&scale)
15140                .arg(&cz)
15141                .arg(&wi);
15142            unsafe {
15143                b.launch(cfg)?;
15144            }
15145        } else {
15146            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15147            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15148            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15149            let __s_b = self.gpu.stream();
15150            let mut b = __s_b.launch_builder(&f);
15151            b.arg(&qb)
15152                .arg(&kb)
15153                .arg(&vb)
15154                .arg(o)
15155                .arg(&hd)
15156                .arg(&nh)
15157                .arg(&nhkv)
15158                .arg(&ti)
15159                .arg(&tkvi)
15160                .arg(&scale)
15161                .arg(&cz)
15162                .arg(&wi);
15163            unsafe {
15164                b.launch(cfg)?;
15165            }
15166        }
15167        Ok(())
15168    }
15169
15170    /// hd512 FA prefill (gemma4 GLOBAL layers): BLOCK_Q=32 x 2 warps, Q staged in smem,
15171    /// grid.z = 2 O-halves (each CTA computes the full 512-dim scores, accumulates half the
15172    /// V dims). Replaces the scalar sdpa_naive on the prime path's globals.
15173    #[allow(clippy::too_many_arguments)]
15174    pub fn fa_prefill_hd512(
15175        &self,
15176        q: &CudaSlice<f32>,
15177        k: &CudaSlice<f32>,
15178        v: &CudaSlice<f32>,
15179        o: &mut CudaSlice<f32>,
15180        head_dim: usize,
15181        n_head: usize,
15182        n_head_kv: usize,
15183        t: usize,
15184        t_kv: usize,
15185        scale: f32,
15186        causal: bool,
15187    ) -> Result<(), Box<dyn std::error::Error>> {
15188        // sm_90a rides the mma twins (portable_mma_gated, 2026-07-31 — same flip as _w).
15189        if portable_mma_gated() {
15190            return self.sdpa_naive(
15191                q, k, v, o, head_dim, n_head, n_head_kv, t, t_kv, scale, causal,
15192            );
15193        }
15194        // Default: pre-convert Q/K/V to bf16 once and stage int4 (8 bf16/copy) — at 1 CTA/SM the
15195        // synchronous stage serializes with compute and MQA re-stages the same K/V per head CTA;
15196        // pre-converting halves staged bytes and cuts stage instructions 8x. BIT-IDENTICAL to the
15197        // f32-staged kernel (the converter applies the same __float2bfloat16 the stage applied;
15198        // kernel_check gates the identity). MEMRA_FA512_STAGE=f32 = rollback to the f32 kernel.
15199        static F32_STAGE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15200        let f32_stage =
15201            *F32_STAGE.get_or_init(|| std::env::var("MEMRA_FA512_STAGE").as_deref() == Ok("f32"));
15202        // Single-pass arm (MEMRA_FA512_SP=0 reverts to the z=2 bf16 kernel): GEMM0 split-K across
15203        // the 2 warps instead of recomputed per O-half CTA — the 2026-07-22 kernel-diff excess.
15204        // Own numeric config (partial-sum order) — battery-gated.
15205        static SP_ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
15206        let sp = !f32_stage
15207            && *SP_ON.get_or_init(|| {
15208                std::env::var("MEMRA_FA512_SP")
15209                    .map(|v| v != "0")
15210                    .unwrap_or(true)
15211            });
15212        self.fa_prefill_hd512_arm(
15213            q,
15214            k,
15215            v,
15216            o,
15217            head_dim,
15218            n_head,
15219            n_head_kv,
15220            t,
15221            t_kv,
15222            scale,
15223            causal,
15224            f32_stage,
15225            sp,
15226            sp && fa_f16pv_on(),
15227        )
15228    }
15229
15230    /// hd512 single-pass FA with PRE-CONVERTED bf16 operands (producer-emitted).
15231    #[allow(clippy::too_many_arguments)]
15232    pub fn fa_prefill_hd512_pre(
15233        &self,
15234        qb: &CudaSlice<u8>,
15235        kb: &CudaSlice<u8>,
15236        vb: &CudaSlice<u8>,
15237        o: &mut CudaSlice<f32>,
15238        head_dim: usize,
15239        n_head: usize,
15240        n_head_kv: usize,
15241        t: usize,
15242        t_kv: usize,
15243        scale: f32,
15244        causal: bool,
15245        v_f16: bool,
15246    ) -> Result<(), Box<dyn std::error::Error>> {
15247        debug_assert_eq!(head_dim, 512);
15248        const SP_M: usize = 16;
15249        const BKS: usize = 32;
15250        // f16-P/V door (MEMRA_FA_F16PV=1): P and the P@V accumulation in f16 (llama's fa=1 VKQ
15251        // class); KQ/softmax/rescale-band/final-normalize stay f32. Own numeric config —
15252        // battery-gated. V bytes must be f16 for the sp16 kernel (stage/ldmatrix are typeless).
15253        let f16pv = fa_f16pv_on();
15254        let nw = if f16pv { fa512_wide_warps() } else { 2 };
15255        let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15256        debug_assert!(!v_f16 || f16pv, "f16 V emitted without the door on");
15257        let mut vguard = self.fa_vf16_scratch.lock().unwrap();
15258        let vref: &CudaSlice<u8> = if f16pv && !v_f16 {
15259            // Fallback re-encode (producer emitted bf16); the emit lane normally hands f16.
15260            let n = t_kv * n_head_kv * head_dim;
15261            let need = n * 2;
15262            if vguard.as_ref().map(|b| b.len() < need).unwrap_or(true) {
15263                *vguard = Some(self.alloc_uninit::<u8>(need)?);
15264            }
15265            let dst = vguard.as_mut().unwrap();
15266            self.bf16_to_f16_into(vb, n, dst)?;
15267            vguard.as_ref().unwrap()
15268        } else {
15269            vb
15270        };
15271        let f = self.func(if hp {
15272            "fa_prefill_bf16_hd512_sp16h2"
15273        } else {
15274            match (f16pv, nw) {
15275                (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15276                (true, _) => "fa_prefill_bf16_hd512_sp16",
15277                _ => "fa_prefill_bf16_hd512_sp",
15278            }
15279        });
15280        let (nwarp, npart) = if hp {
15281            (4usize, 4usize)
15282        } else if nw > 2 {
15283            (nw, nw)
15284        } else {
15285            (2, 1)
15286        };
15287        // h2 drops sQ (Q register-resident) and doubles sP/sS/sL for the head pair.
15288        let shmem = if hp {
15289            (2 * (2 * BKS * head_dim + 2 * SP_M * BKS) + 4 * (2 * npart * SP_M * BKS + 2 * SP_M))
15290                as u32
15291        } else {
15292            (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15293                + 4 * (npart * SP_M * BKS + SP_M)) as u32
15294        };
15295        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15296        f.set_attribute(
15297            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15298            shmem as i32,
15299        )?;
15300        let grid_y = if hp {
15301            (n_head / 2) as u32
15302        } else {
15303            n_head as u32
15304        };
15305        let cfg = LaunchConfig {
15306            grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15307            block_dim: (32, nwarp as u32, 1),
15308            shared_mem_bytes: shmem,
15309        };
15310        let (hd, nh, nhkv, ti, tkvi, cz) = (
15311            head_dim as i32,
15312            n_head as i32,
15313            n_head_kv as i32,
15314            t as i32,
15315            t_kv as i32,
15316            causal as i32,
15317        );
15318        let __s_b = self.gpu.stream();
15319        let mut b = __s_b.launch_builder(&f);
15320        b.arg(qb)
15321            .arg(kb)
15322            .arg(vref)
15323            .arg(o)
15324            .arg(&hd)
15325            .arg(&nh)
15326            .arg(&nhkv)
15327            .arg(&ti)
15328            .arg(&tkvi)
15329            .arg(&scale)
15330            .arg(&cz);
15331        unsafe {
15332            b.launch(cfg)?;
15333        }
15334        Ok(())
15335    }
15336
15337    /// hd512 FA prefill with the stage/sp arms FORCED — the kernel_check gate entry
15338    /// (`fa_prefill_hd512` picks the arms from MEMRA_FA512_STAGE / MEMRA_FA512_SP).
15339    #[allow(clippy::too_many_arguments)]
15340    pub fn fa_prefill_hd512_arm(
15341        &self,
15342        q: &CudaSlice<f32>,
15343        k: &CudaSlice<f32>,
15344        v: &CudaSlice<f32>,
15345        o: &mut CudaSlice<f32>,
15346        head_dim: usize,
15347        n_head: usize,
15348        n_head_kv: usize,
15349        t: usize,
15350        t_kv: usize,
15351        scale: f32,
15352        causal: bool,
15353        f32_stage: bool,
15354        sp: bool,
15355        f16pv: bool,
15356    ) -> Result<(), Box<dyn std::error::Error>> {
15357        debug_assert_eq!(head_dim, 512, "fa_prefill_hd512 is hd512 only");
15358        if sp && !f32_stage {
15359            // Single-pass: 16 q-rows/CTA, 2 warps, grid (ceil(T/16), n_head, 1).
15360            // smem: sQ[16][512] + sK[32][512] + sV[32][512] + sP[16][32] (bf16) + sS[16][32]+sL f32.
15361            // f16pv: sp16 kernel — f16 P + f16 P@V accum, V operand encoded f16.
15362            const SP_M: usize = 16;
15363            const BKS: usize = 32;
15364            let nw = if f16pv { fa512_wide_warps() } else { 2 };
15365            let hp = f16pv && fa512_hp_on() && n_head % 2 == 0 && (n_head / n_head_kv) % 2 == 0;
15366            let f = self.func(if hp {
15367                "fa_prefill_bf16_hd512_sp16h2"
15368            } else {
15369                match (f16pv, nw) {
15370                    (true, 4) => "fa_prefill_bf16_hd512_sp16w4",
15371                    (true, _) => "fa_prefill_bf16_hd512_sp16",
15372                    _ => "fa_prefill_bf16_hd512_sp",
15373                }
15374            });
15375            let (nwarp, npart) = if hp {
15376                (4usize, 4usize)
15377            } else if nw > 2 {
15378                (nw, nw)
15379            } else {
15380                (2, 1)
15381            };
15382            let shmem = if hp {
15383                (2 * (2 * BKS * head_dim + 2 * SP_M * BKS)
15384                    + 4 * (2 * npart * SP_M * BKS + 2 * SP_M)) as u32
15385            } else {
15386                (2 * (SP_M * head_dim + 2 * BKS * head_dim + SP_M * BKS)
15387                    + 4 * (npart * SP_M * BKS + SP_M)) as u32
15388            };
15389            use cudarc::driver::sys::CUfunction_attribute_enum as A;
15390            f.set_attribute(
15391                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15392                shmem as i32,
15393            )?;
15394            let grid_y = if hp {
15395                (n_head / 2) as u32
15396            } else {
15397                n_head as u32
15398            };
15399            let cfg = LaunchConfig {
15400                grid_dim: ((t as u32).div_ceil(SP_M as u32), grid_y, 1),
15401                block_dim: (32, nwarp as u32, 1),
15402                shared_mem_bytes: shmem,
15403            };
15404            let (hd, nh, nhkv, ti, tkvi, cz) = (
15405                head_dim as i32,
15406                n_head as i32,
15407                n_head_kv as i32,
15408                t as i32,
15409                t_kv as i32,
15410                causal as i32,
15411            );
15412            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15413            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15414            let vb = if f16pv {
15415                self.f32_to_f16(v, t_kv * n_head_kv * head_dim)?
15416            } else {
15417                self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?
15418            };
15419            let __s_b = self.gpu.stream();
15420            let mut b = __s_b.launch_builder(&f);
15421            b.arg(&qb)
15422                .arg(&kb)
15423                .arg(&vb)
15424                .arg(o)
15425                .arg(&hd)
15426                .arg(&nh)
15427                .arg(&nhkv)
15428                .arg(&ti)
15429                .arg(&tkvi)
15430                .arg(&scale)
15431                .arg(&cz);
15432            unsafe {
15433                b.launch(cfg)?;
15434            }
15435            return Ok(());
15436        }
15437        const BLOCK_Q: usize = 32;
15438        const BK: usize = 32;
15439        const HALF: usize = 256;
15440        let f = self.func(if f32_stage {
15441            "fa_prefill_f32_hd512"
15442        } else {
15443            "fa_prefill_bf16_hd512"
15444        });
15445        // sQ[32][512] + sK[BK][512] + sV[BK][256] + sP[32][BK] (bf16) + sL[32] f32
15446        let shmem = (2 * (BLOCK_Q * head_dim + BK * head_dim + BK * HALF + BLOCK_Q * BK)
15447            + 4 * BLOCK_Q) as u32;
15448        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15449        f.set_attribute(
15450            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15451            shmem as i32,
15452        )?;
15453        let cfg = LaunchConfig {
15454            grid_dim: (
15455                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15456                n_head as u32,
15457                2,
15458            ),
15459            block_dim: (32, 2, 1),
15460            shared_mem_bytes: shmem,
15461        };
15462        let (hd, nh, nhkv, ti, tkvi, cz) = (
15463            head_dim as i32,
15464            n_head as i32,
15465            n_head_kv as i32,
15466            t as i32,
15467            t_kv as i32,
15468            causal as i32,
15469        );
15470        if f32_stage {
15471            let __s_b = self.gpu.stream();
15472            let mut b = __s_b.launch_builder(&f);
15473            b.arg(q)
15474                .arg(k)
15475                .arg(v)
15476                .arg(o)
15477                .arg(&hd)
15478                .arg(&nh)
15479                .arg(&nhkv)
15480                .arg(&ti)
15481                .arg(&tkvi)
15482                .arg(&scale)
15483                .arg(&cz);
15484            unsafe {
15485                b.launch(cfg)?;
15486            }
15487        } else {
15488            let qb = self.f32_to_bf16(q, t * n_head * head_dim)?;
15489            let kb = self.f32_to_bf16(k, t_kv * n_head_kv * head_dim)?;
15490            let vb = self.f32_to_bf16(v, t_kv * n_head_kv * head_dim)?;
15491            let __s_b = self.gpu.stream();
15492            let mut b = __s_b.launch_builder(&f);
15493            b.arg(&qb)
15494                .arg(&kb)
15495                .arg(&vb)
15496                .arg(o)
15497                .arg(&hd)
15498                .arg(&nh)
15499                .arg(&nhkv)
15500                .arg(&ti)
15501                .arg(&tkvi)
15502                .arg(&scale)
15503                .arg(&cz);
15504            unsafe {
15505                b.launch(cfg)?;
15506            }
15507        }
15508        Ok(())
15509    }
15510
15511    /// rope_neox2 with bf16 EMIT (31B glue lane): identical rope math/stores plus the post-rope
15512    /// values written as bf16 — the FA q/k operands come from this launch (bit-identical to the
15513    /// separate f32_to_bf16 the FA entries would run).
15514    #[allow(clippy::too_many_arguments)]
15515    pub fn rope_neox2_bf16e(
15516        &self,
15517        q: &mut CudaSlice<f32>,
15518        k: &mut CudaSlice<f32>,
15519        qb: &mut CudaSlice<u8>,
15520        kb: &mut CudaSlice<u8>,
15521        pos: &CudaSlice<i32>,
15522        head_dim: usize,
15523        n_dims: usize,
15524        nh_q: usize,
15525        nh_k: usize,
15526        n_tokens: usize,
15527        base: f32,
15528        freq_scale: f32,
15529        ff: Option<&CudaSlice<f32>>,
15530    ) -> Result<(), Box<dyn std::error::Error>> {
15531        let f = self.func("rope_neox2_bf16e_f32");
15532        let rows = ((nh_q + nh_k) * n_tokens) as u32;
15533        let cfg = LaunchConfig {
15534            grid_dim: (rows, 1, 1),
15535            block_dim: ((head_dim / 2) as u32, 1, 1),
15536            shared_mem_bytes: 0,
15537        };
15538        let theta_scale = base.powf(-2.0 / n_dims as f32);
15539        let (hd, nd, nhq, nhk, nt) = (
15540            head_dim as i32,
15541            n_dims as i32,
15542            nh_q as i32,
15543            nh_k as i32,
15544            n_tokens as i32,
15545        );
15546        let __s_b = self.gpu.stream();
15547        let mut b = __s_b.launch_builder(&f);
15548        match ff {
15549            Some(t) => {
15550                b.arg(&mut *q)
15551                    .arg(&mut *k)
15552                    .arg(&mut *qb)
15553                    .arg(&mut *kb)
15554                    .arg(pos)
15555                    .arg(&hd)
15556                    .arg(&nd)
15557                    .arg(&nhq)
15558                    .arg(&nhk)
15559                    .arg(&nt)
15560                    .arg(&theta_scale)
15561                    .arg(&freq_scale)
15562                    .arg(t);
15563                unsafe {
15564                    b.launch(cfg)?;
15565                }
15566            }
15567            None => {
15568                let null: u64 = 0;
15569                b.arg(&mut *q)
15570                    .arg(&mut *k)
15571                    .arg(&mut *qb)
15572                    .arg(&mut *kb)
15573                    .arg(pos)
15574                    .arg(&hd)
15575                    .arg(&nd)
15576                    .arg(&nhq)
15577                    .arg(&nhk)
15578                    .arg(&nt)
15579                    .arg(&theta_scale)
15580                    .arg(&freq_scale)
15581                    .arg(&null);
15582                unsafe {
15583                    b.launch(cfg)?;
15584                }
15585            }
15586        }
15587        Ok(())
15588    }
15589
15590    /// Flat f32 -> bf16 conversion into a fresh scratch buffer (2 bytes/elem). `n % 4 == 0`
15591    /// (float4 in, 4x bf16 out). Feeds the bf16-staged hd512 FA prefill.
15592    pub fn f32_to_bf16(
15593        &self,
15594        x: &CudaSlice<f32>,
15595        n: usize,
15596    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15597        assert!(n % 4 == 0, "f32_to_bf16 requires n % 4 == 0, got {n}");
15598        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15599        let f = self.func("f32_to_bf16_flat");
15600        let n_i = n as i64;
15601        let cfg = LaunchConfig {
15602            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15603            block_dim: (256, 1, 1),
15604            shared_mem_bytes: 0,
15605        };
15606        let __s_b = self.gpu.stream();
15607        let mut b = __s_b.launch_builder(&f);
15608        b.arg(x).arg(&mut y).arg(&n_i);
15609        unsafe {
15610            b.launch(cfg)?;
15611        }
15612        Ok(y)
15613    }
15614
15615    pub fn f32_to_f16(
15616        &self,
15617        x: &CudaSlice<f32>,
15618        n: usize,
15619    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15620        assert!(n % 4 == 0, "f32_to_f16 requires n % 4 == 0, got {n}");
15621        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15622        let f = self.func("f32_to_f16_flat");
15623        let n_i = n as i64;
15624        let cfg = LaunchConfig {
15625            grid_dim: (((n / 4) as u32).div_ceil(256), 1, 1),
15626            block_dim: (256, 1, 1),
15627            shared_mem_bytes: 0,
15628        };
15629        let __s_b = self.gpu.stream();
15630        let mut b = __s_b.launch_builder(&f);
15631        b.arg(x).arg(&mut y).arg(&n_i);
15632        unsafe {
15633            b.launch(cfg)?;
15634        }
15635        Ok(y)
15636    }
15637
15638    /// bf16 bytes -> f16 bytes, n elements (the f16-P/V door's V re-encode on the emit lane).
15639    pub fn bf16_to_f16(
15640        &self,
15641        xb: &CudaSlice<u8>,
15642        n: usize,
15643    ) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
15644        let mut y = self.alloc_uninit::<u8>(n * 2)?;
15645        self.bf16_to_f16_into(xb, n, &mut y)?;
15646        Ok(y)
15647    }
15648
15649    /// Same conversion into a caller-owned (pooled) buffer; `y.len() >= n*2`.
15650    pub fn bf16_to_f16_into(
15651        &self,
15652        xb: &CudaSlice<u8>,
15653        n: usize,
15654        y: &mut CudaSlice<u8>,
15655    ) -> Result<(), Box<dyn std::error::Error>> {
15656        assert!(n % 2 == 0, "bf16_to_f16 requires n % 2 == 0, got {n}");
15657        assert!(y.len() >= n * 2);
15658        let f = self.func("bf16_to_f16_flat");
15659        let n2 = (n / 2) as i64;
15660        let cfg = LaunchConfig {
15661            grid_dim: (((n / 2) as u32).div_ceil(256), 1, 1),
15662            block_dim: (256, 1, 1),
15663            shared_mem_bytes: 0,
15664        };
15665        let __s_b = self.gpu.stream();
15666        let mut b = __s_b.launch_builder(&f);
15667        b.arg(xb).arg(y).arg(&n2);
15668        unsafe {
15669            b.launch(cfg)?;
15670        }
15671        Ok(())
15672    }
15673
15674    /// task #18 (attn side): varlen FA — bf16 K/V mirrors (2 launches) + ONE
15675    /// fa_prefill_bf16kv launch for every fresh sequence. Same per-block math as the
15676    /// per-seq path (bit-gateable). Caller guarantees: fresh causal (T_kv == T),
15677    /// head_dim in {256, 128}, bf16kv lane on.
15678    #[allow(clippy::too_many_arguments)]
15679    pub fn fa_prefill_vl8(
15680        &self,
15681        seqs: &[FaSeqVl],
15682        head_dim: usize,
15683        n_head: usize,
15684        n_head_kv: usize,
15685        scale: f32,
15686    ) -> Result<(), Box<dyn std::error::Error>> {
15687        const BK: usize = 32;
15688        let b = seqs.len();
15689        assert!(b >= 1 && b <= 8);
15690        let mut packed = [FaSeqVl::default(); 8];
15691        packed[..b].copy_from_slice(seqs);
15692        let v = FaVl8(packed);
15693        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
15694        let ept = (n_head_kv * head_dim) as i32;
15695        {
15696            let f = self.func("fa_mirror_vl");
15697            let max_n = (max_t as i64) * ept as i64;
15698            let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
15699            for which in 0..2i32 {
15700                let cfg = LaunchConfig {
15701                    grid_dim: (blocks, 1, b as u32),
15702                    block_dim: (256, 1, 1),
15703                    shared_mem_bytes: 0,
15704                };
15705                let __s_lb = self.gpu.stream();
15706                let mut lb = __s_lb.launch_builder(&f);
15707                lb.arg(&v).arg(&ept).arg(&which);
15708                unsafe {
15709                    lb.launch(cfg)?;
15710                }
15711            }
15712        }
15713        let hd_sfx = fa_hd_suffix(head_dim)?;
15714        let f = self.func(&format!("fa_prefill_bf16kv_vl{hd_sfx}"));
15715        let block_q = 64usize;
15716        let kv_stages = 2usize;
15717        let shmem = (2 * (kv_stages * 2 * BK * head_dim + block_q * BK)
15718            + 4 * (block_q * BK + 2 * block_q)) as u32;
15719        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15720        f.set_attribute(
15721            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15722            shmem as i32,
15723        )?;
15724        let cfg = LaunchConfig {
15725            grid_dim: (max_t.div_ceil(block_q as u32), n_head as u32, b as u32),
15726            block_dim: (32, 4, 1),
15727            shared_mem_bytes: shmem,
15728        };
15729        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
15730        let __s_lb = self.gpu.stream();
15731        let mut lb = __s_lb.launch_builder(&f);
15732        lb.arg(&v).arg(&hd).arg(&nh).arg(&nhkv).arg(&scale);
15733        unsafe {
15734            lb.launch(cfg)?;
15735        }
15736        Ok(())
15737    }
15738
15739    /// task #18 (attn pre-FA): varlen split + QK-norm + RoPE + KV-append — FOUR launches
15740    /// for every fresh sequence (was 6 x B, plus the q/k/v split copies which the view
15741    /// inputs remove entirely). Fresh-only (append at t0=0, RoPE pos = token index).
15742    #[allow(clippy::too_many_arguments)]
15743    pub fn attn_pre_vl8(
15744        &self,
15745        seqs: &[AttnPreVl],
15746        wq: &CudaSlice<f32>,
15747        wk: &CudaSlice<f32>,
15748        head_dim: usize,
15749        rope_dims: usize,
15750        n_head: usize,
15751        n_head_kv: usize,
15752        eps: f32,
15753        freq_base: f32,
15754        freq_scale: f32,
15755        kv_dim_k: usize,
15756        kv_dim_v: usize,
15757        k_tok_bytes: usize,
15758        v_tok_bytes: usize,
15759    ) -> Result<(), Box<dyn std::error::Error>> {
15760        let b = seqs.len();
15761        assert!(b >= 1 && b <= 8);
15762        let mut packed = [AttnPreVl::default(); 8];
15763        packed[..b].copy_from_slice(seqs);
15764        let v = AttnPreVl8(packed);
15765        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
15766        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
15767        {
15768            let f = self.func("q_gate_split_vl");
15769            let n = max_t * (n_head * head_dim) as u32;
15770            let cfg = LaunchConfig {
15771                grid_dim: (n.div_ceil(256), 1, b as u32),
15772                block_dim: (256, 1, 1),
15773                shared_mem_bytes: 0,
15774            };
15775            let __s_lb = self.gpu.stream();
15776            let mut lb = __s_lb.launch_builder(&f);
15777            lb.arg(&v).arg(&hd).arg(&nh);
15778            unsafe {
15779                lb.launch(cfg)?;
15780            }
15781        }
15782        {
15783            let f = self.func("attn_rms_vl");
15784            let cfg = LaunchConfig {
15785                grid_dim: (max_t * n_head as u32, 2, b as u32),
15786                block_dim: (rms_block(), 1, 1),
15787                shared_mem_bytes: 0,
15788            };
15789            let __s_lb = self.gpu.stream();
15790            let mut lb = __s_lb.launch_builder(&f);
15791            lb.arg(&v)
15792                .arg(wq)
15793                .arg(wk)
15794                .arg(&hd)
15795                .arg(&nh)
15796                .arg(&nhkv)
15797                .arg(&eps);
15798            unsafe {
15799                lb.launch(cfg)?;
15800            }
15801        }
15802        {
15803            let f = self.func("attn_rope_vl");
15804            let theta_scale = freq_base.powf(-2.0 / rope_dims as f32);
15805            let nd = rope_dims as i32;
15806            let cfg = LaunchConfig {
15807                grid_dim: (max_t * n_head as u32, 2, b as u32),
15808                block_dim: ((head_dim / 2) as u32, 1, 1),
15809                shared_mem_bytes: 0,
15810            };
15811            let __s_lb = self.gpu.stream();
15812            let mut lb = __s_lb.launch_builder(&f);
15813            lb.arg(&v)
15814                .arg(&hd)
15815                .arg(&nd)
15816                .arg(&nh)
15817                .arg(&nhkv)
15818                .arg(&theta_scale)
15819                .arg(&freq_scale);
15820            unsafe {
15821                lb.launch(cfg)?;
15822            }
15823        }
15824        {
15825            let f = self.func("append_kv_vl");
15826            let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
15827            let cfg = LaunchConfig {
15828                grid_dim: (nblk, max_t, b as u32),
15829                block_dim: (32, 1, 1),
15830                shared_mem_bytes: 0,
15831            };
15832            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
15833            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15834            let __s_lb = self.gpu.stream();
15835            let mut lb = __s_lb.launch_builder(&f);
15836            lb.arg(&v).arg(&kdk).arg(&kdv).arg(&ktb).arg(&vtb);
15837            unsafe {
15838                lb.launch(cfg)?;
15839            }
15840        }
15841        Ok(())
15842    }
15843
15844    /// FA prefill where K/V are QUANTIZED CudaViews into the resident byte KV cache (the T=K verify
15845    /// path, MTP-PLAN §D.3). Uses `fa_prefill_q` (inline-dequant during stage-to-smem). The view's
15846    /// base+offset pointer is honored; the kernel reads [0..t_kv*tok_bytes). Q is the T fresh query
15847    /// rows; t = T, t_kv = cache len. k_tok_bytes/v_tok_bytes are the per-token byte strides.
15848    pub fn fa_prefill_view(
15849        &self,
15850        q: &CudaSlice<f32>,
15851        k: &cudarc::driver::CudaView<u8>,
15852        v: &cudarc::driver::CudaView<u8>,
15853        o: &mut CudaSlice<f32>,
15854        head_dim: usize,
15855        n_head: usize,
15856        n_head_kv: usize,
15857        t: usize,
15858        t_kv: usize,
15859        scale: f32,
15860        causal: bool,
15861        k_tok_bytes: usize,
15862        v_tok_bytes: usize,
15863        g: bool,
15864    ) -> Result<(), Box<dyn std::error::Error>> {
15865        if portable_mma_gated() {
15866            return self.sdpa_naive_quantized_view(
15867                q,
15868                k,
15869                v,
15870                o,
15871                head_dim,
15872                n_head,
15873                n_head_kv,
15874                t,
15875                t_kv,
15876                scale,
15877                causal,
15878                k_tok_bytes,
15879                v_tok_bytes,
15880            );
15881        }
15882        const BLOCK_Q: usize = 64;
15883        const BK: usize = 32;
15884        // g = e4m3 cache: the kernel parses via DQ_K_ELEM/DQ_V_ELEM (format macros) — the
15885        // kf8vf8-module stamp reads fp8 with the identical MMA/softmax/PV body.
15886        let name = format!("fa_prefill_q{}", fa_hd_suffix(head_dim)?);
15887        let f = if g {
15888            self.func_g(&name)
15889        } else {
15890            self.func(&name)
15891        };
15892        let shmem =
15893            (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32;
15894        use cudarc::driver::sys::CUfunction_attribute_enum as A;
15895        f.set_attribute(
15896            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
15897            shmem as i32,
15898        )?;
15899        let cfg = LaunchConfig {
15900            grid_dim: (
15901                (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
15902                n_head as u32,
15903                1,
15904            ),
15905            block_dim: (32, 4, 1),
15906            shared_mem_bytes: shmem,
15907        };
15908        let (hd, nh, nhkv, ti, tkvi, cz) = (
15909            head_dim as i32,
15910            n_head as i32,
15911            n_head_kv as i32,
15912            t as i32,
15913            t_kv as i32,
15914            causal as i32,
15915        );
15916        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
15917        let __s_b = self.gpu.stream();
15918        let mut b = __s_b.launch_builder(&f);
15919        b.arg(q)
15920            .arg(k)
15921            .arg(v)
15922            .arg(o)
15923            .arg(&hd)
15924            .arg(&nh)
15925            .arg(&nhkv)
15926            .arg(&ti)
15927            .arg(&tkvi)
15928            .arg(&scale)
15929            .arg(&cz)
15930            .arg(&ktb)
15931            .arg(&vtb);
15932        unsafe {
15933            b.launch(cfg)?;
15934        }
15935        Ok(())
15936    }
15937
15938    /// ARC B (2026-07-05): dequant-once chunk-prime FA. Same contract as `fa_prefill_view`, but
15939    /// instead of every (q-block, head) CTA re-dequanting the whole quantized KV stream inline
15940    /// (T/64 x n_head redundant at chunk prime — 30.5% of the 32k prime wall), dequant the full
15941    /// [t_kv, kv_dim] K and V ONCE into a resident bf16 workspace (fa_dequant_kv_ws_bf16), then
15942    /// run `fa_prefill_qw` (the bf16-workspace twin) over it. EXACT: the workspace holds the same
15943    /// __float2bfloat16(dq_*_elem(...)) values fa_prefill_q stages to smem, and the twin's MMA/
15944    /// softmax/PV code is byte-identical -> bit-identical O (kernel_check pins bitdiff=0).
15945    /// The workspace allocation is REUSED across layers/chunks (grown to the largest shape);
15946    /// contents are rewritten per call. MEMRA_PRIME_DEQW=0 falls back to fa_prefill_view (callers gate).
15947    #[allow(clippy::too_many_arguments)]
15948    pub fn fa_prefill_view_ws(
15949        &self,
15950        q: &CudaSlice<f32>,
15951        k: &cudarc::driver::CudaView<u8>,
15952        v: &cudarc::driver::CudaView<u8>,
15953        o: &mut CudaSlice<f32>,
15954        head_dim: usize,
15955        n_head: usize,
15956        n_head_kv: usize,
15957        t: usize,
15958        t_kv: usize,
15959        scale: f32,
15960        causal: bool,
15961        k_tok_bytes: usize,
15962        v_tok_bytes: usize,
15963        g: bool,
15964    ) -> Result<(), Box<dyn std::error::Error>> {
15965        if portable_mma_gated() {
15966            return self.sdpa_naive_quantized_view(
15967                q,
15968                k,
15969                v,
15970                o,
15971                head_dim,
15972                n_head,
15973                n_head_kv,
15974                t,
15975                t_kv,
15976                scale,
15977                causal,
15978                k_tok_bytes,
15979                v_tok_bytes,
15980            );
15981        }
15982        const BLOCK_Q: usize = 64;
15983        const BK: usize = 32;
15984        let kv_dim_k = n_head_kv * head_dim;
15985        let kv_dim_v = n_head_kv * head_dim;
15986        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
15987        let v_ws_bytes = t_kv * kv_dim_v * 2;
15988        // Lock held across BOTH launches: enqueue-only (µs), all compute serializes on gpu.stream.
15989        let mut guard = self.prime_deqw_ws.lock().unwrap();
15990        let need_grow = match guard.as_ref() {
15991            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
15992            None => true,
15993        };
15994        if need_grow {
15995            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
15996            let (ck, cv) = guard
15997                .as_ref()
15998                .map(|(a, b)| (a.len(), b.len()))
15999                .unwrap_or((0, 0));
16000            *guard = Some((
16001                self.alloc_u8(grow(ck, k_ws_bytes))?,
16002                self.alloc_u8(grow(cv, v_ws_bytes))?,
16003            ));
16004        }
16005        let (kw, vw) = guard.as_mut().unwrap();
16006        // pass 1: dequant K+V once into the bf16 workspace (grid-stride, 1 thread/elem)
16007        {
16008            // only THIS pass parses KV bytes — pass 2 reads the bf16 workspace (format-free).
16009            let f = if g {
16010                self.func_g("fa_dequant_kv_ws_bf16")
16011            } else {
16012                self.func("fa_dequant_kv_ws_bf16")
16013            };
16014            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16015            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16016            let cfg = LaunchConfig {
16017                grid_dim: (nblk.max(1), 1, 1),
16018                block_dim: (256, 1, 1),
16019                shared_mem_bytes: 0,
16020            };
16021            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16022            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16023            let __s_b = self.gpu.stream();
16024            let mut b = __s_b.launch_builder(&f);
16025            b.arg(k)
16026                .arg(v)
16027                .arg(&mut *kw)
16028                .arg(&mut *vw)
16029                .arg(&kdk)
16030                .arg(&kdv)
16031                .arg(&tkvi)
16032                .arg(&ktb)
16033                .arg(&vtb);
16034            unsafe {
16035                b.launch(cfg)?;
16036            }
16037        }
16038        // pass 2: the bf16-workspace prefill twin (same tile sizes/loop structure as fa_prefill_q).
16039        // DEFAULT: cp.async double-buffered staging twin (fa_prefill_qw_db, +32KB smem for the
16040        // second K/V tile pair, 1 CTA/SM): overlaps tile n+1's L2->smem copy with tile n's MMA.
16041        // Bit-identical output (staging is a pure byte copy; kernel_check pins bitdiff=0 under
16042        // both twins). A/B (27B g7e, N=3): 32k prime 17.10->16.51s, 16k 9.09->8.65s — the copy
16043        // latency hides behind the MMA pipe and beats the 2-CTA/SM occupancy of the sync twin.
16044        // MEMRA_PRIME_DEQW_DB=0 falls back to the single-buffer twin.
16045        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16046            .map(|v| v != "0")
16047            .unwrap_or(true);
16048        {
16049            let hd_sfx = fa_hd_suffix(head_dim)?;
16050            let f = self.func(&format!(
16051                "fa_prefill_qw{}{hd_sfx}",
16052                if db { "_db" } else { "" }
16053            ));
16054            let shmem = if db {
16055                // 4x KV tile buffers (bf16) + sP (bf16) + sL (f32)
16056                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16057            } else {
16058                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16059            };
16060            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16061            f.set_attribute(
16062                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16063                shmem as i32,
16064            )?;
16065            let cfg = LaunchConfig {
16066                grid_dim: (
16067                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16068                    n_head as u32,
16069                    1,
16070                ),
16071                block_dim: (32, 4, 1),
16072                shared_mem_bytes: shmem,
16073            };
16074            let (hd, nh, nhkv, ti, tkvi, cz) = (
16075                head_dim as i32,
16076                n_head as i32,
16077                n_head_kv as i32,
16078                t as i32,
16079                t_kv as i32,
16080                causal as i32,
16081            );
16082            let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16083            let __s_b = self.gpu.stream();
16084            let mut b = __s_b.launch_builder(&f);
16085            b.arg(q)
16086                .arg(&*kw)
16087                .arg(&*vw)
16088                .arg(o)
16089                .arg(&hd)
16090                .arg(&nh)
16091                .arg(&nhkv)
16092                .arg(&ti)
16093                .arg(&tkvi)
16094                .arg(&scale)
16095                .arg(&cz)
16096                .arg(&kdk)
16097                .arg(&kdv);
16098            unsafe {
16099                b.launch(cfg)?;
16100            }
16101        }
16102        Ok(())
16103    }
16104
16105    /// WINDOWED `fa_prefill_view_ws` twin at head_dim 128 (lane/pp-prefill 2026-08-07):
16106    /// step35's SWA prefill (win=512, 33 of 45 layers) previously had NO windowed FA prefill
16107    /// stamp — every windowed twin was hd256-only — and took `sdpa_naive_w_quantized_view`,
16108    /// the f32 floor, at 565 ms/layer on a pp4096 where the hd128 FA family does the harder
16109    /// causal-4096 in 3.3 ms (41% of the whole prime; research/pp-prefill-20260807 anatomy).
16110    /// Same two-pass shape as the unwindowed function: dequant K/V ONCE into the resident
16111    /// bf16 workspace, then the windowed qw kernel (`fa_prefill_qw_db_w_hd128`, cp.async
16112    /// double-buffered; MEMRA_PRIME_DEQW_DB=0 selects the single-buffer twin). The window
16113    /// mask is `fa_prefill_f32_body`'s exact predicate; `window == 0` is bit-identical to
16114    /// `fa_prefill_view_ws` by construction (default-arg body). NEW NUMERIC CLASS vs the
16115    /// f32 floor on SWA rows (bf16 MMA online-softmax vs f32 serial softmax) — adoption is
16116    /// gated by the full battery, and the class must change UNIFORMLY for a whole request
16117    /// (kernel selection keys on seq_end, never per chunk — the chunkfix law).
16118    /// hd128-only deliberately: the only windowed-prefill consumer at another head_dim is
16119    /// gemma4 (hd256), which already has `fa_prefill_w_f32`.
16120    #[allow(clippy::too_many_arguments)]
16121    pub fn fa_prefill_view_ws_w_hd128(
16122        &self,
16123        q: &CudaSlice<f32>,
16124        k: &cudarc::driver::CudaView<u8>,
16125        v: &cudarc::driver::CudaView<u8>,
16126        o: &mut CudaSlice<f32>,
16127        head_dim: usize,
16128        n_head: usize,
16129        n_head_kv: usize,
16130        t: usize,
16131        t_kv: usize,
16132        scale: f32,
16133        causal: bool,
16134        window: usize,
16135        k_tok_bytes: usize,
16136        v_tok_bytes: usize,
16137    ) -> Result<(), Box<dyn std::error::Error>> {
16138        assert_eq!(
16139            head_dim, 128,
16140            "fa_prefill_view_ws_w_hd128: only the hd128 twin is stamped"
16141        );
16142        if portable_mma_gated() {
16143            return self.sdpa_naive_w_quantized_view(
16144                q,
16145                k,
16146                v,
16147                o,
16148                head_dim,
16149                n_head,
16150                n_head_kv,
16151                t,
16152                t_kv,
16153                scale,
16154                causal,
16155                window,
16156                k_tok_bytes,
16157                v_tok_bytes,
16158            );
16159        }
16160        const BLOCK_Q: usize = 64;
16161        const BK: usize = 32;
16162        let kv_dim_k = n_head_kv * head_dim;
16163        let kv_dim_v = n_head_kv * head_dim;
16164        let k_ws_bytes = t_kv * kv_dim_k * 2; // bf16
16165        let v_ws_bytes = t_kv * kv_dim_v * 2;
16166        let mut guard = self.prime_deqw_ws.lock().unwrap();
16167        let need_grow = match guard.as_ref() {
16168            Some((kw, vw)) => kw.len() < k_ws_bytes || vw.len() < v_ws_bytes,
16169            None => true,
16170        };
16171        if need_grow {
16172            let grow = |cur: usize, need: usize| if cur >= need { cur } else { need };
16173            let (ck, cv) = guard
16174                .as_ref()
16175                .map(|(a, b)| (a.len(), b.len()))
16176                .unwrap_or((0, 0));
16177            *guard = Some((
16178                self.alloc_u8(grow(ck, k_ws_bytes))?,
16179                self.alloc_u8(grow(cv, v_ws_bytes))?,
16180            ));
16181        }
16182        let (kw, vw) = guard.as_mut().unwrap();
16183        // pass 1: dequant K+V once into the bf16 workspace (identical to fa_prefill_view_ws —
16184        // the workspace bytes are the SAME __float2bfloat16(dq(...)) values either way).
16185        {
16186            let f = self.func("fa_dequant_kv_ws_bf16");
16187            let total = (t_kv * (kv_dim_k + kv_dim_v)) as u64;
16188            let nblk = ((total + 255) / 256).min(65535 * 16) as u32;
16189            let cfg = LaunchConfig {
16190                grid_dim: (nblk.max(1), 1, 1),
16191                block_dim: (256, 1, 1),
16192                shared_mem_bytes: 0,
16193            };
16194            let (kdk, kdv, tkvi) = (kv_dim_k as i32, kv_dim_v as i32, t_kv as i32);
16195            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16196            let __s_b = self.gpu.stream();
16197            let mut b = __s_b.launch_builder(&f);
16198            b.arg(k)
16199                .arg(v)
16200                .arg(&mut *kw)
16201                .arg(&mut *vw)
16202                .arg(&kdk)
16203                .arg(&kdv)
16204                .arg(&tkvi)
16205                .arg(&ktb)
16206                .arg(&vtb);
16207            unsafe {
16208                b.launch(cfg)?;
16209            }
16210        }
16211        // pass 2: the WINDOWED qw twin (db default, same as the unwindowed wrapper).
16212        let db = std::env::var("MEMRA_PRIME_DEQW_DB")
16213            .map(|v| v != "0")
16214            .unwrap_or(true);
16215        {
16216            let f = self.func(if db {
16217                "fa_prefill_qw_db_w_hd128"
16218            } else {
16219                "fa_prefill_qw_w_hd128"
16220            });
16221            let shmem = if db {
16222                (2 * (4 * BK * head_dim + BLOCK_Q * BK) + 4 * BLOCK_Q) as u32
16223            } else {
16224                (2 * (2 * BK * head_dim + BLOCK_Q * BK) + 4 * (BLOCK_Q * BK + 2 * BLOCK_Q)) as u32
16225            };
16226            use cudarc::driver::sys::CUfunction_attribute_enum as A;
16227            f.set_attribute(
16228                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16229                shmem as i32,
16230            )?;
16231            let cfg = LaunchConfig {
16232                grid_dim: (
16233                    (t as u32 + BLOCK_Q as u32 - 1) / BLOCK_Q as u32,
16234                    n_head as u32,
16235                    1,
16236                ),
16237                block_dim: (32, 4, 1),
16238                shared_mem_bytes: shmem,
16239            };
16240            let (hd, nh, nhkv, ti, tkvi, cz) = (
16241                head_dim as i32,
16242                n_head as i32,
16243                n_head_kv as i32,
16244                t as i32,
16245                t_kv as i32,
16246                causal as i32,
16247            );
16248            let (kdk, kdv, wnd) = (kv_dim_k as i32, kv_dim_v as i32, window as i32);
16249            let __s_b = self.gpu.stream();
16250            let mut b = __s_b.launch_builder(&f);
16251            b.arg(q)
16252                .arg(&*kw)
16253                .arg(&*vw)
16254                .arg(o)
16255                .arg(&hd)
16256                .arg(&nh)
16257                .arg(&nhkv)
16258                .arg(&ti)
16259                .arg(&tkvi)
16260                .arg(&scale)
16261                .arg(&cz)
16262                .arg(&kdk)
16263                .arg(&kdv)
16264                .arg(&wnd);
16265            unsafe {
16266                b.launch(cfg)?;
16267            }
16268        }
16269        Ok(())
16270    }
16271
16272    /// FA decode (T=1 split-K) over the resident QUANTIZED KV cache (q8_0 K / q5_1 V) as u8 views.
16273    /// Replaces sdpa_naive_view for decode; inline-dequants per element. k_tok_bytes/v_tok_bytes are
16274    /// the per-token byte strides (differ: q8_0=34*nblk, q5_1=24*nblk per token).
16275    pub fn fa_decode(
16276        &self,
16277        q: &CudaSlice<f32>,
16278        k: &cudarc::driver::CudaView<u8>,
16279        v: &cudarc::driver::CudaView<u8>,
16280        o: &mut CudaSlice<f32>,
16281        head_dim: usize,
16282        n_head: usize,
16283        n_head_kv: usize,
16284        t_kv: usize,
16285        scale: f32,
16286        k_tok_bytes: usize,
16287        v_tok_bytes: usize,
16288    ) -> Result<(), Box<dyn std::error::Error>> {
16289        self.fa_decode_kvmod(
16290            q,
16291            k,
16292            v,
16293            o,
16294            head_dim,
16295            n_head,
16296            n_head_kv,
16297            t_kv,
16298            scale,
16299            k_tok_bytes,
16300            v_tok_bytes,
16301            false,
16302        )
16303    }
16304
16305    /// `fa_decode` with an explicit fp8-module flag (`g`): gemma windowed layers under
16306    /// MEMRA_GEMMA_WKV read an e4m3 cache — every kernel must come from the kf8vf8 module
16307    /// and the v4 lane (q8_0-hardcoded staging) is excluded.
16308    #[allow(clippy::too_many_arguments)]
16309    /// UNIFIED scalar decode launch (fa_decode_f32, nullable-ctr): ONE symbol for host-len
16310    /// (kvmod eager) and device-len (graph/stream) callers — the textually-identical f32_dc
16311    /// twin compiled apart and its ULP drift flipped 31B verify argmaxes (2026-07-12).
16312    #[allow(clippy::too_many_arguments)]
16313    #[allow(clippy::too_many_arguments)]
16314    fn fa_decode_scalar_unified(
16315        &self,
16316        q: &cudarc::driver::CudaView<f32>,
16317        k: &cudarc::driver::CudaView<u8>,
16318        v: &cudarc::driver::CudaView<u8>,
16319        o: &mut cudarc::driver::CudaViewMut<f32>,
16320        head_dim: usize,
16321        n_head: usize,
16322        n_head_kv: usize,
16323        t_kv_host: usize,
16324        t_kv_dev: Option<&CudaSlice<i32>>,
16325        scale: f32,
16326        n_splits: usize,
16327        split_keys: usize,
16328        k_tok_bytes: usize,
16329        v_tok_bytes: usize,
16330        g: bool,
16331        part_o: &mut CudaSlice<f32>,
16332        part_m: &mut CudaSlice<f32>,
16333        part_l: &mut CudaSlice<f32>,
16334        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
16335    ) -> Result<(), Box<dyn std::error::Error>> {
16336        let f = if g {
16337            self.func_g("fa_decode_f32")
16338        } else {
16339            self.fa_func("fa_decode_f32", head_dim)
16340        };
16341        let cfg = LaunchConfig {
16342            grid_dim: (n_head as u32, n_splits as u32, 1),
16343            block_dim: (head_dim as u32, 1, 1),
16344            shared_mem_bytes: (4 * (head_dim + 32)) as u32,
16345        };
16346        let (hd, nh, nhkv, nsp) = (
16347            head_dim as i32,
16348            n_head as i32,
16349            n_head_kv as i32,
16350            n_splits as i32,
16351        );
16352        let (ktb, vtb, tkvi, ski) = (
16353            k_tok_bytes as i64,
16354            v_tok_bytes as i64,
16355            t_kv_host as i32,
16356            split_keys as i32,
16357        );
16358        let __s_b = self.gpu.stream();
16359        let mut b = __s_b.launch_builder(&f);
16360        match t_kv_dev {
16361            Some(d) => {
16362                b.arg(q)
16363                    .arg(k)
16364                    .arg(v)
16365                    .arg(&mut *part_o)
16366                    .arg(&mut *part_m)
16367                    .arg(&mut *part_l)
16368                    .arg(&hd)
16369                    .arg(&nh)
16370                    .arg(&nhkv)
16371                    .arg(&tkvi)
16372                    .arg(d)
16373                    .arg(&scale)
16374                    .arg(&nsp)
16375                    .arg(&ski)
16376                    .arg(&ktb)
16377                    .arg(&vtb);
16378                unsafe {
16379                    b.launch(cfg)?;
16380                }
16381            }
16382            None => {
16383                let null: u64 = 0;
16384                b.arg(q)
16385                    .arg(k)
16386                    .arg(v)
16387                    .arg(&mut *part_o)
16388                    .arg(&mut *part_m)
16389                    .arg(&mut *part_l)
16390                    .arg(&hd)
16391                    .arg(&nh)
16392                    .arg(&nhkv)
16393                    .arg(&tkvi)
16394                    .arg(&null)
16395                    .arg(&scale)
16396                    .arg(&nsp)
16397                    .arg(&ski)
16398                    .arg(&ktb)
16399                    .arg(&vtb);
16400                unsafe {
16401                    b.launch(cfg)?;
16402                }
16403            }
16404        }
16405        let cfg2 = LaunchConfig {
16406            grid_dim: (n_head as u32, 1, 1),
16407            block_dim: (head_dim as u32, 1, 1),
16408            shared_mem_bytes: 0,
16409        };
16410        if let Some((oq, od)) = q8_out {
16411            // wave-5b: q8-emitting combine — the wo matmul_pre consumes the pair directly.
16412            let fc = if g {
16413                self.func_g("fa_decode_combine_q8_1")
16414            } else {
16415                self.fa_func("fa_decode_combine_q8_1", head_dim)
16416            };
16417            let __s_b2 = self.gpu.stream();
16418            let mut b2 = __s_b2.launch_builder(&fc);
16419            b2.arg(&*part_o)
16420                .arg(&*part_m)
16421                .arg(&*part_l)
16422                .arg(oq)
16423                .arg(od)
16424                .arg(&hd)
16425                .arg(&nh)
16426                .arg(&nsp);
16427            unsafe {
16428                b2.launch(cfg2)?;
16429            }
16430            return Ok(());
16431        }
16432        let fc = if g {
16433            self.func_g("fa_decode_combine_f32")
16434        } else {
16435            self.fa_func("fa_decode_combine_f32", head_dim)
16436        };
16437        let __s_b2 = self.gpu.stream();
16438        let mut b2 = __s_b2.launch_builder(&fc);
16439        b2.arg(&*part_o)
16440            .arg(&*part_m)
16441            .arg(&*part_l)
16442            .arg(o)
16443            .arg(&hd)
16444            .arg(&nh)
16445            .arg(&nsp);
16446        unsafe {
16447            b2.launch(cfg2)?;
16448        }
16449        Ok(())
16450    }
16451
16452    pub fn fa_decode_kvmod(
16453        &self,
16454        q: &CudaSlice<f32>,
16455        k: &cudarc::driver::CudaView<u8>,
16456        v: &cudarc::driver::CudaView<u8>,
16457        o: &mut CudaSlice<f32>,
16458        head_dim: usize,
16459        n_head: usize,
16460        n_head_kv: usize,
16461        t_kv: usize,
16462        scale: f32,
16463        k_tok_bytes: usize,
16464        v_tok_bytes: usize,
16465        g: bool,
16466    ) -> Result<(), Box<dyn std::error::Error>> {
16467        let q_view = q.as_view();
16468        let mut o_view = o.as_view_mut();
16469        self.fa_decode_kvmod_view(
16470            &q_view,
16471            k,
16472            v,
16473            &mut o_view,
16474            head_dim,
16475            n_head,
16476            n_head_kv,
16477            t_kv,
16478            scale,
16479            k_tok_bytes,
16480            v_tok_bytes,
16481            g,
16482        )
16483    }
16484
16485    /// Row-view entry into `fa_decode_kvmod`. The kernel sees the selected Q/output rows as its
16486    /// base pointers, so the launch geometry and arithmetic are identical to the owned-slice entry.
16487    /// Batched fallback callers use this to avoid materializing rows around an otherwise unchanged
16488    /// per-session KV view and FA launch.
16489    #[allow(clippy::too_many_arguments)]
16490    pub fn fa_decode_kvmod_view(
16491        &self,
16492        q: &cudarc::driver::CudaView<f32>,
16493        k: &cudarc::driver::CudaView<u8>,
16494        v: &cudarc::driver::CudaView<u8>,
16495        o: &mut cudarc::driver::CudaViewMut<f32>,
16496        head_dim: usize,
16497        n_head: usize,
16498        n_head_kv: usize,
16499        t_kv: usize,
16500        scale: f32,
16501        k_tok_bytes: usize,
16502        v_tok_bytes: usize,
16503        g: bool,
16504    ) -> Result<(), Box<dyn std::error::Error>> {
16505        // PERF-4: the warp-per-token vec path replaces the scalar element-per-thread fa_decode_f32 —
16506        // warp-per-token fa_decode_vec_q (grid=(n_head_kv,n_splits), block=(32,gqa_ratio)).
16507        // The block dequants each KV tile ONCE into smem (bf16) and broadcasts to all gqa Q-head
16508        // warps -> each KV byte leaves HBM/L2 ~1x/group (vs 4x). ARGS identical; func/grid/block/
16509        // smem/n_splits differ. fa_decode_f32 stays the bit-reference fallback. Combine is shared.
16510        //
16511        // SPLIT-K: the scalar path has grid.x=n_head (32) blocks; the vec path only has
16512        // grid.x=n_head_kv (8). To avoid starving the GPU at mid ctx, the vec path splits MORE
16513        // aggressively (64 keys/split vs 256) so grid.y rises and 8*n_splits fills the SMs.
16514        // At VERY short ctx (t_kv<96) even 1 split can't fill the GPU from 8 KV heads, so the
16515        // broadcast can't beat the scalar path's 4x-more-blocks latency hiding — fall back to
16516        // scalar there (measured crossover: vec 0.68x at t_kv=64, 1.23x at t_kv=96, 2.2x at 256).
16517        // DEFAULT-ON (2026-06-28): clean clock-locked sweep proved vec beats scalar at every
16518        // t_kv>=96 and the gain WIDENS with ctx (graph decode: +9.5% @128, +11.6% @512, +11.8%
16519        // @2048) — the KV-byte-broadcast (4x fewer HBM reads/group) compounds as attention grows.
16520        // MEMRA_NO_FA_VEC forces the scalar bit-reference. Below FA_VEC_MIN_TKV the scalar path's
16521        // 4x-more-blocks (grid.x=n_head=32 vs n_head_kv=8) hides latency better, so keep scalar there.
16522        // g + no-v4: the g-module REGISTER twin mis-decodes the gemma windowed shape
16523        // (root-cause open, jsonl) — only reachable by forcing v4 off (MEMRA_FA_V4_MAX);
16524        // fall to the exact scalar there instead of the broken register arm.
16525        let mut fa_vec = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
16526        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
16527        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
16528        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
16529        if g && head_dim == 256 && !fa_v4_at(t_kv) {
16530            fa_vec = false;
16531        }
16532        let sp = fa_split_keys(t_kv, n_head_kv);
16533        let n_splits = if fa_vec {
16534            ((t_kv + sp - 1) / sp).max(1)
16535        } else {
16536            ((t_kv + 255) / 256).max(1)
16537        };
16538        let o_len = n_head * n_splits * head_dim;
16539        let ml_len = n_head * n_splits;
16540        let mut part_guard = self.fa_part_pool.lock().unwrap();
16541        if part_guard
16542            .as_ref()
16543            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
16544            .unwrap_or(true)
16545        {
16546            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
16547            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
16548            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
16549            // later live allocations land at those addresses, and the next graph REPLAY writes
16550            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
16551            // output corruption began the burst after the trunk's t_kv growth first realloc'd
16552            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
16553            // the baked addresses alive (single-stream: eager writes the new buffers, replays
16554            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
16555            // (total retired < final size).
16556            let old = part_guard.take();
16557            let (co, cm) = old
16558                .as_ref()
16559                .map(|pp| (pp.0.len(), pp.1.len()))
16560                .unwrap_or((0, 0));
16561            if let Some(old) = old {
16562                self.fa_part_retired.lock().unwrap().push(old);
16563            }
16564            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
16565                eprintln!(
16566                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
16567                    co, o_len, cm, ml_len
16568                );
16569            }
16570            *part_guard = Some((
16571                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
16572                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16573                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16574            ));
16575        }
16576        let pg = part_guard.as_mut().unwrap();
16577        self.gpu
16578            .stream()
16579            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
16580        self.gpu
16581            .stream()
16582            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
16583        self.gpu
16584            .stream()
16585            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
16586        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
16587        let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
16588        let (hd, nh, nhkv, tkvi, nsp) = (
16589            head_dim as i32,
16590            n_head as i32,
16591            n_head_kv as i32,
16592            t_kv as i32,
16593            n_splits as i32,
16594        );
16595        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16596        // The vec kernel holds head_dim/32 register accumulators (FA_DEC_MAX_DPL=8 -> head_dim<=256).
16597        // All shipped models use head_dim=256; fall back to scalar for anything wider rather than
16598        // silently truncating the accumulator.
16599        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
16600        // hd-512 vec crossover (MEMRA_FA512_MIN, default 512): the DPL16 twin wins at depth
16601        // (82.5 -> vec at 1736) but the scalar's more-blocks latency hiding wins at tiny t_kv
16602        // (the same scalar-floor physics as hd256's old 96 floor; short-ctx plain regressed
16603        // 178.4 -> 173.7 when 512 rode vec unconditionally).
16604        let fa512_min = fa512_min_tkv();
16605        // FA-DEEP pick (bit-identical twins, see fa_deep_at): default module only — the
16606        // g-module keeps the v4 pick (its class is not the depth-decay class).
16607        let deep = fa_vec
16608            && head_dim == 256
16609            && fa_v4_at(t_kv)
16610            && !g
16611            && fa_deep_at(t_kv)
16612            && !matches!(fa_v4_mode(), "noB3" | "stage");
16613        let (f, cfg) = if fa_vec && head_dim == 512 && t_kv >= fa512_min {
16614            // gemma4 globals (hd 512): the DPL16 register twin (fa_decode_vec_q body with a
16615            // 16-slot accumulator ceiling). Scalar fallback measured 82.5us/layer at 1736 ctx.
16616            let gqa = (n_head / n_head_kv).max(1) as u32;
16617            let fv = self.fa_func("fa_decode_vec_q_dpl16", head_dim);
16618            (
16619                fv,
16620                LaunchConfig {
16621                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16622                    block_dim: (32, gqa, 1),
16623                    shared_mem_bytes: 0,
16624                },
16625            )
16626        } else if fa_vec && head_dim <= 256 {
16627            let gqa = (n_head / n_head_kv).max(1) as u32;
16628            // DEEP-CTX smem twin (2026-07-05): the register-dequant path's GQA reuse rides L2,
16629            // which holds to ~8k ctx but dies at 40k (layer KV ~37MB) — the 4 GQA warps then
16630            // re-read every KV byte from DRAM (4x traffic). Above MEMRA_FA_SMEM_TKV (default
16631            // 1024 — the 2026-07-05 crossover re-sweep on real prompts: p3 spec 73.8->79.2 at
16632            // 2048, flat down to 512, p2 +5%, p1/9B unchanged; the ARC-A probe's synthetic
16633            // 2.1x smem-at-all-depths pointed here; 0=never) dispatch the smem-broadcast twin:
16634            // dequant each tile ONCE per block.
16635            // Bit-identical per (token,split): same bf16 round-trip, same accumulation order,
16636            // same partial layout -> same combine. Short/mid ctx keeps the register path (it won
16637            // there by 12x — latency, not bandwidth, rules small KV).
16638            static SMEM_TKV: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
16639            let smem_tkv = *SMEM_TKV.get_or_init(|| {
16640                std::env::var("MEMRA_FA_SMEM_TKV")
16641                    .ok()
16642                    .and_then(|v| v.parse().ok())
16643                    .unwrap_or_else(|| {
16644                        FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
16645                    })
16646            });
16647            if fa_v4_at(t_kv) && head_dim == 256 {
16648                // FA v4 lane (2026-07-10): key-per-lane score phase, zero shuffles per key.
16649                // NEW NUMERIC CONFIG (chunk-serial per-key dot) — battery-arbitrated.
16650                // g (fp8-windowed): the v4 staging is format-aware (2026-07-12) — kf8vf8 module.
16651                let v4name = match fa_v4_mode() {
16652                    "noB3" => "fa_decode_vec_q_v4_noB3", // phase probe (WRONG OUTPUT)
16653                    "stage" => "fa_decode_vec_q_v4_stage", // phase probe (WRONG OUTPUT)
16654                    _ if deep => "fa_decode_vec_q_v4_deep",
16655                    _ => "fa_decode_vec_q_v4",
16656                };
16657                let fv = if g {
16658                    self.func_g(v4name)
16659                } else {
16660                    self.func(v4name)
16661                };
16662                // fa_v4_smem (deep: fa_v4_deep_smem, +640B row pads) + sV (g: raw e4m3 sV
16663                // tile = 1B/elem — half the smem, 3->5 blocks/SM)
16664                let shmem = (if deep { 12160 } else { 11520 }
16665                    + 32 * head_dim * if g { 1 } else { 2 }) as u32;
16666                use cudarc::driver::sys::CUfunction_attribute_enum as A;
16667                fv.set_attribute(
16668                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16669                    shmem as i32,
16670                )?;
16671                (
16672                    fv,
16673                    LaunchConfig {
16674                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16675                        block_dim: (32, gqa, 1),
16676                        shared_mem_bytes: shmem,
16677                    },
16678                )
16679            } else if fa_v3_active(head_dim) {
16680                // FA v3 lane: dp4a-K hybrid (register-quantized Q, raw q8_0 K, staged-V kept).
16681                // smem = sV only (half of v2's).
16682                let fv = if g {
16683                    self.func_g("fa_decode_vec_q_v3")
16684                } else {
16685                    self.func("fa_decode_vec_q_v3")
16686                };
16687                let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
16688                (
16689                    fv,
16690                    LaunchConfig {
16691                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16692                        block_dim: (32, gqa, 1),
16693                        shared_mem_bytes: shmem,
16694                    },
16695                )
16696            } else if fa_v2_on() {
16697                // FAVENDOR lane: llama fattn-vec tile-batched softmax + wide-load staging on
16698                // OUR smem KV broadcast. Replaces BOTH per-key twins when on; same grid/block/
16699                // partials; same 32KB sK+sV tile as the smem twin.
16700                let fv = if g {
16701                    self.func_g("fa_decode_vec_q_v2")
16702                } else {
16703                    self.func("fa_decode_vec_q_v2")
16704                };
16705                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
16706                (
16707                    fv,
16708                    LaunchConfig {
16709                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16710                        block_dim: (32, gqa, 1),
16711                        shared_mem_bytes: shmem,
16712                    },
16713                )
16714            } else if smem_tkv > 0 && t_kv >= smem_tkv && !g && !(head_dim == 512 && Self::gkv_on())
16715            {
16716                // (fp8 exclusions: the smem twin's V-stage is q5_1-hardcoded — neither the wkv
16717                // windowed layers (g) nor the gkv globals (hd512) may be forced onto it via
16718                // MEMRA_FA_SMEM_TKV; they fall through to the format-clean register/scalar arms.)
16719                let fv = if g {
16720                    self.func_g("fa_decode_vec_q_smem")
16721                } else {
16722                    self.func("fa_decode_vec_q_smem")
16723                };
16724                let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
16725                use cudarc::driver::sys::CUfunction_attribute_enum as A;
16726                fv.set_attribute(
16727                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16728                    shmem as i32,
16729                )?;
16730                (
16731                    fv,
16732                    LaunchConfig {
16733                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16734                        block_dim: (32, gqa, 1),
16735                        shared_mem_bytes: shmem,
16736                    },
16737                )
16738            } else {
16739                // REGISTER-DEQUANT kernel (2026-07-03): per-warp direct q8_0/q5_1 register
16740                // dequant, zero dynamic shared memory.
16741                let fv = if g {
16742                    self.func_g("fa_decode_vec_q")
16743                } else {
16744                    self.func("fa_decode_vec_q")
16745                };
16746                (
16747                    fv,
16748                    LaunchConfig {
16749                        grid_dim: (n_head_kv as u32, n_splits as u32, 1),
16750                        block_dim: (32, gqa, 1),
16751                        shared_mem_bytes: 0,
16752                    },
16753                )
16754            }
16755        } else {
16756            // UNIFIED scalar (nullable-ctr symbol shared with graph/stream callers). The
16757            // split ladder value rides along so ns_eff reproduces THIS n_splits in-kernel.
16758            return self.fa_decode_scalar_unified(
16759                q,
16760                k,
16761                v,
16762                o,
16763                head_dim,
16764                n_head,
16765                n_head_kv,
16766                t_kv,
16767                None,
16768                scale,
16769                n_splits,
16770                if fa_vec { sp } else { 256 },
16771                k_tok_bytes,
16772                v_tok_bytes,
16773                g,
16774                part_o,
16775                part_m,
16776                part_l,
16777                None,
16778            );
16779        };
16780        let __s_b = self.gpu.stream();
16781        let mut b = __s_b.launch_builder(&f);
16782        b.arg(q)
16783            .arg(k)
16784            .arg(v)
16785            .arg(&mut *part_o)
16786            .arg(&mut *part_m)
16787            .arg(&mut *part_l)
16788            .arg(&hd)
16789            .arg(&nh)
16790            .arg(&nhkv)
16791            .arg(&tkvi)
16792            .arg(&scale)
16793            .arg(&nsp)
16794            .arg(&ktb)
16795            .arg(&vtb);
16796        unsafe {
16797            b.launch(cfg)?;
16798        }
16799        // (combine re-tile refuted in the fa-deep lane — flat/worse both shapes; the v4
16800        // combine stays for all arms. Receipts research/fa-decode-deep-20260802/.)
16801        let (fc, cfg2) = (
16802            if g {
16803                self.func_g("fa_decode_combine_f32")
16804            } else {
16805                self.fa_func("fa_decode_combine_f32", head_dim)
16806            },
16807            LaunchConfig {
16808                grid_dim: (n_head as u32, 1, 1),
16809                block_dim: (head_dim as u32, 1, 1),
16810                shared_mem_bytes: 0,
16811            },
16812        );
16813        let __s_b2 = self.gpu.stream();
16814        let mut b2 = __s_b2.launch_builder(&fc);
16815        b2.arg(&*part_o)
16816            .arg(&*part_m)
16817            .arg(&*part_l)
16818            .arg(o)
16819            .arg(&hd)
16820            .arg(&nh)
16821            .arg(&nsp);
16822        unsafe {
16823            b2.launch(cfg2)?;
16824        }
16825        Ok(())
16826    }
16827
16828    /// BATCHED-TICK increment 2: ONE fa_decode launch covering ALL B sequences of the
16829    /// batched decode step (blockIdx.z = sequence). Per-seq K/V cache bases ride a device
16830    /// pointer table (`kv_ptrs`, [2B] interleaved k0,v0,...); per-seq key bounds ride the
16831    /// tick's position table (`pos_seq`, T_kv = pos+1). v4-lane only: the CALLER
16832    /// (decode_batch) gates every row through `fa_seqs_eligible` AND one `fa_split_keys`
16833    /// rung (`split_keys`), so each sequence's split partition, key walk and combine order
16834    /// reproduce its per-seq eager v4 program exactly (kernel-check pins seqs-vs-loop bit
16835    /// identity; decode-batch-gate strict pins the whole tick vs decode_step_h).
16836    /// q is the stacked [B, n_head, head_dim] tick buffer read in place (no per-seq q
16837    /// copies); o is written [B, n_head, head_dim] in place (no per-seq a copies).
16838    #[allow(clippy::too_many_arguments)]
16839    pub fn fa_decode_batch_seqs_v4(
16840        &self,
16841        q: &CudaSlice<f32>,
16842        kv_ptrs: &cudarc::driver::CudaView<u64>,
16843        pos_seq: &CudaSlice<i32>,
16844        o: &mut CudaSlice<f32>,
16845        head_dim: usize,
16846        n_head: usize,
16847        n_head_kv: usize,
16848        b_n: usize,
16849        t_kv_max: usize,
16850        scale: f32,
16851        split_keys: usize,
16852        k_tok_bytes: usize,
16853        v_tok_bytes: usize,
16854    ) -> Result<(), Box<dyn std::error::Error>> {
16855        debug_assert!(head_dim == 256, "seqs twin is v4-stamped (hd256 only)");
16856        let n_splits_max = (t_kv_max + split_keys - 1) / split_keys;
16857        let o_len = b_n * n_head * n_splits_max * head_dim;
16858        let ml_len = b_n * n_head * n_splits_max;
16859        let mut part_guard = self.fa_part_pool.lock().unwrap();
16860        if part_guard
16861            .as_ref()
16862            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
16863            .unwrap_or(true)
16864        {
16865            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
16866            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
16867            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
16868            // later live allocations land at those addresses, and the next graph REPLAY writes
16869            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
16870            // output corruption began the burst after the trunk's t_kv growth first realloc'd
16871            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
16872            // the baked addresses alive (single-stream: eager writes the new buffers, replays
16873            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
16874            // (total retired < final size).
16875            let old = part_guard.take();
16876            let (co, cm) = old
16877                .as_ref()
16878                .map(|pp| (pp.0.len(), pp.1.len()))
16879                .unwrap_or((0, 0));
16880            if let Some(old) = old {
16881                self.fa_part_retired.lock().unwrap().push(old);
16882            }
16883            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
16884                eprintln!(
16885                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
16886                    co, o_len, cm, ml_len
16887                );
16888            }
16889            *part_guard = Some((
16890                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
16891                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16892                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
16893            ));
16894        }
16895        let pg = part_guard.as_mut().unwrap();
16896        self.gpu
16897            .stream()
16898            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
16899        self.gpu
16900            .stream()
16901            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
16902        self.gpu
16903            .stream()
16904            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
16905        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
16906        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
16907        let (nspm, spk) = (n_splits_max as i32, split_keys as i32);
16908        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16909        let gqa = (n_head / n_head_kv).max(1) as u32;
16910        let f = self.func("fa_decode_vec_q_seqs_v4");
16911        // fa_v4_smem (11520B) + sV bf16 tile — the v4 eager arm's sizing on the default module.
16912        let shmem = (11520 + 32 * head_dim * 2) as u32;
16913        use cudarc::driver::sys::CUfunction_attribute_enum as A;
16914        f.set_attribute(
16915            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
16916            shmem as i32,
16917        )?;
16918        let cfg = LaunchConfig {
16919            grid_dim: (n_head_kv as u32, n_splits_max as u32, b_n as u32),
16920            block_dim: (32, gqa, 1),
16921            shared_mem_bytes: shmem,
16922        };
16923        {
16924            let __s_b = self.gpu.stream();
16925            let mut b = __s_b.launch_builder(&f);
16926            b.arg(q)
16927                .arg(kv_ptrs)
16928                .arg(pos_seq)
16929                .arg(&mut *part_o)
16930                .arg(&mut *part_m)
16931                .arg(&mut *part_l)
16932                .arg(&hd)
16933                .arg(&nh)
16934                .arg(&nhkv)
16935                .arg(&scale)
16936                .arg(&nspm)
16937                .arg(&spk)
16938                .arg(&ktb)
16939                .arg(&vtb);
16940            unsafe {
16941                b.launch(cfg)?;
16942            }
16943        }
16944        let fc = self.func("fa_decode_combine_seqs");
16945        let cfg2 = LaunchConfig {
16946            grid_dim: (n_head as u32, b_n as u32, 1),
16947            block_dim: (head_dim as u32, 1, 1),
16948            shared_mem_bytes: 0,
16949        };
16950        let __s_b2 = self.gpu.stream();
16951        let mut b2 = __s_b2.launch_builder(&fc);
16952        b2.arg(&*part_o)
16953            .arg(&*part_m)
16954            .arg(&*part_l)
16955            .arg(o)
16956            .arg(&hd)
16957            .arg(&nh)
16958            .arg(pos_seq)
16959            .arg(&nspm)
16960            .arg(&spk);
16961        unsafe {
16962            b2.launch(cfg2)?;
16963        }
16964        Ok(())
16965    }
16966
16967    /// BATCHED-TICK increment 2: z-batched decode KV append — one launch appends this
16968    /// step's B rows, each into ITS OWN sequence cache at slot pos_seq[z], through the same
16969    /// [2B] interleaved pointer table the seqs FA reads. Each (block, z) warp executes the
16970    /// per-token appender's exact warp program on row z of the stacked [B, kv_dim] k/v —
16971    /// written cache bytes are BIT-IDENTICAL to the B per-seq calls it replaces
16972    /// (kernel-check pins the bytes). Default flash module only (callers exclude fp8-KV).
16973    #[allow(clippy::too_many_arguments)]
16974    pub fn append_kv_quantized_seqs(
16975        &self,
16976        k_rows: &CudaSlice<f32>,
16977        v_rows: &CudaSlice<f32>,
16978        kv_ptrs: &cudarc::driver::CudaView<u64>,
16979        pos_seq: &CudaSlice<i32>,
16980        b_n: usize,
16981        kv_dim_k: usize,
16982        kv_dim_v: usize,
16983        k_tok_bytes: usize,
16984        v_tok_bytes: usize,
16985    ) -> Result<(), Box<dyn std::error::Error>> {
16986        let f = self.func("append_quantize_kv_q8_0_q5_1_seqs");
16987        let nblk = (kv_dim_k.max(kv_dim_v) / 32) as u32;
16988        let cfg = LaunchConfig {
16989            grid_dim: (nblk, b_n as u32, 1),
16990            block_dim: (32, 1, 1),
16991            shared_mem_bytes: 0,
16992        };
16993        let (kdk, kdv) = (kv_dim_k as i32, kv_dim_v as i32);
16994        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
16995        let __s_b = self.gpu.stream();
16996        let mut b = __s_b.launch_builder(&f);
16997        b.arg(k_rows)
16998            .arg(v_rows)
16999            .arg(kv_ptrs)
17000            .arg(pos_seq)
17001            .arg(&kdk)
17002            .arg(&kdv)
17003            .arg(&ktb)
17004            .arg(&vtb);
17005        unsafe {
17006            b.launch(cfg)?;
17007        }
17008        Ok(())
17009    }
17010
17011    /// True iff the MULTI-ROW verify FA (`fa_decode_rows`) is usable for a verify batch whose
17012    /// FIRST row attends `base_len + 1` keys: every row must take the SAME kernel eager decode
17013    /// would (the vec path) — mirrors fa_decode's gate exactly (MEMRA_NO_FA_VEC + FA_VEC_MIN_TKV +
17014    /// head_dim), evaluated at the MINIMUM row bound so no row could have picked scalar.
17015    /// MEMRA_FA_ROWS_OFF=1 is the A/B + fallback seam (per-row loop).
17016    pub fn fa_rows_eligible(&self, base_len: usize, head_dim: usize) -> bool {
17017        std::env::var("MEMRA_NO_FA_VEC").is_err()
17018            && std::env::var("MEMRA_FA_ROWS_OFF").is_err()
17019            && base_len + 1 >= fa_vec_min_tkv()
17020            && head_dim <= 256
17021            && head_dim % 32 == 0
17022    }
17023
17024    /// MULTI-ROW verify FA: run fa_decode_vec_q's EXACT per-row program for T causal query rows
17025    /// (row r attends keys [0..base_len+r+1)) in ONE kernel launch with grid.z = row, plus ONE
17026    /// row-batched combine. Replaces the T separate (fa_decode + combine) launches of the spec
17027    /// verify — same per-row split partition (n_splits_r = ceil(t_kv_r/split_keys), the
17028    /// fa_split_keys formula), same key-walk order, same reduce shapes => bit-identical outputs
17029    /// per row (kernel-check pins rows-vs-loop byte identity; run-spec is the end gate).
17030    /// Caller must have checked `fa_rows_eligible(base_len, head_dim)`.
17031    /// q is the verify's token-major [T, n_head, head_dim] stack; o is written [T, n_head, head_dim].
17032    #[allow(clippy::too_many_arguments)]
17033    pub fn fa_decode_rows(
17034        &self,
17035        q: &CudaSlice<f32>,
17036        k: &cudarc::driver::CudaView<u8>,
17037        v: &cudarc::driver::CudaView<u8>,
17038        o: &mut CudaSlice<f32>,
17039        head_dim: usize,
17040        n_head: usize,
17041        n_head_kv: usize,
17042        base_len: usize,
17043        t: usize,
17044        scale: f32,
17045        k_tok_bytes: usize,
17046        v_tok_bytes: usize,
17047        // hd512 dpl16 twin is DEVICE-LEN (graph arc): base_dev/plus feed the
17048        // kernel; host base_len keeps sizing the splits/partials. hd256 twins
17049        // keep the host arg. None is a bug for hd512 (asserted below).
17050        base_dev: Option<(&CudaSlice<i32>, i32)>,
17051        // K and V planes hold the same values (gemma globals, wv:=wk): pick
17052        // the _kv twin — V plane never read, value rides the q8_0 key dq.
17053        kv_shared: bool,
17054        // this layer's cache is e4m3 (gemma windowed under wkv): resolve the
17055        // hd256 rows kernel from the kf8vf8 module. PER-CALL — a global env
17056        // check here hijacked qwen/kernel-check hd256 rows (8 FAILs, 230ebbe).
17057        g: bool,
17058        // t=1 decode arm only: emit (int8, per-32 scales) from the dc combine
17059        // (hd512 path) — the standalone quantize launch folds away.
17060        mut q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17061    ) -> Result<(), Box<dyn std::error::Error>> {
17062        debug_assert!(base_len + 1 >= fa_vec_min_tkv() && head_dim <= 512 && head_dim % 32 == 0);
17063        let t_kv_max = base_len + t; // LAST row's key bound
17064        let mut sp = fa_split_keys(t_kv_max, n_head_kv); // env/default — same value every row
17065        // hd512 split override (MEMRA_FA_SP512, 2026-07-11): gemma globals have n_head_kv=2 so
17066        // the grid is (2 x n_splits) — at depth ~29 splits = 58 blocks on 82 SMs (half idle,
17067        // rows_dpl16 8x off its byte floor). EVERY gemma hd512 caller shares THIS wrapper
17068        // (parity law), so the partition is freely tunable — verify and decode move together.
17069        if head_dim == 512 {
17070            static SP512: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17071            // default 16 (2026-07-11 depth sweep, N=2: plain 155.4->156.5, depth spec
17072            // 236.9->250.4; 12/24/32 all worse). hd512 exists only on gemma globals.
17073            let v = *SP512.get_or_init(|| {
17074                std::env::var("MEMRA_FA_SP512")
17075                    .ok()
17076                    .and_then(|x| x.parse().ok())
17077                    .unwrap_or(0)
17078            });
17079            sp = if v >= 8 {
17080                v
17081            } else {
17082                FA_SP512_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17083            };
17084        }
17085        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17086        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17087        let gqa = (n_head / n_head_kv).max(1) as u32;
17088        // LADDER-RUNG STRADDLE FIX (issue #10, 2026-07-13, g7e-proven): one sp for every row
17089        // diverges from eager decode when a split-ladder rung falls INSIDE the batch — row r's
17090        // eager twin used fa_split_keys(t_kv_r), the batch used fa_split_keys(t_kv_max), and
17091        // the different partition changes the combine's FP order (greedy tie flips at depth;
17092        // MEMRA_FA_SPLIT=64 pin -> PASS on the exact g7e failing config). Fix: group
17093        // consecutive rows by their OWN ladder value and launch once per group — each row then
17094        // executes the exact per-row program eager ran. Rungs land once per doubling, so this
17095        // is 1 launch in the common case and 2 on a crossing round. hd512 keeps one group (its
17096        // sp override is t_kv-independent by construction).
17097        let mut groups: Vec<(usize, usize, usize)> = Vec::new(); // (row0, t_g, sp_g)
17098        if head_dim == 512 || fa_split_keys(base_len + 1, n_head_kv) == sp {
17099            groups.push((0, t, sp));
17100        } else {
17101            let mut r0 = 0usize;
17102            while r0 < t {
17103                let sp_g = fa_split_keys(base_len + r0 + 1, n_head_kv);
17104                let mut r1 = r0 + 1;
17105                while r1 < t && fa_split_keys(base_len + r1 + 1, n_head_kv) == sp_g {
17106                    r1 += 1;
17107                }
17108                groups.push((r0, r1 - r0, sp_g));
17109                r0 = r1;
17110            }
17111        }
17112        // Deep-ctx smem twin for the VERIFY rows (2026-07-05): same threshold + rationale as
17113        // fa_decode's dispatch — at 40k the register path's GQA L2-reuse premise is dead and the
17114        // verify multiplies the 4x DRAM re-read by T rows. Bit-identical per (row,token,split).
17115        static SMEM_TKV_R: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17116        let smem_tkv = *SMEM_TKV_R.get_or_init(|| {
17117            std::env::var("MEMRA_FA_SMEM_TKV")
17118                .ok()
17119                .and_then(|v| v.parse().ok())
17120                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17121        });
17122        let v4 = fa_v4_at(base_len + t) && head_dim == 256;
17123        let v3 = fa_v3_active(head_dim);
17124        let smem_rows =
17125            head_dim <= 256 && !v3 && !fa_v2_on() && smem_tkv > 0 && t_kv_max >= smem_tkv;
17126        // kv_shared twin RETIRED (2026-07-11 depth run-gen gate): the wv:=wk premise fails
17127        // POST-cache — cached K is k-normed+roped, cached V is not; the twin fed roped keys
17128        // in as values. Verify/decode/stream gates were blind (both sides shared the wrong
17129        // symbol — the parity law's blind spot); only prefill-vs-decode at depth caught it.
17130        let _ = kv_shared;
17131        // i2 twin: 2-key interleaved walk (MEMRA_FA_I2=0 reverts). i4 probed NEGATIVE
17132        // (157.3 vs 161.2 depth plain — register pressure past i2's sweet spot; jsonl).
17133        let i2 = head_dim == 512 && std::env::var("MEMRA_FA_I2").as_deref() != Ok("0");
17134        // v4-hd512 (MEMRA_FA_V512=1 opt-in, 2026-07-14): the v4 key-per-lane recipe on the
17135        // globals lane (depth profile: i2 ~4.6x off its byte floor — the v3-class
17136        // reduce-per-key latency signature). NEW NUMERIC CONFIG shared by every hd512
17137        // caller (decode+verify flip together); run-gen argmax + acceptance arbitrate.
17138        // T-BATCHED hd512 (DEFAULT ON 2026-07-14, MEMRA_FA_TB512=0 seam): one block per
17139        // (kv_head, split) stages its tile once and loops the rows over it — kills the
17140        // x t DRAM re-read of the full-ctx globals (depth cell +1.4%, plain flat, N=3
17141        // interleaved). FIXED absolute partition = NEW NUMERIC for the combine order,
17142        // shared by every hd512 caller through this wrapper (decode+verify flip together;
17143        // depth stream identical, acceptance unshifted, spec 256/256 x3 models).
17144        // Requires sp <= 32 (single staged tile; acc reused per row). The z-form v4_512
17145        // sibling (in-kernel dp4a port alone) probed FLAT — hd512 was DRAM-re-read-bound,
17146        // not unpack-bound; jsonl 2026-07-14.
17147        static TB512: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
17148        // gqa <= 16 = fa_v4_smem_512's q-array capacity; past it fall to the register twins.
17149        let tb512 = head_dim == 512
17150            && sp <= 32
17151            && n_head / n_head_kv.max(1) <= 16
17152            && *TB512.get_or_init(|| std::env::var("MEMRA_FA_TB512").as_deref() != Ok("0"));
17153        let fname = if tb512 {
17154            "fa_decode_vec_q_rows_v4_512_tb"
17155        } else if i2 {
17156            "fa_decode_vec_q_rows_dpl16_i2"
17157        } else if head_dim == 512 {
17158            "fa_decode_vec_q_rows_dpl16"
17159        }
17160        // gemma globals (parity law)
17161        else if v4 {
17162            "fa_decode_vec_q_rows_v4"
17163        } else if v3 {
17164            "fa_decode_vec_q_rows_v3"
17165        } else if fa_v2_on() {
17166            "fa_decode_vec_q_rows_v2"
17167        } else if smem_rows {
17168            "fa_decode_vec_q_rows_smem"
17169        } else {
17170            "fa_decode_vec_q_rows"
17171        };
17172        let f = if head_dim == 512 {
17173            self.fa_func(fname, head_dim)
17174        } else if g {
17175            // FP8-WINDOWED: hd256 rows over an e4m3 cache — kf8vf8 module, SAME symbol
17176            // choice as decode's kvmod dispatch (parity law: excluding v4 here paired
17177            // g-module rows against decode's g-module v4 — different programs, short-VG
17178            // maxdiff 2.0 / spec stream 0/128, 2026-07-12). rows_v4 is format-aware
17179            // since fda9790; only the smem twin stays excluded (V-stage q5_1-only).
17180            // hd128 (qwen fp8-KV) lands on the base/register rows via fname — the
17181            // dq macros are format-aware.
17182            self.func_g(if smem_rows {
17183                "fa_decode_vec_q_rows"
17184            } else {
17185                fname
17186            })
17187        } else {
17188            self.func(fname)
17189        };
17190        let shmem = if tb512 {
17191            // fa_v4_smem_512 (q 9KB gqa<=16 + k tile 18KB) + sV 32*512 (e4m3 module halves it)
17192            let gk = Self::gkv_on();
17193            let sh =
17194                (8192 + 1024 + 32 * 512 + 32 * 64 + 32 * head_dim * if gk { 1 } else { 2 }) as u32;
17195            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17196            f.set_attribute(
17197                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17198                sh as i32,
17199            )?;
17200            sh
17201        } else if v4 || v3 || smem_rows || fa_v2_on() {
17202            // v4: fa_v4_smem (11.5KB) + sV; v3 stages sV only; v2/smem twins stage sK+sV.
17203            let sh = (if v4 {
17204                11520 + 32 * head_dim * if g { 1 } else { 2 }
17205            } else if v3 {
17206                32 * head_dim * 2
17207            } else {
17208                2 * 32 * head_dim * 2
17209            }) as u32;
17210            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17211            f.set_attribute(
17212                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17213                sh as i32,
17214            )?;
17215            sh
17216        } else {
17217            0
17218        };
17219        // Per-GROUP launches (single group in the common case — identical to the pre-fix
17220        // single launch there): each group gets its own partials (the rows kernel indexes
17221        // partials by its LOCAL grid.z row) and q/o row-offset views.
17222        for &(r0, t_g, sp_g) in &groups {
17223            let n_splits_g = (base_len + r0 + t_g).div_ceil(sp_g);
17224            let (nspm, spk) = (n_splits_g as i32, sp_g as i32);
17225            let base_i = (base_len + r0) as i32;
17226            let o_len = t_g * n_head * n_splits_g * head_dim;
17227            let ml_len = t_g * n_head * n_splits_g;
17228            let mut part_guard = self.fa_part_pool.lock().unwrap();
17229            if part_guard
17230                .as_ref()
17231                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17232                .unwrap_or(true)
17233            {
17234                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17235                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17236                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17237                // later live allocations land at those addresses, and the next graph REPLAY writes
17238                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17239                // output corruption began the burst after the trunk's t_kv growth first realloc'd
17240                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17241                // the baked addresses alive (single-stream: eager writes the new buffers, replays
17242                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17243                // (total retired < final size).
17244                let old = part_guard.take();
17245                let (co, cm) = old
17246                    .as_ref()
17247                    .map(|pp| (pp.0.len(), pp.1.len()))
17248                    .unwrap_or((0, 0));
17249                if let Some(old) = old {
17250                    self.fa_part_retired.lock().unwrap().push(old);
17251                }
17252                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17253                    eprintln!(
17254                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17255                        co, o_len, cm, ml_len
17256                    );
17257                }
17258                *part_guard = Some((
17259                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17260                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17261                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17262                ));
17263            }
17264            let pg = part_guard.as_mut().unwrap();
17265            self.gpu
17266                .stream()
17267                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17268            self.gpu
17269                .stream()
17270                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17271            self.gpu
17272                .stream()
17273                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17274            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17275            let (part_o, part_m, part_l) = (&mut *part_o, &mut *part_m, &mut *part_l);
17276            let qv = self.view(q, t * n_head * head_dim);
17277            let q_g = qv.slice(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17278            let cfg = LaunchConfig {
17279                grid_dim: (n_head_kv as u32, n_splits_g as u32, t_g as u32),
17280                block_dim: (32, gqa, 1),
17281                shared_mem_bytes: shmem,
17282            };
17283            {
17284                let __s_b = self.gpu.stream();
17285                let mut b = __s_b.launch_builder(&f);
17286                if tb512 {
17287                    // rows-inner launch: grid.z dropped, the kernel loops n_rows itself.
17288                    let (bd, plus) =
17289                        base_dev.expect("hd512 rows twin requires a device base counter");
17290                    let plus_g = plus + r0 as i32;
17291                    let nr = t_g as i32;
17292                    if Self::pdl_on() && Self::pdl_wb_on() {
17293                        // wave-B2b: flavor mirrors fa_func(fname, 512) = gkv.
17294                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17295                        let s = &self.gpu.stream();
17296                        let (pq, _b0) = q_g.device_ptr(s);
17297                        let (pk, _b1) = k.device_ptr(s);
17298                        let (pv, _b2) = v.device_ptr(s);
17299                        let (po, _b3) = part_o.device_ptr_mut(s);
17300                        let (pm, _b4) = part_m.device_ptr_mut(s);
17301                        let (pl, _b5) = part_l.device_ptr_mut(s);
17302                        let (pb, _b6) = bd.device_ptr(s);
17303                        let mut ps = [
17304                            &pq as *const _ as *mut std::ffi::c_void,
17305                            &pk as *const _ as *mut _,
17306                            &pv as *const _ as *mut _,
17307                            &po as *const _ as *mut _,
17308                            &pm as *const _ as *mut _,
17309                            &pl as *const _ as *mut _,
17310                            &hd as *const _ as *mut _,
17311                            &nh as *const _ as *mut _,
17312                            &nhkv as *const _ as *mut _,
17313                            &pb as *const _ as *mut _,
17314                            &plus_g as *const _ as *mut _,
17315                            &scale as *const _ as *mut _,
17316                            &nspm as *const _ as *mut _,
17317                            &spk as *const _ as *mut _,
17318                            &ktb as *const _ as *mut _,
17319                            &vtb as *const _ as *mut _,
17320                            &nr as *const _ as *mut _,
17321                        ];
17322                        unsafe {
17323                            self.launch_pdl_flash(
17324                                Self::gkv_on(),
17325                                "fa_decode_vec_q_rows_v4_512_tb",
17326                                (n_head_kv as u32, n_splits_g as u32, 1),
17327                                (32, gqa, 1),
17328                                shmem,
17329                                &mut ps,
17330                            )?;
17331                        }
17332                    } else {
17333                        let cfg_tb = LaunchConfig {
17334                            grid_dim: (n_head_kv as u32, n_splits_g as u32, 1),
17335                            block_dim: (32, gqa, 1),
17336                            shared_mem_bytes: shmem,
17337                        };
17338                        b.arg(&q_g)
17339                            .arg(k)
17340                            .arg(v)
17341                            .arg(&mut *part_o)
17342                            .arg(&mut *part_m)
17343                            .arg(&mut *part_l)
17344                            .arg(&hd)
17345                            .arg(&nh)
17346                            .arg(&nhkv)
17347                            .arg(bd)
17348                            .arg(&plus_g)
17349                            .arg(&scale)
17350                            .arg(&nspm)
17351                            .arg(&spk)
17352                            .arg(&ktb)
17353                            .arg(&vtb)
17354                            .arg(&nr);
17355                        unsafe {
17356                            b.launch(cfg_tb)?;
17357                        }
17358                    }
17359                } else if head_dim == 512 {
17360                    let (bd, plus) =
17361                        base_dev.expect("hd512 rows twin requires a device base counter");
17362                    let plus_g = plus + r0 as i32;
17363                    b.arg(&q_g)
17364                        .arg(k)
17365                        .arg(v)
17366                        .arg(&mut *part_o)
17367                        .arg(&mut *part_m)
17368                        .arg(&mut *part_l)
17369                        .arg(&hd)
17370                        .arg(&nh)
17371                        .arg(&nhkv)
17372                        .arg(bd)
17373                        .arg(&plus_g)
17374                        .arg(&scale)
17375                        .arg(&nspm)
17376                        .arg(&spk)
17377                        .arg(&ktb)
17378                        .arg(&vtb);
17379                    unsafe {
17380                        b.launch(cfg)?;
17381                    }
17382                } else {
17383                    b.arg(&q_g)
17384                        .arg(k)
17385                        .arg(v)
17386                        .arg(&mut *part_o)
17387                        .arg(&mut *part_m)
17388                        .arg(&mut *part_l)
17389                        .arg(&hd)
17390                        .arg(&nh)
17391                        .arg(&nhkv)
17392                        .arg(&base_i)
17393                        .arg(&scale)
17394                        .arg(&nspm)
17395                        .arg(&spk)
17396                        .arg(&ktb)
17397                        .arg(&vtb);
17398                    unsafe {
17399                        b.launch(cfg)?;
17400                    }
17401                }
17402            }
17403            let cfg2 = LaunchConfig {
17404                grid_dim: (n_head as u32, t_g as u32, 1),
17405                block_dim: (head_dim as u32, 1, 1),
17406                shared_mem_bytes: 0,
17407            };
17408            let mut o_g = o.slice_mut(r0 * n_head * head_dim..(r0 + t_g) * n_head * head_dim);
17409            if head_dim == 512 {
17410                // device-len combine (shared by verify/eager/graph — parity by symbol): the
17411                // per-row n_splits derives from the SAME counter the rows kernel read.
17412                let (bd, plus) = base_dev.unwrap();
17413                let plus_g = plus + r0 as i32;
17414                if let Some((oq, od)) = q8_out.as_mut() {
17415                    // wave-5b port (2026-07-23, t=1 decode only): q8-emitting dc combine.
17416                    debug_assert!(t == 1, "rows q8 emit is a t=1 decode arm");
17417                    if Self::pdl_on() && Self::pdl_wb_on() {
17418                        // wave-B2: flavor mirrors fa_func (hd512 + gkv → kf8vf8).
17419                        use cudarc::driver::{DevicePtr, DevicePtrMut};
17420                        let s = &self.gpu.stream();
17421                        let (po, _g0) = part_o.device_ptr(s);
17422                        let (pm, _g1) = part_m.device_ptr(s);
17423                        let (pl, _g2) = part_l.device_ptr(s);
17424                        let (pq, _g3) = oq.device_ptr_mut(s);
17425                        let (pd, _g4) = od.device_ptr_mut(s);
17426                        let (pb, _g5) = bd.device_ptr(s);
17427                        let mut ps = [
17428                            &po as *const _ as *mut std::ffi::c_void,
17429                            &pm as *const _ as *mut _,
17430                            &pl as *const _ as *mut _,
17431                            &pq as *const _ as *mut _,
17432                            &pd as *const _ as *mut _,
17433                            &hd as *const _ as *mut _,
17434                            &nh as *const _ as *mut _,
17435                            &pb as *const _ as *mut _,
17436                            &plus_g as *const _ as *mut _,
17437                            &nspm as *const _ as *mut _,
17438                            &spk as *const _ as *mut _,
17439                        ];
17440                        unsafe {
17441                            self.launch_pdl_flash(
17442                                Self::gkv_on(),
17443                                "fa_decode_combine_rows_dc_q8_1",
17444                                cfg2.grid_dim,
17445                                cfg2.block_dim,
17446                                0,
17447                                &mut ps,
17448                            )?;
17449                        }
17450                        continue;
17451                    }
17452                    let fc = self.fa_func("fa_decode_combine_rows_dc_q8_1", head_dim);
17453                    let __s_b2 = self.gpu.stream();
17454                    let mut b2 = __s_b2.launch_builder(&fc);
17455                    b2.arg(&*part_o)
17456                        .arg(&*part_m)
17457                        .arg(&*part_l)
17458                        .arg(&mut **oq)
17459                        .arg(&mut **od)
17460                        .arg(&hd)
17461                        .arg(&nh)
17462                        .arg(bd)
17463                        .arg(&plus_g)
17464                        .arg(&nspm)
17465                        .arg(&spk);
17466                    unsafe {
17467                        b2.launch(cfg2)?;
17468                    }
17469                    continue;
17470                }
17471                let fc = self.fa_func("fa_decode_combine_rows_dc", head_dim);
17472                let __s_b2 = self.gpu.stream();
17473                let mut b2 = __s_b2.launch_builder(&fc);
17474                b2.arg(&*part_o)
17475                    .arg(&*part_m)
17476                    .arg(&*part_l)
17477                    .arg(&mut o_g)
17478                    .arg(&hd)
17479                    .arg(&nh)
17480                    .arg(bd)
17481                    .arg(&plus_g)
17482                    .arg(&nspm)
17483                    .arg(&spk);
17484                unsafe {
17485                    b2.launch(cfg2)?;
17486                }
17487            } else {
17488                // q8 emit is wired for the hd512 dc-combine arm only — a Some here would
17489                // leave the caller's pair unwritten (consumer would read garbage).
17490                assert!(
17491                    q8_out.is_none(),
17492                    "rows q8 emit requires the hd512 dc combine"
17493                );
17494                let fc = self.func("fa_decode_combine_rows");
17495                let __s_b2 = self.gpu.stream();
17496                let mut b2 = __s_b2.launch_builder(&fc);
17497                b2.arg(&*part_o)
17498                    .arg(&*part_m)
17499                    .arg(&*part_l)
17500                    .arg(&mut o_g)
17501                    .arg(&hd)
17502                    .arg(&nh)
17503                    .arg(&base_i)
17504                    .arg(&nspm)
17505                    .arg(&spk);
17506                unsafe {
17507                    b2.launch(cfg2)?;
17508                }
17509            }
17510        }
17511        Ok(())
17512    }
17513
17514    /// WINDOWED verify rows (gemma R6 deep-ctx): every row attends exactly `window` keys —
17515    /// bit-identical per row to the T=1 decode's fa_decode over the window VIEW. Caller gates
17516    /// base_len + 1 >= window (no under-window rows) and head_dim == 256 (v4 stamp).
17517    #[allow(clippy::too_many_arguments)]
17518    pub fn fa_decode_rows_w(
17519        &self,
17520        q: &CudaSlice<f32>,
17521        k: &cudarc::driver::CudaView<u8>,
17522        v: &cudarc::driver::CudaView<u8>,
17523        o: &mut CudaSlice<f32>,
17524        head_dim: usize,
17525        n_head: usize,
17526        n_head_kv: usize,
17527        base_dev: &CudaSlice<i32>,
17528        base_plus: i32,
17529        t: usize,
17530        scale: f32,
17531        window: usize,
17532        k_tok_bytes: usize,
17533        v_tok_bytes: usize,
17534        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
17535    ) -> Result<(), Box<dyn std::error::Error>> {
17536        // DEVICE-LEN (graph arc step 1, 2026-07-11): the causal base rides an i32 counter
17537        // (kernel T_kv = dev[0] + base_plus + r + 1) so depth graphs can replay with len
17538        // advancing on-device. dc paths pass kvl.len_d with plus=-1; verify/eager sync the
17539        // counter with one async set_i32_one first. Partials/splits size from `window` (host).
17540        debug_assert!(head_dim == 256);
17541        // windowed split (MEMRA_FA_SPW, default 32 — re-swept 2026-07-12 under the raw-e4m3 sV
17542        // occupancy ceiling (4 blocks/SM): t=1 decode is GRID-limited (win/sp splits x nkv
17543        // blocks), so smaller splits fill the ceiling — 1.7k 174.4/174.0 vs 48's 170.7/170.3,
17544        // 4.9k 159.8 vs 157.4 (N=2 interleaved, stable window). Spec serving prefers 64
17545        // (verify t=K+1 fills the grid via grid.z=t; depth K=7 281.3 vs 249.3 at 32) — set
17546        // MEMRA_FA_SPW=64 there, same config law as MEMRA_GEMMA_GKV=0. MUST be one value for
17547        // ALL widths: a t-keyed probe broke decode-vs-verify combine order (stream 9/128).
17548        let sp = {
17549            static SPW: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17550            let v = *SPW.get_or_init(|| {
17551                std::env::var("MEMRA_FA_SPW")
17552                    .ok()
17553                    .and_then(|x| x.parse().ok())
17554                    .unwrap_or(0)
17555            });
17556            if v >= 8 {
17557                v
17558            } else {
17559                FA_SPW_DEFAULT.load(std::sync::atomic::Ordering::Relaxed)
17560            }
17561        };
17562        let n_splits_max = (window + sp - 1) / sp;
17563        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17564        let (nspm, spk, wini) = (n_splits_max as i32, sp as i32, window as i32);
17565        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17566        let gqa = (n_head / n_head_kv).max(1) as u32;
17567        let o_len = t * n_head * n_splits_max * head_dim;
17568        let ml_len = t * n_head * n_splits_max;
17569        let mut part_guard = self.fa_part_pool.lock().unwrap();
17570        if part_guard
17571            .as_ref()
17572            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17573            .unwrap_or(true)
17574        {
17575            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17576            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17577            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17578            // later live allocations land at those addresses, and the next graph REPLAY writes
17579            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17580            // output corruption began the burst after the trunk's t_kv growth first realloc'd
17581            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17582            // the baked addresses alive (single-stream: eager writes the new buffers, replays
17583            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17584            // (total retired < final size).
17585            let old = part_guard.take();
17586            let (co, cm) = old
17587                .as_ref()
17588                .map(|pp| (pp.0.len(), pp.1.len()))
17589                .unwrap_or((0, 0));
17590            if let Some(old) = old {
17591                self.fa_part_retired.lock().unwrap().push(old);
17592            }
17593            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17594                eprintln!(
17595                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17596                    co, o_len, cm, ml_len
17597                );
17598            }
17599            *part_guard = Some((
17600                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17601                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17602                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17603            ));
17604        }
17605        let pg = part_guard.as_mut().unwrap();
17606        self.gpu
17607            .stream()
17608            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17609        self.gpu
17610            .stream()
17611            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17612        self.gpu
17613            .stream()
17614            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17615        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17616        // Lane pick: decode AND verify both land here in the windowed regime (parity law —
17617        // hybrid_forward verify_attn), so the pick only needs internal consistency, not
17618        // clone-of-decode bit fidelity (SASS-proven impossible for textually identical
17619        // kernels, jsonl 2026-07-10). v4 under the threshold; smem twin at/above the smem
17620        // floor (deep-ctx broadcast win); register twin between.
17621        static SMEM_TKV_W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
17622        let smem_tkv = *SMEM_TKV_W.get_or_init(|| {
17623            std::env::var("MEMRA_FA_SMEM_TKV")
17624                .ok()
17625                .and_then(|v| v.parse().ok())
17626                .unwrap_or_else(|| FA_SMEM_TKV_DEFAULT.load(std::sync::atomic::Ordering::Relaxed))
17627        });
17628        // MULTI-ROW v4: resurrected 2026-07-14 (the '33 tok/s collapse' was a paired-map
17629        // partial-write bug, not the mechanism) and falsified HONESTLY at gqa 2: bit-exact
17630        // but −1.7% on the 31B depth cell — the sp helper warp already hides staging
17631        // in-block, and mr trades L2-cheap redundant bytes for serialized per-warp gqa
17632        // score/B3 chains. Arm deleted; jsonl row 2026-07-14 is the record.
17633        use cudarc::driver::sys::CUfunction_attribute_enum as A;
17634        // FP8-WINDOWED (wkv): the v4 family is format-aware (2026-07-12 KFMT/VFMT staging
17635        // arms) — wkv rides the SAME lane logic, resolved from the kf8vf8 module. One symbol
17636        // per (lane, format-module) keeps parity structural; the old register-i2 detour
17637        // (-33%) is retired.
17638        let wg = Self::wkv_on();
17639        // STAGING-PARALLEL v4 (MEMRA_FA_SPW2, default ON at gqa==1): warp 1 = staging helper
17640        // (v4 is 61% staging); score phases identical to v4_w. Same symbol all t.
17641        let sp2 =
17642            gqa <= 4 && fa_v4_at(window) && std::env::var("MEMRA_FA_SPW2").as_deref() != Ok("0");
17643        if sp2 {
17644            let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
17645            if Self::pdl_on() && Self::pdl_wb_on() {
17646                // wave-B2b: flavor mirrors wg.
17647                use cudarc::driver::{DevicePtr, DevicePtrMut};
17648                let s = &self.gpu.stream();
17649                let (pq, _b0) = q.device_ptr(s);
17650                let (pk, _b1) = k.device_ptr(s);
17651                let (pv, _b2) = v.device_ptr(s);
17652                let (po, _b3) = part_o.device_ptr_mut(s);
17653                let (pm, _b4) = part_m.device_ptr_mut(s);
17654                let (pl, _b5) = part_l.device_ptr_mut(s);
17655                let (pb, _b6) = base_dev.device_ptr(s);
17656                let mut ps = [
17657                    &pq as *const _ as *mut std::ffi::c_void,
17658                    &pk as *const _ as *mut _,
17659                    &pv as *const _ as *mut _,
17660                    &po as *const _ as *mut _,
17661                    &pm as *const _ as *mut _,
17662                    &pl as *const _ as *mut _,
17663                    &hd as *const _ as *mut _,
17664                    &nh as *const _ as *mut _,
17665                    &nhkv as *const _ as *mut _,
17666                    &pb as *const _ as *mut _,
17667                    &base_plus as *const _ as *mut _,
17668                    &scale as *const _ as *mut _,
17669                    &nspm as *const _ as *mut _,
17670                    &spk as *const _ as *mut _,
17671                    &ktb as *const _ as *mut _,
17672                    &vtb as *const _ as *mut _,
17673                    &wini as *const _ as *mut _,
17674                ];
17675                unsafe {
17676                    self.launch_pdl_flash(
17677                        wg,
17678                        "fa_decode_vec_q_rows_v4_w_sp",
17679                        (n_head_kv as u32, n_splits_max as u32, t as u32),
17680                        (32, gqa + 1, 1),
17681                        sh,
17682                        &mut ps,
17683                    )?;
17684                }
17685            } else {
17686                let f = if wg {
17687                    self.func_g("fa_decode_vec_q_rows_v4_w_sp")
17688                } else {
17689                    self.func("fa_decode_vec_q_rows_v4_w_sp")
17690                };
17691                f.set_attribute(
17692                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17693                    sh as i32,
17694                )?;
17695                let cfg = LaunchConfig {
17696                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
17697                    block_dim: (32, gqa + 1, 1),
17698                    shared_mem_bytes: sh,
17699                };
17700                let __s_b = self.gpu.stream();
17701                let mut b = __s_b.launch_builder(&f);
17702                b.arg(q)
17703                    .arg(k)
17704                    .arg(v)
17705                    .arg(&mut *part_o)
17706                    .arg(&mut *part_m)
17707                    .arg(&mut *part_l)
17708                    .arg(&hd)
17709                    .arg(&nh)
17710                    .arg(&nhkv)
17711                    .arg(base_dev)
17712                    .arg(&base_plus)
17713                    .arg(&scale)
17714                    .arg(&nspm)
17715                    .arg(&spk)
17716                    .arg(&ktb)
17717                    .arg(&vtb)
17718                    .arg(&wini);
17719                unsafe {
17720                    b.launch(cfg)?;
17721                }
17722            }
17723        } else {
17724            if fa_v4_at(window) && Self::pdl_on() && Self::pdl_wb_on() {
17725                // wave-B2b: the v4_w pick only (smem/reg twins stay builder-launched).
17726                let sh = (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32;
17727                use cudarc::driver::{DevicePtr, DevicePtrMut};
17728                let s = &self.gpu.stream();
17729                let (pq, _b0) = q.device_ptr(s);
17730                let (pk, _b1) = k.device_ptr(s);
17731                let (pv, _b2) = v.device_ptr(s);
17732                let (po, _b3) = part_o.device_ptr_mut(s);
17733                let (pm, _b4) = part_m.device_ptr_mut(s);
17734                let (pl, _b5) = part_l.device_ptr_mut(s);
17735                let (pb, _b6) = base_dev.device_ptr(s);
17736                let mut ps = [
17737                    &pq as *const _ as *mut std::ffi::c_void,
17738                    &pk as *const _ as *mut _,
17739                    &pv as *const _ as *mut _,
17740                    &po as *const _ as *mut _,
17741                    &pm as *const _ as *mut _,
17742                    &pl as *const _ as *mut _,
17743                    &hd as *const _ as *mut _,
17744                    &nh as *const _ as *mut _,
17745                    &nhkv as *const _ as *mut _,
17746                    &pb as *const _ as *mut _,
17747                    &base_plus as *const _ as *mut _,
17748                    &scale as *const _ as *mut _,
17749                    &nspm as *const _ as *mut _,
17750                    &spk as *const _ as *mut _,
17751                    &ktb as *const _ as *mut _,
17752                    &vtb as *const _ as *mut _,
17753                    &wini as *const _ as *mut _,
17754                ];
17755                unsafe {
17756                    self.launch_pdl_flash(
17757                        wg,
17758                        "fa_decode_vec_q_rows_v4_w",
17759                        (n_head_kv as u32, n_splits_max as u32, t as u32),
17760                        (32, gqa, 1),
17761                        sh,
17762                        &mut ps,
17763                    )?;
17764                }
17765            } else {
17766                let pick = |name: &str| {
17767                    if wg {
17768                        self.func_g(name)
17769                    } else {
17770                        self.func(name)
17771                    }
17772                };
17773                let (f, sh) = if fa_v4_at(window) {
17774                    let f = pick("fa_decode_vec_q_rows_v4_w");
17775                    (f, (11520 + 32 * head_dim * if wg { 1 } else { 2 }) as u32)
17776                } else if smem_tkv > 0 && window >= smem_tkv {
17777                    // NOTE: the smem twin's V-stage is still q5_1-hardcoded — unreachable under wkv
17778                    // at the gemma window (v4 covers it); revisit if the smem floor ever drops.
17779                    (
17780                        pick("fa_decode_vec_q_rows_smem_w"),
17781                        (2 * 32 * head_dim * 2) as u32,
17782                    )
17783                } else {
17784                    (pick("fa_decode_vec_q_rows_reg_w"), 0u32)
17785                };
17786                f.set_attribute(
17787                    A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17788                    sh as i32,
17789                )?;
17790                let cfg = LaunchConfig {
17791                    grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
17792                    block_dim: (32, gqa, 1),
17793                    shared_mem_bytes: sh,
17794                };
17795                let __s_b = self.gpu.stream();
17796                let mut b = __s_b.launch_builder(&f);
17797                b.arg(q)
17798                    .arg(k)
17799                    .arg(v)
17800                    .arg(&mut *part_o)
17801                    .arg(&mut *part_m)
17802                    .arg(&mut *part_l)
17803                    .arg(&hd)
17804                    .arg(&nh)
17805                    .arg(&nhkv)
17806                    .arg(base_dev)
17807                    .arg(&base_plus)
17808                    .arg(&scale)
17809                    .arg(&nspm)
17810                    .arg(&spk)
17811                    .arg(&ktb)
17812                    .arg(&vtb)
17813                    .arg(&wini);
17814                unsafe {
17815                    b.launch(cfg)?;
17816                }
17817            }
17818        }
17819        let cfg2 = LaunchConfig {
17820            grid_dim: (n_head as u32, t as u32, 1),
17821            block_dim: (head_dim as u32, 1, 1),
17822            shared_mem_bytes: 0,
17823        };
17824        if let Some((oq, od)) = q8_out {
17825            // wave-5b port (2026-07-23): q8-emitting combine — the t=1 decode's wo matvec
17826            // consumes the pair directly; the standalone quantize launch folds away.
17827            if Self::pdl_on() && Self::pdl_wb_on() {
17828                // wave-B2: flavor mirrors the builder's wg choice.
17829                use cudarc::driver::{DevicePtr, DevicePtrMut};
17830                let s = &self.gpu.stream();
17831                let (po, _g0) = part_o.device_ptr(s);
17832                let (pm, _g1) = part_m.device_ptr(s);
17833                let (pl, _g2) = part_l.device_ptr(s);
17834                let (pq, _g3) = oq.device_ptr_mut(s);
17835                let (pd, _g4) = od.device_ptr_mut(s);
17836                let mut ps = [
17837                    &po as *const _ as *mut std::ffi::c_void,
17838                    &pm as *const _ as *mut _,
17839                    &pl as *const _ as *mut _,
17840                    &pq as *const _ as *mut _,
17841                    &pd as *const _ as *mut _,
17842                    &hd as *const _ as *mut _,
17843                    &nh as *const _ as *mut _,
17844                    &nspm as *const _ as *mut _,
17845                    &spk as *const _ as *mut _,
17846                    &wini as *const _ as *mut _,
17847                ];
17848                unsafe {
17849                    self.launch_pdl_flash(
17850                        wg,
17851                        "fa_decode_combine_rows_w_q8_1",
17852                        cfg2.grid_dim,
17853                        cfg2.block_dim,
17854                        0,
17855                        &mut ps,
17856                    )?;
17857                }
17858                return Ok(());
17859            }
17860            let fc = if wg {
17861                self.func_g("fa_decode_combine_rows_w_q8_1")
17862            } else {
17863                self.func("fa_decode_combine_rows_w_q8_1")
17864            };
17865            let __s_b2 = self.gpu.stream();
17866            let mut b2 = __s_b2.launch_builder(&fc);
17867            b2.arg(&*part_o)
17868                .arg(&*part_m)
17869                .arg(&*part_l)
17870                .arg(oq)
17871                .arg(od)
17872                .arg(&hd)
17873                .arg(&nh)
17874                .arg(&nspm)
17875                .arg(&spk)
17876                .arg(&wini);
17877            unsafe {
17878                b2.launch(cfg2)?;
17879            }
17880            return Ok(());
17881        }
17882        let fc = if wg {
17883            self.func_g("fa_decode_combine_rows_w")
17884        } else {
17885            self.func("fa_decode_combine_rows_w")
17886        };
17887        let __s_b2 = self.gpu.stream();
17888        let mut b2 = __s_b2.launch_builder(&fc);
17889        b2.arg(&*part_o)
17890            .arg(&*part_m)
17891            .arg(&*part_l)
17892            .arg(o)
17893            .arg(&hd)
17894            .arg(&nh)
17895            .arg(&nspm)
17896            .arg(&spk)
17897            .arg(&wini);
17898        unsafe {
17899            b2.launch(cfg2)?;
17900        }
17901        Ok(())
17902    }
17903
17904    /// ROUND-STREAM stage (c): fa rows with the causal base from a device counter. Two lanes:
17905    /// v3 (qwen stream, fa_v3_active) and v4 (gemma hd256 burst — rows_v4_dc, g-module aware);
17906    /// `t_kv_upper` sizes splits/partials — the same one-sp-for-all-rows approximation class
17907    /// the host rows path already uses (battery-arbitrated); actual per-row bounds derive
17908    /// in-kernel from the counter (+ base_plus, v4 lane only — v3's kernel has no plus arg).
17909    #[allow(clippy::too_many_arguments)]
17910    pub fn fa_decode_rows_dc(
17911        &self,
17912        q: &CudaSlice<f32>,
17913        k: &cudarc::driver::CudaView<u8>,
17914        v: &cudarc::driver::CudaView<u8>,
17915        o: &mut CudaSlice<f32>,
17916        head_dim: usize,
17917        n_head: usize,
17918        n_head_kv: usize,
17919        base_dev: &CudaSlice<i32>,
17920        t_kv_upper: usize,
17921        t: usize,
17922        scale: f32,
17923        k_tok_bytes: usize,
17924        v_tok_bytes: usize,
17925        base_plus: i32,
17926        g: bool,
17927    ) -> Result<(), Box<dyn std::error::Error>> {
17928        let v4 = head_dim == 256 && fa_v4_at(t_kv_upper);
17929        assert!(
17930            v4 || fa_v3_active(head_dim),
17931            "stream fa rows requires the v3 or v4 lane"
17932        );
17933        assert!(v4 || base_plus == 0, "v3_dc kernel takes no plus arg");
17934        if v4 {
17935            let sp = fa_split_keys(t_kv_upper, n_head_kv);
17936            let n_splits_max = (t_kv_upper + sp - 1) / sp;
17937            let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
17938            let (nspm, spk) = (n_splits_max as i32, sp as i32);
17939            let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
17940            let gqa = (n_head / n_head_kv).max(1) as u32;
17941            let o_len = t * n_head * n_splits_max * head_dim;
17942            let ml_len = t * n_head * n_splits_max;
17943            let mut part_guard = self.fa_part_pool.lock().unwrap();
17944            if part_guard
17945                .as_ref()
17946                .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
17947                .unwrap_or(true)
17948            {
17949                // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
17950                // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
17951                // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
17952                // later live allocations land at those addresses, and the next graph REPLAY writes
17953                // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
17954                // output corruption began the burst after the trunk's t_kv growth first realloc'd
17955                // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
17956                // the baked addresses alive (single-stream: eager writes the new buffers, replays
17957                // touch only the old — never concurrently). Doubling growth bounds retired VRAM
17958                // (total retired < final size).
17959                let old = part_guard.take();
17960                let (co, cm) = old
17961                    .as_ref()
17962                    .map(|pp| (pp.0.len(), pp.1.len()))
17963                    .unwrap_or((0, 0));
17964                if let Some(old) = old {
17965                    self.fa_part_retired.lock().unwrap().push(old);
17966                }
17967                if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
17968                    eprintln!(
17969                        "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
17970                        co, o_len, cm, ml_len
17971                    );
17972                }
17973                *part_guard = Some((
17974                    self.alloc_uninit::<f32>(o_len.max(2 * co))?,
17975                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17976                    self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
17977                ));
17978            }
17979            let pg = part_guard.as_mut().unwrap();
17980            self.gpu
17981                .stream()
17982                .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
17983            self.gpu
17984                .stream()
17985                .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
17986            self.gpu
17987                .stream()
17988                .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
17989            let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
17990            let f = if g {
17991                self.func_g("fa_decode_vec_q_rows_v4_dc")
17992            } else {
17993                self.func("fa_decode_vec_q_rows_v4_dc")
17994            };
17995            let sh = (11520 + 32 * head_dim * if g { 1 } else { 2 }) as u32;
17996            use cudarc::driver::sys::CUfunction_attribute_enum as A;
17997            f.set_attribute(
17998                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
17999                sh as i32,
18000            )?;
18001            let cfg = LaunchConfig {
18002                grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18003                block_dim: (32, gqa, 1),
18004                shared_mem_bytes: sh,
18005            };
18006            let __s_b = self.gpu.stream();
18007            let mut b = __s_b.launch_builder(&f);
18008            b.arg(q)
18009                .arg(k)
18010                .arg(v)
18011                .arg(&mut *part_o)
18012                .arg(&mut *part_m)
18013                .arg(&mut *part_l)
18014                .arg(&hd)
18015                .arg(&nh)
18016                .arg(&nhkv)
18017                .arg(base_dev)
18018                .arg(&base_plus)
18019                .arg(&scale)
18020                .arg(&nspm)
18021                .arg(&spk)
18022                .arg(&ktb)
18023                .arg(&vtb);
18024            unsafe {
18025                b.launch(cfg)?;
18026            }
18027            let fc = self.func("fa_decode_combine_rows_dc");
18028            let cfg2 = LaunchConfig {
18029                grid_dim: (n_head as u32, t as u32, 1),
18030                block_dim: (head_dim as u32, 1, 1),
18031                shared_mem_bytes: 0,
18032            };
18033            let __s_b2 = self.gpu.stream();
18034            let mut b2 = __s_b2.launch_builder(&fc);
18035            b2.arg(&*part_o)
18036                .arg(&*part_m)
18037                .arg(&*part_l)
18038                .arg(o)
18039                .arg(&hd)
18040                .arg(&nh)
18041                .arg(base_dev)
18042                .arg(&base_plus)
18043                .arg(&nspm)
18044                .arg(&spk);
18045            unsafe {
18046                b2.launch(cfg2)?;
18047            }
18048            return Ok(());
18049        }
18050        let sp = fa_split_keys(t_kv_upper, n_head_kv);
18051        let n_splits_max = (t_kv_upper + sp - 1) / sp;
18052        let (hd, nh, nhkv) = (head_dim as i32, n_head as i32, n_head_kv as i32);
18053        let (nspm, spk) = (n_splits_max as i32, sp as i32);
18054        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18055        let gqa = (n_head / n_head_kv).max(1) as u32;
18056        let o_len = t * n_head * n_splits_max * head_dim;
18057        let ml_len = t * n_head * n_splits_max;
18058        let mut part_guard = self.fa_part_pool.lock().unwrap();
18059        if part_guard
18060            .as_ref()
18061            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18062            .unwrap_or(true)
18063        {
18064            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18065            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18066            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18067            // later live allocations land at those addresses, and the next graph REPLAY writes
18068            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18069            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18070            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18071            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18072            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18073            // (total retired < final size).
18074            let old = part_guard.take();
18075            let (co, cm) = old
18076                .as_ref()
18077                .map(|pp| (pp.0.len(), pp.1.len()))
18078                .unwrap_or((0, 0));
18079            if let Some(old) = old {
18080                self.fa_part_retired.lock().unwrap().push(old);
18081            }
18082            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18083                eprintln!(
18084                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18085                    co, o_len, cm, ml_len
18086                );
18087            }
18088            *part_guard = Some((
18089                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18090                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18091                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18092            ));
18093        }
18094        let pg = part_guard.as_mut().unwrap();
18095        self.gpu
18096            .stream()
18097            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18098        self.gpu
18099            .stream()
18100            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18101        self.gpu
18102            .stream()
18103            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18104        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18105        let f = self.func("fa_decode_vec_q_rows_v3_dc");
18106        let sh = (32 * head_dim * 2) as u32;
18107        use cudarc::driver::sys::CUfunction_attribute_enum as A;
18108        f.set_attribute(
18109            A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18110            sh as i32,
18111        )?;
18112        let cfg = LaunchConfig {
18113            grid_dim: (n_head_kv as u32, n_splits_max as u32, t as u32),
18114            block_dim: (32, gqa, 1),
18115            shared_mem_bytes: sh,
18116        };
18117        let __s_b = self.gpu.stream();
18118        let mut b = __s_b.launch_builder(&f);
18119        b.arg(q)
18120            .arg(k)
18121            .arg(v)
18122            .arg(&mut *part_o)
18123            .arg(&mut *part_m)
18124            .arg(&mut *part_l)
18125            .arg(&hd)
18126            .arg(&nh)
18127            .arg(&nhkv)
18128            .arg(base_dev)
18129            .arg(&scale)
18130            .arg(&nspm)
18131            .arg(&spk)
18132            .arg(&ktb)
18133            .arg(&vtb);
18134        unsafe {
18135            b.launch(cfg)?;
18136        }
18137        let fc = self.func("fa_decode_combine_rows_dc");
18138        let cfg2 = LaunchConfig {
18139            grid_dim: (n_head as u32, t as u32, 1),
18140            block_dim: (head_dim as u32, 1, 1),
18141            shared_mem_bytes: 0,
18142        };
18143        let plus0 = 0i32;
18144        let __s_b2 = self.gpu.stream();
18145        let mut b2 = __s_b2.launch_builder(&fc);
18146        b2.arg(&*part_o)
18147            .arg(&*part_m)
18148            .arg(&*part_l)
18149            .arg(o)
18150            .arg(&hd)
18151            .arg(&nh)
18152            .arg(base_dev)
18153            .arg(&plus0)
18154            .arg(&nspm)
18155            .arg(&spk);
18156        unsafe {
18157            b2.launch(cfg2)?;
18158        }
18159        Ok(())
18160    }
18161
18162    /// Device-counter variant of `fa_decode` (CUDA-GRAPH-PLAN Phase 2). The sequence length is read
18163    /// from `t_kv_dev[0]` (resident device i32[1]) for the attention loop bound + per-split key range;
18164    /// the GRID `n_splits` is sized for `bucket_max` (the bucket's max t_kv — baked at capture time).
18165    /// Empty splits (key range beyond the actual t_kv) write an empty partial (m=NEG_INF) so the
18166    /// shared combine skips them -> bit-correct for ANY actual t_kv <= bucket_max.
18167    ///
18168    /// BIT-IDENTITY (the gate): pass `bucket_max == actual_t_kv` and this reproduces `fa_decode`
18169    /// EXACTLY (same n_splits, same per, same split boundaries, same combine) while reading t_kv from
18170    /// device. Bucketing (bucket_max > t_kv) is for the future captured path and changes split
18171    /// grouping (different but mathematically-equal log-sum-exp merge).
18172    pub fn fa_decode_dc(
18173        &self,
18174        q: &CudaSlice<f32>,
18175        k: &cudarc::driver::CudaView<u8>,
18176        v: &cudarc::driver::CudaView<u8>,
18177        o: &mut CudaSlice<f32>,
18178        head_dim: usize,
18179        n_head: usize,
18180        n_head_kv: usize,
18181        t_kv_dev: &CudaSlice<i32>,
18182        bucket_max: usize,
18183        scale: f32,
18184        k_tok_bytes: usize,
18185        v_tok_bytes: usize,
18186        g: bool,
18187    ) -> Result<(), Box<dyn std::error::Error>> {
18188        self.fa_decode_dc_q8(
18189            q,
18190            k,
18191            v,
18192            o,
18193            head_dim,
18194            n_head,
18195            n_head_kv,
18196            t_kv_dev,
18197            bucket_max,
18198            scale,
18199            k_tok_bytes,
18200            v_tok_bytes,
18201            g,
18202            None,
18203        )
18204    }
18205
18206    /// `fa_decode_dc` with an optional q8_1 sink (wave 5b): when `q8_out` is given the
18207    /// combine emits (int8, per-32 scales) for the wo matmul_pre and skips the f32 O write.
18208    #[allow(clippy::too_many_arguments)]
18209    pub fn fa_decode_dc_q8(
18210        &self,
18211        q: &CudaSlice<f32>,
18212        k: &cudarc::driver::CudaView<u8>,
18213        v: &cudarc::driver::CudaView<u8>,
18214        o: &mut CudaSlice<f32>,
18215        head_dim: usize,
18216        n_head: usize,
18217        n_head_kv: usize,
18218        t_kv_dev: &CudaSlice<i32>,
18219        bucket_max: usize,
18220        scale: f32,
18221        k_tok_bytes: usize,
18222        v_tok_bytes: usize,
18223        g: bool,
18224        q8_out: Option<(&mut CudaSlice<i8>, &mut CudaSlice<f32>)>,
18225    ) -> Result<(), Box<dyn std::error::Error>> {
18226        // The fa_vec gate + n_splits are sized from bucket_max (host, fixed at capture). The kernel
18227        // reads the ACTUAL t_kv from t_kv_dev for the per-split bound. DEFAULT-ON to MATCH the eager
18228        // `fa_decode` gate above — graph capture must mirror eager's kernel choice or the graph-vs-eager
18229        // bit-identity gate breaks. MEMRA_NO_FA_VEC forces scalar on BOTH paths in lockstep.
18230        // `g` = this layer's cache is e4m3 (gemma windowed under wkv) — every pick below must
18231        // mirror fa_decode_kvmod's g-routing or the graph diverges from eager (short/mid 1/96,
18232        // 2026-07-12).
18233        let mut fa_vec =
18234            std::env::var("MEMRA_NO_FA_VEC").is_err() && bucket_max >= fa_vec_min_tkv();
18235        if g && head_dim == 256 && !fa_v4_at(bucket_max) {
18236            fa_vec = false;
18237        } // mirror kvmod/geom
18238        let sp = fa_split_keys(bucket_max, n_head_kv);
18239        let n_splits = if fa_vec {
18240            ((bucket_max + sp - 1) / sp).max(1)
18241        } else {
18242            ((bucket_max + 255) / 256).max(1)
18243        };
18244        let o_len = n_head * n_splits * head_dim;
18245        let ml_len = n_head * n_splits;
18246        let mut part_guard = self.fa_part_pool.lock().unwrap();
18247        if part_guard
18248            .as_ref()
18249            .map(|pp| pp.0.len() < o_len || pp.1.len() < ml_len)
18250            .unwrap_or(true)
18251        {
18252            // RETIRE-ON-GROW, never free (#68 root cause, 2026-08-04): captured graphs (the
18253            // per-session persistent draft graph, the decode/prime graph doors) BAKE these pool
18254            // buffer addresses. Dropping the old buffers on grow returns them to the async pool,
18255            // later live allocations land at those addresses, and the next graph REPLAY writes
18256            // its fa partials over them — the ST serve-spec corruption (acceptance collapse +
18257            // output corruption began the burst after the trunk's t_kv growth first realloc'd
18258            // this pool past the draft-capture size; research/fp8ship-20260804). Retiring keeps
18259            // the baked addresses alive (single-stream: eager writes the new buffers, replays
18260            // touch only the old — never concurrently). Doubling growth bounds retired VRAM
18261            // (total retired < final size).
18262            let old = part_guard.take();
18263            let (co, cm) = old
18264                .as_ref()
18265                .map(|pp| (pp.0.len(), pp.1.len()))
18266                .unwrap_or((0, 0));
18267            if let Some(old) = old {
18268                self.fa_part_retired.lock().unwrap().push(old);
18269            }
18270            if std::env::var("MEMRA_DEBUG_FAPOOL").is_ok() {
18271                eprintln!(
18272                    "[fa-pool] REALLOC o {} -> {} ml {} -> {} (old retired)",
18273                    co, o_len, cm, ml_len
18274                );
18275            }
18276            *part_guard = Some((
18277                self.alloc_uninit::<f32>(o_len.max(2 * co))?,
18278                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18279                self.alloc_uninit::<f32>(ml_len.max(2 * cm))?,
18280            ));
18281        }
18282        let pg = part_guard.as_mut().unwrap();
18283        self.gpu
18284            .stream()
18285            .memset_zeros(&mut pg.0.slice_mut(0..o_len))?;
18286        self.gpu
18287            .stream()
18288            .memset_zeros(&mut pg.1.slice_mut(0..ml_len))?;
18289        self.gpu
18290            .stream()
18291            .memset_zeros(&mut pg.2.slice_mut(0..ml_len))?;
18292        let (part_o, part_m, part_l) = (&mut pg.0, &mut pg.1, &mut pg.2);
18293        let (hd, nh, nhkv, nsp) = (
18294            head_dim as i32,
18295            n_head as i32,
18296            n_head_kv as i32,
18297            n_splits as i32,
18298        );
18299        let (ktb, vtb) = (k_tok_bytes as i64, v_tok_bytes as i64);
18300        let fa_vec = fa_vec && head_dim <= 512 && head_dim % 32 == 0;
18301        // FA-DEEP pick keyed on bucket_max (the fa_v4_at precedent) — bit-identical twins,
18302        // so a threshold falling between t_kv and bucket_max cannot diverge eager-vs-graph.
18303        let deep = fa_vec
18304            && head_dim == 256
18305            && fa_v4_at(bucket_max)
18306            && !g
18307            && fa_deep_at(bucket_max)
18308            && !matches!(fa_v4_mode(), "noB3" | "stage");
18309        let (f, cfg) = if fa_vec
18310            && head_dim == 512
18311            && bucket_max >= {
18312                static FA512_MIN_DC: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18313                *FA512_MIN_DC.get_or_init(|| {
18314                    std::env::var("MEMRA_FA512_MIN")
18315                        .ok()
18316                        .and_then(|v| v.parse().ok())
18317                        .unwrap_or(512)
18318                })
18319            } {
18320            // gemma globals dc twin (mirror the eager dpl16 pick incl the crossover floor).
18321            let gqa = (n_head / n_head_kv).max(1) as u32;
18322            (
18323                self.fa_func("fa_decode_vec_q_dpl16_dc", head_dim),
18324                LaunchConfig {
18325                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18326                    block_dim: (32, gqa, 1),
18327                    shared_mem_bytes: 0,
18328                },
18329            )
18330        } else if fa_vec && head_dim == 512 {
18331            // under the 512 floor eager runs scalar — the SAME unified symbol, ctr non-null;
18332            // ns_eff in-kernel reproduces eager's ceil(t_kv/sp) partition for the LIVE len.
18333            let q_view = q.as_view();
18334            let mut o_view = o.as_view_mut();
18335            return self.fa_decode_scalar_unified(
18336                &q_view,
18337                k,
18338                v,
18339                &mut o_view,
18340                head_dim,
18341                n_head,
18342                n_head_kv,
18343                0,
18344                Some(t_kv_dev),
18345                scale,
18346                n_splits,
18347                sp,
18348                k_tok_bytes,
18349                v_tok_bytes,
18350                g,
18351                &mut *part_o,
18352                &mut *part_m,
18353                &mut *part_l,
18354                q8_out,
18355            );
18356        } else if fa_vec && head_dim == 256 && fa_v4_at(bucket_max) {
18357            // gemma/qwen v4 dc twin (eager default lane) — capture must mirror eager's pick,
18358            // incl the g-module route + raw-e4m3 sV sizing.
18359            let gqa = (n_head / n_head_kv).max(1) as u32;
18360            let fv = if g {
18361                self.func_g("fa_decode_vec_q_v4_dc")
18362            } else if deep {
18363                self.func("fa_decode_vec_q_v4_deep_dc")
18364            } else {
18365                self.func("fa_decode_vec_q_v4_dc")
18366            };
18367            let shmem =
18368                (if deep { 12160 } else { 11520 } + 32 * head_dim * if g { 1 } else { 2 }) as u32;
18369            use cudarc::driver::sys::CUfunction_attribute_enum as A;
18370            fv.set_attribute(
18371                A::CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES,
18372                shmem as i32,
18373            )?;
18374            (
18375                fv,
18376                LaunchConfig {
18377                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18378                    block_dim: (32, gqa, 1),
18379                    shared_mem_bytes: shmem,
18380                },
18381            )
18382        } else if fa_vec && fa_v3_active(head_dim) {
18383            // FA v3 lane _dc twin: the captured graph must run the SAME walk body as eager
18384            // under MEMRA_FA_V3=1 (eager, rows-verify and graph switch together).
18385            let gqa = (n_head / n_head_kv).max(1) as u32;
18386            let fv = if g {
18387                self.func_g("fa_decode_vec_q_v3_dc")
18388            } else {
18389                self.func("fa_decode_vec_q_v3_dc")
18390            };
18391            let shmem = (32 * head_dim * 2) as u32; // sV bf16 [FA_DEC_TILE=32][hd]
18392            (
18393                fv,
18394                LaunchConfig {
18395                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18396                    block_dim: (32, gqa, 1),
18397                    shared_mem_bytes: shmem,
18398                },
18399            )
18400        } else if fa_vec && fa_v2_on() {
18401            // FAVENDOR lane: v2 _dc twin — the captured graph must run the SAME walk body as
18402            // eager under MEMRA_FA_V2=1 or graph_decode_gate's bit-identity breaks (the flag is
18403            // a numeric config; eager, rows-verify and graph all switch together).
18404            let gqa = (n_head / n_head_kv).max(1) as u32;
18405            let fv = if g {
18406                self.func_g("fa_decode_vec_q_v2_dc")
18407            } else {
18408                self.func("fa_decode_vec_q_v2_dc")
18409            };
18410            let shmem = (2 * 32 * head_dim * 2) as u32; // sK+sV bf16 [FA_DEC_TILE=32][hd]
18411            (
18412                fv,
18413                LaunchConfig {
18414                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18415                    block_dim: (32, gqa, 1),
18416                    shared_mem_bytes: shmem,
18417                },
18418            )
18419        } else if fa_vec {
18420            let gqa = (n_head / n_head_kv).max(1) as u32;
18421            // REGISTER-DEQUANT twin: zero dynamic smem (see fa_decode above).
18422            let fv = if g {
18423                self.func_g("fa_decode_vec_q_dc")
18424            } else {
18425                self.func("fa_decode_vec_q_dc")
18426            };
18427            (
18428                fv,
18429                LaunchConfig {
18430                    grid_dim: (n_head_kv as u32, n_splits as u32, 1),
18431                    block_dim: (32, gqa, 1),
18432                    shared_mem_bytes: 0,
18433                },
18434            )
18435        } else {
18436            let q_view = q.as_view();
18437            let mut o_view = o.as_view_mut();
18438            return self.fa_decode_scalar_unified(
18439                &q_view,
18440                k,
18441                v,
18442                &mut o_view,
18443                head_dim,
18444                n_head,
18445                n_head_kv,
18446                0,
18447                Some(t_kv_dev),
18448                scale,
18449                n_splits,
18450                if fa_vec { sp } else { 256 },
18451                k_tok_bytes,
18452                v_tok_bytes,
18453                g,
18454                &mut *part_o,
18455                &mut *part_m,
18456                &mut *part_l,
18457                q8_out,
18458            );
18459        };
18460        let ski = sp as i32; // one-partition law: the twins derive ns_eff from (T_kv, ski)
18461        let __s_b = self.gpu.stream();
18462        let mut b = __s_b.launch_builder(&f);
18463        b.arg(q)
18464            .arg(k)
18465            .arg(v)
18466            .arg(&mut *part_o)
18467            .arg(&mut *part_m)
18468            .arg(&mut *part_l)
18469            .arg(&hd)
18470            .arg(&nh)
18471            .arg(&nhkv)
18472            .arg(t_kv_dev)
18473            .arg(&scale)
18474            .arg(&nsp)
18475            .arg(&ski)
18476            .arg(&ktb)
18477            .arg(&vtb);
18478        unsafe {
18479            b.launch(cfg)?;
18480        }
18481        let cfg2 = LaunchConfig {
18482            grid_dim: (n_head as u32, 1, 1),
18483            block_dim: (head_dim as u32, 1, 1),
18484            shared_mem_bytes: 0,
18485        };
18486        if let Some((oq, od)) = q8_out {
18487            let fc = if g {
18488                self.func_g("fa_decode_combine_q8_1")
18489            } else {
18490                self.fa_func("fa_decode_combine_q8_1", head_dim)
18491            };
18492            let __s_b2 = self.gpu.stream();
18493            let mut b2 = __s_b2.launch_builder(&fc);
18494            b2.arg(&*part_o)
18495                .arg(&*part_m)
18496                .arg(&*part_l)
18497                .arg(oq)
18498                .arg(od)
18499                .arg(&hd)
18500                .arg(&nh)
18501                .arg(&nsp);
18502            unsafe {
18503                b2.launch(cfg2)?;
18504            }
18505            return Ok(());
18506        }
18507        let fc = if g {
18508            self.func_g("fa_decode_combine_f32")
18509        } else {
18510            self.fa_func("fa_decode_combine_f32", head_dim)
18511        };
18512        let __s_b2 = self.gpu.stream();
18513        let mut b2 = __s_b2.launch_builder(&fc);
18514        b2.arg(&*part_o)
18515            .arg(&*part_m)
18516            .arg(&*part_l)
18517            .arg(o)
18518            .arg(&hd)
18519            .arg(&nh)
18520            .arg(&nsp);
18521        unsafe {
18522            b2.launch(cfg2)?;
18523        }
18524        Ok(())
18525    }
18526
18527    /// EAGER fa_decode geometry for a given actual `t_kv` (CUDA-GRAPH-PLAN §3.3 bucketing). Returns
18528    /// `(fa_vec, n_splits)` EXACTLY as `fa_decode` computes them so the graph-capture path can key its
18529    /// bucket on the same `(kernel, n_splits)` pair and pass a `bucket_max` that reproduces eager's
18530    /// n_splits bit-for-bit. (Per = ceil(t_kv/n_splits) is then recomputed from the DEVICE t_kv inside
18531    /// the kernel and matches eager when n_splits matches — the bit-identity contract.)
18532    pub fn fa_geom_eager(
18533        &self,
18534        t_kv: usize,
18535        head_dim: usize,
18536        n_head_kv: usize,
18537        g: bool,
18538    ) -> (bool, usize) {
18539        // MUST mirror `fa_decode` / `fa_decode_dc` (default-ON 2026-06-28). This is the bucket-key
18540        // source: if it disagrees with the actual kernel pick, the graph captures the wrong path and
18541        // replay diverges from eager. All three sites read MEMRA_NO_FA_VEC in lockstep.
18542        let fa_ok = std::env::var("MEMRA_NO_FA_VEC").is_err() && t_kv >= fa_vec_min_tkv();
18543        // hd512 dpl16 vec lane (gemma globals, 2026-07-11 graph-arc fix): the original key
18544        // hardcoded vec = hd<=256, so for hd512 it bucketed by the SCALAR 256-key splits while
18545        // the dpl16/rows_dpl16 kernels split by the ladder — n_splits changed WITHIN a bucket
18546        // (mid-ctx graph mismatch at pos 19 + partials OOB at longer runs). Mirror the real
18547        // fa_decode dispatch: vec512 above the fa512 floor, vec256 as before.
18548        let vec512 = fa_ok && head_dim == 512 && t_kv >= fa512_min_tkv();
18549        let mut fa_vec = vec512 || (fa_ok && head_dim <= 256 && head_dim % 32 == 0);
18550        // g (fp8-windowed): mirror kvmod's clamp — only the v4 lane parses e4m3 in the vec
18551        // family; everything else falls to the g-module scalar.
18552        // hd256 under g: v4-or-scalar. hd128/other under g ride the REGISTER g-lane (the
18553        // dq_K_lane/dq_V_lane macros are format-aware; v3 is format-gated off, v2/smem
18554        // arms are excluded under g). Mirrored in kvmod / fa_decode_dc / fa_geom_eager.
18555        if g && head_dim == 256 && !fa_v4_at(t_kv) {
18556            fa_vec = false;
18557        }
18558        let sp = fa_split_keys(t_kv, n_head_kv);
18559        let n_splits = if fa_vec {
18560            ((t_kv + sp - 1) / sp).max(1)
18561        } else {
18562            ((t_kv + 255) / 256).max(1)
18563        };
18564        (fa_vec, n_splits)
18565    }
18566
18567    /// `bucket_max` (host t_kv to feed `fa_decode_dc` / `full_attn_decode_dc`) that makes the _dc
18568    /// kernel pick the SAME (fa_vec, n_splits) as eager would for actual `t_kv`. Because the dc
18569    /// launcher derives both from `bucket_max` via the same formulas, we just hand it `t_kv` itself:
18570    /// the n_splits is then identical, and the per-split boundaries (computed from the DEVICE t_kv in
18571    /// the kernel) match eager exactly. The bucket KEY (for the graph HashMap) is `(fa_vec, n_splits)`.
18572    pub fn fa_bucket_key(
18573        &self,
18574        t_kv: usize,
18575        head_dim: usize,
18576        n_head_kv: usize,
18577        g: bool,
18578    ) -> (bool, usize) {
18579        self.fa_geom_eager(t_kv, head_dim, n_head_kv, g)
18580    }
18581
18582    /// CUDA-graph capture wrapper (CUDA-GRAPH-PLAN §3.2, llama.cpp warmup pattern). Runs `step`
18583    /// inline TWICE (warmup — lets the caching allocator settle to stable pointers and any one-time
18584    /// kernel attribute/JIT happen outside capture), then captures a THIRD invocation on the Engine's
18585    /// decode stream (RELAXED mode) and instantiates it into a replayable `CudaGraph`. The closure
18586    /// must enqueue ONLY device work on `e.stream()` (no dtoh / no synchronize / no host branch on
18587    /// device data) — every per-step varying scalar must come from a device counter. Returns the
18588    /// instantiated graph; `CudaGraph::launch()` replays the whole step in one dispatch.
18589    /// `capture_graph` with CAPTURE-RETAIN: every Engine allocation made during the warmups
18590    /// and the capture is kept alive in the returned keeper — hold it as long as the graph
18591    /// replays (transients returning to the pool get reused by unrelated work and corrupt
18592    /// replays; the draft-graph root cause). Model-generic, next capture reuses it.
18593    pub fn capture_graph_retained<F>(
18594        &self,
18595        step: F,
18596    ) -> Result<
18597        (
18598            cudarc::driver::CudaGraph,
18599            Vec<Box<dyn std::any::Any + Send>>,
18600        ),
18601        Box<dyn std::error::Error>,
18602    >
18603    where
18604        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18605    {
18606        use cudarc::driver::sys::CUgraphInstantiate_flags;
18607        self.capture_graph_retained_flags(
18608            CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
18609            step,
18610        )
18611    }
18612
18613    /// Retained capture with an explicit instantiate flag. ALLOC-FREE captured graphs
18614    /// (zero mem nodes — the gemma slotted door) should pass UPLOAD instead of
18615    /// AUTO_FREE_ON_LAUNCH: the auto-free flag's launch-time mem-pool scan was measured at
18616    /// ~0.25us/node (205us on the 826-node step) even with nothing to free.
18617    pub fn capture_graph_retained_flags<F>(
18618        &self,
18619        flags: cudarc::driver::sys::CUgraphInstantiate_flags,
18620        mut step: F,
18621    ) -> Result<
18622        (
18623            cudarc::driver::CudaGraph,
18624            Vec<Box<dyn std::any::Any + Send>>,
18625        ),
18626        Box<dyn std::error::Error>,
18627    >
18628    where
18629        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18630    {
18631        use cudarc::driver::sys::CUstreamCaptureMode;
18632        // KEEP scope = WARMUPS ONLY (2026-07-13): keep_if_capturing retains via
18633        // CudaSlice::clone, which is a device ALLOC + D2D COPY on the stream — clones made
18634        // while the capture region is open become dead copy NODES replayed every launch
18635        // (E4B: 1440 copies = 0.74ms/token, the whole graph-vs-eager regression). The
18636        // warmup runs allocate the same transient sequence at the same pool addresses, so
18637        // retaining the warmup clones preserves the draft-graph fix without polluting the
18638        // captured graph.
18639        self.capture_keep.lock().unwrap().clear();
18640        let was_tracking = self.gpu.ctx.is_event_tracking();
18641        if was_tracking {
18642            unsafe {
18643                self.gpu.ctx.disable_event_tracking();
18644            }
18645        }
18646        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
18647            self.capture_keep_on
18648                .store(true, std::sync::atomic::Ordering::Relaxed);
18649            let w = (|| {
18650                step(self)?;
18651                step(self)
18652            })();
18653            self.capture_keep_on
18654                .store(false, std::sync::atomic::Ordering::Relaxed);
18655            w?;
18656            self.gpu.stream().synchronize()?;
18657            self.gpu
18658                .stream()
18659                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
18660            let r = step(self);
18661            let g = self.gpu.stream().end_capture(flags);
18662            r?;
18663            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
18664            graph.upload()?;
18665            Ok(graph)
18666        };
18667        let result = run();
18668        self.capture_keep_on
18669            .store(false, std::sync::atomic::Ordering::Relaxed);
18670        if was_tracking {
18671            unsafe {
18672                self.gpu.ctx.enable_event_tracking();
18673            }
18674        }
18675        let keeper = std::mem::take(&mut *self.capture_keep.lock().unwrap());
18676        Ok((result?, keeper))
18677    }
18678
18679    pub fn capture_graph<F>(
18680        &self,
18681        mut step: F,
18682    ) -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>>
18683    where
18684        F: FnMut(&Engine) -> Result<(), Box<dyn std::error::Error>>,
18685    {
18686        use cudarc::driver::sys::{CUgraphInstantiate_flags, CUstreamCaptureMode};
18687        // EVENT TRACKING OFF for capture. The Engine creates a 2nd stream (copy_stream) so cudarc is in
18688        // multi-stream mode and, by default, records a CudaEvent per CudaSlice alloc/use to serialize
18689        // cross-stream access. Those per-buffer event waits issue stream ops that are NOT permitted
18690        // inside a capture region (CUDA_ERROR_STREAM_CAPTURE_UNSUPPORTED). The captured decode step is
18691        // strictly SINGLE-STREAM (every kernel on gpu.stream), so this synchronization is unnecessary
18692        // here — disable it for the whole warmup+capture, re-enable after. SAFETY: the decode-dc path
18693        // touches only gpu.stream; no buffer crosses to copy_stream during capture.
18694        let was_tracking = self.gpu.ctx.is_event_tracking();
18695        if was_tracking {
18696            unsafe {
18697                self.gpu.ctx.disable_event_tracking();
18698            }
18699        }
18700        // Q1 PROBE (MEMRA_GRAPH_IFLAG): the generic capture body's cuMemAllocAsync nodes are
18701        // EXACTLY BALANCED by in-graph free nodes (measured census q27: 1589 ALLOC / 1589
18702        // FREE), so AUTO_FREE_ON_LAUNCH has nothing to reclaim at launch — it only pays its
18703        // per-node launch-time mem-pool scan. `upload` / `none` select the alternatives to
18704        // measure that scan's real cost on the generic path. Diagnostic door only; the
18705        // default stays AUTO_FREE until a measured A/B justifies moving it.
18706        let iflag = {
18707            static F: std::sync::OnceLock<CUgraphInstantiate_flags> = std::sync::OnceLock::new();
18708            *F.get_or_init(|| match std::env::var("MEMRA_GRAPH_IFLAG").as_deref() {
18709                // UPLOAD = the gemma slotted door's zero-mem-node choice; PRIORITY = the flag
18710                // hybrid_forward.rs:5935 actually ships (both drop the auto-free launch scan).
18711                Ok("upload") => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD,
18712                Ok("priority") => {
18713                    CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY
18714                }
18715                _ => CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH,
18716            })
18717        };
18718        // MEMRA_GRAPH_CAPTIME=1 (Q1 lane): phase-resolved capture cost. Recapture is paid at
18719        // every kernel-class crossing, so it — not steady-state decode — is the quantity a
18720        // mem-node reduction could plausibly shrink. Only `instantiate` (cuStreamEndCapture +
18721        // cuGraphInstantiateWithFlags) and `upload` scale with node count; the warmups are
18722        // eager step executions and are node-count-invariant. Printing the split bounds the
18723        // refactor's ceiling instead of assuming it.
18724        let ct = {
18725            static T: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
18726            *T.get_or_init(|| std::env::var("MEMRA_GRAPH_CAPTIME").as_deref() == Ok("1"))
18727        };
18728        // MEMRA_GRAPH_WARMUPS (Q1 lane; DEFAULT 1 since lane/graph-warmups 2026-08-05): the
18729        // phase split showed the eager warmups are 80% of recapture cost (q27 27.4 of 34.4 ms
18730        // pod / 42% of 52.6 ms 5090) — 3x larger than the ENTIRE mem-node ceiling the audit
18731        // chased, and node-count-invariant, so no capture-body refactor could touch it.
18732        // Warmup 2's theorized job was async-pool ADDRESS STABILITY: warmup 1's allocs may
18733        // grow/map the pool, warmup 2 re-walks the same sequence over the freed blocks so the
18734        // captured third run bakes settled addresses. That hazard is the #68 stale-baked-
18735        // address class — which the engine now guards STRUCTURALLY rather than by re-walking:
18736        // in-body transients are captured as BALANCED in-graph alloc/free node pairs (census
18737        // 1589/1589 — replays allocate for themselves; no baked transient pointers), every
18738        // externally-referenced buffer is stable-pointer by design (fa_part_pool retires-on-
18739        // grow and never frees, resident counters/scratch, cache set in place), and the
18740        // draft-graph path additionally rides capture_graph_retained (capture_keep holds all
18741        // warmup+capture allocs alive). One warmup therefore suffices for kernel-attr
18742        // settling and pool mapping. Arbitrated adversarially, not by taste:
18743        // graph-warmup-stress (pool-growth cycles large<->small x10, overlap arm, forced
18744        // recaptures over freed blocks — bit-identity vs eager + canary teeth) is GREEN at
18745        // warmups=1 on the deployment rig, plus graph-decode-gate 256-step bit-identity,
18746        // graph-session-gate, run-spec K=1..8 (receipts research/graph-warmups-5090-20260805/
18747        // + the pod's research/graph-allocfree-20260805/). Measured: recapture -38..-42% q27 /
18748        // -41% q9, decode +~1%, capture+prime -13ms. MEMRA_GRAPH_WARMUPS=2 = the rollback
18749        // seam; tools/graph-warmup-stress-gate.sh = the gate any regression re-runs.
18750        let warmups = {
18751            static W: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
18752            *W.get_or_init(|| {
18753                std::env::var("MEMRA_GRAPH_WARMUPS")
18754                    .ok()
18755                    .and_then(|v| v.parse().ok())
18756                    .filter(|n| *n >= 1)
18757                    .unwrap_or(1)
18758            })
18759        };
18760        let mut run = || -> Result<cudarc::driver::CudaGraph, Box<dyn std::error::Error>> {
18761            let t_w = std::time::Instant::now();
18762            // warmup: inline runs (no capture) so allocator pointers + kernel attrs are stable.
18763            for _ in 0..warmups {
18764                step(self)?;
18765            }
18766            self.gpu.stream().synchronize()?;
18767            let ms_warm = t_w.elapsed().as_secs_f64() * 1e3;
18768            // capture the third run.
18769            let t_c = std::time::Instant::now();
18770            self.gpu
18771                .stream()
18772                .begin_capture(CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED)?;
18773            // If the body errors mid-capture, end the capture before propagating so the stream isn't
18774            // left in a capturing state.
18775            let r = step(self);
18776            let ms_body = t_c.elapsed().as_secs_f64() * 1e3;
18777            let t_i = std::time::Instant::now();
18778            let g = self.gpu.stream().end_capture(iflag);
18779            let ms_inst = t_i.elapsed().as_secs_f64() * 1e3;
18780            r?;
18781            let graph = g?.ok_or("capture produced no graph (stream was not capturing)")?;
18782            let t_u = std::time::Instant::now();
18783            graph.upload()?;
18784            if ct {
18785                println!(
18786                    "[graph-captime] warmup2x {ms_warm:.2} ms  capture-body {ms_body:.2} ms  \
18787                          instantiate {ms_inst:.2} ms  upload {:.2} ms",
18788                    t_u.elapsed().as_secs_f64() * 1e3
18789                );
18790            }
18791            Ok(graph)
18792        };
18793        let result = run();
18794        if was_tracking {
18795            unsafe {
18796                self.gpu.ctx.enable_event_tracking();
18797            }
18798        }
18799        result
18800    }
18801
18802    /// gdn_scan variant where state_in/out are CudaViews (resident SSM state, in-place per step).
18803    pub fn gdn_scan_s128_view(
18804        &self,
18805        q: &CudaSlice<f32>,
18806        k: &CudaSlice<f32>,
18807        v: &CudaSlice<f32>,
18808        g: &CudaSlice<f32>,
18809        beta: &CudaSlice<f32>,
18810        state_in: &cudarc::driver::CudaView<f32>,
18811        state_out: &mut cudarc::driver::CudaViewMut<f32>,
18812        o: &mut CudaSlice<f32>,
18813        n_head: usize,
18814        t: usize,
18815        scale: f32,
18816    ) -> Result<(), Box<dyn std::error::Error>> {
18817        let f = self.func("gdn_scan_s128");
18818        const S_V: u32 = 128;
18819        const WARP: u32 = 32;
18820        const COLS: u32 = 4;
18821        let cfg = LaunchConfig {
18822            grid_dim: (n_head as u32, 1, S_V / COLS),
18823            block_dim: (WARP, COLS, 1),
18824            shared_mem_bytes: 0,
18825        };
18826        let (h, ti) = (n_head as i32, t as i32);
18827        let __s_b = self.gpu.stream();
18828        let mut b = __s_b.launch_builder(&f);
18829        b.arg(q)
18830            .arg(k)
18831            .arg(v)
18832            .arg(g)
18833            .arg(beta)
18834            .arg(state_in)
18835            .arg(state_out)
18836            .arg(o)
18837            .arg(&h)
18838            .arg(&ti)
18839            .arg(&scale);
18840        unsafe {
18841            b.launch(cfg)?;
18842        }
18843        Ok(())
18844    }
18845
18846    /// conv1d where the input is a CudaView (resident conv state assembled in place).
18847    pub fn ssm_conv1d_view(
18848        &self,
18849        x: &cudarc::driver::CudaView<f32>,
18850        w: &CudaSlice<f32>,
18851        y: &mut CudaSlice<f32>,
18852        conv_dim: usize,
18853        t: usize,
18854        d_conv: usize,
18855        silu: bool,
18856    ) -> Result<(), Box<dyn std::error::Error>> {
18857        let f = self.func("ssm_conv1d_silu_f32");
18858        // grid.x = channel, grid.y = T-tiles (block 256 strides over T) — parallel over both axes.
18859        let cfg = LaunchConfig {
18860            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
18861            block_dim: (256, 1, 1),
18862            shared_mem_bytes: 0,
18863        };
18864        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
18865        let __s_b = self.gpu.stream();
18866        let mut b = __s_b.launch_builder(&f);
18867        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
18868        unsafe {
18869            b.launch(cfg)?;
18870        }
18871        Ok(())
18872    }
18873
18874    /// Depthwise causal conv1d + optional SiLU.
18875    /// x:[conv_dim, T+d_conv-1] channel-major (first d_conv-1 cols = carried state),
18876    /// w:[d_conv, conv_dim] kernel-major, y:[conv_dim, T] channel-major.
18877    /// FUSED prefill conv (token-major input, zero left-state): replaces
18878    /// transpose + zeros + conv_left_pad + ssm_conv1d with ONE launch reading the matmul output
18879    /// directly. Output channel-major [conv_dim, T], SiLU applied. BIT-IDENTICAL accumulation.
18880    pub fn ssm_conv1d_tm(
18881        &self,
18882        qkv_tm: &CudaSlice<f32>,
18883        w: &CudaSlice<f32>,
18884        y: &mut CudaSlice<f32>,
18885        conv_dim: usize,
18886        t: usize,
18887        d_conv: usize,
18888    ) -> Result<(), Box<dyn std::error::Error>> {
18889        let f = self.func("ssm_conv1d_tm_f32");
18890        let cfg = LaunchConfig {
18891            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
18892            block_dim: (256, 1, 1),
18893            shared_mem_bytes: 0,
18894        };
18895        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
18896        let __s_b = self.gpu.stream();
18897        let mut b = __s_b.launch_builder(&f);
18898        b.arg(qkv_tm).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc);
18899        unsafe {
18900            b.launch(cfg)?;
18901        }
18902        Ok(())
18903    }
18904
18905    /// BATCHED verify conv (T>1, carried state): window reads the resident conv ring for
18906    /// negative rows; separate ring-update launch afterwards. BIT-IDENTICAL per value to the
18907    /// T=1 chain. T >= pad rides the pure input-column ring update (unchanged legacy path);
18908    /// T < pad (the MEMRA_SPEC_M2 t=2 verify arm) needs old-ring sources for the roll — the
18909    /// update kernel would race reading the ring it rewrites, so that arm clones the ring
18910    /// (dtod) and rolls via ssm_conv_ring_rebuild (PURE COPIES: the ring stores raw input
18911    /// columns; the final ring == what T sequential decode ring rolls leave).
18912    pub fn ssm_conv1d_tm_state(
18913        &self,
18914        qkv_tm: &CudaSlice<f32>,
18915        conv_state: &mut CudaSlice<f32>,
18916        w: &CudaSlice<f32>,
18917        y: &mut CudaSlice<f32>,
18918        conv_dim: usize,
18919        t: usize,
18920        d_conv: usize,
18921    ) -> Result<(), Box<dyn std::error::Error>> {
18922        self.ssm_conv1d_tm_state_pad(qkv_tm, conv_state, w, y, conv_dim, t, d_conv, None)
18923    }
18924
18925    /// task #14: `pad_len` = device true length for PADDED prime graphs — the ring update
18926    /// reads rows [len-pad, len) instead of the pad tail. None = the classic host-T path.
18927    #[allow(clippy::too_many_arguments)]
18928    pub fn ssm_conv1d_tm_state_pad(
18929        &self,
18930        qkv_tm: &CudaSlice<f32>,
18931        conv_state: &mut CudaSlice<f32>,
18932        w: &CudaSlice<f32>,
18933        y: &mut CudaSlice<f32>,
18934        conv_dim: usize,
18935        t: usize,
18936        d_conv: usize,
18937        pad_len: Option<&CudaSlice<i32>>,
18938    ) -> Result<(), Box<dyn std::error::Error>> {
18939        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
18940        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
18941        // the window kernel both read the pre-roll ring; the roll launches after both) — but
18942        // cloning first keeps the ordering trivially correct under any future stream split.
18943        let ring_old = if t < d_conv - 1 {
18944            Some(self.clone_dtod(conv_state)?)
18945        } else {
18946            None
18947        };
18948        {
18949            let f = self.func("ssm_conv1d_tm_state_f32");
18950            let cfg = LaunchConfig {
18951                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
18952                block_dim: (256, 1, 1),
18953                shared_mem_bytes: 0,
18954            };
18955            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
18956            let __s_b = self.gpu.stream();
18957            let mut b = __s_b.launch_builder(&f);
18958            b.arg(qkv_tm)
18959                .arg(&*conv_state)
18960                .arg(w)
18961                .arg(y)
18962                .arg(&cd)
18963                .arg(&ti)
18964                .arg(&dc);
18965            unsafe {
18966                b.launch(cfg)?;
18967            }
18968        }
18969        match (ring_old, pad_len) {
18970            (None, Some(len_d)) => {
18971                let f = self.func("ssm_conv_ring_update_dev_f32");
18972                let n = conv_dim * (d_conv - 1);
18973                let cfg = LaunchConfig::for_num_elems(n as u32);
18974                let (cd, dc) = (conv_dim as i32, d_conv as i32);
18975                let __s_b = self.gpu.stream();
18976                let mut b = __s_b.launch_builder(&f);
18977                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
18978                unsafe {
18979                    b.launch(cfg)?;
18980                }
18981            }
18982            (None, None) => {
18983                let f = self.func("ssm_conv_ring_update_f32");
18984                let n = conv_dim * (d_conv - 1);
18985                let cfg = LaunchConfig::for_num_elems(n as u32);
18986                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
18987                let __s_b = self.gpu.stream();
18988                let mut b = __s_b.launch_builder(&f);
18989                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
18990                unsafe {
18991                    b.launch(cfg)?;
18992                }
18993            }
18994            (Some(old), _) => {
18995                self.ssm_conv_ring_rebuild(qkv_tm, &old, conv_state, conv_dim, t, d_conv)?
18996            }
18997        }
18998        Ok(())
18999    }
19000
19001    /// qkv-view twin (task #16): batched prime reads the concat GEMM output directly.
19002    pub fn ssm_conv1d_tm_state_pad_v(
19003        &self,
19004        qkv_tm: &cudarc::driver::CudaView<f32>,
19005        conv_state: &mut CudaSlice<f32>,
19006        w: &CudaSlice<f32>,
19007        y: &mut CudaSlice<f32>,
19008        conv_dim: usize,
19009        t: usize,
19010        d_conv: usize,
19011        pad_len: Option<&CudaSlice<i32>>,
19012    ) -> Result<(), Box<dyn std::error::Error>> {
19013        assert!(t >= 1, "ssm_conv1d_tm_state requires T >= 1");
19014        // clone BEFORE the window kernel is issued is not required (stream-ordered: the dtod and
19015        // the window kernel both read the pre-roll ring; the roll launches after both) — but
19016        // cloning first keeps the ordering trivially correct under any future stream split.
19017        let ring_old = if t < d_conv - 1 {
19018            Some(self.clone_dtod(conv_state)?)
19019        } else {
19020            None
19021        };
19022        {
19023            let f = self.func("ssm_conv1d_tm_state_f32");
19024            let cfg = LaunchConfig {
19025                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19026                block_dim: (256, 1, 1),
19027                shared_mem_bytes: 0,
19028            };
19029            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19030            let __s_b = self.gpu.stream();
19031            let mut b = __s_b.launch_builder(&f);
19032            b.arg(qkv_tm)
19033                .arg(&*conv_state)
19034                .arg(w)
19035                .arg(y)
19036                .arg(&cd)
19037                .arg(&ti)
19038                .arg(&dc);
19039            unsafe {
19040                b.launch(cfg)?;
19041            }
19042        }
19043        match (ring_old, pad_len) {
19044            (None, Some(len_d)) => {
19045                let f = self.func("ssm_conv_ring_update_dev_f32");
19046                let n = conv_dim * (d_conv - 1);
19047                let cfg = LaunchConfig::for_num_elems(n as u32);
19048                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19049                let __s_b = self.gpu.stream();
19050                let mut b = __s_b.launch_builder(&f);
19051                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19052                unsafe {
19053                    b.launch(cfg)?;
19054                }
19055            }
19056            (None, None) => {
19057                let f = self.func("ssm_conv_ring_update_f32");
19058                let n = conv_dim * (d_conv - 1);
19059                let cfg = LaunchConfig::for_num_elems(n as u32);
19060                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19061                let __s_b = self.gpu.stream();
19062                let mut b = __s_b.launch_builder(&f);
19063                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19064                unsafe {
19065                    b.launch(cfg)?;
19066                }
19067            }
19068            (Some(_), _) => unreachable!(
19069                "ssm_conv1d_tm_state_pad_v: T < d_conv-1 has no view path (PRIME_MIN_T gates it)"
19070            ),
19071        }
19072        Ok(())
19073    }
19074
19075    /// PREFIX conv-ring rebuild (spec REPLAY-FREE partial accept): overwrite the resident ring
19076    /// with the state a T=1 chain holds after only the FIRST `tc` columns of `qkv_tm` — the last
19077    /// `pad` entries of [ring_old | cols 0..tc-1]. PURE COPIES (the ring stores raw inputs; no
19078    /// arithmetic, cannot perturb FP order). `ring_old` = the pre-round snapshot ring.
19079    pub fn ssm_conv_ring_rebuild(
19080        &self,
19081        qkv_tm: &CudaSlice<f32>,
19082        ring_old: &CudaSlice<f32>,
19083        conv_state: &mut CudaSlice<f32>,
19084        conv_dim: usize,
19085        tc: usize,
19086        d_conv: usize,
19087    ) -> Result<(), Box<dyn std::error::Error>> {
19088        let f = self.func("ssm_conv_ring_rebuild_f32");
19089        let n = conv_dim * (d_conv - 1);
19090        let cfg = LaunchConfig::for_num_elems(n as u32);
19091        let (cd, ti, dc) = (conv_dim as i32, tc as i32, d_conv as i32);
19092        let __s_b = self.gpu.stream();
19093        let mut b = __s_b.launch_builder(&f);
19094        b.arg(qkv_tm)
19095            .arg(ring_old)
19096            .arg(conv_state)
19097            .arg(&cd)
19098            .arg(&ti)
19099            .arg(&dc);
19100        unsafe {
19101            b.launch(cfg)?;
19102        }
19103        Ok(())
19104    }
19105
19106    /// FUSED decode GDN prep (T=1): repack + q/k L2-norm + beta sigmoid + g_log in one launch.
19107    /// Replaces 5 tiny serialized kernels on the decode critical path. L2 reduce runs as a 32-lane
19108    /// warp tree (vs l2_norm_f32's 256-thread two-level tree) — same math, different FP sum order;
19109    /// the argmax + run-spec gates are the authority.
19110    #[allow(clippy::too_many_arguments)]
19111    pub fn gdn_prep_decode(
19112        &self,
19113        conv_out: &CudaSlice<f32>,
19114        beta_raw: &CudaSlice<f32>,
19115        alpha: &CudaSlice<f32>,
19116        dt_bias: &CudaSlice<f32>,
19117        a: &CudaSlice<f32>,
19118        q_l2: &mut CudaSlice<f32>,
19119        k_l2: &mut CudaSlice<f32>,
19120        v_g: &mut CudaSlice<f32>,
19121        beta: &mut CudaSlice<f32>,
19122        g_log: &mut CudaSlice<f32>,
19123        d_state: usize,
19124        num_v: usize,
19125        num_k: usize,
19126        key_dim: usize,
19127        eps: f32,
19128    ) -> Result<(), Box<dyn std::error::Error>> {
19129        let f = self.func("gdn_prep_decode_f32");
19130        let cfg = LaunchConfig {
19131            grid_dim: (num_v as u32, 1, 1),
19132            block_dim: (32, 4, 1),
19133            shared_mem_bytes: 0,
19134        };
19135        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19136        let __s_b = self.gpu.stream();
19137        let mut b = __s_b.launch_builder(&f);
19138        b.arg(conv_out)
19139            .arg(beta_raw)
19140            .arg(alpha)
19141            .arg(dt_bias)
19142            .arg(a)
19143            .arg(q_l2)
19144            .arg(k_l2)
19145            .arg(v_g)
19146            .arg(beta)
19147            .arg(g_log)
19148            .arg(&ds)
19149            .arg(&nv)
19150            .arg(&nk)
19151            .arg(&kd)
19152            .arg(&eps);
19153        unsafe {
19154            b.launch(cfg)?;
19155        }
19156        Ok(())
19157    }
19158
19159    /// FUSED prefill conv + GDN repack: token-major qkv -> q_g/k_g/v_g in ONE launch (no conv_out
19160    /// materialization, no qkv_to_gdn_repack pass). BIT-IDENTICAL values; scatter matches
19161    /// qkv_to_gdn_repack's modulo head-repeat mapping exactly.
19162    #[allow(clippy::too_many_arguments)]
19163    pub fn ssm_conv1d_gdn(
19164        &self,
19165        qkv_tm: &CudaSlice<f32>,
19166        w: &CudaSlice<f32>,
19167        q_g: &mut CudaSlice<f32>,
19168        k_g: &mut CudaSlice<f32>,
19169        v_g: &mut CudaSlice<f32>,
19170        conv_dim: usize,
19171        t: usize,
19172        d_conv: usize,
19173        d_state: usize,
19174        num_v: usize,
19175        num_k: usize,
19176        key_dim: usize,
19177    ) -> Result<(), Box<dyn std::error::Error>> {
19178        let f = self.func("ssm_conv1d_gdn_f32");
19179        let cfg = LaunchConfig {
19180            grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19181            block_dim: (256, 1, 1),
19182            shared_mem_bytes: 0,
19183        };
19184        let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19185        let (ds, nv, nk, kd) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
19186        let __s_b = self.gpu.stream();
19187        let mut b = __s_b.launch_builder(&f);
19188        b.arg(qkv_tm)
19189            .arg(w)
19190            .arg(q_g)
19191            .arg(k_g)
19192            .arg(v_g)
19193            .arg(&cd)
19194            .arg(&ti)
19195            .arg(&dc)
19196            .arg(&ds)
19197            .arg(&nv)
19198            .arg(&nk)
19199            .arg(&kd);
19200        unsafe {
19201            b.launch(cfg)?;
19202        }
19203        Ok(())
19204    }
19205
19206    pub fn ssm_conv1d(
19207        &self,
19208        x: &CudaSlice<f32>,
19209        w: &CudaSlice<f32>,
19210        y: &mut CudaSlice<f32>,
19211        conv_dim: usize,
19212        t: usize,
19213        d_conv: usize,
19214        silu: bool,
19215    ) -> Result<(), Box<dyn std::error::Error>> {
19216        let f = self.func("ssm_conv1d_silu_f32");
19217        let cfg = LaunchConfig {
19218            grid_dim: (conv_dim as u32, ((t as u32 + 255) / 256).max(1), 1),
19219            block_dim: (256, 1, 1),
19220            shared_mem_bytes: 0,
19221        };
19222        let (cd, ti, dc, s) = (conv_dim as i32, t as i32, d_conv as i32, silu as i32);
19223        let __s_b = self.gpu.stream();
19224        let mut b = __s_b.launch_builder(&f);
19225        b.arg(x).arg(w).arg(y).arg(&cd).arg(&ti).arg(&dc).arg(&s);
19226        unsafe {
19227            b.launch(cfg)?;
19228        }
19229        Ok(())
19230    }
19231
19232    /// Gated DeltaNet scan, S_v=128. q,k,v:[128,H,T]; g,beta:[H,T]; state:[128,128,H] transposed;
19233    /// o:[128,H,T]. Single sequence.
19234    pub fn gdn_scan_s128(
19235        &self,
19236        q: &CudaSlice<f32>,
19237        k: &CudaSlice<f32>,
19238        v: &CudaSlice<f32>,
19239        g: &CudaSlice<f32>,
19240        beta: &CudaSlice<f32>,
19241        state_in: &CudaSlice<f32>,
19242        state_out: &mut CudaSlice<f32>,
19243        o: &mut CudaSlice<f32>,
19244        n_head: usize,
19245        t: usize,
19246        scale: f32,
19247    ) -> Result<(), Box<dyn std::error::Error>> {
19248        let f = self.func("gdn_scan_s128");
19249        const S_V: u32 = 128;
19250        const WARP: u32 = 32;
19251        const COLS_PER_BLOCK: u32 = 4;
19252        let cfg = LaunchConfig {
19253            grid_dim: (n_head as u32, 1, S_V / COLS_PER_BLOCK),
19254            block_dim: (WARP, COLS_PER_BLOCK, 1),
19255            shared_mem_bytes: 0,
19256        };
19257        let (h, ti) = (n_head as i32, t as i32);
19258        let __s_b = self.gpu.stream();
19259        let mut b = __s_b.launch_builder(&f);
19260        b.arg(q)
19261            .arg(k)
19262            .arg(v)
19263            .arg(g)
19264            .arg(beta)
19265            .arg(state_in)
19266            .arg(state_out)
19267            .arg(o)
19268            .arg(&h)
19269            .arg(&ti)
19270            .arg(&scale);
19271        unsafe {
19272            b.launch(cfg)?;
19273        }
19274        Ok(())
19275    }
19276
19277    // ==== B2' batched decode state ops (decode_batch.rs) ====
19278    // Per-seq state pointers ride device u64 arrays (views into the per-step pointer table).
19279    // Bodies are the single-seq kernels per sequence — bit-identical per row.
19280
19281    #[allow(clippy::too_many_arguments)]
19282    pub fn ssm_conv1d_fused_decode_b(
19283        &self,
19284        qkv_cols: &CudaSlice<f32>,
19285        conv_state_ptrs: &cudarc::driver::CudaView<u64>,
19286        w: &CudaSlice<f32>,
19287        conv_outs: &mut CudaSlice<f32>,
19288        conv_dim: usize,
19289        d_conv: usize,
19290        b_n: usize,
19291    ) -> Result<(), Box<dyn std::error::Error>> {
19292        let f = self.func("ssm_conv1d_fused_decode_b_f32");
19293        let cfg = LaunchConfig {
19294            grid_dim: (((conv_dim + 255) / 256) as u32, 1, b_n as u32),
19295            block_dim: (256, 1, 1),
19296            shared_mem_bytes: 0,
19297        };
19298        let (cd, dc) = (conv_dim as i32, d_conv as i32);
19299        let __s_b = self.gpu.stream();
19300        let mut b = __s_b.launch_builder(&f);
19301        b.arg(qkv_cols)
19302            .arg(conv_state_ptrs)
19303            .arg(w)
19304            .arg(conv_outs)
19305            .arg(&cd)
19306            .arg(&dc);
19307        unsafe {
19308            b.launch(cfg)?;
19309        }
19310        Ok(())
19311    }
19312
19313    #[allow(clippy::too_many_arguments)]
19314    pub fn gdn_prep_decode_b(
19315        &self,
19316        conv_outs: &CudaSlice<f32>,
19317        beta_raws: &CudaSlice<f32>,
19318        alphas: &CudaSlice<f32>,
19319        dt_bias: &CudaSlice<f32>,
19320        a: &CudaSlice<f32>,
19321        q_l2: &mut CudaSlice<f32>,
19322        k_l2: &mut CudaSlice<f32>,
19323        v_g: &mut CudaSlice<f32>,
19324        beta: &mut CudaSlice<f32>,
19325        g_log: &mut CudaSlice<f32>,
19326        d_state: usize,
19327        num_v: usize,
19328        num_k: usize,
19329        key_dim: usize,
19330        eps: f32,
19331        conv_dim: usize,
19332        b_n: usize,
19333    ) -> Result<(), Box<dyn std::error::Error>> {
19334        let f = self.func("gdn_prep_decode_b_f32");
19335        let cfg = LaunchConfig {
19336            grid_dim: (num_v as u32, 1, b_n as u32),
19337            block_dim: (32, 4, 1),
19338            shared_mem_bytes: 0,
19339        };
19340        let (ds, nv, nk, kd, cd) = (
19341            d_state as i32,
19342            num_v as i32,
19343            num_k as i32,
19344            key_dim as i32,
19345            conv_dim as i32,
19346        );
19347        let __s_b = self.gpu.stream();
19348        let mut b = __s_b.launch_builder(&f);
19349        b.arg(conv_outs)
19350            .arg(beta_raws)
19351            .arg(alphas)
19352            .arg(dt_bias)
19353            .arg(a)
19354            .arg(q_l2)
19355            .arg(k_l2)
19356            .arg(v_g)
19357            .arg(beta)
19358            .arg(g_log)
19359            .arg(&ds)
19360            .arg(&nv)
19361            .arg(&nk)
19362            .arg(&kd)
19363            .arg(&eps)
19364            .arg(&cd);
19365        unsafe {
19366            b.launch(cfg)?;
19367        }
19368        Ok(())
19369    }
19370
19371    #[allow(clippy::too_many_arguments)]
19372    pub fn gdn_scan_s128_batched(
19373        &self,
19374        q: &CudaSlice<f32>,
19375        k: &CudaSlice<f32>,
19376        v: &CudaSlice<f32>,
19377        g: &CudaSlice<f32>,
19378        beta: &CudaSlice<f32>,
19379        state_in_ptrs: &cudarc::driver::CudaView<u64>,
19380        state_out_ptrs: &cudarc::driver::CudaView<u64>,
19381        o: &mut CudaSlice<f32>,
19382        n_head: usize,
19383        b_n: usize,
19384        scale: f32,
19385    ) -> Result<(), Box<dyn std::error::Error>> {
19386        let f = self.func("gdn_scan_s128_b");
19387        const S_V: u32 = 128;
19388        const WARP: u32 = 32;
19389        const COLS_PER_BLOCK: u32 = 4;
19390        let cfg = LaunchConfig {
19391            grid_dim: (n_head as u32, b_n as u32, S_V / COLS_PER_BLOCK),
19392            block_dim: (WARP, COLS_PER_BLOCK, 1),
19393            shared_mem_bytes: 0,
19394        };
19395        let h = n_head as i32;
19396        let __s_b = self.gpu.stream();
19397        let mut b = __s_b.launch_builder(&f);
19398        b.arg(q)
19399            .arg(k)
19400            .arg(v)
19401            .arg(g)
19402            .arg(beta)
19403            .arg(state_in_ptrs)
19404            .arg(state_out_ptrs)
19405            .arg(o)
19406            .arg(&h)
19407            .arg(&scale);
19408        unsafe {
19409            b.launch(cfg)?;
19410        }
19411        Ok(())
19412    }
19413
19414    /// A4 seam: chunked WY GDN prefill. DEFAULT ON (`MEMRA_GDN_CHUNKED=0` = rollback to the
19415    /// sequential scan). Flipped 2026-07-04 with the full battery green: kernel-check ALL
19416    /// GREEN x {9B, 27B} incl the f64-truth chunk gates; run-gen argmax 82==82 both models
19417    /// on AND off (24/24 sweep runs); run-spec K={1,2,3,4,6,8} PASS x {9B synth, 9B text,
19418    /// 27B p2, 27B p3}; e2e first-16-token agreement 6/6 (full-256 drifts at index 47-125
19419    /// on 5/6 prompts — accepted cache-state-FP class, batched-prime precedent).
19420    /// PREFILL-ONLY: decode + spec verify never route here (decode==verify dispatch
19421    /// identity law); prime_cache/forward/forward_last are the only callers.
19422    pub fn gdn_chunked_enabled() -> bool {
19423        static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
19424        *E.get_or_init(|| {
19425            std::env::var("MEMRA_GDN_CHUNKED")
19426                .map(|v| v != "0")
19427                .unwrap_or(true)
19428        })
19429    }
19430
19431    /// A4 chunk size (MEMRA_GDN_CHUNK, default 32 — the sweep winner: the O(T*C) chunk
19432    /// matrices grow with C while the sequential state pass is C-flat, so smaller chunks
19433    /// win; C=32/64 also get the register-history solve template). Clamped to multiples
19434    /// of 32 in [32, 128] (kernel row mappings require it).
19435    pub fn gdn_chunk_size() -> usize {
19436        static C: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
19437        *C.get_or_init(|| {
19438            let c: usize = std::env::var("MEMRA_GDN_CHUNK")
19439                .ok()
19440                .and_then(|v| v.parse().ok())
19441                .unwrap_or(32);
19442            c.clamp(32, 128) / 32 * 32
19443        })
19444    }
19445
19446    /// A4: chunked WY / blockwise-inverse GDN prefill (see cu/hybrid.cu K1-K5 header for the
19447    /// math). Same contract as `gdn_scan_s128` (layouts, state ping-pong) but chunk-parallel:
19448    /// NOT bit-identical to the sequential scan (chunked FP accumulation order); run-gen
19449    /// argmax + run-spec batteries are the accuracy authority. PREFILL callers only.
19450    #[allow(clippy::too_many_arguments)]
19451    /// task #18: K1-K3 of the chunked WY scan (shared by the per-seq path and the
19452    /// batched-prime varlen path). Returns (gcum, P, U, W); `A` is K3-internal.
19453    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
19454    #[allow(clippy::too_many_arguments)]
19455    pub fn gdn_chunk_k123(
19456        &self,
19457        q: &CudaSlice<f32>,
19458        k: &CudaSlice<f32>,
19459        v: &CudaSlice<f32>,
19460        g: &CudaSlice<f32>,
19461        beta: &CudaSlice<f32>,
19462        wb16: Option<&mut CudaSlice<u8>>,
19463        n_head: usize,
19464        t: usize,
19465        c: usize,
19466        hk: usize,
19467        k2w: Option<(&CudaSlice<u8>, &CudaSlice<u8>, &mut CudaSlice<u8>)>,
19468    ) -> Result<
19469        (
19470            CudaSlice<f32>,
19471            CudaSlice<f32>,
19472            CudaSlice<f32>,
19473            CudaSlice<f32>,
19474        ),
19475        Box<dyn std::error::Error>,
19476    > {
19477        const D: usize = 128;
19478        let h = n_head;
19479        let nc = (t + c - 1) / c;
19480        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
19481        let mut gcum = self.uninit(t * h)?;
19482        let mut a = self.uninit(nc * h * c * c)?;
19483        let mut p = self.uninit(nc * h * c * c)?;
19484        let mut u = self.uninit(nc * h * c * D)?;
19485        let mut w = self.uninit(nc * h * c * D)?;
19486        {
19487            // K1
19488            let f = self.func("gdn_chunk_cumgate_f32");
19489            let cfg = LaunchConfig {
19490                grid_dim: (nc as u32, h as u32, 1),
19491                block_dim: (32, 1, 1),
19492                shared_mem_bytes: 0,
19493            };
19494            let __s_b = self.gpu.stream();
19495            let mut b = __s_b.launch_builder(&f);
19496            b.arg(g).arg(&mut gcum).arg(&hi).arg(&ti).arg(&ci);
19497            unsafe {
19498                b.launch(cfg)?;
19499            }
19500        }
19501        if let Some((qb, kb, pb)) = k2w {
19502            // K2-wgmma (MEMRA_GDN_WGMMA path, c==32): A + pre-masked Pb16 in one kernel;
19503            // the P f32 buffer stays UNWRITTEN (its only wgmma-path consumer is Pb16).
19504            assert!(c == 32, "gdn_k2_wgmma is a C==32 tile");
19505            let f = self.func("gdn_k2_wgmma");
19506            let cfg = LaunchConfig {
19507                grid_dim: (nc as u32, h as u32, 1),
19508                block_dim: (128, 1, 1),
19509                shared_mem_bytes: 0,
19510            };
19511            let hki = hk as i32;
19512            let __s_b = self.gpu.stream();
19513            let mut b = __s_b.launch_builder(&f);
19514            b.arg(qb)
19515                .arg(kb)
19516                .arg(&gcum)
19517                .arg(beta)
19518                .arg(&mut a)
19519                .arg(&mut *pb)
19520                .arg(&hi)
19521                .arg(&ti)
19522                .arg(&ci)
19523                .arg(&hki);
19524            unsafe {
19525                b.launch(cfg)?;
19526            }
19527        } else if c <= 64 && !portable_mma_gated() {
19528            // K2 register-tiled (2x2 outputs/thread, whole-chunk smem k tile)
19529            let f = self.func("gdn_chunk_attn_f32");
19530            let jt = ((c + 31) / 32) as u32;
19531            let cfg = LaunchConfig {
19532                grid_dim: (nc as u32, h as u32, jt),
19533                block_dim: (256, 1, 1),
19534                shared_mem_bytes: 0,
19535            };
19536            let hki = hk as i32;
19537            let __s_b = self.gpu.stream();
19538            let mut b = __s_b.launch_builder(&f);
19539            b.arg(q)
19540                .arg(k)
19541                .arg(&gcum)
19542                .arg(beta)
19543                .arg(&mut a)
19544                .arg(&mut p)
19545                .arg(&hi)
19546                .arg(&ti)
19547                .arg(&ci)
19548                .arg(&hki);
19549            unsafe {
19550                b.launch(cfg)?;
19551            }
19552        } else {
19553            // K2 generic (C = 128, or the portable target's low-smem fallback)
19554            assert!(
19555                hk == h,
19556                "generic K2 is broadcast-only (de-broadcast rides C==32)"
19557            );
19558            let f = self.func("gdn_chunk_attn_g_f32");
19559            let cfg = LaunchConfig {
19560                grid_dim: (nc as u32, h as u32, 1),
19561                block_dim: (32, 8, 1),
19562                shared_mem_bytes: 0,
19563            };
19564            let __s_b = self.gpu.stream();
19565            let mut b = __s_b.launch_builder(&f);
19566            b.arg(q)
19567                .arg(k)
19568                .arg(&gcum)
19569                .arg(beta)
19570                .arg(&mut a)
19571                .arg(&mut p)
19572                .arg(&hi)
19573                .arg(&ti)
19574                .arg(&ci);
19575            unsafe {
19576                b.launch(cfg)?;
19577            }
19578        }
19579        {
19580            // K3 (register-history templates for C=32/64; local-memory generic otherwise)
19581            let cfg = LaunchConfig {
19582                grid_dim: (nc as u32, h as u32, 1),
19583                block_dim: (256, 1, 1),
19584                shared_mem_bytes: 0,
19585            };
19586            match c {
19587                32 | 64 => {
19588                    let f = self.func(if c == 32 {
19589                        "gdn_chunk_solve32_f32"
19590                    } else {
19591                        "gdn_chunk_solve64_f32"
19592                    });
19593                    // mirror-fold: W's bf16 twin emitted on store (0 = skip)
19594                    let wb: u64 = match wb16 {
19595                        Some(d) => self.addr_u8(d),
19596                        None => 0,
19597                    };
19598                    let hki = hk as i32;
19599                    let __s_b = self.gpu.stream();
19600                    let mut b = __s_b.launch_builder(&f);
19601                    b.arg(v)
19602                        .arg(k)
19603                        .arg(&a)
19604                        .arg(&gcum)
19605                        .arg(&mut u)
19606                        .arg(&mut w)
19607                        .arg(&wb)
19608                        .arg(&hi)
19609                        .arg(&ti)
19610                        .arg(&hki);
19611                    unsafe {
19612                        b.launch(cfg)?;
19613                    }
19614                }
19615                _ => {
19616                    assert!(hk == h, "generic K3 is broadcast-only");
19617                    let f = self.func("gdn_chunk_solve_f32");
19618                    let __s_b = self.gpu.stream();
19619                    let mut b = __s_b.launch_builder(&f);
19620                    b.arg(v)
19621                        .arg(k)
19622                        .arg(&a)
19623                        .arg(&gcum)
19624                        .arg(&mut u)
19625                        .arg(&mut w)
19626                        .arg(&hi)
19627                        .arg(&ti)
19628                        .arg(&ci);
19629                    unsafe {
19630                        b.launch(cfg)?;
19631                    }
19632                }
19633            }
19634        }
19635        Ok((gcum, p, u, w))
19636    }
19637
19638    /// task #21 de-broadcast seam: q/k stored at num_k distinct GQA heads instead of
19639    /// the num_v broadcast. MEMRA_GDN_DB=0 reverts. Only the chunked prefill path
19640    /// consumes the compact layout (hk plumbed; hk == H reproduces broadcast exactly).
19641    pub fn gdn_db_on() -> bool {
19642        std::env::var("MEMRA_GDN_DB").as_deref() != Ok("0")
19643    }
19644
19645    /// Whether the K4/K5 mma pair serves at chunk size `c` (mirrors gdn_scan_chunked's
19646    /// seam read — env re-read per call ON PURPOSE, kernel-check pins both configs).
19647    pub fn gdn_mma_enabled(&self, c: usize) -> bool {
19648        !portable_mma_gated()
19649            && c == 32
19650            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
19651                Ok("1") => true,
19652                Ok("0") => false,
19653                _ => cfg!(memra_hopper_mma),
19654            }
19655    }
19656
19657    /// task #22: whether the fused K4+K5 (+K2) wgmma path serves (nested inside the
19658    /// mma config; same per-call env read discipline).
19659    pub fn gdn_wgmma_on(&self, c: usize) -> bool {
19660        self.gdn_mma_enabled(c)
19661            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
19662                Ok("0") => false,
19663                Ok("1") => true,
19664                _ => cfg!(memra_hopper_mma),
19665            }
19666    }
19667
19668    /// task #18 conv-fuse: carried-ring conv + SiLU + GDN repack in ONE pass (the
19669    /// conv_out intermediate and its transposed re-read disappear — 11.8ms of the
19670    /// T=2048 prime). Ring update stays the separate follow-up launch (pad-aware).
19671    /// BIT-IDENTICAL values to ssm_conv1d_tm_state_pad + qkv_to_gdn_repack.
19672    #[allow(clippy::too_many_arguments)]
19673    pub fn ssm_conv1d_gdn_state_pad(
19674        &self,
19675        qkv_tm: &cudarc::driver::CudaView<f32>,
19676        conv_state: &mut CudaSlice<f32>,
19677        w: &CudaSlice<f32>,
19678        q_g: &mut CudaSlice<f32>,
19679        k_g: &mut CudaSlice<f32>,
19680        v_g: &mut CudaSlice<f32>,
19681        conv_dim: usize,
19682        t: usize,
19683        d_conv: usize,
19684        d_state: usize,
19685        num_v: usize,
19686        num_k: usize,
19687        key_dim: usize,
19688        hk: usize,
19689        pad_len: Option<&CudaSlice<i32>>,
19690    ) -> Result<(), Box<dyn std::error::Error>> {
19691        assert!(
19692            t >= d_conv - 1,
19693            "fused state conv requires T >= pad (PRIME_MIN_T gates)"
19694        );
19695        {
19696            let f = self.func("ssm_conv1d_gdn_state_f32");
19697            let cfg = LaunchConfig {
19698                grid_dim: (((conv_dim + 255) / 256) as u32, t as u32, 1),
19699                block_dim: (256, 1, 1),
19700                shared_mem_bytes: 0,
19701            };
19702            let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19703            let (ds, nv, nk, kd, hki) = (
19704                d_state as i32,
19705                num_v as i32,
19706                num_k as i32,
19707                key_dim as i32,
19708                hk as i32,
19709            );
19710            let __s_b = self.gpu.stream();
19711            let mut b = __s_b.launch_builder(&f);
19712            b.arg(qkv_tm)
19713                .arg(&*conv_state)
19714                .arg(w)
19715                .arg(q_g)
19716                .arg(k_g)
19717                .arg(v_g)
19718                .arg(&cd)
19719                .arg(&ti)
19720                .arg(&dc)
19721                .arg(&ds)
19722                .arg(&nv)
19723                .arg(&nk)
19724                .arg(&kd)
19725                .arg(&hki);
19726            unsafe {
19727                b.launch(cfg)?;
19728            }
19729        }
19730        match pad_len {
19731            Some(len_d) => {
19732                let f = self.func("ssm_conv_ring_update_dev_f32");
19733                let n = conv_dim * (d_conv - 1);
19734                let cfg = LaunchConfig::for_num_elems(n as u32);
19735                let (cd, dc) = (conv_dim as i32, d_conv as i32);
19736                let __s_b = self.gpu.stream();
19737                let mut b = __s_b.launch_builder(&f);
19738                b.arg(qkv_tm).arg(conv_state).arg(len_d).arg(&cd).arg(&dc);
19739                unsafe {
19740                    b.launch(cfg)?;
19741                }
19742            }
19743            None => {
19744                let f = self.func("ssm_conv_ring_update_f32");
19745                let n = conv_dim * (d_conv - 1);
19746                let cfg = LaunchConfig::for_num_elems(n as u32);
19747                let (cd, ti, dc) = (conv_dim as i32, t as i32, d_conv as i32);
19748                let __s_b = self.gpu.stream();
19749                let mut b = __s_b.launch_builder(&f);
19750                b.arg(qkv_tm).arg(conv_state).arg(&cd).arg(&ti).arg(&dc);
19751                unsafe {
19752                    b.launch(cfg)?;
19753                }
19754            }
19755        }
19756        Ok(())
19757    }
19758
19759    /// task #18 increment 2: allocate ONE sequence's chunk buffers (no launches) —
19760    /// K1-K5 all run varlen afterwards. `a`/`w` become struct members so the varlen
19761    /// K2/K3 can write them.
19762    pub fn gdn_chunk_alloc(
19763        &self,
19764        n_head: usize,
19765        t: usize,
19766        c: usize,
19767        hk: usize,
19768    ) -> Result<GdnChunkBufs, Box<dyn std::error::Error>> {
19769        const D: usize = 128;
19770        assert!(
19771            c == 32,
19772            "gdn_chunk_alloc: varlen chain is the C==32 mma pair"
19773        );
19774        let h = n_head;
19775        let nc = (t + c - 1) / c;
19776        Ok(GdnChunkBufs {
19777            gcum: self.uninit(t * h)?,
19778            a: self.uninit(nc * h * c * c)?,
19779            p: self.uninit(nc * h * c * c)?,
19780            u: self.uninit(nc * h * c * D)?,
19781            w: self.uninit(nc * h * c * D)?,
19782            kb16: self.alloc_u8_uninit(t * hk * D * 2)?,
19783            wb16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
19784            y16: self.alloc_u8_uninit(nc * h * c * D * 2)?,
19785            ssnap16: self.alloc_u8_uninit(nc * h * D * D * 2)?,
19786            qb16: self.alloc_u8_uninit(t * hk * D * 2)?,
19787            pb16: self.alloc_u8_uninit(nc * h * c * c * 2)?,
19788            o: self.uninit(D * h * t)?,
19789            t,
19790            nc,
19791        })
19792    }
19793
19794    /// view-source twin of f32_to_bf16 (the batched FA3 v mirror reads a concat view).
19795    pub fn f32_to_bf16_v(
19796        &self,
19797        x: &cudarc::driver::CudaView<f32>,
19798        dst: &mut CudaSlice<u8>,
19799        n: usize,
19800    ) -> Result<(), Box<dyn std::error::Error>> {
19801        let f = self.func("f32_to_bf16_bulk");
19802        let ni = n as i64;
19803        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
19804        let __s_b = self.gpu.stream();
19805        let mut b = __s_b.launch_builder(&f);
19806        b.arg(x).arg(dst).arg(&ni);
19807        unsafe {
19808            b.launch(cfg)?;
19809        }
19810        Ok(())
19811    }
19812
19813    /// f32 -> bf16 bulk mirror into a caller buffer (the K4/K5 operand mirrors).
19814    pub fn f32_to_bf16_into(
19815        &self,
19816        x: &CudaSlice<f32>,
19817        dst: &mut CudaSlice<u8>,
19818        n: usize,
19819    ) -> Result<(), Box<dyn std::error::Error>> {
19820        let f = self.func("f32_to_bf16_bulk");
19821        let ni = n as i64;
19822        let cfg = LaunchConfig::for_num_elems((n as u32).div_ceil(4));
19823        let __s_b = self.gpu.stream();
19824        let mut b = __s_b.launch_builder(&f);
19825        b.arg(x).arg(dst).arg(&ni);
19826        unsafe {
19827            b.launch(cfg)?;
19828        }
19829        Ok(())
19830    }
19831
19832    /// task #18 increment 2: varlen K1+K2+K3 — three launches run every sequence's
19833    /// cumgate/attn/solve (per-block math identical to the per-seq kernels).
19834    pub fn gdn_chunk_k123_vl8(
19835        &self,
19836        seqs: &[GdnSeqVl],
19837        n_head: usize,
19838        hk: usize,
19839        wq: Option<&GdnWVl8>,
19840    ) -> Result<(), Box<dyn std::error::Error>> {
19841        let b = seqs.len();
19842        assert!(b >= 1 && b <= 8, "gdn_chunk_k123_vl8: 1..=8 sequences");
19843        let mut packed = [GdnSeqVl::default(); 8];
19844        packed[..b].copy_from_slice(seqs);
19845        let v = GdnVl8(packed);
19846        let (hi, ci) = (n_head as i32, 32i32);
19847        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
19848        {
19849            let f = self.func("gdn_chunk_cumgate_vl");
19850            let cfg = LaunchConfig {
19851                grid_dim: (max_nc, n_head as u32, b as u32),
19852                block_dim: (32, 1, 1),
19853                shared_mem_bytes: 0,
19854            };
19855            let __s_lb = self.gpu.stream();
19856            let mut lb = __s_lb.launch_builder(&f);
19857            lb.arg(&v).arg(&hi).arg(&ci);
19858            unsafe {
19859                lb.launch(cfg)?;
19860            }
19861        }
19862        let hki = hk as i32;
19863        if let Some(w) = wq {
19864            // K2-wgmma vl twin (writes A + pre-masked Pb16)
19865            let f = self.func("gdn_k2_wgmma_vl");
19866            let cfg = LaunchConfig {
19867                grid_dim: (max_nc, n_head as u32, b as u32),
19868                block_dim: (128, 1, 1),
19869                shared_mem_bytes: 0,
19870            };
19871            let __s_lb = self.gpu.stream();
19872            let mut lb = __s_lb.launch_builder(&f);
19873            lb.arg(&v).arg(w).arg(&hi).arg(&ci).arg(&hki);
19874            unsafe {
19875                lb.launch(cfg)?;
19876            }
19877        } else {
19878            let f = self.func("gdn_chunk_attn_vl");
19879            let cfg = LaunchConfig {
19880                grid_dim: (max_nc, n_head as u32, b as u32),
19881                block_dim: (256, 1, 1),
19882                shared_mem_bytes: 0,
19883            };
19884            let __s_lb = self.gpu.stream();
19885            let mut lb = __s_lb.launch_builder(&f);
19886            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
19887            unsafe {
19888                lb.launch(cfg)?;
19889            }
19890        }
19891        {
19892            let f = self.func("gdn_chunk_solve32_vl");
19893            let cfg = LaunchConfig {
19894                grid_dim: (max_nc, n_head as u32, b as u32),
19895                block_dim: (256, 1, 1),
19896                shared_mem_bytes: 0,
19897            };
19898            let __s_lb = self.gpu.stream();
19899            let mut lb = __s_lb.launch_builder(&f);
19900            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
19901            unsafe {
19902                lb.launch(cfg)?;
19903            }
19904        }
19905        Ok(())
19906    }
19907
19908    /// task #18 increment 3: varlen PREP chain — conv(+ring) / repack / fused-l2 /
19909    /// fused gate-prep, 5 launches for every sequence (per-element math identical
19910    /// to the per-seq kernels; l2/gate fusions write disjoint outputs).
19911    #[allow(clippy::too_many_arguments)]
19912    pub fn gdn_prep_vl8(
19913        &self,
19914        seqs: &[GdnPrepVl],
19915        conv_w: &CudaSlice<f32>,
19916        dt_bias: &CudaSlice<f32>,
19917        a: &CudaSlice<f32>,
19918        conv_dim: usize,
19919        d_conv: usize,
19920        d_state: usize,
19921        num_v: usize,
19922        num_k: usize,
19923        key_dim: usize,
19924        hk: usize,
19925        eps: f32,
19926    ) -> Result<(), Box<dyn std::error::Error>> {
19927        let b = seqs.len();
19928        assert!(b >= 1 && b <= 8);
19929        let mut packed = [GdnPrepVl::default(); 8];
19930        packed[..b].copy_from_slice(seqs);
19931        let v = GdnPrepVl8(packed);
19932        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
19933        let (cdi, dci) = (conv_dim as i32, d_conv as i32);
19934        let conv_fuse = std::env::var("MEMRA_CONV_FUSE").as_deref() != Ok("0");
19935        assert!(
19936            conv_fuse || hk == num_v,
19937            "de-broadcast requires the fused conv"
19938        );
19939        if conv_fuse {
19940            let f = self.func("ssm_conv1d_gdn_state_vl");
19941            let cfg = LaunchConfig {
19942                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
19943                block_dim: (256, 1, 1),
19944                shared_mem_bytes: 0,
19945            };
19946            let (dsi, nvi, nki, kdi, hki) = (
19947                d_state as i32,
19948                num_v as i32,
19949                num_k as i32,
19950                key_dim as i32,
19951                hk as i32,
19952            );
19953            let __s_lb = self.gpu.stream();
19954            let mut lb = __s_lb.launch_builder(&f);
19955            lb.arg(&v)
19956                .arg(conv_w)
19957                .arg(&cdi)
19958                .arg(&dci)
19959                .arg(&dsi)
19960                .arg(&nvi)
19961                .arg(&nki)
19962                .arg(&kdi)
19963                .arg(&hki);
19964            unsafe {
19965                lb.launch(cfg)?;
19966            }
19967        } else {
19968            let f = self.func("ssm_conv1d_tm_state_vl");
19969            let cfg = LaunchConfig {
19970                grid_dim: ((conv_dim as u32).div_ceil(256), max_t, b as u32),
19971                block_dim: (256, 1, 1),
19972                shared_mem_bytes: 0,
19973            };
19974            let __s_lb = self.gpu.stream();
19975            let mut lb = __s_lb.launch_builder(&f);
19976            lb.arg(&v).arg(conv_w).arg(&cdi).arg(&dci);
19977            unsafe {
19978                lb.launch(cfg)?;
19979            }
19980        }
19981        {
19982            let f = self.func("ssm_conv_ring_update_vl");
19983            let n = (conv_dim * (d_conv - 1)) as u32;
19984            let cfg = LaunchConfig {
19985                grid_dim: (n.div_ceil(256), 1, b as u32),
19986                block_dim: (256, 1, 1),
19987                shared_mem_bytes: 0,
19988            };
19989            let __s_lb = self.gpu.stream();
19990            let mut lb = __s_lb.launch_builder(&f);
19991            lb.arg(&v).arg(&cdi).arg(&dci);
19992            unsafe {
19993                lb.launch(cfg)?;
19994            }
19995        }
19996        if !conv_fuse {
19997            let f = self.func("qkv_to_gdn_repack_vl");
19998            let n = max_t * (num_v * d_state) as u32;
19999            let cfg = LaunchConfig {
20000                grid_dim: (n.div_ceil(256), 1, b as u32),
20001                block_dim: (256, 1, 1),
20002                shared_mem_bytes: 0,
20003            };
20004            let (dsi, nvi, nki, kdi) = (d_state as i32, num_v as i32, num_k as i32, key_dim as i32);
20005            let __s_lb = self.gpu.stream();
20006            let mut lb = __s_lb.launch_builder(&f);
20007            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&nki).arg(&kdi);
20008            unsafe {
20009                lb.launch(cfg)?;
20010            }
20011        }
20012        if Self::l2_v2_on(d_state) {
20013            let f = self.func("gdn_l2_v2_vl");
20014            let cfg = LaunchConfig {
20015                grid_dim: ((max_t * hk as u32).div_ceil(8), 2, b as u32),
20016                block_dim: (256, 1, 1),
20017                shared_mem_bytes: 0,
20018            };
20019            let (dsi, nvi) = (d_state as i32, hk as i32);
20020            let __s_lb = self.gpu.stream();
20021            let mut lb = __s_lb.launch_builder(&f);
20022            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20023            unsafe {
20024                lb.launch(cfg)?;
20025            }
20026        } else {
20027            let f = self.func("gdn_l2_vl");
20028            let cfg = LaunchConfig {
20029                grid_dim: (max_t * hk as u32, 2, b as u32),
20030                block_dim: (256, 1, 1),
20031                shared_mem_bytes: 0,
20032            };
20033            let (dsi, nvi) = (d_state as i32, hk as i32);
20034            let __s_lb = self.gpu.stream();
20035            let mut lb = __s_lb.launch_builder(&f);
20036            lb.arg(&v).arg(&dsi).arg(&nvi).arg(&eps);
20037            unsafe {
20038                lb.launch(cfg)?;
20039            }
20040        }
20041        {
20042            let f = self.func("gdn_gate_prep_vl");
20043            let n = max_t * num_v as u32;
20044            let cfg = LaunchConfig {
20045                grid_dim: (n.div_ceil(256), 1, b as u32),
20046                block_dim: (256, 1, 1),
20047                shared_mem_bytes: 0,
20048            };
20049            let nvi = num_v as i32;
20050            let __s_lb = self.gpu.stream();
20051            let mut lb = __s_lb.launch_builder(&f);
20052            lb.arg(&v).arg(dt_bias).arg(a).arg(&nvi);
20053            unsafe {
20054                lb.launch(cfg)?;
20055            }
20056        }
20057        Ok(())
20058    }
20059
20060    /// varlen bf16 mirrors over the gdnseq_t table (which: 0 = k_l2 -> kb16, 1 = w -> wb16).
20061    pub fn gdn_mirror_vl8(
20062        &self,
20063        seqs: &[GdnSeqVl],
20064        n_head: usize,
20065        which: i32,
20066        hk: usize,
20067    ) -> Result<(), Box<dyn std::error::Error>> {
20068        let b = seqs.len();
20069        assert!(b >= 1 && b <= 8);
20070        let mut packed = [GdnSeqVl::default(); 8];
20071        packed[..b].copy_from_slice(seqs);
20072        let v = GdnVl8(packed);
20073        let ept = (if which == 0 { hk } else { n_head } * 128) as i32;
20074        let max_n = seqs
20075            .iter()
20076            .map(|s| {
20077                if which == 0 {
20078                    s.t as i64 * ept as i64
20079                } else {
20080                    s.nc as i64 * ept as i64 * 32
20081                }
20082            })
20083            .max()
20084            .unwrap();
20085        let f = self.func("gdn_mirror_vl");
20086        let blocks = ((max_n as u32).div_ceil(4)).div_ceil(256);
20087        let cfg = LaunchConfig {
20088            grid_dim: (blocks, 1, b as u32),
20089            block_dim: (256, 1, 1),
20090            shared_mem_bytes: 0,
20091        };
20092        let __s_lb = self.gpu.stream();
20093        let mut lb = __s_lb.launch_builder(&f);
20094        lb.arg(&v).arg(&ept).arg(&which);
20095        unsafe {
20096            lb.launch(cfg)?;
20097        }
20098        Ok(())
20099    }
20100
20101    /// varlen gated-norm tail (+f16out) — one launch replaces B gated_rmsnorm calls.
20102    pub fn gdn_tail_vl8(
20103        &self,
20104        seqs: &[GdnPrepVl],
20105        norm_w: &CudaSlice<f32>,
20106        d_state: usize,
20107        num_v: usize,
20108        eps: f32,
20109    ) -> Result<(), Box<dyn std::error::Error>> {
20110        let b = seqs.len();
20111        assert!(b >= 1 && b <= 8);
20112        let mut packed = [GdnPrepVl::default(); 8];
20113        packed[..b].copy_from_slice(seqs);
20114        let v = GdnPrepVl8(packed);
20115        let max_t = seqs.iter().map(|s| s.t).max().unwrap() as u32;
20116        let f = self.func("gated_rmsnorm_f16out_vl");
20117        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
20118        let cfg = LaunchConfig {
20119            grid_dim: (max_t * num_v as u32, 1, b as u32),
20120            block_dim: (128, 1, 1),
20121            shared_mem_bytes: 0,
20122        };
20123        let (dsi, nvi) = (d_state as i32, num_v as i32);
20124        let __s_lb = self.gpu.stream();
20125        let mut lb = __s_lb.launch_builder(&f);
20126        lb.arg(&v).arg(norm_w).arg(&dsi).arg(&nvi).arg(&eps);
20127        unsafe {
20128            lb.launch(cfg)?;
20129        }
20130        Ok(())
20131    }
20132
20133    /// Raw device address helpers for the varlen by-value arg struct (single-stream
20134    /// launches; every buffer outlives the call — the f16 FFI discipline).
20135    pub fn addr_f32(&self, x: &CudaSlice<f32>) -> u64 {
20136        use cudarc::driver::DevicePtr;
20137        let s = self.gpu.stream();
20138        let (p, _g) = x.device_ptr(&s);
20139        p as u64
20140    }
20141    pub fn addr_f32_mut(&self, x: &mut CudaSlice<f32>) -> u64 {
20142        use cudarc::driver::DevicePtrMut;
20143        let s = self.gpu.stream();
20144        let (p, _g) = x.device_ptr_mut(&s);
20145        p as u64
20146    }
20147    pub fn addr_f32v(&self, x: &cudarc::driver::CudaView<f32>) -> u64 {
20148        use cudarc::driver::DevicePtr;
20149        let s = self.gpu.stream();
20150        let (p, _g) = x.device_ptr(&s);
20151        p as u64
20152    }
20153    pub fn addr_u8(&self, x: &CudaSlice<u8>) -> u64 {
20154        use cudarc::driver::DevicePtr;
20155        let s = self.gpu.stream();
20156        let (p, _g) = x.device_ptr(&s);
20157        p as u64
20158    }
20159
20160    /// task #18: the varlen K4+K5 pair — TWO launches run every sequence's state pass
20161    /// and output pass (grid gains a seq dim; per-block math identical to the per-seq
20162    /// launches, so this is strictly bit-gateable against them).
20163    pub fn gdn_chunk_vl8(
20164        &self,
20165        seqs: &[GdnSeqVl],
20166        n_head: usize,
20167        scale: f32,
20168        hk: usize,
20169        wq: Option<&GdnWVl8>,
20170    ) -> Result<(), Box<dyn std::error::Error>> {
20171        const NSPLIT: u32 = 4;
20172        let b = seqs.len();
20173        assert!(b >= 1 && b <= 8, "gdn_chunk_vl8: 1..=8 sequences");
20174        let mut packed = [GdnSeqVl::default(); 8];
20175        packed[..b].copy_from_slice(seqs);
20176        let v = GdnVl8(packed);
20177        let (hi, ci) = (n_head as i32, 32i32);
20178        let max_nc = seqs.iter().map(|a| a.nc).max().unwrap() as u32;
20179        let hki = hk as i32;
20180        if let Some(w) = wq {
20181            // K4+K5 fused wgmma vl twin: one launch, Y/Ssnap never materialized.
20182            let f = self.func("gdn_k45_wgmma_vl");
20183            let cfg = LaunchConfig {
20184                grid_dim: (n_head as u32, NSPLIT, b as u32),
20185                block_dim: (256, 1, 1),
20186                shared_mem_bytes: 0,
20187            };
20188            let __s_lb = self.gpu.stream();
20189            let mut lb = __s_lb.launch_builder(&f);
20190            lb.arg(&v).arg(w).arg(&scale).arg(&hi).arg(&ci).arg(&hki);
20191            unsafe {
20192                lb.launch(cfg)?;
20193            }
20194            let _ = max_nc;
20195            return Ok(());
20196        }
20197        {
20198            let f = self.func("gdn_chunk_state_mma_vl");
20199            let cfg = LaunchConfig {
20200                grid_dim: (n_head as u32, NSPLIT, b as u32),
20201                block_dim: (256, 1, 1),
20202                shared_mem_bytes: 0,
20203            };
20204            let __s_lb = self.gpu.stream();
20205            let mut lb = __s_lb.launch_builder(&f);
20206            lb.arg(&v).arg(&hi).arg(&ci).arg(&hki);
20207            unsafe {
20208                lb.launch(cfg)?;
20209            }
20210        }
20211        {
20212            let f = self.func("gdn_chunk_output_mma_vl");
20213            let cfg = LaunchConfig {
20214                grid_dim: (max_nc, n_head as u32, b as u32),
20215                block_dim: (256, 1, 1),
20216                shared_mem_bytes: 0,
20217            };
20218            let __s_lb = self.gpu.stream();
20219            let mut lb = __s_lb.launch_builder(&f);
20220            lb.arg(&v).arg(&hi).arg(&ci).arg(&scale).arg(&hki);
20221            unsafe {
20222                lb.launch(cfg)?;
20223            }
20224        }
20225        Ok(())
20226    }
20227    pub fn gdn_scan_chunked(
20228        &self,
20229        q: &CudaSlice<f32>,
20230        k: &CudaSlice<f32>,
20231        v: &CudaSlice<f32>,
20232        g: &CudaSlice<f32>,
20233        beta: &CudaSlice<f32>,
20234        kb16_pre: Option<&CudaSlice<u8>>,
20235        qb16_pre: Option<&CudaSlice<u8>>,
20236        state_in: &CudaSlice<f32>,
20237        state_out: &mut CudaSlice<f32>,
20238        o: &mut CudaSlice<f32>,
20239        n_head: usize,
20240        t: usize,
20241        scale: f32,
20242        c: usize,
20243        hk: usize,
20244    ) -> Result<(), Box<dyn std::error::Error>> {
20245        const D: usize = 128;
20246        const NSPLIT: u32 = 4;
20247        assert!(c >= 1 && c <= 128, "gdn_scan_chunked: C must be in 1..=128");
20248        let h = n_head;
20249        let nc = (t + c - 1) / c;
20250        let (hi, ti, ci) = (h as i32, t as i32, c as i32);
20251        // mirror-fold (round 27): on the mma path W's bf16 twin is emitted by K3's store
20252        // (wb16 pre-allocated and threaded through k123) and k's by the producer l2 when
20253        // the caller hands `kb16_pre` — both standalone mirror passes disappear.
20254        let gdn_mma_pre = !portable_mma_gated()
20255            && c == 32
20256            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20257                Ok("1") => true,
20258                Ok("0") => false,
20259                _ => cfg!(memra_hopper_mma),
20260            };
20261        let mut wb16_pre: Option<CudaSlice<u8>> = if gdn_mma_pre {
20262            Some(self.alloc_u8_uninit(nc * h * c * D * 2)?)
20263        } else {
20264            None
20265        };
20266        // K2-wgmma pre-work (MEMRA_GDN_WGMMA): the kb16/qb16 mirrors hoist ABOVE K123 so
20267        // K2 rides them via cp.async; K2 writes the pre-masked Pb16 directly (the
20268        // gdn_p_bf16_masked pass and the in-branch mirror builds disappear).
20269        let gdn_wgmma_pre = gdn_mma_pre
20270            && match std::env::var("MEMRA_GDN_WGMMA").as_deref() {
20271                Ok("0") => false,
20272                Ok("1") => true,
20273                _ => cfg!(memra_hopper_mma),
20274            };
20275        let nk = t * hk * D;
20276        let mut kb16_local: Option<CudaSlice<u8>> = None;
20277        if gdn_mma_pre && kb16_pre.is_none() {
20278            let mut kb = self.alloc_u8_uninit(nk * 2)?;
20279            let f = self.func("f32_to_bf16_bulk");
20280            let n2 = nk as i64;
20281            let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20282            let __s_b = self.gpu.stream();
20283            let mut b = __s_b.launch_builder(&f);
20284            b.arg(k).arg(&mut kb).arg(&n2);
20285            unsafe {
20286                b.launch(cfg2)?;
20287            }
20288            kb16_local = Some(kb);
20289        }
20290        let kb16_ref0: Option<&CudaSlice<u8>> = kb16_local.as_ref().or(kb16_pre);
20291        if let Some(kb) = kb16_pre {
20292            assert!(kb.len() >= nk * 2, "kb16_pre too small");
20293        }
20294        let mut qb16: Option<CudaSlice<u8>> = None;
20295        let mut pb16: Option<CudaSlice<u8>> = None;
20296        if gdn_wgmma_pre {
20297            // mirror-fold (round 35): prep's l2 v2 emits qb16 in-epilogue (kb16 pattern);
20298            // the standalone bulk cvt only serves callers without the prep mirror.
20299            if qb16_pre.is_none() {
20300                let mut qb = self.alloc_u8_uninit(nk * 2)?;
20301                let f = self.func("f32_to_bf16_bulk");
20302                let n2 = nk as i64;
20303                let cfg2 = LaunchConfig::for_num_elems((nk as u32).div_ceil(4));
20304                let __s_b = self.gpu.stream();
20305                let mut b = __s_b.launch_builder(&f);
20306                b.arg(q).arg(&mut qb).arg(&n2);
20307                unsafe {
20308                    b.launch(cfg2)?;
20309                }
20310                qb16 = Some(qb);
20311            } else if let Some(qb) = qb16_pre {
20312                assert!(qb.len() >= nk * 2, "qb16_pre too small");
20313            }
20314            pb16 = Some(self.alloc_u8_uninit(nc * h * c * c * 2)?);
20315        }
20316        let qb16_ref0: Option<&CudaSlice<u8>> = qb16.as_ref().or(qb16_pre);
20317        let k2w = if gdn_wgmma_pre {
20318            Some((
20319                *qb16_ref0.as_ref().unwrap(),
20320                *kb16_ref0.as_ref().unwrap(),
20321                pb16.as_mut().unwrap(),
20322            ))
20323        } else {
20324            None
20325        };
20326        let (gcum, p, u, w) =
20327            self.gdn_chunk_k123(q, k, v, g, beta, wb16_pre.as_mut(), n_head, t, c, hk, k2w)?;
20328        let _ = &w;
20329        let mut y = self.uninit(nc * h * c * D)?;
20330        let mut ssnap = self.uninit(nc * h * D * D)?; // chunk-start state snapshots (K5 phase 1)
20331        // K4-MMA seam (MEMRA_GDN_MMA; harness verdict 1.75x — tools/bench_gdn_k4.cu, ledger
20332        // 2026-07-26): M in mma accumulator fragments, bf16 W/k mirrors through a cp.async
20333        // ring. C==32 only (the kernel's tile). PROMOTED default-ON on the Hopper lane
20334        // after the STATE-CARRY battery (2026-07-26): 2048-token prime (64 in-kernel state
20335        // carries) -> 256 greedy decode tokens IDENTICAL to f32 on 3 seeds, AND chunked-
20336        // continuation prime (MEMRA_PRIME_CHUNK=512, 4 cross-call carries via cache.recur)
20337        // IDENTICAL on 2 seeds; plus argmax MATCH, pp512 +3.5% (17286), oracle out
20338        // mean_rel ~1e-4. kernel-check pins BOTH configs (f32 tight band forced =0; mma
20339        // band 8e-2/8e-1 vs f64 truth). =0 reverts; portable stays f32. NOT read via
20340        // OnceLock ON PURPOSE: kernel-check toggles the env per call to pin both forms.
20341        let gdn_mma = !portable_mma_gated()
20342            && c == 32
20343            && match std::env::var("MEMRA_GDN_MMA").as_deref() {
20344                Ok("1") => true,
20345                Ok("0") => false,
20346                _ => cfg!(memra_hopper_mma),
20347            };
20348        if gdn_mma {
20349            let wb16 = wb16_pre
20350                .take()
20351                .expect("mma path pre-allocates wb16 (K3 store fold)");
20352            let kb16_ref: &CudaSlice<u8> = kb16_ref0.expect("mma path pre-builds kb16 above K123");
20353            // K4+K5 FUSED wgmma seam (MEMRA_GDN_WGMMA, task #22; harness verdict
20354            // tools/bench_gdn_wgmma.cu v5, ledger 1f08b997: in-band Y 1.07e-2 / state
20355            // 1.03e-2 / O 1.08e-2, 91.3us vs 70.4 K4-only at H=32 T=512). K5's output
20356            // pass runs inside the persistent-M kernel; Y and Ssnap are never
20357            // materialized. New numeric class (gk folds into k^T instead of ys) —
20358            // explicit opt-in until the state-carry battery promotes it. Env read per
20359            // call (kernel-check pins configs by toggling env, GDN_MMA precedent).
20360            // PROMOTED default-ON hopper (2026-07-27): full battery green — harness
20361            // in-band, argmax gate PASS, 3-seed greedy IDENTICAL after ~2k prime,
20362            // chunked-continuation IDENTICAL, kernel-check + decode-batch gates green,
20363            // official prefill lane +0.74% interleaved x5 (5/5 rounds). =0 reverts.
20364            if gdn_wgmma_pre {
20365                // qb16/pb16 pre-built above K123 (K2-wgmma wrote the masked Pb16).
20366                let qb16 = qb16_ref0.unwrap();
20367                let pb16 = pb16.as_ref().unwrap();
20368                {
20369                    let f = self.func("gdn_k45_wgmma");
20370                    let cfg = LaunchConfig {
20371                        grid_dim: (h as u32, 4, 1),
20372                        block_dim: (256, 1, 1),
20373                        shared_mem_bytes: 0,
20374                    };
20375                    let hki = hk as i32;
20376                    let __s_b = self.gpu.stream();
20377                    let mut b = __s_b.launch_builder(&f);
20378                    b.arg(kb16_ref)
20379                        .arg(&gcum)
20380                        .arg(beta)
20381                        .arg(&u)
20382                        .arg(&wb16)
20383                        .arg(qb16)
20384                        .arg(pb16)
20385                        .arg(o)
20386                        .arg(&scale)
20387                        .arg(state_in)
20388                        .arg(&mut *state_out)
20389                        .arg(&hi)
20390                        .arg(&ti)
20391                        .arg(&ci)
20392                        .arg(&hki);
20393                    unsafe {
20394                        b.launch(cfg)?;
20395                    }
20396                }
20397                return Ok(());
20398            }
20399            // COUPLED PAIR: K4-mma writes Y and Ssnap as bf16 (their only consumer is
20400            // K5-mma, which rounds to bf16 regardless — identical numerics, half the
20401            // traffic; harness K5 63.0 -> 35.3us). Fresh bf16 buffers replace the f32 ones.
20402            let mut y16 = self.alloc_u8_uninit(nc * h * c * D * 2)?;
20403            let mut ssnap16 = self.alloc_u8_uninit(nc * h * D * D * 2)?;
20404            {
20405                let f = self.func("gdn_chunk_state_mma");
20406                let cfg = LaunchConfig {
20407                    grid_dim: (h as u32, NSPLIT, 1),
20408                    block_dim: (256, 1, 1),
20409                    shared_mem_bytes: 0,
20410                };
20411                let hki = hk as i32;
20412                let __s_b = self.gpu.stream();
20413                let mut b = __s_b.launch_builder(&f);
20414                b.arg(kb16_ref)
20415                    .arg(&gcum)
20416                    .arg(beta)
20417                    .arg(&u)
20418                    .arg(&wb16)
20419                    .arg(&mut y16)
20420                    .arg(&mut ssnap16)
20421                    .arg(state_in)
20422                    .arg(&mut *state_out)
20423                    .arg(&hi)
20424                    .arg(&ti)
20425                    .arg(&ci)
20426                    .arg(&hki);
20427                unsafe {
20428                    b.launch(cfg)?;
20429                }
20430            }
20431            {
20432                // K5-mma (bf16 St/Y consumers)
20433                let f = self.func("gdn_chunk_output_mma");
20434                let jt = ((c + 31) / 32) as u32;
20435                let cfg = LaunchConfig {
20436                    grid_dim: (nc as u32, h as u32, jt),
20437                    block_dim: (256, 1, 1),
20438                    shared_mem_bytes: 0,
20439                };
20440                let hki = hk as i32;
20441                let __s_b = self.gpu.stream();
20442                let mut b = __s_b.launch_builder(&f);
20443                b.arg(q)
20444                    .arg(&gcum)
20445                    .arg(&p)
20446                    .arg(&y16)
20447                    .arg(&ssnap16)
20448                    .arg(o)
20449                    .arg(&hi)
20450                    .arg(&ti)
20451                    .arg(&ci)
20452                    .arg(&scale)
20453                    .arg(&hki);
20454                unsafe {
20455                    b.launch(cfg)?;
20456                }
20457            }
20458            return Ok(());
20459        }
20460        {
20461            // K4 (sequential over chunks inside; blocks col-partition the state)
20462            let f = self.func("gdn_chunk_state_f32");
20463            let cfg = LaunchConfig {
20464                grid_dim: (h as u32, NSPLIT, 1),
20465                block_dim: (256, 1, 1),
20466                shared_mem_bytes: 0,
20467            };
20468            let __s_b = self.gpu.stream();
20469            let mut b = __s_b.launch_builder(&f);
20470            b.arg(k)
20471                .arg(&gcum)
20472                .arg(beta)
20473                .arg(&u)
20474                .arg(&w)
20475                .arg(&mut y)
20476                .arg(&mut ssnap)
20477                .arg(state_in)
20478                .arg(&mut *state_out)
20479                .arg(&hi)
20480                .arg(&ti)
20481                .arg(&ci);
20482            unsafe {
20483                b.launch(cfg)?;
20484            }
20485        }
20486        {
20487            // K5 (j-blocked: grid.z = 32-row output blocks per chunk; writes o fully)
20488            let f = self.func("gdn_chunk_output_f32");
20489            let jt = ((c + 31) / 32) as u32;
20490            let cfg = LaunchConfig {
20491                grid_dim: (nc as u32, h as u32, jt),
20492                block_dim: (256, 1, 1),
20493                shared_mem_bytes: 0,
20494            };
20495            let __s_b = self.gpu.stream();
20496            let mut b = __s_b.launch_builder(&f);
20497            b.arg(q)
20498                .arg(&gcum)
20499                .arg(&p)
20500                .arg(&y)
20501                .arg(&ssnap)
20502                .arg(o)
20503                .arg(&hi)
20504                .arg(&ti)
20505                .arg(&ci)
20506                .arg(&scale);
20507            unsafe {
20508                b.launch(cfg)?;
20509            }
20510        }
20511        Ok(())
20512    }
20513
20514    /// PREFILL GDN scan dispatch (the A4 seam): chunked WY form when enabled and T is in the
20515    /// batched-prefill regime, else the sequential scan. Callers: hybrid_forward::linear_attn
20516    /// (forward/forward_last) + linear_attn_prime (prime_cache). Decode (T=1) and the spec
20517    /// verify call `gdn_scan_s128` DIRECTLY — the decode==verify dispatch identity is untouched.
20518    ///
20519    /// MEMRA_GDN_DIFF=1: numerical-oracle mode — runs BOTH forms on the same inputs, prints the
20520    /// per-call (== per-layer, in call order) output/state error distribution, and keeps the
20521    /// SEQUENTIAL results so the run stays on the shipped path (stage-1 prototype evidence).
20522    #[allow(clippy::too_many_arguments)]
20523    #[allow(clippy::too_many_arguments)]
20524    pub fn gdn_scan_prefill(
20525        &self,
20526        q: &CudaSlice<f32>,
20527        k: &CudaSlice<f32>,
20528        v: &CudaSlice<f32>,
20529        g: &CudaSlice<f32>,
20530        beta: &CudaSlice<f32>,
20531        kb16_pre: Option<&CudaSlice<u8>>,
20532        qb16_pre: Option<&CudaSlice<u8>>,
20533        state_in: &CudaSlice<f32>,
20534        state_out: &mut CudaSlice<f32>,
20535        o: &mut CudaSlice<f32>,
20536        n_head: usize,
20537        t: usize,
20538        scale: f32,
20539        hk: usize,
20540    ) -> Result<(), Box<dyn std::error::Error>> {
20541        if std::env::var("MEMRA_GDN_DIFF").is_ok() && t >= 16 {
20542            assert!(hk == n_head, "GDN_DIFF oracle is broadcast-only");
20543            return self.gdn_scan_diff(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale);
20544        }
20545        if Self::gdn_chunked_enabled() && t >= 16 {
20546            self.gdn_scan_chunked(
20547                q,
20548                k,
20549                v,
20550                g,
20551                beta,
20552                kb16_pre,
20553                qb16_pre,
20554                state_in,
20555                state_out,
20556                o,
20557                n_head,
20558                t,
20559                scale,
20560                Self::gdn_chunk_size(),
20561                hk,
20562            )
20563        } else {
20564            assert!(
20565                hk == n_head,
20566                "s128 scan is broadcast-only (prep guarantees by predicate)"
20567            );
20568            self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)
20569        }
20570    }
20571
20572    /// Stage-1 oracle: run sequential AND chunked, report per-call error stats, keep sequential.
20573    #[allow(clippy::too_many_arguments)]
20574    fn gdn_scan_diff(
20575        &self,
20576        q: &CudaSlice<f32>,
20577        k: &CudaSlice<f32>,
20578        v: &CudaSlice<f32>,
20579        g: &CudaSlice<f32>,
20580        beta: &CudaSlice<f32>,
20581        state_in: &CudaSlice<f32>,
20582        state_out: &mut CudaSlice<f32>,
20583        o: &mut CudaSlice<f32>,
20584        n_head: usize,
20585        t: usize,
20586        scale: f32,
20587    ) -> Result<(), Box<dyn std::error::Error>> {
20588        static CALL: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
20589        let call = CALL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
20590        let mut o_c = self.uninit(o.len())?;
20591        let mut st_c = self.uninit(state_out.len())?;
20592        self.gdn_scan_chunked(
20593            q,
20594            k,
20595            v,
20596            g,
20597            beta,
20598            None,
20599            None,
20600            state_in,
20601            &mut st_c,
20602            &mut o_c,
20603            n_head,
20604            t,
20605            scale,
20606            Self::gdn_chunk_size(),
20607            n_head,
20608        )?;
20609        self.gdn_scan_s128(q, k, v, g, beta, state_in, state_out, o, n_head, t, scale)?;
20610        let (oh_s, oh_c) = (self.dtoh(o)?, self.dtoh(&o_c)?);
20611        let (sh_s, sh_c) = (self.dtoh(state_out)?, self.dtoh(&st_c)?);
20612        let stats = |a: &[f32], b: &[f32]| -> (f32, f32, f64) {
20613            let mut max_abs = 0f32;
20614            let mut max_rel = 0f32;
20615            let mut sum_rel = 0f64;
20616            for (x, y) in a.iter().zip(b) {
20617                let ad = (x - y).abs();
20618                let rel = ad / x.abs().max(y.abs()).max(1e-3);
20619                if ad > max_abs {
20620                    max_abs = ad;
20621                }
20622                if rel > max_rel {
20623                    max_rel = rel;
20624                }
20625                sum_rel += rel as f64;
20626            }
20627            (max_abs, max_rel, sum_rel / a.len() as f64)
20628        };
20629        let (o_ma, o_mr, o_mean) = stats(&oh_s, &oh_c);
20630        let (s_ma, s_mr, s_mean) = stats(&sh_s, &sh_c);
20631        println!(
20632            "[gdn-diff call {call:3} T={t} C={}] out: max_abs={o_ma:.3e} max_rel={o_mr:.3e} mean_rel={o_mean:.3e} | \
20633                  state: max_abs={s_ma:.3e} max_rel={s_mr:.3e} mean_rel={s_mean:.3e}",
20634            Self::gdn_chunk_size()
20635        );
20636        Ok(())
20637    }
20638
20639    /// softplus-based g_log: g_log[h,t] = a[h] * softplus(alpha[h,t] + dt_bias[h]). a pre-negated.
20640    pub fn gdn_glog(
20641        &self,
20642        alpha: &CudaSlice<f32>,
20643        dt_bias: &CudaSlice<f32>,
20644        a: &CudaSlice<f32>,
20645        g_log: &mut CudaSlice<f32>,
20646        n_head: usize,
20647        t: usize,
20648    ) -> Result<(), Box<dyn std::error::Error>> {
20649        let f = self.func("gdn_glog_f32");
20650        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
20651        let (h, ti) = (n_head as i32, t as i32);
20652        let __s_b = self.gpu.stream();
20653        let mut b = __s_b.launch_builder(&f);
20654        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
20655        unsafe {
20656            b.launch(cfg)?;
20657        }
20658        Ok(())
20659    }
20660
20661    /// view twins (task #16): the batched prime's GDN core reads the CONCAT projection
20662    /// buffers at row offsets (CudaView) — same kernels, same values, no split copies.
20663    pub fn sigmoid_v(
20664        &self,
20665        x: &cudarc::driver::CudaView<f32>,
20666        y: &mut CudaSlice<f32>,
20667        n: usize,
20668    ) -> Result<(), Box<dyn std::error::Error>> {
20669        let f = self.func("sigmoid_f32");
20670        let cfg = LaunchConfig::for_num_elems(n as u32);
20671        let ni = n as i32;
20672        let __s_b = self.gpu.stream();
20673        let mut b = __s_b.launch_builder(&f);
20674        b.arg(x).arg(y).arg(&ni);
20675        unsafe {
20676            b.launch(cfg)?;
20677        }
20678        Ok(())
20679    }
20680
20681    pub fn gdn_glog_v(
20682        &self,
20683        alpha: &cudarc::driver::CudaView<f32>,
20684        dt_bias: &CudaSlice<f32>,
20685        a: &CudaSlice<f32>,
20686        g_log: &mut CudaSlice<f32>,
20687        n_head: usize,
20688        t: usize,
20689    ) -> Result<(), Box<dyn std::error::Error>> {
20690        let f = self.func("gdn_glog_f32");
20691        let cfg = LaunchConfig::for_num_elems((n_head * t) as u32);
20692        let (h, ti) = (n_head as i32, t as i32);
20693        let __s_b = self.gpu.stream();
20694        let mut b = __s_b.launch_builder(&f);
20695        b.arg(alpha).arg(dt_bias).arg(a).arg(g_log).arg(&h).arg(&ti);
20696        unsafe {
20697            b.launch(cfg)?;
20698        }
20699        Ok(())
20700    }
20701
20702    pub fn sigmoid(
20703        &self,
20704        x: &CudaSlice<f32>,
20705        y: &mut CudaSlice<f32>,
20706        n: usize,
20707    ) -> Result<(), Box<dyn std::error::Error>> {
20708        let f = self.func("sigmoid_f32");
20709        let cfg = LaunchConfig::for_num_elems(n as u32);
20710        let ni = n as i32;
20711        let __s_b = self.gpu.stream();
20712        let mut b = __s_b.launch_builder(&f);
20713        b.arg(x).arg(y).arg(&ni);
20714        unsafe {
20715            b.launch(cfg)?;
20716        }
20717        Ok(())
20718    }
20719
20720    /// attn out-gate fused epilogue (task #17): dst = a * sigmoid(g) + fp16 twin, one launch
20721    /// (replaces sigmoid + mul + convert). Bit-identical class.
20722    pub fn sig_mul_f16out(
20723        &self,
20724        a: &CudaSlice<f32>,
20725        g: &CudaSlice<f32>,
20726        dst: &mut CudaSlice<f32>,
20727        dst16: &mut CudaSlice<u8>,
20728        n: usize,
20729    ) -> Result<(), Box<dyn std::error::Error>> {
20730        let f = self.func("sig_mul_f16out_f32");
20731        let cfg = LaunchConfig::for_num_elems(n as u32);
20732        let ni = n as i32;
20733        let __s_b = self.gpu.stream();
20734        let mut b = __s_b.launch_builder(&f);
20735        b.arg(a).arg(g).arg(dst).arg(dst16).arg(&ni);
20736        unsafe {
20737            b.launch(cfg)?;
20738        }
20739        Ok(())
20740    }
20741
20742    /// step35 (Step-3.7-Flash) SEPARATE head-wise attention gate: one scalar per query head,
20743    /// broadcast over head_dim. `dst = a * sigmoid(g)` where `a`/`dst` are `[head_dim, n_head, T]`
20744    /// (the `q_gate_split` layout) and `g` is the PRE-sigmoid `attn_gate` projection output in
20745    /// token-major `[T, n_head]`. `dst16` is the optional fp16 operand for wo (None -> skipped).
20746    ///
20747    /// NOT interchangeable with `sig_mul_f16out`, which gates FULL WIDTH (qwen35 packs one gate
20748    /// value per (head, dim) element inside wq). Using this for that, or that for this, silently
20749    /// applies the wrong number of distinct gate values.
20750    #[allow(clippy::too_many_arguments)]
20751    pub fn attn_head_gate(
20752        &self,
20753        a: &CudaSlice<f32>,
20754        g: &CudaSlice<f32>,
20755        dst: &mut CudaSlice<f32>,
20756        dst16: Option<&mut CudaSlice<u8>>,
20757        head_dim: usize,
20758        n_head: usize,
20759        t: usize,
20760    ) -> Result<(), Box<dyn std::error::Error>> {
20761        let f = self.func("attn_head_gate_f32");
20762        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
20763        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
20764        // nullable device pointer by value (0 = skip), same convention as `l2_norm_pp`.
20765        let d16: u64 = match dst16 {
20766            Some(d) => self.addr_u8(d),
20767            None => 0,
20768        };
20769        let __s_b = self.gpu.stream();
20770        let mut b = __s_b.launch_builder(&f);
20771        b.arg(a)
20772            .arg(g)
20773            .arg(dst)
20774            .arg(&d16)
20775            .arg(&hd)
20776            .arg(&nh)
20777            .arg(&ti);
20778        unsafe {
20779            b.launch(cfg)?;
20780        }
20781        Ok(())
20782    }
20783
20784    /// step35 CLAMPED SwiGLU: `dst = min(silu(gate*gs), limit) * clamp(up*us, +-limit)`.
20785    /// Verbatim from llama.cpp `llama-graph.cpp:2146-2165` (routed, `swiglu_clamp_exp`) and
20786    /// `:1751-1770` (shared, `swiglu_clamp_shexp`), non-DEEPSEEK4 branch.
20787    ///
20788    /// This is NOT `swigluoai_mul_scaled`: that one clamps the gate BEFORE swish and multiplies by
20789    /// `(1 + clamp(up))`. Caller MUST check `limit > 1e-6` (upstream's eps gate) and use the plain
20790    /// `silu_mul_scaled` path otherwise — at limit=0 this kernel would clamp every positive
20791    /// activation to zero. On Step-3.7-Flash only layers 43 (7.0) and 44 (16.0) have a live limit.
20792    #[allow(clippy::too_many_arguments)]
20793    pub fn swiglu_clamped_mul_scaled(
20794        &self,
20795        gate: &CudaSlice<f32>,
20796        up: &CudaSlice<f32>,
20797        gs: f32,
20798        us: f32,
20799        limit: f32,
20800        dst: &mut CudaSlice<f32>,
20801        n: usize,
20802    ) -> Result<(), Box<dyn std::error::Error>> {
20803        debug_assert!(
20804            limit > 1e-6,
20805            "swiglu_clamped needs a live limit; use silu_mul_scaled"
20806        );
20807        let f = self.func("swiglu_clamped_mul_scaled_f32");
20808        let cfg = LaunchConfig::for_num_elems(n as u32);
20809        let ni = n as i32;
20810        let __s_b = self.gpu.stream();
20811        let mut b = __s_b.launch_builder(&f);
20812        b.arg(gate)
20813            .arg(up)
20814            .arg(&gs)
20815            .arg(&us)
20816            .arg(&limit)
20817            .arg(dst)
20818            .arg(&ni);
20819        unsafe {
20820            b.launch(cfg)?;
20821        }
20822        Ok(())
20823    }
20824
20825    /// gated RMSNorm: dst = RMSNorm(o, w[ncols]) * silu(z), per row of ncols. nrows blocks.
20826    pub fn gated_rmsnorm(
20827        &self,
20828        o: &CudaSlice<f32>,
20829        w: &CudaSlice<f32>,
20830        z: &CudaSlice<f32>,
20831        dst: &mut CudaSlice<f32>,
20832        ncols: usize,
20833        nrows: usize,
20834        eps: f32,
20835    ) -> Result<(), Box<dyn std::error::Error>> {
20836        let f = self.func("gated_rmsnorm_f32");
20837        let cfg = LaunchConfig {
20838            grid_dim: (nrows as u32, 1, 1),
20839            block_dim: (128, 1, 1),
20840            shared_mem_bytes: 0,
20841        };
20842        let (nc, e) = (ncols as i32, eps);
20843        let __s_b = self.gpu.stream();
20844        let mut b = __s_b.launch_builder(&f);
20845        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
20846        unsafe {
20847            b.launch(cfg)?;
20848        }
20849        Ok(())
20850    }
20851
20852    /// f16out twin of `gated_rmsnorm` (task #17): epilogue also emits the fp16 operand for
20853    /// the ssm_out GEMM. Bit-identical class (same floats + the cvt kernel's __float2half).
20854    pub fn gated_rmsnorm_f16out(
20855        &self,
20856        o: &CudaSlice<f32>,
20857        w: &CudaSlice<f32>,
20858        z: &CudaSlice<f32>,
20859        dst: &mut CudaSlice<f32>,
20860        dst16: &mut CudaSlice<u8>,
20861        ncols: usize,
20862        nrows: usize,
20863        eps: f32,
20864    ) -> Result<(), Box<dyn std::error::Error>> {
20865        let f = self.func("gated_rmsnorm_f16out_f32");
20866        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
20867        let cfg = LaunchConfig {
20868            grid_dim: (nrows as u32, 1, 1),
20869            block_dim: (128, 1, 1),
20870            shared_mem_bytes: 0,
20871        };
20872        let (nc, e) = (ncols as i32, eps);
20873        let __s_b = self.gpu.stream();
20874        let mut b = __s_b.launch_builder(&f);
20875        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
20876        unsafe {
20877            b.launch(cfg)?;
20878        }
20879        Ok(())
20880    }
20881
20882    /// add+RMSNorm emitting the f32 normed row AND its q8_1 quantization in one launch (the MoE
20883    /// layer input: z feeds the router matmul as f32, the expert dp4a as q8_1). BIT-IDENTICAL to
20884    /// add_rms_norm + quantize_q8_1. Returns (q, d) alongside the caller-provided res/z buffers.
20885    #[allow(clippy::too_many_arguments)]
20886    pub fn add_rms_norm_zq8(
20887        &self,
20888        a: &CudaSlice<f32>,
20889        b_in: &CudaSlice<f32>,
20890        w: &CudaSlice<f32>,
20891        res: &mut CudaSlice<f32>,
20892        z: &mut CudaSlice<f32>,
20893        ncols: usize,
20894        nrows: usize,
20895        eps: f32,
20896    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
20897        assert!(ncols % 32 == 0);
20898        let mut q = self.alloc_uninit::<i8>(nrows * ncols)?;
20899        let mut d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
20900        let f = self.func("add_rms_norm_zq8");
20901        let cfg = LaunchConfig {
20902            grid_dim: (nrows as u32, 1, 1),
20903            block_dim: (1024, 1, 1),
20904            shared_mem_bytes: 0,
20905        };
20906        let (nc, ep) = (ncols as i32, eps);
20907        let __s_b = self.gpu.stream();
20908        let mut b = __s_b.launch_builder(&f);
20909        b.arg(a)
20910            .arg(b_in)
20911            .arg(w)
20912            .arg(res)
20913            .arg(z)
20914            .arg(&mut q)
20915            .arg(&mut d)
20916            .arg(&nc)
20917            .arg(&ep);
20918        unsafe {
20919            b.launch(cfg)?;
20920        }
20921        Ok((q, d))
20922    }
20923
20924    /// gated RMSNorm emitting q8_1 directly (fused quantize epilogue) — the ssm_out matvec input.
20925    /// BIT-IDENTICAL bytes to gated_rmsnorm + quantize_q8_1 (ncols % 32 == 0; blocks never straddle
20926    /// rows). Saves one launch per linear-attn layer (36/token on the 9B).
20927    /// z-view twins of gated_rmsnorm(+f16out) — task #16 batched-prime split removal.
20928    pub fn gated_rmsnorm_zv(
20929        &self,
20930        o: &CudaSlice<f32>,
20931        w: &CudaSlice<f32>,
20932        z: &cudarc::driver::CudaView<f32>,
20933        dst: &mut CudaSlice<f32>,
20934        ncols: usize,
20935        nrows: usize,
20936        eps: f32,
20937    ) -> Result<(), Box<dyn std::error::Error>> {
20938        let f = self.func("gated_rmsnorm_f32");
20939        let cfg = LaunchConfig {
20940            grid_dim: (nrows as u32, 1, 1),
20941            block_dim: (128, 1, 1),
20942            shared_mem_bytes: 0,
20943        };
20944        let (nc, e) = (ncols as i32, eps);
20945        let __s_b = self.gpu.stream();
20946        let mut b = __s_b.launch_builder(&f);
20947        b.arg(o).arg(w).arg(z).arg(dst).arg(&nc).arg(&e);
20948        unsafe {
20949            b.launch(cfg)?;
20950        }
20951        Ok(())
20952    }
20953
20954    pub fn gated_rmsnorm_f16out_zv(
20955        &self,
20956        o: &CudaSlice<f32>,
20957        w: &CudaSlice<f32>,
20958        z: &cudarc::driver::CudaView<f32>,
20959        dst: &mut CudaSlice<f32>,
20960        dst16: &mut CudaSlice<u8>,
20961        ncols: usize,
20962        nrows: usize,
20963        eps: f32,
20964    ) -> Result<(), Box<dyn std::error::Error>> {
20965        let f = self.func("gated_rmsnorm_f16out_f32");
20966        // block_dim MUST match gated_rmsnorm's (128): the reduction tree order pins the scale
20967        let cfg = LaunchConfig {
20968            grid_dim: (nrows as u32, 1, 1),
20969            block_dim: (128, 1, 1),
20970            shared_mem_bytes: 0,
20971        };
20972        let (nc, e) = (ncols as i32, eps);
20973        let __s_b = self.gpu.stream();
20974        let mut b = __s_b.launch_builder(&f);
20975        b.arg(o).arg(w).arg(z).arg(dst).arg(dst16).arg(&nc).arg(&e);
20976        unsafe {
20977            b.launch(cfg)?;
20978        }
20979        Ok(())
20980    }
20981
20982    pub fn gated_rmsnorm_q8_1(
20983        &self,
20984        o: &CudaSlice<f32>,
20985        w: &CudaSlice<f32>,
20986        z: &CudaSlice<f32>,
20987        ncols: usize,
20988        nrows: usize,
20989        eps: f32,
20990    ) -> Result<(CudaSlice<i8>, CudaSlice<f32>), Box<dyn std::error::Error>> {
20991        assert!(ncols % 32 == 0);
20992        let f = self.func("gated_rmsnorm_q8_1");
20993        let mut out_q = self.alloc_uninit::<i8>(nrows * ncols)?;
20994        let mut out_d = self.alloc_uninit::<f32>(nrows * (ncols / 32))?;
20995        let cfg = LaunchConfig {
20996            grid_dim: (nrows as u32, 1, 1),
20997            block_dim: (128, 1, 1),
20998            shared_mem_bytes: 0,
20999        };
21000        let (nc, ep) = (ncols as i32, eps);
21001        let __s_b = self.gpu.stream();
21002        let mut b = __s_b.launch_builder(&f);
21003        b.arg(o)
21004            .arg(w)
21005            .arg(z)
21006            .arg(&mut out_q)
21007            .arg(&mut out_d)
21008            .arg(&nc)
21009            .arg(&ep);
21010        unsafe {
21011            b.launch(cfg)?;
21012        }
21013        Ok((out_q, out_d))
21014    }
21015
21016    /// transpose [rows,cols] row-major -> [cols,rows] row-major.
21017    pub fn transpose(
21018        &self,
21019        inp: &CudaSlice<f32>,
21020        rows: usize,
21021        cols: usize,
21022    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21023        let f = self.func("transpose_f32");
21024        let mut out = self.zeros(rows * cols)?;
21025        let cfg = LaunchConfig::for_num_elems((rows * cols) as u32);
21026        let (r, c) = (rows as i32, cols as i32);
21027        let __s_b = self.gpu.stream();
21028        let mut b = __s_b.launch_builder(&f);
21029        b.arg(inp).arg(&mut out).arg(&r).arg(&c);
21030        unsafe {
21031            b.launch(cfg)?;
21032        }
21033        Ok(out)
21034    }
21035
21036    /// repeat-interleave heads: in[head_dim,n_in,T] -> out[head_dim,n_out,T].
21037    pub fn repeat_heads(
21038        &self,
21039        inp: &CudaSlice<f32>,
21040        out: &mut CudaSlice<f32>,
21041        head_dim: usize,
21042        n_in: usize,
21043        n_out: usize,
21044        t: usize,
21045    ) -> Result<(), Box<dyn std::error::Error>> {
21046        let f = self.func("repeat_heads_f32");
21047        let cfg = LaunchConfig::for_num_elems((head_dim * n_out * t) as u32);
21048        let (hd, ni, no, ti) = (head_dim as i32, n_in as i32, n_out as i32, t as i32);
21049        let __s_b = self.gpu.stream();
21050        let mut b = __s_b.launch_builder(&f);
21051        b.arg(inp).arg(out).arg(&hd).arg(&ni).arg(&no).arg(&ti);
21052        unsafe {
21053            b.launch(cfg)?;
21054        }
21055        Ok(())
21056    }
21057
21058    /// q|gate split (on-device). qf:[T, n_head*2*head_dim] -> q_out,gate_out:[head_dim,n_head,T].
21059    /// Replaces the dtoh->host-double-loop->htod in full_attn / full_attn_decode.
21060    pub fn q_gate_split(
21061        &self,
21062        qf: &CudaSlice<f32>,
21063        q_out: &mut CudaSlice<f32>,
21064        gate_out: &mut CudaSlice<f32>,
21065        head_dim: usize,
21066        n_head: usize,
21067        t: usize,
21068    ) -> Result<(), Box<dyn std::error::Error>> {
21069        let f = self.func("q_gate_split_f32");
21070        let cfg = LaunchConfig::for_num_elems((head_dim * n_head * t) as u32);
21071        let (hd, nh, ti) = (head_dim as i32, n_head as i32, t as i32);
21072        let __s_b = self.gpu.stream();
21073        let mut b = __s_b.launch_builder(&f);
21074        b.arg(qf)
21075            .arg(q_out)
21076            .arg(gate_out)
21077            .arg(&hd)
21078            .arg(&nh)
21079            .arg(&ti);
21080        unsafe {
21081            b.launch(cfg)?;
21082        }
21083        Ok(())
21084    }
21085
21086    /// qkv->GDN repack (on-device). conv_out:[conv_dim,T] channel-major ->
21087    /// q_g/k_g/v_g:[d_state,num_v,T] with q/k head-repeat kh = vh % num_k (validated modulo mapping).
21088    /// Replaces the dtoh->host-q/k/v-repack->3x-htod in linear_attn / linear_attn_decode.
21089    pub fn qkv_to_gdn_repack(
21090        &self,
21091        conv_out: &CudaSlice<f32>,
21092        q_g: &mut CudaSlice<f32>,
21093        k_g: &mut CudaSlice<f32>,
21094        v_g: &mut CudaSlice<f32>,
21095        d_state: usize,
21096        num_v: usize,
21097        num_k: usize,
21098        key_dim: usize,
21099        t: usize,
21100    ) -> Result<(), Box<dyn std::error::Error>> {
21101        let f = self.func("qkv_to_gdn_repack_f32");
21102        let cfg = LaunchConfig::for_num_elems((d_state * num_v * t) as u32);
21103        let (ds, nv, nk, kd, ti) = (
21104            d_state as i32,
21105            num_v as i32,
21106            num_k as i32,
21107            key_dim as i32,
21108            t as i32,
21109        );
21110        let __s_b = self.gpu.stream();
21111        let mut b = __s_b.launch_builder(&f);
21112        b.arg(conv_out)
21113            .arg(q_g)
21114            .arg(k_g)
21115            .arg(v_g)
21116            .arg(&ds)
21117            .arg(&nv)
21118            .arg(&nk)
21119            .arg(&kd)
21120            .arg(&ti);
21121        unsafe {
21122            b.launch(cfg)?;
21123        }
21124        Ok(())
21125    }
21126
21127    /// conv left zero-pad (prefill from zero state). src:[conv_dim,T] -> dst:[conv_dim,T+pad],
21128    /// cols 0..pad = 0, cols pad..pad+T = src. `dst` MUST be pre-zeroed. No dtoh/host-loop/htod.
21129    pub fn conv_left_pad(
21130        &self,
21131        src: &CudaSlice<f32>,
21132        dst: &mut CudaSlice<f32>,
21133        conv_dim: usize,
21134        t: usize,
21135        pad: usize,
21136    ) -> Result<(), Box<dyn std::error::Error>> {
21137        let f = self.func("conv_left_pad_f32");
21138        let cfg = LaunchConfig::for_num_elems((conv_dim * t) as u32);
21139        let (cd, ti, p) = (conv_dim as i32, t as i32, pad as i32);
21140        let __s_b = self.gpu.stream();
21141        let mut b = __s_b.launch_builder(&f);
21142        b.arg(src).arg(dst).arg(&cd).arg(&ti).arg(&p);
21143        unsafe {
21144            b.launch(cfg)?;
21145        }
21146        Ok(())
21147    }
21148
21149    /// conv-state assemble + ring roll (decode T=1). conv_state:[conv_dim,pad] (resident),
21150    /// qkv_col:[conv_dim] -> conv_in:[conv_dim,pad+1]; AND rolls conv_state (keep last pad cols).
21151    /// Replaces the dtoh->host-conv-ring-assemble->ring-update->htod in linear_attn_decode.
21152    pub fn conv_assemble_and_roll(
21153        &self,
21154        qkv_col: &CudaSlice<f32>,
21155        conv_state: &mut CudaSlice<f32>,
21156        conv_in: &mut CudaSlice<f32>,
21157        conv_dim: usize,
21158        pad: usize,
21159    ) -> Result<(), Box<dyn std::error::Error>> {
21160        let f = self.func("conv_assemble_and_roll_f32");
21161        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21162        let (cd, p) = (conv_dim as i32, pad as i32);
21163        let __s_b = self.gpu.stream();
21164        let mut b = __s_b.launch_builder(&f);
21165        b.arg(qkv_col).arg(conv_state).arg(conv_in).arg(&cd).arg(&p);
21166        unsafe {
21167            b.launch(cfg)?;
21168        }
21169        Ok(())
21170    }
21171
21172    /// RANK3 LEVER (conv fuse, T=1 DECODE): fused conv_assemble_and_roll + ssm_conv1d_silu in ONE
21173    /// launch. Assembles the conv window [conv_state | qkv_col] in registers, computes the depthwise
21174    /// causal conv + SiLU into `conv_out`, and rolls the ring — never materializing conv_in to HBM.
21175    /// Replaces e.conv_assemble_and_roll(...) + e.ssm_conv1d(...). BIT-IDENTICAL to that two-kernel
21176    /// sequence (same 8-wide accumulation order, same SiLU). `conv_out` is [conv_dim] (T=1).
21177    pub fn ssm_conv1d_fused_decode(
21178        &self,
21179        qkv_col: &CudaSlice<f32>,
21180        conv_state: &mut CudaSlice<f32>,
21181        w: &CudaSlice<f32>,
21182        conv_out: &mut CudaSlice<f32>,
21183        conv_dim: usize,
21184        d_conv: usize,
21185    ) -> Result<(), Box<dyn std::error::Error>> {
21186        let f = self.func("ssm_conv1d_fused_decode_f32");
21187        let cfg = LaunchConfig::for_num_elems(conv_dim as u32);
21188        let (cd, dc) = (conv_dim as i32, d_conv as i32);
21189        let __s_b = self.gpu.stream();
21190        let mut b = __s_b.launch_builder(&f);
21191        b.arg(qkv_col)
21192            .arg(conv_state)
21193            .arg(w)
21194            .arg(conv_out)
21195            .arg(&cd)
21196            .arg(&dc);
21197        unsafe {
21198            b.launch(cfg)?;
21199        }
21200        Ok(())
21201    }
21202
21203    /// Copy a contiguous range [start, start+len) out of src into a fresh slice (device→device via host).
21204    /// Used for qkv split views. Small/rare; not perf-critical in Stage 1.
21205    pub fn slice_range(
21206        &self,
21207        src: &CudaSlice<f32>,
21208        start: usize,
21209        len: usize,
21210    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21211        let host = self.gpu.stream().clone_dtoh(src)?;
21212        self.gpu.stream().synchronize()?;
21213        Ok(self.htod(&host[start..start + len])?)
21214    }
21215}
21216
21217#[cfg(test)]
21218mod target_dispatch_tests {
21219    use super::legacy_quant_gemm_allowed;
21220
21221    #[test]
21222    fn legacy_quant_gemm_arch_policy_honors_the_escape_hatch() {
21223        // sm_120a native lane
21224        assert!(legacy_quant_gemm_allowed(false, false, false));
21225        assert!(!legacy_quant_gemm_allowed(false, false, true));
21226        // pure portable lane (sm_89): gated
21227        assert!(!legacy_quant_gemm_allowed(true, false, false));
21228        assert!(!legacy_quant_gemm_allowed(true, false, true));
21229        // Hopper-MMA lane (sm_90a): portable build, int8-MMA GEMM re-admitted
21230        assert!(legacy_quant_gemm_allowed(true, true, false));
21231        assert!(!legacy_quant_gemm_allowed(true, true, true));
21232    }
21233
21234    #[cfg(all(memra_portable_cuda, not(memra_hopper_mma)))]
21235    #[test]
21236    fn portable_build_disables_legacy_quant_gemm_without_an_env_override() {
21237        assert!(!legacy_quant_gemm_allowed(
21238            cfg!(memra_portable_cuda),
21239            cfg!(memra_hopper_mma),
21240            false
21241        ));
21242    }
21243
21244    #[cfg(memra_hopper_mma)]
21245    #[test]
21246    fn hopper_mma_build_re_admits_legacy_quant_gemm() {
21247        assert!(legacy_quant_gemm_allowed(
21248            cfg!(memra_portable_cuda),
21249            cfg!(memra_hopper_mma),
21250            false
21251        ));
21252        assert!(super::portable_mma_gated() == false);
21253    }
21254}
21255
21256/// The memra-kv device seam (Phase D): the cache's 7 ops delegate to the engine's
21257/// inherent methods (inherent methods win name resolution, so no recursion).
21258impl memra_kv::KvDev for Engine {
21259    fn zeros(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21260        Engine::zeros(self, n)
21261    }
21262    fn uninit(&self, n: usize) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21263        Engine::uninit(self, n)
21264    }
21265    fn alloc_u8(&self, n: usize) -> Result<CudaSlice<u8>, Box<dyn std::error::Error>> {
21266        Engine::alloc_u8(self, n)
21267    }
21268    fn htod_i32(&self, v: &[i32]) -> Result<CudaSlice<i32>, Box<dyn std::error::Error>> {
21269        Engine::htod_i32(self, v)
21270    }
21271    fn clone_dtod(
21272        &self,
21273        src: &CudaSlice<f32>,
21274    ) -> Result<CudaSlice<f32>, Box<dyn std::error::Error>> {
21275        Engine::clone_dtod(self, src)
21276    }
21277    fn copy_into(
21278        &self,
21279        dst: &mut CudaSlice<f32>,
21280        off: usize,
21281        src: &CudaSlice<f32>,
21282        len: usize,
21283    ) -> Result<(), Box<dyn std::error::Error>> {
21284        Engine::copy_into(self, dst, off, src, len)
21285    }
21286    fn set_i32_one(
21287        &self,
21288        d: &mut CudaSlice<i32>,
21289        v: i32,
21290    ) -> Result<(), Box<dyn std::error::Error>> {
21291        Engine::set_i32_one(self, d, v)
21292    }
21293}